visitVariableDeclarationList method

  1. @override
Object? visitVariableDeclarationList(
  1. SVariableDeclarationList node
)
override

Implementation

@override
Object? visitVariableDeclarationList(SVariableDeclarationList node) {
  // We need to ensure variables are defined in the current environment.
  // visitVariableDeclaration is NOT automatically called for node.variables
  // by the generalizing visitor when visiting the list itself.
  for (final variable in node.variables) {
    if (variable.name!.name == '_') {
      // Evaluate initializer for potential side effects, but don't define
      variable.initializer?.accept<Object?>(this);
    } else {
      // Check if this is a late variable
      final isLate = node.isLate;
      final isFinal = node.isFinal;
      final variableName = variable.name!.name;

      // S3c (plan_3 §4.6 / §9.3): a slotted local dual-writes into BOTH the
      // frame's slot array and the name map [_values]. Resolved reads go
      // through [Environment.getSlot] (see [visitSimpleIdentifier]) — the hot
      // path that previously paid the per-level name hash — while name-based
      // resolution paths the interpreter still uses for the SAME local (the
      // function-invocation callee lookup `environment.get(name)` for
      // tear-offs / bridged-static / extension-call values) keep working
      // against [_values]. Full demotion out of [_values] is deferred until
      // every name-read site is audited (plan_3 §11). The slot index lives on
      // the mirror node itself ([SVariableDeclaration.declSlot]) — no Expando,
      // so it survives serialization.
      final declSlot = variable.declSlot;
      void def(Object? v) {
        environment.define(variableName, v);
        if (declSlot != null) {
          environment.defineSlot(declSlot, variableName, v);
        }
      }

      // SCE103: the fourth and last binding site. `int x = 'two';` bound the
      // String; so did every later `x = 'two';`, and so did the for-each
      // IDENTIFIER form `for (x in xs)`, which is an assignment to an
      // already-declared variable and therefore the same job.
      //
      // Resolved ONCE per declaration and handed to the environment, because
      // an assignment does not carry the annotation — the declaration is the
      // only place the written type exists. `resolveBinding` returns null for
      // everything it cannot answer confidently (unannotated, `dynamic`, a
      // type parameter, an annotation that will not resolve), and a null
      // binding is never recorded, so an untyped local costs one map probe at
      // declaration and nothing afterwards.
      final declaredBinding = InterpretedFunction.resolveBinding(
        environment,
        node.type,
        describedAs: variableName,
      );
      // LATE IS OUT OF SCOPE, deliberately. A late declaration stores a
      // `LateVariable` wrapper rather than the value, so the check here would
      // reject the wrapper, and the moment the initializer actually runs is
      // not an assignment this can see. Recording nothing leaves late locals
      // exactly as permissive as they are today rather than half-checked.
      if (declaredBinding != null && !isLate) {
        environment.declareType(variableName, declaredBinding);
      }

      if (isLate) {
        // Handle late variable
        if (variable.initializer != null) {
          // Late variable with lazy initializer
          final lateVar = LateVariable(variableName, () {
            // Create a closure that will evaluate the initializer when accessed
            return variable.initializer!.accept<Object?>(this);
          }, isFinal: isFinal);
          def(lateVar);
          Logger.debug(
            "[VariableDeclList] Defined late variable '$variableName' with lazy initializer.",
          );
        } else {
          // Late variable without initializer
          final lateVar = LateVariable(variableName, null, isFinal: isFinal);
          def(lateVar);
          Logger.debug(
            "[VariableDeclList] Defined late variable '$variableName' without initializer.",
          );
        }
      } else {
        // Regular (non-late) variable handling
        Object? initValue;
        Object? result; // Value returned by accept() (could be suspension)

        if (variable.initializer != null) {
          result = variable.initializer!.accept<Object?>(this);
          if (result is AsyncSuspensionRequest) {
            // Async initializer: Define as null for now, result holds suspension
            Logger.debug(
              "[VariableDeclList] Async init for '$variableName'. Defined as null.",
            );
            def(null);
            // Propagate the suspension request.
            // If there are multiple async inits, the LAST suspension request wins.
          } else {
            // Sync initializer: Use the computed value
            // SCE103: checked HERE rather than inside `def`, because `def` is
            // also the path for `int x;` and for an async initializer that
            // has not produced its value yet — both of which define null, and
            // neither of which is a write real Dart would reject. The
            // declared type is still recorded for both, so the assignment
            // that eventually supplies the value is held.
            initValue = declaredBinding == null
                ? result
                : declaredBinding.bind(environment, result);
            if (Logger.isDebug) {
              Logger.debug(
                "[VariableDeclList] Sync init for '$variableName'. Defined as $initValue.",
              );
            }
            def(initValue);
          }
        } else {
          // No initializer: Define as null
          Logger.debug(
            "[VariableDeclList] No init for '$variableName'. Defined as null.",
          );
          def(null);
          result = null; // No suspension
        }

        // If the result of the variable's init was a suspension, we need to return it
        // to signal the state machine.
        if (result is AsyncSuspensionRequest) {
          // If any variable initialization caused suspension, return that suspension immediately.
          // The state machine needs to handle this before processing subsequent variables.
          Logger.debug(
            "[VariableDeclList] Propagating suspension from initializer of '$variableName'.",
          );
          return result;
        }
      }
    }
  }
  // If no suspension occurred (or only sync initializers for all variables)
  return null; // Original behavior
}