Skip to content

Commit fffedc7

Browse files
committed
Update error messages
This patch restructures how we emit the effect errors for missing awaits. As we're traversing the expressions, we collect all of the locations where a missing await is, and metadata about the reason for the error, as well as the "anchor". The anchor is where the await should actually be placed in the expression to fix things. The error is emitted for the whole sub-expression instead of for the exact synchronization/call-site of the async function. This avoids the erroneous message for ``` _ = 32 + asyncFunc() ``` The old behaviour was to put the `await` between the `+` operator and the call to `asyncFunc()`, but this is not a valid place to put effects. Note; this issue is also present in the fix-it for `try` insertions and also needs to be fixed. Instead, the anchor points between the assignment expression and 32, labelling the whole right-side of the assignment as being the asynchronous function. We emit notes for each uncovered async point in the entire expression though, so that someone can see why the expression is async.
1 parent 3ece004 commit fffedc7

File tree

2 files changed

+138
-76
lines changed

2 files changed

+138
-76
lines changed

Diff for: include/swift/AST/DiagnosticsSema.def

+13-13
Original file line numberDiff line numberDiff line change
@@ -4253,17 +4253,17 @@ WARNING(no_throw_in_do_with_catch,none,
42534253
//------------------------------------------------------------------------------
42544254
// MARK: Concurrency
42554255
//------------------------------------------------------------------------------
4256-
ERROR(async_call_without_await,none,
4257-
"call is 'async' but is not marked with 'await'", ())
4258-
ERROR(async_call_without_await_in_autoclosure,none,
4259-
"call is 'async' in an autoclosure argument that is not marked with 'await'", ())
4260-
ERROR(async_call_without_await_in_async_let,none,
4261-
"call is 'async' in an 'async let' initializer that is not marked "
4262-
"with 'await'", ())
4263-
ERROR(async_prop_access_without_await,none,
4264-
"property access is 'async' but is not marked with 'await'", ())
4265-
ERROR(async_subscript_access_without_await,none,
4266-
"subscript access is 'async' but is not marked with 'await'", ())
4256+
ERROR(async_expr_without_await,none,
4257+
"expression is 'async' but is not marked with 'await'", ())
4258+
4259+
NOTE(async_access_without_await,none,
4260+
"%select{call|property access|subscript access|}0 is 'async'", (unsigned))
4261+
4262+
NOTE(async_call_without_await_in_autoclosure,none,
4263+
"call is 'async' in an autoclosure argument", ())
4264+
NOTE(async_call_without_await_in_async_let,none,
4265+
"call is 'async' in an 'async let' initializer", ())
4266+
42674267
WARNING(no_async_in_await,none,
42684268
"no 'async' operations occur within 'await' expression", ())
42694269
ERROR(async_call_in_illegal_context,none,
@@ -4310,8 +4310,8 @@ ERROR(async_let_not_initialized,none,
43104310
"'async let' binding requires an initializer expression", ())
43114311
ERROR(async_let_no_variables,none,
43124312
"'async let' requires at least one named variable", ())
4313-
ERROR(async_let_without_await,none,
4314-
"reference to async let %0 is not marked with 'await'", (DeclName))
4313+
NOTE(async_let_without_await,none,
4314+
"reference to async let %0 is 'async'", (DeclName))
43154315
ERROR(async_let_in_illegal_context,none,
43164316
"async let %0 cannot be referenced in "
43174317
"%select{<<ERROR>>|a default argument|a property wrapper initializer|a property initializer|a global variable initializer|an enum case raw value|a catch pattern|a catch guard expression|a defer body}1",

Diff for: lib/Sema/TypeCheckEffects.cpp

+125-63
Original file line numberDiff line numberDiff line change
@@ -1907,67 +1907,6 @@ class Context {
19071907
}
19081908
}
19091909

1910-
void diagnoseUncoveredAsyncSite(ASTContext &ctx, ASTNode node,
1911-
PotentialEffectReason reason) {
1912-
SourceRange highlight = node.getSourceRange();
1913-
auto diag = diag::async_call_without_await;
1914-
1915-
switch (reason.getKind()) {
1916-
case PotentialEffectReason::Kind::AsyncLet:
1917-
// Reference to an 'async let' missing an 'await'.
1918-
if (auto declR = dyn_cast_or_null<DeclRefExpr>(node.dyn_cast<Expr*>())) {
1919-
if (auto var = dyn_cast<VarDecl>(declR->getDecl())) {
1920-
if (var->isAsyncLet()) {
1921-
ctx.Diags.diagnose(declR->getLoc(), diag::async_let_without_await,
1922-
var->getName());
1923-
return;
1924-
}
1925-
}
1926-
}
1927-
LLVM_FALLTHROUGH; // fallthrough to a message about property access
1928-
1929-
case PotentialEffectReason::Kind::PropertyAccess:
1930-
diag = diag::async_prop_access_without_await;
1931-
break;
1932-
1933-
case PotentialEffectReason::Kind::SubscriptAccess:
1934-
diag = diag::async_subscript_access_without_await;
1935-
break;
1936-
1937-
case PotentialEffectReason::Kind::ByClosure:
1938-
case PotentialEffectReason::Kind::ByDefaultClosure:
1939-
case PotentialEffectReason::Kind::ByConformance:
1940-
case PotentialEffectReason::Kind::Apply: {
1941-
if (Function) {
1942-
// To produce a better error message, check if it is an autoclosure.
1943-
// We do not use 'Context::isAutoClosure' b/c it gives conservative
1944-
// answers.
1945-
if (auto autoclosure = dyn_cast_or_null<AutoClosureExpr>(
1946-
Function->getAbstractClosureExpr())) {
1947-
switch (autoclosure->getThunkKind()) {
1948-
case AutoClosureExpr::Kind::None:
1949-
diag = diag::async_call_without_await_in_autoclosure;
1950-
break;
1951-
1952-
case AutoClosureExpr::Kind::AsyncLet:
1953-
diag = diag::async_call_without_await_in_async_let;
1954-
break;
1955-
1956-
case AutoClosureExpr::Kind::SingleCurryThunk:
1957-
case AutoClosureExpr::Kind::DoubleCurryThunk:
1958-
break;
1959-
}
1960-
}
1961-
}
1962-
break;
1963-
}
1964-
};
1965-
1966-
ctx.Diags.diagnose(node.getStartLoc(), diag)
1967-
.fixItInsert(node.getStartLoc(), "await ")
1968-
.highlight(highlight);
1969-
}
1970-
19711910
void diagnoseAsyncInIllegalContext(DiagnosticEngine &Diags, ASTNode node) {
19721911
if (auto *e = node.dyn_cast<Expr*>()) {
19731912
if (isa<ApplyExpr>(e)) {
@@ -2119,8 +2058,43 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
21192058
/// context.
21202059
ConditionalEffectKind MaxThrowingKind;
21212060

2061+
struct DiagnosticInfo {
2062+
DiagnosticInfo(Expr &failingExpr,
2063+
PotentialEffectReason reason) :
2064+
reason(reason),
2065+
expr(failingExpr) {}
2066+
2067+
/// Reason for throwing
2068+
PotentialEffectReason reason;
2069+
2070+
/// Failing expression
2071+
Expr &expr;
2072+
};
2073+
2074+
SmallVector<Expr *, 4> errorOrder;
2075+
llvm::DenseMap<Expr *, std::vector<DiagnosticInfo>> uncoveredAsync;
21222076
llvm::DenseMap<Expr *, Expr *> parentMap;
21232077

2078+
static bool isEffectAnchor(Expr *e) {
2079+
return isa<AbstractClosureExpr>(e) || isa<DiscardAssignmentExpr>(e) || isa<AssignExpr>(e);
2080+
}
2081+
2082+
static bool isAnchorTooEarly(Expr *e) {
2083+
return isa<AssignExpr>(e) || isa<DiscardAssignmentExpr>(e);
2084+
}
2085+
2086+
/// Find the top location where we should put the await
2087+
static Expr* walkToAnchor(Expr *e,
2088+
llvm::DenseMap<Expr *, Expr*> &parentMap) {
2089+
Expr *parent = e;
2090+
Expr *lastParent = e;
2091+
while (parent && !isEffectAnchor(parent)) {
2092+
lastParent = parent;
2093+
parent = parentMap[parent];
2094+
}
2095+
return parent && !isAnchorTooEarly(parent) ? parent : lastParent;
2096+
}
2097+
21242098
void flagInvalidCode() {
21252099
// Suppress warnings about useless try or catch.
21262100
Flags.set(ContextFlags::HasAnyThrowSite);
@@ -2282,6 +2256,13 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
22822256
}
22832257
}
22842258

2259+
~CheckEffectsCoverage() {
2260+
for (Expr *anchor: errorOrder) {
2261+
diagnoseUncoveredAsyncSite(anchor);
2262+
}
2263+
}
2264+
2265+
22852266
/// Mark that the current context is top-level code with
22862267
/// throw-without-try enabled.
22872268
void setTopLevelThrowWithoutTry() {
@@ -2638,8 +2619,17 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
26382619
}
26392620
// Diagnose async calls that are outside of an await context.
26402621
else if (!Flags.has(ContextFlags::IsAsyncCovered)) {
2641-
CurContext.diagnoseUncoveredAsyncSite(Ctx, E,
2642-
classification.getAsyncReason());
2622+
Expr *expr = E.dyn_cast<Expr*>();
2623+
Expr *anchor = walkToAnchor(expr, parentMap);
2624+
2625+
auto key = uncoveredAsync.find(anchor);
2626+
if (key == uncoveredAsync.end()) {
2627+
uncoveredAsync.insert({anchor, {}});
2628+
errorOrder.push_back(anchor);
2629+
}
2630+
uncoveredAsync[anchor].emplace_back(
2631+
*expr,
2632+
classification.getAsyncReason());
26432633
}
26442634
}
26452635

@@ -2788,6 +2778,78 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27882778

27892779
return ShouldRecurse;
27902780
}
2781+
2782+
void diagnoseUncoveredAsyncSite(const Expr *anchor) const {
2783+
auto asyncPointIter = uncoveredAsync.find(anchor);
2784+
if (asyncPointIter == uncoveredAsync.end())
2785+
return;
2786+
const std::vector<DiagnosticInfo> &errors = asyncPointIter->getSecond();
2787+
SourceLoc awaitInsertLoc = anchor->getStartLoc();
2788+
if (const AnyTryExpr *tryExpr = dyn_cast<AnyTryExpr>(anchor))
2789+
awaitInsertLoc = tryExpr->getSubExpr()->getStartLoc();
2790+
else if (const AutoClosureExpr *autoClosure = dyn_cast<AutoClosureExpr>(anchor)) {
2791+
if (const AnyTryExpr *tryExpr = dyn_cast<AnyTryExpr>(autoClosure->getSingleExpressionBody()))
2792+
awaitInsertLoc = tryExpr->getSubExpr()->getStartLoc();
2793+
}
2794+
2795+
Ctx.Diags.diagnose(anchor->getStartLoc(), diag::async_expr_without_await)
2796+
.fixItInsert(awaitInsertLoc, "await ")
2797+
.highlight(anchor->getSourceRange());
2798+
2799+
for (const DiagnosticInfo &diag: errors) {
2800+
switch (diag.reason.getKind()) {
2801+
case PotentialEffectReason::Kind::AsyncLet:
2802+
if (auto declR = dyn_cast<DeclRefExpr>(&diag.expr)) {
2803+
if (auto var = dyn_cast<VarDecl>(declR->getDecl())) {
2804+
if (var->isAsyncLet()) {
2805+
Ctx.Diags.diagnose(declR->getLoc(),
2806+
diag::async_let_without_await,
2807+
var->getName());
2808+
continue;
2809+
}
2810+
}
2811+
}
2812+
LLVM_FALLTHROUGH; // fallthrough to a message about PropertyAccess
2813+
case PotentialEffectReason::Kind::PropertyAccess:
2814+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2815+
diag::async_access_without_await, 1);
2816+
continue;
2817+
2818+
case PotentialEffectReason::Kind::SubscriptAccess:
2819+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2820+
diag::async_access_without_await, 2);
2821+
continue;
2822+
2823+
case PotentialEffectReason::Kind::ByClosure:
2824+
case PotentialEffectReason::Kind::ByDefaultClosure:
2825+
case PotentialEffectReason::Kind::ByConformance:
2826+
case PotentialEffectReason::Kind::Apply: {
2827+
if (auto autoclosure = dyn_cast<AutoClosureExpr>(anchor)) {
2828+
switch(autoclosure->getThunkKind()) {
2829+
case AutoClosureExpr::Kind::None:
2830+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2831+
diag::async_call_without_await_in_autoclosure);
2832+
break;
2833+
case AutoClosureExpr::Kind::AsyncLet:
2834+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2835+
diag::async_call_without_await_in_async_let);
2836+
break;
2837+
case AutoClosureExpr::Kind::SingleCurryThunk:
2838+
case AutoClosureExpr::Kind::DoubleCurryThunk:
2839+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2840+
diag::async_access_without_await, 0);
2841+
break;
2842+
}
2843+
continue;
2844+
}
2845+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2846+
diag::async_access_without_await, 0);
2847+
2848+
continue;
2849+
}
2850+
}
2851+
}
2852+
}
27912853
};
27922854

27932855
// Find nested functions and perform effects checking on them.

0 commit comments

Comments
 (0)