Skip to content

Commit dfee59e

Browse files
committed
Emit warning suggesting async function
This patch adds a diagnostic to recommend using the async function instead of using the completion handler version. The diagnostic is only emitted when the call is made in an async context and the completion-handler version of the function is attributed with the @completionHandleyAsync attribute.
1 parent bdd7d83 commit dfee59e

File tree

5 files changed

+72
-3
lines changed

5 files changed

+72
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3377,6 +3377,9 @@ ERROR(attr_completion_handler_async_ambiguous_function,none,
33773377
ERROR(attr_completion_handler_async_no_suitable_function,none,
33783378
"no corresponding async function named %0", (DeclNameRef))
33793379

3380+
WARNING(warn_use_async_alternative,none,
3381+
"consider using asynchronous alternative function",())
3382+
33803383
//------------------------------------------------------------------------------
33813384
// MARK: Type Check Expressions
33823385
//------------------------------------------------------------------------------

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3087,3 +3087,38 @@ NormalProtocolConformance *GetImplicitSendableRequest::evaluate(
30873087

30883088
return conformance;
30893089
}
3090+
3091+
namespace {
3092+
class CompletionHandlerUsageChecker final : public ASTWalker {
3093+
ASTContext &ctx;
3094+
3095+
public:
3096+
CompletionHandlerUsageChecker(ASTContext &ctx) : ctx(ctx) {}
3097+
3098+
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
3099+
if (ApplyExpr *call = dyn_cast<ApplyExpr>(expr)) {
3100+
if (DeclRefExpr *fnDeclExpr = dyn_cast<DeclRefExpr>(call->getFn())) {
3101+
ValueDecl *fnDecl = fnDeclExpr->getDecl();
3102+
CompletionHandlerAsyncAttr *asyncAltAttr =
3103+
fnDecl->getAttrs().getAttribute<CompletionHandlerAsyncAttr>();
3104+
if (asyncAltAttr && asyncAltAttr->AsyncFunctionDecl) {
3105+
ctx.Diags.diagnose(call->getLoc(), diag::warn_use_async_alternative);
3106+
ctx.Diags.diagnose(asyncAltAttr->AsyncFunctionDecl->getLoc(),
3107+
diag::decl_declared_here,
3108+
asyncAltAttr->AsyncFunctionDecl->getName());
3109+
}
3110+
}
3111+
}
3112+
return {true, expr};
3113+
}
3114+
};
3115+
} // namespace
3116+
3117+
void swift::checkFunctionAsyncUsage(AbstractFunctionDecl *decl) {
3118+
if (!decl->isAsyncContext())
3119+
return;
3120+
CompletionHandlerUsageChecker checker(decl->getASTContext());
3121+
BraceStmt *body = decl->getBody();
3122+
if (body)
3123+
body->walk(checker);
3124+
}

lib/Sema/TypeCheckConcurrency.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ Type getExplicitGlobalActor(ClosureExpr *closure);
239239
bool checkSendableConformance(
240240
ProtocolConformance *conformance, SendableCheck check);
241241

242+
/// Check that we use the async version of a function where available
243+
///
244+
/// If a completion-handler function is called from an async context and it has
245+
/// a '@completionHandlerAsync' attribute, we emit a diagnostic suggesting the
246+
/// async call.
247+
void checkFunctionAsyncUsage(AbstractFunctionDecl *decl);
248+
242249
} // end namespace swift
243250

244251
#endif /* SWIFT_SEMA_TYPECHECKCONCURRENCY_H */

lib/Sema/TypeCheckStmt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,7 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
20382038
TypeChecker::computeCaptures(AFD);
20392039
if (!AFD->getDeclContext()->isLocalContext()) {
20402040
checkFunctionActorIsolation(AFD);
2041+
checkFunctionAsyncUsage(AFD);
20412042
TypeChecker::checkFunctionEffects(AFD);
20422043
}
20432044

test/attr/attr_completionhandlerasync.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
// Parsing
77
// ===================
88

9-
func asyncFunc() async -> Int { }
9+
// expected-note@+1{{'asyncFunc' declared here}}
10+
func asyncFunc(_ text: String) async -> Int { }
1011

11-
@completionHandlerAsync("asyncFunc()", completionHandlerIndex: 1)
12+
@completionHandlerAsync("asyncFunc(_:)", completionHandlerIndex: 1)
1213
func goodFunc1(value: String, completionHandler: @escaping (Int) -> Void) {}
1314

14-
@completionHandlerAsync("asyncFunc()")
15+
@completionHandlerAsync("asyncFunc(_:)")
1516
func goodFunc2(value: String, completionHandler: @escaping (Int) -> Void) {}
1617

18+
// expected-error@+1{{no corresponding async function named 'asyncFunc()'}}
19+
@completionHandlerAsync("asyncFunc()")
20+
func badFunc(value: String, completionHandler: @escaping (Int) -> Void) {}
21+
1722
// expected-error@+1:24{{expected '(' in 'completionHandlerAsync' attribute}}
1823
@completionHandlerAsync
1924
func func1() {}
@@ -137,3 +142,21 @@ func matchingAsyncFunc(value: String) async {} // expected-note{{'matchingAsyncF
137142
// expected-error@+1:25{{ambiguous '@completionHandlerAsync' async function 'matchingAsyncFunc(value:)'}}
138143
@completionHandlerAsync("matchingAsyncFunc(value:)")
139144
func typecheckFunc9(handler: @escaping () -> Void) {}
145+
146+
// Suggest using async alternative function in async context
147+
148+
149+
func asyncContext() async {
150+
// expected-warning@+1:3{{consider using asynchronous alternative}}
151+
goodFunc1(value: "Hello") { _ in }
152+
153+
let _ = await asyncFunc("World")
154+
155+
// This doesn't get the warning because the completionHandlerAsync failed to
156+
// resolve the decl name
157+
badFunc(value: "Hello") { _ in }
158+
}
159+
160+
func syncContext() {
161+
goodFunc1(value: "Hello") { _ in }
162+
}

0 commit comments

Comments
 (0)