Skip to content

Commit 11154b1

Browse files
authored
Merge pull request #33807 from DougGregor/concurrency-nested-await-try
[Concurrency] Fix nested await/try parsing and effects checking.
2 parents 903a06f + 6489e1a commit 11154b1

File tree

4 files changed

+103
-9
lines changed

4 files changed

+103
-9
lines changed

lib/Parse/ParseExpr.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ ParserResult<Expr> Parser::parseExprSequenceElement(Diag<> message,
394394

395395
if (shouldParseExperimentalConcurrency() && Tok.isContextualKeyword("await")) {
396396
SourceLoc awaitLoc = consumeToken();
397-
ParserResult<Expr> sub = parseExprUnary(message, isExprBasic);
397+
ParserResult<Expr> sub =
398+
parseExprSequenceElement(diag::expected_expr_after_await, isExprBasic);
398399
if (!sub.hasCodeCompletion() && !sub.isNull()) {
399400
ElementContext.setCreateSyntax(SyntaxKind::AwaitExpr);
400401
sub = makeParserResult(new (Context) AwaitExpr(awaitLoc, sub.get()));
@@ -428,7 +429,9 @@ ParserResult<Expr> Parser::parseExprSequenceElement(Diag<> message,
428429
}
429430
}
430431

431-
ParserResult<Expr> sub = parseExprUnary(message, isExprBasic);
432+
ParserResult<Expr> sub = hadTry
433+
? parseExprSequenceElement(message, isExprBasic)
434+
: parseExprUnary(message, isExprBasic);
432435

433436
if (hadTry && !sub.hasCodeCompletion() && !sub.isNull()) {
434437
ElementContext.setCreateSyntax(SyntaxKind::TryExpr);

lib/Sema/TypeCheckEffects.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,29 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
13291329
void mergeFrom(ContextFlag flag, ContextFlags other) {
13301330
Bits |= (other.Bits & flag);
13311331
}
1332+
1333+
void mergeFrom(ContextFlags flags, ContextFlags other) {
1334+
Bits |= (other.Bits & flags.Bits);
1335+
}
1336+
1337+
// All of the flags that can be set by throw checking.
1338+
static ContextFlags throwFlags() {
1339+
ContextFlags result;
1340+
result.set(IsTryCovered);
1341+
result.set(IsInTry);
1342+
result.set(HasAnyThrowSite);
1343+
result.set(HasTryThrowSite);
1344+
return result;
1345+
}
1346+
1347+
// All of the flags that can be set by async/await checking.
1348+
static ContextFlags asyncAwaitFlags() {
1349+
ContextFlags result;
1350+
result.set(IsAsyncCovered);
1351+
result.set(HasAnyAsyncSite);
1352+
result.set(HasAnyAwait);
1353+
return result;
1354+
}
13321355
};
13331356

13341357
ContextFlags Flags;
@@ -1408,6 +1431,10 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
14081431
// body for the purposes of deciding whether a try contained
14091432
// a throwing call.
14101433
OldFlags.mergeFrom(ContextFlags::HasTryThrowSite, Self.Flags);
1434+
1435+
// "await" doesn't work this way; the "await" needs to be part of
1436+
// the autoclosure expression itself, and the autoclosure must be
1437+
// 'async'.
14111438
}
14121439

14131440
void preserveCoverageFromNonExhaustiveCatch() {
@@ -1417,16 +1444,21 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
14171444

14181445
void preserveCoverageFromAwaitOperand() {
14191446
OldFlags.mergeFrom(ContextFlags::HasAnyAwait, Self.Flags);
1447+
OldFlags.mergeFrom(ContextFlags::throwFlags(), Self.Flags);
1448+
OldMaxThrowingKind = std::max(OldMaxThrowingKind, Self.MaxThrowingKind);
14201449
}
14211450

14221451
void preserveCoverageFromTryOperand() {
14231452
OldFlags.mergeFrom(ContextFlags::HasAnyThrowSite, Self.Flags);
1453+
OldFlags.mergeFrom(ContextFlags::asyncAwaitFlags(), Self.Flags);
14241454
OldMaxThrowingKind = std::max(OldMaxThrowingKind, Self.MaxThrowingKind);
14251455
}
14261456

14271457
void preserveCoverageFromInterpolatedString() {
14281458
OldFlags.mergeFrom(ContextFlags::HasAnyThrowSite, Self.Flags);
14291459
OldFlags.mergeFrom(ContextFlags::HasTryThrowSite, Self.Flags);
1460+
OldFlags.mergeFrom(ContextFlags::HasAnyAsyncSite, Self.Flags);
1461+
OldFlags.mergeFrom(ContextFlags::HasAnyAwait, Self.Flags);
14301462
OldMaxThrowingKind = std::max(OldMaxThrowingKind, Self.MaxThrowingKind);
14311463
}
14321464

test/ClangImporter/objc_async.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-sil -I %S/Inputs/custom-modules -enable-experimental-concurrency %s -verify
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -enable-experimental-concurrency %s -verify
22

33
// REQUIRES: objc_interop
44
import Foundation
55
import ObjCConcurrency
66

7-
func testSlowServer(slowServer: SlowServer) async {
7+
func testSlowServer(slowServer: SlowServer) async throws {
88
let _: Int = await slowServer.doSomethingSlow("mail")
99
let _: Bool = await slowServer.checkAvailability()
10-
let _: String = await slowServer.findAnswer() ?? "nope"
11-
let _: String = await slowServer.findAnswerFailingly() ?? "nope"
12-
// FIXME: expected-error@-2{{call can throw, but it is not marked with 'try'}}
13-
// FIXME: expected-error@-2{{call can throw, but it is not marked with 'try'}}
10+
let _: String = try await slowServer.findAnswer() ?? "nope"
11+
let _: String = await try slowServer.findAnswerFailingly() ?? "nope"
1412
let _: Void = await slowServer.doSomethingFun("jump")
1513
let _: (Int) -> Void = slowServer.completionHandler
1614
}

test/expr/unary/async_await.swift

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency
1+
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency -verify-syntax-tree
22

33
func test1(asyncfp : () async -> Int, fp : () -> Int) async {
44
_ = await asyncfp()
@@ -72,3 +72,64 @@ func testClosure() {
7272

7373
let _: () -> Int = closure2 // expected-error{{cannot convert value of type '() async -> Int' to specified type '() -> Int'}}
7474
}
75+
76+
// Nesting async and await together
77+
func throwingAndAsync() async throws -> Int { return 0 }
78+
79+
enum HomeworkError : Error {
80+
case dogAteIt
81+
}
82+
83+
func testThrowingAndAsync() async throws {
84+
_ = await try throwingAndAsync()
85+
_ = try await throwingAndAsync()
86+
_ = await (try throwingAndAsync())
87+
_ = try (await throwingAndAsync())
88+
89+
// Errors
90+
_ = await throwingAndAsync() // expected-error{{call can throw but is not marked with 'try'}}
91+
// expected-note@-1{{did you mean to use 'try'?}}
92+
// expected-note@-2{{did you mean to handle error as optional value?}}
93+
// expected-note@-3{{did you mean to disable error propagation?}}
94+
_ = try throwingAndAsync() // expected-error{{call is 'async' but is not marked with 'await'}}
95+
}
96+
97+
func testExhaustiveDoCatch() async {
98+
do {
99+
_ = await try throwingAndAsync()
100+
} catch {
101+
}
102+
103+
do {
104+
_ = await try throwingAndAsync()
105+
// expected-error@-1{{errors thrown from here are not handled because the enclosing catch is not exhaustive}}
106+
} catch let e as HomeworkError {
107+
}
108+
109+
// Ensure that we infer 'async' through an exhaustive do-catch.
110+
let fn = {
111+
do {
112+
_ = await try throwingAndAsync()
113+
} catch {
114+
}
115+
}
116+
117+
let _: Int = fn // expected-error{{cannot convert value of type '() async -> ()'}}
118+
119+
// Ensure that we infer 'async' through a non-exhaustive do-catch.
120+
let fn2 = {
121+
do {
122+
_ = await try throwingAndAsync()
123+
} catch let e as HomeworkError {
124+
}
125+
}
126+
127+
let _: Int = fn2 // expected-error{{cannot convert value of type '() async throws -> ()'}}
128+
}
129+
130+
// String interpolation
131+
func testStringInterpolation() async throws {
132+
_ = "Eventually produces \(getInt())" // expected-error{{call is 'async' but is not marked with 'await'}}
133+
_ = "Eventually produces \(await getInt())"
134+
_ = await "Eventually produces \(getInt())"
135+
}

0 commit comments

Comments
 (0)