visitAwaitExpression method
Visit a SAwaitExpression.
Implementation
@override
Object? visitAwaitExpression(SAwaitExpression node) {
// Check for async state machine: do we have an async state?
if (currentAsyncState == null) {
// This shouldn't happen if the call is properly orchestrated
throw StateD4rtException(
"Internal error: 'await' encountered outside of a managed async execution state.",
);
}
// Initial check: Are we in an async function?
if (!currentAsyncState!.function.isAsync) {
throw RuntimeD4rtException(
"'await' can only be used inside an async function.",
);
}
// Resuming a statement re-evaluates it from the top, so this await site may
// already have been resolved on an earlier pass. Replay ITS OWN value.
//
// SCC40: this used to read `lastAwaitResult` whenever the frame was in
// invocation-resumption mode, which is a single slot shared by every await
// in the statement — so `(await a) + (await b)` produced `'AA'`. Falling
// through when the site is absent from the map is the other half of the
// fix: an await that has not been reached yet must still suspend, rather
// than silently adopting a neighbour's value.
final resolvedAwaits = currentAsyncState!.resolvedAwaitResults;
if (resolvedAwaits.containsKey(node)) {
Logger.debug(
"[SAwaitExpression] Replaying already-resolved await site: ${resolvedAwaits[node]}",
);
return resolvedAwaits[node];
}
Logger.debug("[SAwaitExpression] Evaluating expression for await...");
final expressionValue = node.expression!.accept<Object?>(this);
// HANDLING NESTED SUSPENSIONS
if (expressionValue is AsyncSuspensionRequest) {
// If the awaited expression itself is an await, just propagate its suspension request.
Logger.debug(
"[SAwaitExpression] Awaited expression itself suspended. Propagating AsyncSuspensionRequest.",
);
return expressionValue;
}
// Bug-92: Unwrap BridgedInstance containing a Future
// When Future is created via bridged constructor, it gets wrapped in BridgedInstance
// We need to unwrap it to get the actual Future for await
Object? futureValue = expressionValue;
if (expressionValue is BridgedInstance &&
expressionValue.nativeObject is Future) {
futureValue = expressionValue.nativeObject;
Logger.debug(
"[SAwaitExpression] Unwrapped BridgedInstance to get native Future.",
);
}
if (futureValue is Future) {
Logger.debug(
"[SAwaitExpression] Expression evaluated to a Future. Returning AsyncSuspensionRequest.",
);
final future = futureValue as Future<Object?>;
// CRUCIAL: Return the suspension request with the future and the current state.
// The async state machine will use this information.
// Note: currentAsyncState cannot be null here because of the previous check.
return AsyncSuspensionRequest(
future,
currentAsyncState!,
awaitNode: node,
);
} else {
// The argument to 'await' MUST be a Future.
throw RuntimeD4rtException(
"The argument to 'await' must be a Future, but received type: ${expressionValue?.runtimeType}",
);
}
}