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.
// SCF43: every collection-`for` body in flight records the await sites
// it reaches, so a completed iteration can drop them from the node cache
// and the next iteration suspends afresh.
for (final scope in _collectionForAwaitScopes) {
scope.add(node);
}
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 {
// SCE223: Dart allows `await` on any value — `await 5` is 5 and
// `await null` is null — and still yields to the event loop. So a
// non-Future is wrapped and suspended on like any other; it used to be
// refused as an error.
return AsyncSuspensionRequest(
Future<Object?>.value(futureValue),
currentAsyncState!,
awaitNode: node,
);
}
}