Skip to content

Commit f5e672e

Browse files
authored
Merge pull request swiftlang#37216 from xedin/rdar-70610141
[Diagnostics] Improve diagnostics when passing `async` to a sync parameter
2 parents 0cca70b + c842773 commit f5e672e

File tree

6 files changed

+99
-57
lines changed

6 files changed

+99
-57
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,13 @@ ERROR(async_functiontype_mismatch,none,
559559
"invalid conversion from 'async' function of type %0 to "
560560
"synchronous function type %1", (Type, Type))
561561

562+
ERROR(cannot_pass_async_func_to_sync_parameter,none,
563+
"cannot pass function of type %0 "
564+
"to parameter expecting synchronous function type", (Type))
565+
566+
NOTE(async_inferred_from_operation,none,
567+
"'async' inferred from asynchronous operation used here", ())
568+
562569
// Key-path expressions.
563570
ERROR(expr_keypath_no_objc_runtime,none,
564571
"'#keyPath' can only be used with the Objective-C runtime", ())

include/swift/Sema/ConstraintSystem.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5593,6 +5593,11 @@ Type getConcreteReplacementForProtocolSelfType(ValueDecl *member);
55935593
/// of operator overload choices.
55945594
bool isOperatorDisjunction(Constraint *disjunction);
55955595

5596+
/// Find out whether closure body has any `async` or `await` expressions,
5597+
/// declarations, or statements directly in its body (no in other closures
5598+
/// or nested declarations).
5599+
ASTNode findAsyncNode(ClosureExpr *closure);
5600+
55965601
} // end namespace constraints
55975602

55985603
template<typename ...Args>

lib/Sema/CSDiagnostics.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5884,6 +5884,27 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
58845884
}
58855885

58865886
bool AsyncFunctionConversionFailure::diagnoseAsError() {
5887+
auto *locator = getLocator();
5888+
5889+
if (locator->isLastElement<LocatorPathElt::ApplyArgToParam>()) {
5890+
emitDiagnostic(diag::cannot_pass_async_func_to_sync_parameter,
5891+
getFromType());
5892+
5893+
if (auto *closure = getAsExpr<ClosureExpr>(getAnchor())) {
5894+
auto asyncLoc = closure->getAsyncLoc();
5895+
5896+
// 'async' effect is inferred from the body of the closure.
5897+
if (asyncLoc.isInvalid()) {
5898+
if (auto asyncNode = findAsyncNode(closure)) {
5899+
emitDiagnosticAt(::getLoc(asyncNode),
5900+
diag::async_inferred_from_operation);
5901+
}
5902+
}
5903+
}
5904+
5905+
return true;
5906+
}
5907+
58875908
emitDiagnostic(diag::async_functiontype_mismatch, getFromType(),
58885909
getToType());
58895910
return true;

lib/Sema/ConstraintSystem.cpp

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,52 +2486,6 @@ FunctionType::ExtInfo ConstraintSystem::closureEffects(ClosureExpr *expr) {
24862486
bool foundThrow() { return FoundThrow; }
24872487
};
24882488

2489-
// A walker that looks for 'async' and 'await' expressions
2490-
// that aren't nested within closures or nested declarations.
2491-
class FindInnerAsync : public ASTWalker {
2492-
bool FoundAsync = false;
2493-
2494-
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
2495-
// If we've found an 'await', record it and terminate the traversal.
2496-
if (isa<AwaitExpr>(expr)) {
2497-
FoundAsync = true;
2498-
return { false, nullptr };
2499-
}
2500-
2501-
// Do not recurse into other closures.
2502-
if (isa<ClosureExpr>(expr))
2503-
return { false, expr };
2504-
2505-
return { true, expr };
2506-
}
2507-
2508-
bool walkToDeclPre(Decl *decl) override {
2509-
// Do not walk into function or type declarations.
2510-
if (auto *patternBinding = dyn_cast<PatternBindingDecl>(decl)) {
2511-
if (patternBinding->isSpawnLet())
2512-
FoundAsync = true;
2513-
2514-
return true;
2515-
}
2516-
2517-
return false;
2518-
}
2519-
2520-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
2521-
if (auto forEach = dyn_cast<ForEachStmt>(stmt)) {
2522-
if (forEach->getAwaitLoc().isValid()) {
2523-
FoundAsync = true;
2524-
return { false, nullptr };
2525-
}
2526-
}
2527-
2528-
return { true, stmt };
2529-
}
2530-
2531-
public:
2532-
bool foundAsync() { return FoundAsync; }
2533-
};
2534-
25352489
// If either 'throws' or 'async' was explicitly specified, use that
25362490
// set of effects.
25372491
bool throws = expr->getThrowsLoc().isValid();
@@ -2552,13 +2506,11 @@ FunctionType::ExtInfo ConstraintSystem::closureEffects(ClosureExpr *expr) {
25522506

25532507
auto throwFinder = FindInnerThrows(*this, expr);
25542508
body->walk(throwFinder);
2555-
auto asyncFinder = FindInnerAsync();
2556-
body->walk(asyncFinder);
25572509
auto result = ASTExtInfoBuilder()
2558-
.withThrows(throwFinder.foundThrow())
2559-
.withAsync(asyncFinder.foundAsync())
2560-
.withConcurrent(concurrent)
2561-
.build();
2510+
.withThrows(throwFinder.foundThrow())
2511+
.withAsync(bool(findAsyncNode(expr)))
2512+
.withConcurrent(concurrent)
2513+
.build();
25622514
closureEffectsCache[expr] = result;
25632515
return result;
25642516
}
@@ -5708,3 +5660,60 @@ bool constraints::isOperatorDisjunction(Constraint *disjunction) {
57085660
auto *decl = getOverloadChoiceDecl(choices.front());
57095661
return decl ? decl->isOperator() : false;
57105662
}
5663+
5664+
ASTNode constraints::findAsyncNode(ClosureExpr *closure) {
5665+
auto *body = closure->getBody();
5666+
if (!body)
5667+
return ASTNode();
5668+
5669+
// A walker that looks for 'async' and 'await' expressions
5670+
// that aren't nested within closures or nested declarations.
5671+
class FindInnerAsync : public ASTWalker {
5672+
ASTNode AsyncNode;
5673+
5674+
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
5675+
// If we've found an 'await', record it and terminate the traversal.
5676+
if (isa<AwaitExpr>(expr)) {
5677+
AsyncNode = expr;
5678+
return {false, nullptr};
5679+
}
5680+
5681+
// Do not recurse into other closures.
5682+
if (isa<ClosureExpr>(expr))
5683+
return {false, expr};
5684+
5685+
return {true, expr};
5686+
}
5687+
5688+
bool walkToDeclPre(Decl *decl) override {
5689+
// Do not walk into function or type declarations.
5690+
if (auto *patternBinding = dyn_cast<PatternBindingDecl>(decl)) {
5691+
if (patternBinding->isSpawnLet())
5692+
AsyncNode = patternBinding;
5693+
5694+
return true;
5695+
}
5696+
5697+
return false;
5698+
}
5699+
5700+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
5701+
if (auto forEach = dyn_cast<ForEachStmt>(stmt)) {
5702+
if (forEach->getAwaitLoc().isValid()) {
5703+
AsyncNode = forEach;
5704+
return {false, nullptr};
5705+
}
5706+
}
5707+
5708+
return {true, stmt};
5709+
}
5710+
5711+
public:
5712+
ASTNode getAsyncNode() { return AsyncNode; }
5713+
};
5714+
5715+
FindInnerAsync asyncFinder;
5716+
body->walk(asyncFinder);
5717+
5718+
return asyncFinder.getAsyncNode();
5719+
}

test/Concurrency/async_sequence_syntax.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ func missingTryInBlock<T : AsyncSequence>(_ seq: T) {
3333

3434
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
3535
func missingAsyncInBlock<T : AsyncSequence>(_ seq: T) {
36-
execute { // expected-error{{invalid conversion from 'async' function of type '() async -> Void' to synchronous function type '() -> Void'}}
36+
execute { // expected-error{{cannot pass function of type '() async -> Void' to parameter expecting synchronous function type}}
3737
do {
38-
for try await _ in seq { }
38+
for try await _ in seq { } // expected-note {{'async' inferred from asynchronous operation used here}}
3939
} catch { }
4040
}
4141
}
@@ -81,4 +81,4 @@ func forAwaitWithConcreteType(_ seq: ThrowingAsyncSequence) throws { // expected
8181
for try await elt in seq { // expected-error {{'async' in a function that does not support concurrency}}
8282
_ = elt
8383
}
84-
}
84+
}

test/Concurrency/async_tasks.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ func buyVegetables(shoppingList: [String]) async throws -> [Vegetable] {
4747
func test_unsafeContinuations() async {
4848
// the closure should not allow async operations;
4949
// after all: if you have async code, just call it directly, without the unsafe continuation
50-
let _: String = withUnsafeContinuation { continuation in // expected-error{{invalid conversion from 'async' function of type '(UnsafeContinuation<String, Never>) async -> Void' to synchronous function type '(UnsafeContinuation<String, Never>) -> Void'}}
51-
let s = await someAsyncFunc() // rdar://70610141 for getting a better error message here
50+
let _: String = withUnsafeContinuation { continuation in // expected-error{{cannot pass function of type '(UnsafeContinuation<String, Never>) async -> Void' to parameter expecting synchronous function type}}
51+
let s = await someAsyncFunc() // expected-note {{'async' inferred from asynchronous operation used here}}
5252
continuation.resume(returning: s)
5353
}
5454

0 commit comments

Comments
 (0)