Skip to content

[code_builder] Add null-aware cascades, cascade index, and fluent cascade helpers - #2540

Merged
natebosch merged 3 commits into
dart-lang:mainfrom
hkarmoush:add-cascade-index-nullsafe-2524
Sep 22, 2026
Merged

natebosch merged 3 commits into
dart-lang:mainfrom
hkarmoush:add-cascade-index-nullsafe-2524

Conversation

@hkarmoush

Copy link
Copy Markdown
Contributor

Closes #2524.

Expression could already do cascade, nullSafeProperty, and index, but a few things you'd expect were missing:

  • No ?.. (null-aware cascade)
  • expr.index(k).assign(v) emits expr[k] = v — no way to get expr..[k] = v
  • A bit of boilerplate for the common "cascade a bunch of assignments onto one object" pattern

This adds:

  • nullSafeCascade, cascadeIndex, nullSafeCascadeIndex — same shape as the existing cascade/nullSafeProperty/index, just with the right join operator
  • cascadeAssign, nullSafeCascadeAssign, cascadeInvoke, cascadeAssigns — small conveniences built on top, matching the API proposed in the issue

No new AST node types or visitor changes — everything reuses the existing BinaryExpression machinery, so it's a pretty low-risk addition.

Added tests for each new method, ran dart format and dart analyze (clean), and the full code_builder test suite passes (408/408).

@hkarmoush
hkarmoush requested a review from a team as a code owner August 19, 2026 22:17

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds several new expression builders to code_builder to support null-aware cascades, cascaded index assignments, and fluent multi-property cascade chains, along with comprehensive unit tests. The review feedback suggests improving API completeness and symmetry by adding nullSafeCascadeInvoke and nullSafeCascadeAssigns helpers, and recommends correcting the DartDoc comments to avoid starting with 'Returns' on the first line.

Comment on lines +336 to +342
/// Returns an expression invoking `..<name>(...)` on this expression.
Expression cascadeInvoke(
String name, [
Iterable<Expression> positionalArguments = const [],
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) => cascade(name).call(positionalArguments, namedArguments, typeArguments);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For completeness and API symmetry with cascadeInvoke and nullSafeCascadeAssign, consider adding a nullSafeCascadeInvoke helper to support invoking methods via a null-aware cascade (?..). Note that DartDoc comments should not start with 'Returns' on the first line; instead, describe what the function does.

Suggested change
/// Returns an expression invoking `..<name>(...)` on this expression.
Expression cascadeInvoke(
String name, [
Iterable<Expression> positionalArguments = const [],
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) => cascade(name).call(positionalArguments, namedArguments, typeArguments);
/// Invokes ..name(...) on this expression.
Expression cascadeInvoke(
String name, [
Iterable<Expression> positionalArguments = const [],
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) => cascade(name).call(positionalArguments, namedArguments, typeArguments);
/// Invokes ?..name(...) on this expression.
Expression nullSafeCascadeInvoke(
String name, [
Iterable<Expression> positionalArguments = const [],
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) => nullSafeCascade(name).call(positionalArguments, namedArguments, typeArguments);
References
  1. At least all public members should have documentation, answering the why. (link)
  2. DartDoc comments should not start with "Returns" if it's not the first line of the DartDoc. The first line should describe what the function does, and subsequent lines can describe the return value.

Comment on lines +344 to +350
/// Chains a `..<name> = <value>` cascade assignment for each entry in
/// [assignments] onto this expression.
Expression cascadeAssigns(Map<String, Expression> assignments) =>
assignments.entries.fold<Expression>(
this,
(target, entry) => target.cascadeAssign(entry.key, entry.value),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support chaining multiple assignments starting with a null-aware cascade, consider adding a nullSafeCascadeAssigns helper. In Dart, only the first cascade operator in a chain should be null-aware (?..), while subsequent operators must be regular cascades (..). A dedicated helper can handle this transition automatically.

Suggested change
/// Chains a `..<name> = <value>` cascade assignment for each entry in
/// [assignments] onto this expression.
Expression cascadeAssigns(Map<String, Expression> assignments) =>
assignments.entries.fold<Expression>(
this,
(target, entry) => target.cascadeAssign(entry.key, entry.value),
);
/// Chains a ..name = value cascade assignment for each entry in
/// [assignments] onto this expression.
Expression cascadeAssigns(Map<String, Expression> assignments) =>
assignments.entries.fold<Expression>(
this,
(target, entry) => target.cascadeAssign(entry.key, entry.value),
);
/// Chains a ?..name = value cascade assignment for the first entry,
/// followed by ..name = value for subsequent entries in [assignments]
/// onto this expression.
Expression nullSafeCascadeAssigns(Map<String, Expression> assignments) {
if (assignments.isEmpty) return this;
final first = assignments.entries.first;
final rest = assignments.entries.skip(1);
return rest.fold<Expression>(
nullSafeCascadeAssign(first.key, first.value),
(target, entry) => target.cascadeAssign(entry.key, entry.value),
);
}
References
  1. At least all public members should have documentation, answering the why. (link)

@kevmoo

kevmoo commented Aug 19, 2026

Copy link
Copy Markdown
Member

@hkarmoush thank you! I think the gemini suggestions are...reasonable, but make sure you double check and add tests.

hkarmoush added a commit to hkarmoush/tools that referenced this pull request Aug 20, 2026
…or API symmetry

Addresses review feedback on dart-lang#2540: nullSafeCascadeInvoke mirrors
cascadeInvoke for null-aware method cascades, and
nullSafeCascadeAssigns chains a null-aware first assignment followed
by regular cascade assignments, since only the first cascade in a
chain may be null-aware.

@natebosch natebosch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should land the helpers like cascadeInvoke.

I do like some aspects of nullSafeCascadeAssigns - it handles the first-uses-question-mark behavior, and is a closer match to semantics than the existing syntax matching API. I don't like how it specializes a single form of cascades.

If we were to restart or introduce breaking changes I suspect there's a more orthogonal breakdown we can have for the API without needing to specialize this much, but for where we are I think the best thing would be to land nullSafeCascade, cascadeIndex, and nullSfeCascadeIndex.

hkarmoush pushed a commit to hkarmoush/tools that referenced this pull request Aug 31, 2026
Adds Expression.nullSafeCascade, cascadeIndex, and nullSafeCascadeIndex
to support the `?..`, `..[]`, and `?..[]` operators. Scoped to these
three core methods per review feedback on the upstream PR (dart-lang#2540),
which flagged the additional cascadeInvoke/cascadeAssign/cascadeAssigns
style helpers as unnecessary API surface for now.
Adds Expression.nullSafeCascade, cascadeIndex, and nullSafeCascadeIndex
to support the ?.., ..[], and ?..[] operators. Scoped to these three
core methods per review feedback on this PR, which flagged the
additional cascadeInvoke/cascadeAssign/cascadeAssigns style helpers
as unnecessary API surface for now.
@hkarmoush
hkarmoush force-pushed the add-cascade-index-nullsafe-2524 branch from 9e6df98 to d187f09 Compare August 31, 2026 19:58
@hkarmoush

Copy link
Copy Markdown
Contributor Author

Pushed a scoped-down version landing just nullSafeCascade, cascadeIndex, and nullSafeCascadeIndex as requested — dropped cascadeInvoke, cascadeAssign, nullSafeCascadeAssign, and cascadeAssigns/nullSafeCascadeAssigns. Also clarified in the docs that only the first cascade in a chain needs the null-aware variant. Ready for another look @natebosch.

@hkarmoush

Copy link
Copy Markdown
Contributor Author

@natebosch Following up here — the branch has been scoped down to just nullSafeCascade, cascadeIndex, and nullSafeCascadeIndex since Aug 31, and both of your review comments (doc wording on only the first cascade in a chain needing the null-aware variant) are addressed and marked resolved. Would appreciate another look when you have a chance.

@natebosch
natebosch self-requested a review September 22, 2026 01:46
@natebosch
natebosch merged commit f2157cb into dart-lang:main Sep 22, 2026
21 checks passed
copybara-service Bot pushed a commit to dart-lang/sdk that referenced this pull request Oct 6, 2026
Revisions updated by `dart tools/rev_sdk_deps.dart`.

native (https://github.com/dart-lang/native/compare/f628b26..27f7678):
  27f767827  Mon Sep 28 14:51:28 2026 +0200  Daco Harkes  [hooks_runner] Remove `KernelAssets` and `Target` (dart-lang/native#3703)
  ddc7d6deb  Mon Sep 28 05:40:03 2026 +0200  Jakob Kordež  [ffigen] Skip typedefs to function pointers with unsupported parameter types (dart-lang/native#3694)
  177d283d9  Mon Sep 28 04:30:28 2026 +0200  Jakob Kordež  [ffigen] Generate bindings for C++ enums, structs and unions nested in namespaces and records (dart-lang/native#3644)
  5ee8f67d8  Mon Sep 28 11:39:14 2026 +1000  Liam Appelbe  [objective_c] Clarify block isolate scoping (dart-lang/native#3692)
  25f090116  Fri Sep 25 12:09:12 2026 +1000  Liam Appelbe  [ffigen][jnigen] Deprecate YAML config (dart-lang/native#3654)
  8364413eb  Fri Sep 25 10:09:50 2026 +1000  Liam Appelbe  [jnigen] Document nullability (dart-lang/native#3690)
  e8d518574  Thu Sep 24 18:02:46 2026 +1000  Liam Appelbe  [ffigen] Code snippet-ify ffigen and objective_c (dart-lang/native#3685)
  663385955  Thu Sep 24 03:21:53 2026 +0300  Manar Elhabbal  [ffigen] prevent setting both cpp and objectiveC in ffigenerator (dart-lang/native#3687)
  1cf40952a  Thu Sep 24 09:27:09 2026 +1000  Liam Appelbe  [objective_c] Remove arm64e build hack (dart-lang/native#3680)
  c44550b6e  Wed Sep 23 13:01:33 2026 +0200  Simon Binder  [web_assets] Scaffold package (dart-lang/native#3610)
  57b4bad69  Wed Sep 23 16:21:27 2026 +1000  Liam Appelbe  [swift2objc] Code snippet-ify swift2objc (dart-lang/native#3684)
  5cee1d0be  Wed Sep 23 10:16:29 2026 +0530  Rachit Vikram Singh  [ffigen] Add Input.appendCompilerOptions to append clang args to defaults (`#3630`) (dart-lang/native#3676)
  225a648df  Wed Sep 23 14:37:13 2026 +1000  Liam Appelbe  [jnigen] Code snippet-ify jnigen and co (dart-lang/native#3641)
  2030decf9  Wed Sep 23 13:51:24 2026 +1000  Liam Appelbe  [infra] Move snippets_tool to its own package (dart-lang/native#3683)
  6fa2beea2  Wed Sep 23 12:14:28 2026 +1000  Liam Appelbe  [objective_c] Revert `#3664` (dart-lang/native#3681)
  ed7605f8a  Tue Sep 22 12:03:22 2026 +0530  Sivapriya  [jni] Add deep conversion utils (dart-lang/native#3635)
  8574b7422  Tue Sep 22 08:31:11 2026 +0200  Lukas Klingsbo  [ffigen] Allow package:cli_util 0.5.x and 0.6.x (dart-lang/native#3673)

test (https://github.com/dart-lang/test/compare/9946c95..4f92d53):
  4f92d530  Fri Sep 25 16:46:22 2026 -0700  Nate Bosch  Unskip a test on windows (dart-lang/test#2772)
  7fb84866  Fri Sep 25 14:52:39 2026 -0700  Nate Bosch  Bundle skips at the end of the GitHub reporter (dart-lang/test#2768)
  603e5ec5  Thu Sep 24 14:00:41 2026 -0700  Nate Bosch  Clean up the runner's temporary directories (dart-lang/test#2763)
  4bf04d90  Thu Sep 24 12:41:15 2026 -0700  Nate Bosch  Refactor signal handling in executable.dart (dart-lang/test#2765)
  2c660a84  Tue Sep 22 15:33:34 2026 -0700  Nate Bosch  Regenerate mono_repo output (dart-lang/test#2767)

tools (https://github.com/dart-lang/tools/compare/3614ce0..d87eaf7):
  d87eaf79  Thu Sep 24 08:31:33 2026 -0700  Kevin Moore  fix(benchmark_harness): migrate subprocess spawning to package:cli_util (dart-lang/tools#2585)
  8ccf8ccf  Wed Sep 23 05:09:10 2026 +0530  Aman Maurya  [string_scanner] Extract a mixin for LineScanner and deduplicate eager scanning logic (dart-lang/tools#2569)
  afcdd6d9  Tue Sep 22 15:38:07 2026 +0200  Morgan :)  Document that watch events converge on filesystem state. (dart-lang/tools#2607)
  f2157cbc  Tue Sep 22 04:52:58 2026 +0300  Hasan Armoush  [code_builder] Add null-aware cascades, cascade index, and fluent cascade helpers (dart-lang/tools#2540)

web (https://github.com/dart-lang/web/compare/e5c6c02..ec71a4c):
  ec71a4c  Mon Sep 28 10:07:01 2026 -0700  Kevin Moore  [js_interop_gen] Require `code_builder: ^4.12.0` and update golden headers (dart-lang/web#576)

[email protected]

Change-Id: I766bc5b2b1297e3251990821bb73387ba685e39b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/556320
Commit-Queue: Nate Bosch <[email protected]>
Reviewed-by: Brian Quinlan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[code_builder] Add support for null-aware cascades (?..), cascade indexing (..[]), and cascade assignment helpers on Expression

3 participants