Skip to content

Commit fe1df6e

Browse files
authored
[5.5][Diagnostics] Improve diagnostics when passing async to a sync parameter (#37273)
* [ConstraintSystem] NFC: Extract async node identification into a function It's useful not only for effect determination but for diagnostics as well. * [Diagnostics] Improve diagnostics when passing `async` to a sync parameter If `async` effect has been inferred from the body of the closure, let's find out the first occurrence of `async` node and point it out to make it clear why closure is `async`. Resolves: rdar://70610141 * [Diagnostics] Re-phrase note about inferred async effect from an operation in a closure body
1 parent 9b93699 commit fe1df6e

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
@@ -5595,6 +5595,11 @@ Type getConcreteReplacementForProtocolSelfType(ValueDecl *member);
55955595
/// of operator overload choices.
55965596
bool isOperatorDisjunction(Constraint *disjunction);
55975597

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

56005605
template<typename ...Args>

lib/Sema/CSDiagnostics.cpp

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

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

lib/Sema/ConstraintSystem.cpp

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

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

25622516
auto throwFinder = FindInnerThrows(*this, expr);
25632517
body->walk(throwFinder);
2564-
auto asyncFinder = FindInnerAsync();
2565-
body->walk(asyncFinder);
25662518
auto result = ASTExtInfoBuilder()
2567-
.withThrows(throwFinder.foundThrow())
2568-
.withAsync(asyncFinder.foundAsync())
2569-
.withConcurrent(concurrent)
2570-
.build();
2519+
.withThrows(throwFinder.foundThrow())
2520+
.withAsync(bool(findAsyncNode(expr)))
2521+
.withConcurrent(concurrent)
2522+
.build();
25712523
closureEffectsCache[expr] = result;
25722524
return result;
25732525
}
@@ -5723,3 +5675,60 @@ bool constraints::isOperatorDisjunction(Constraint *disjunction) {
57235675
auto *decl = getOverloadChoiceDecl(choices.front());
57245676
return decl ? decl->isOperator() : false;
57255677
}
5678+
5679+
ASTNode constraints::findAsyncNode(ClosureExpr *closure) {
5680+
auto *body = closure->getBody();
5681+
if (!body)
5682+
return ASTNode();
5683+
5684+
// A walker that looks for 'async' and 'await' expressions
5685+
// that aren't nested within closures or nested declarations.
5686+
class FindInnerAsync : public ASTWalker {
5687+
ASTNode AsyncNode;
5688+
5689+
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
5690+
// If we've found an 'await', record it and terminate the traversal.
5691+
if (isa<AwaitExpr>(expr)) {
5692+
AsyncNode = expr;
5693+
return {false, nullptr};
5694+
}
5695+
5696+
// Do not recurse into other closures.
5697+
if (isa<ClosureExpr>(expr))
5698+
return {false, expr};
5699+
5700+
return {true, expr};
5701+
}
5702+
5703+
bool walkToDeclPre(Decl *decl) override {
5704+
// Do not walk into function or type declarations.
5705+
if (auto *patternBinding = dyn_cast<PatternBindingDecl>(decl)) {
5706+
if (patternBinding->isSpawnLet())
5707+
AsyncNode = patternBinding;
5708+
5709+
return true;
5710+
}
5711+
5712+
return false;
5713+
}
5714+
5715+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
5716+
if (auto forEach = dyn_cast<ForEachStmt>(stmt)) {
5717+
if (forEach->getAwaitLoc().isValid()) {
5718+
AsyncNode = forEach;
5719+
return {false, nullptr};
5720+
}
5721+
}
5722+
5723+
return {true, stmt};
5724+
}
5725+
5726+
public:
5727+
ASTNode getAsyncNode() { return AsyncNode; }
5728+
};
5729+
5730+
FindInnerAsync asyncFinder;
5731+
body->walk(asyncFinder);
5732+
5733+
return asyncFinder.getAsyncNode();
5734+
}

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)