Skip to content

Commit 4eb5583

Browse files
committed
[CSApply] For-in: Extend try injector to handle erasure and opened existential
For synthesized `<async iterator>.next()` calls expression rewriter has to check whether witness is throwing and add `try` when necessary, in order to do that injector needs to look through opened existentials, erasures, and other implicitly injected AST nodes.
1 parent 71f528a commit 4eb5583

File tree

2 files changed

+49
-14
lines changed

2 files changed

+49
-14
lines changed

lib/Sema/CSApply.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8929,21 +8929,46 @@ static Optional<SolutionApplicationTarget> applySolutionToForEachStmt(
89298929

89308930
Expr *nextCall = rewrittenTarget->getAsExpr();
89318931
// Wrap a call to `next()` into `try await` since `AsyncIteratorProtocol`
8932-
// requirement is `async throws`
8932+
// witness could be `async throws`.
89338933
if (isAsync) {
8934-
auto &ctx = cs.getASTContext();
8935-
auto nextRefType =
8936-
solution
8937-
.getResolvedType(
8938-
cast<ApplyExpr>(cast<AwaitExpr>(nextCall)->getSubExpr())
8939-
->getFn())
8940-
->castTo<FunctionType>();
8941-
8942-
// If the inferred witness is throwing, we need to wrap the call
8943-
// into `try` expression.
8944-
if (nextRefType->isThrowing())
8945-
nextCall = TryExpr::createImplicit(ctx, /*tryLoc=*/SourceLoc(),
8946-
nextCall, nextCall->getType());
8934+
// Cannot use `forEachChildExpr` here because we need to
8935+
// to wrap a call in `try` and then stop immediately after.
8936+
struct TryInjector : ASTWalker {
8937+
ASTContext &C;
8938+
const Solution &S;
8939+
8940+
bool ShouldStop = false;
8941+
8942+
TryInjector(ASTContext &ctx, const Solution &solution)
8943+
: C(ctx), S(solution) {}
8944+
8945+
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
8946+
if (ShouldStop)
8947+
return Action::Stop();
8948+
8949+
if (auto *call = dyn_cast<CallExpr>(E)) {
8950+
// There is a single call expression in `nextCall`.
8951+
ShouldStop = true;
8952+
8953+
auto nextRefType =
8954+
S.getResolvedType(call->getFn())->castTo<FunctionType>();
8955+
8956+
// If the inferred witness is throwing, we need to wrap the call
8957+
// into `try` expression.
8958+
if (nextRefType->isThrowing()) {
8959+
auto *tryExpr = TryExpr::createImplicit(
8960+
C, /*tryLoc=*/call->getStartLoc(), call, call->getType());
8961+
// Cannot stop here because we need to make sure that
8962+
// the new expression gets injected into AST.
8963+
return Action::SkipChildren(tryExpr);
8964+
}
8965+
}
8966+
8967+
return Action::Continue(E);
8968+
}
8969+
};
8970+
8971+
nextCall->walk(TryInjector(cs.getASTContext(), solution));
89478972
}
89488973

89498974
stmt->setNextCall(nextCall);

test/Concurrency/async_sequence_syntax.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,13 @@ func forAwaitWithConcreteType(_ seq: ThrowingAsyncSequence) throws { // expected
8787
_ = elt
8888
}
8989
}
90+
91+
@available(SwiftStdlib 5.1, *)
92+
func forTryAwaitReturningExistentialType() async throws {
93+
struct S {
94+
func seq() -> any AsyncSequence { fatalError() }
95+
}
96+
97+
for try await _ in S().seq() { // Ok
98+
}
99+
}

0 commit comments

Comments
 (0)