Skip to content

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

Closed

Description

@kevmoo

Problem & Motivation

code_builder currently provides Expression.cascade(String name), which emits this..{name}. While basic cascades can be chained with .assign(value) (e.g. expr.cascade('prop').assign(val)), several standard Dart cascade features and ergonomic patterns are missing:

  1. Null-Aware Cascades (?..):
    Dart supports null-aware cascades (target?..prop = val), but code_builder has no nullSafeCascade / nullAwareCascade method (despite supporting nullSafeProperty ?. and nullSafeSpread ...?).
  2. Cascade Index Operator (..[key] = value and ?..[key] = value):
    Setting map/list elements via cascade (map..['key'] = value) cannot be represented via AST nodes; refer('map').index(k).assign(v) emits map[k] = v rather than map..[k] = v.
  3. Fluent Ergonomics (cascadeAssign, cascadeInvoke, cascadeAssigns):
    In generators that configure multiple properties on an instantiated object (such as JSON deserializers), authors currently write .cascade('prop1').assign(v1).cascade('prop2').assign(v2). Convenience helpers for assignment and invocation reduce boilerplate.

Proposed API

extension CascadeExpressionExtension on Expression {
  /// Returns `this..{name}`.
  Expression cascade(String name);

  /// Returns `this?..{name}` (null-aware cascade).
  Expression nullSafeCascade(String name);

  /// Returns `this..[{index}]`.
  Expression cascadeIndex(Expression index);

  /// Returns `this?..[{index}]`.
  Expression nullSafeCascadeIndex(Expression index);

  /// Returns `this..{name} = {value}`.
  Expression cascadeAssign(String name, Expression value) =>
      cascade(name).assign(value);

  /// Returns `this?..{name} = {value}`.
  Expression nullSafeCascadeAssign(String name, Expression value) =>
      nullSafeCascade(name).assign(value);

  /// Returns `this..{name}({positionalArguments}, {namedArguments})`.
  Expression cascadeInvoke(
    String name, [
    Iterable<Expression> positionalArguments = const [],
    Map<String, Expression> namedArguments = const {},
    List<Reference> typeArguments = const [],
  ]) =>
      cascade(name).call(positionalArguments, namedArguments, typeArguments);

  /// Chains multiple property assignments on this cascade receiver.
  Expression cascadeAssigns(Map<String, Expression> assignments) =>
      assignments.entries.fold<Expression>(
        this,
        (target, entry) => target.cascadeAssign(entry.key, entry.value),
      );
}

Example Usage

final ctor = refer('Order').newInstanceNamed('custom', [
  refer('json').index(literalString('category')).asA(refer('String')),
]);

final result = ctor.cascadeAssigns({
  'count': refer('json').index(literalString('count')).asA(refer('num?')).nullSafeProperty('toInt').call([]),
  'isRushed': refer('json').index(literalString('isRushed')).asA(refer('bool?')),
});

// Emits:
// Order.custom(json['category'] as String)
//   ..count = (json['count'] as num?)?.toInt()
//   ..isRushed = json['isRushed'] as bool?;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions