visitVariableDeclarationList method
Visit a SVariableDeclarationList.
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);
}
}
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
initValue = 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
}