Skip to content

Commit d0d9bef

Browse files
authored
Merge pull request #37056 from etcwilde/ewilde/fix-await-fixits
[Concurrency] Fix await fix-it placement
2 parents d9ef216 + e9baa3f commit d0d9bef

17 files changed

+434
-207
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 13 deletions
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",

lib/Sema/TypeCheckEffects.cpp

Lines changed: 169 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ class EffectsHandlingWalker : public ASTWalker {
427427
}
428428

429429
std::pair<bool, Expr*> walkToExprPre(Expr *E) override {
430+
visitExprPre(E);
430431
ShouldRecurse_t recurse = ShouldRecurse;
431432
if (isa<ErrorExpr>(E)) {
432433
asImpl().flagInvalidCode();
@@ -486,6 +487,8 @@ class EffectsHandlingWalker : public ASTWalker {
486487
ShouldRecurse_t checkForEach(ForEachStmt *S) {
487488
return ShouldRecurse;
488489
}
490+
491+
void visitExprPre(Expr *expr) { asImpl().visitExprPre(expr); }
489492
};
490493

491494
/// A potential reason why something might have an effect.
@@ -1098,6 +1101,8 @@ class ApplyClassifier {
10981101
// guaranteed to give back None, which leaves our ThrowKind unchanged.
10991102
}
11001103
}
1104+
1105+
void visitExprPre(Expr *expr) { return; }
11011106
};
11021107

11031108
class FunctionAsyncClassifier
@@ -1182,6 +1187,8 @@ class ApplyClassifier {
11821187

11831188
return ShouldRecurse;
11841189
}
1190+
1191+
void visitExprPre(Expr *expr) { return; }
11851192
};
11861193

11871194
Optional<ConditionalEffectKind>
@@ -1429,6 +1436,11 @@ class Context {
14291436
DiagnoseErrorOnTry = b;
14301437
}
14311438

1439+
/// Return true when the current context is under an interpolated string
1440+
bool isWithinInterpolatedString() const {
1441+
return InterpolatedString != nullptr;
1442+
}
1443+
14321444
/// Stores the location of the innermost await
14331445
SourceLoc awaitLoc = SourceLoc();
14341446

@@ -1900,67 +1912,6 @@ class Context {
19001912
}
19011913
}
19021914

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

2066+
struct DiagnosticInfo {
2067+
DiagnosticInfo(Expr &failingExpr,
2068+
PotentialEffectReason reason) :
2069+
reason(reason),
2070+
expr(failingExpr) {}
2071+
2072+
/// Reason for throwing
2073+
PotentialEffectReason reason;
2074+
2075+
/// Failing expression
2076+
Expr &expr;
2077+
};
2078+
2079+
SmallVector<Expr *, 4> errorOrder;
2080+
llvm::DenseMap<Expr *, std::vector<DiagnosticInfo>> uncoveredAsync;
2081+
llvm::DenseMap<Expr *, Expr *> parentMap;
2082+
2083+
static bool isEffectAnchor(Expr *e) {
2084+
return isa<AbstractClosureExpr>(e) || isa<DiscardAssignmentExpr>(e) || isa<AssignExpr>(e);
2085+
}
2086+
2087+
static bool isAnchorTooEarly(Expr *e) {
2088+
return isa<AssignExpr>(e) || isa<DiscardAssignmentExpr>(e);
2089+
}
2090+
2091+
/// Find the top location where we should put the await
2092+
static Expr *walkToAnchor(Expr *e, llvm::DenseMap<Expr *, Expr *> &parentMap,
2093+
bool isInterpolatedString) {
2094+
Expr *parent = e;
2095+
Expr *lastParent = e;
2096+
while (parent && !isEffectAnchor(parent)) {
2097+
lastParent = parent;
2098+
parent = parentMap[parent];
2099+
}
2100+
2101+
if (parent && !isAnchorTooEarly(parent)) {
2102+
return parent;
2103+
}
2104+
2105+
if (isInterpolatedString) {
2106+
// TODO: I'm being gentle with the casts to avoid breaking things
2107+
// If we see incorrect fix-it locations in string interpolations
2108+
// we need to change how this behaves
2109+
// Assert builds will crash giving us a bug to fix, non-asserts will
2110+
// quietly "just work".
2111+
assert(parent == nullptr && "Expected to be at top of expression");
2112+
assert(isa<CallExpr>(lastParent) &&
2113+
"Expected top of string interpolation to be CalExpr");
2114+
assert(isa<ParenExpr>(dyn_cast<CallExpr>(lastParent)->getArg()) &&
2115+
"Expected paren expr in string interpolation call");
2116+
if (CallExpr *callExpr = dyn_cast<CallExpr>(lastParent)) {
2117+
if (ParenExpr *body = dyn_cast<ParenExpr>(callExpr->getArg())) {
2118+
return body->getSubExpr();
2119+
}
2120+
}
2121+
}
2122+
2123+
return lastParent;
2124+
}
2125+
21152126
void flagInvalidCode() {
21162127
// Suppress warnings about useless try or catch.
21172128
Flags.set(ContextFlags::HasAnyThrowSite);
@@ -2273,6 +2284,13 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
22732284
}
22742285
}
22752286

2287+
~CheckEffectsCoverage() {
2288+
for (Expr *anchor: errorOrder) {
2289+
diagnoseUncoveredAsyncSite(anchor);
2290+
}
2291+
}
2292+
2293+
22762294
/// Mark that the current context is top-level code with
22772295
/// throw-without-try enabled.
22782296
void setTopLevelThrowWithoutTry() {
@@ -2290,6 +2308,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
22902308
}
22912309

22922310
private:
2311+
void visitExprPre(Expr *expr) {
2312+
if (parentMap.count(expr) == 0)
2313+
parentMap = expr->getParentMap();
2314+
return;
2315+
}
2316+
22932317
ShouldRecurse_t checkClosure(ClosureExpr *E) {
22942318
ContextScope scope(*this, Context::forClosure(E));
22952319
scope.enterSubFunction();
@@ -2623,8 +2647,18 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
26232647
}
26242648
// Diagnose async calls that are outside of an await context.
26252649
else if (!Flags.has(ContextFlags::IsAsyncCovered)) {
2626-
CurContext.diagnoseUncoveredAsyncSite(Ctx, E,
2627-
classification.getAsyncReason());
2650+
Expr *expr = E.dyn_cast<Expr*>();
2651+
Expr *anchor = walkToAnchor(expr, parentMap,
2652+
CurContext.isWithinInterpolatedString());
2653+
2654+
auto key = uncoveredAsync.find(anchor);
2655+
if (key == uncoveredAsync.end()) {
2656+
uncoveredAsync.insert({anchor, {}});
2657+
errorOrder.push_back(anchor);
2658+
}
2659+
uncoveredAsync[anchor].emplace_back(
2660+
*expr,
2661+
classification.getAsyncReason());
26282662
}
26292663
}
26302664

@@ -2773,6 +2807,78 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27732807

27742808
return ShouldRecurse;
27752809
}
2810+
2811+
void diagnoseUncoveredAsyncSite(const Expr *anchor) const {
2812+
auto asyncPointIter = uncoveredAsync.find(anchor);
2813+
if (asyncPointIter == uncoveredAsync.end())
2814+
return;
2815+
const std::vector<DiagnosticInfo> &errors = asyncPointIter->getSecond();
2816+
SourceLoc awaitInsertLoc = anchor->getStartLoc();
2817+
if (const AnyTryExpr *tryExpr = dyn_cast<AnyTryExpr>(anchor))
2818+
awaitInsertLoc = tryExpr->getSubExpr()->getStartLoc();
2819+
else if (const AutoClosureExpr *autoClosure = dyn_cast<AutoClosureExpr>(anchor)) {
2820+
if (const AnyTryExpr *tryExpr = dyn_cast<AnyTryExpr>(autoClosure->getSingleExpressionBody()))
2821+
awaitInsertLoc = tryExpr->getSubExpr()->getStartLoc();
2822+
}
2823+
2824+
Ctx.Diags.diagnose(anchor->getStartLoc(), diag::async_expr_without_await)
2825+
.fixItInsert(awaitInsertLoc, "await ")
2826+
.highlight(anchor->getSourceRange());
2827+
2828+
for (const DiagnosticInfo &diag: errors) {
2829+
switch (diag.reason.getKind()) {
2830+
case PotentialEffectReason::Kind::AsyncLet:
2831+
if (auto declR = dyn_cast<DeclRefExpr>(&diag.expr)) {
2832+
if (auto var = dyn_cast<VarDecl>(declR->getDecl())) {
2833+
if (var->isAsyncLet()) {
2834+
Ctx.Diags.diagnose(declR->getLoc(),
2835+
diag::async_let_without_await,
2836+
var->getName());
2837+
continue;
2838+
}
2839+
}
2840+
}
2841+
LLVM_FALLTHROUGH; // fallthrough to a message about PropertyAccess
2842+
case PotentialEffectReason::Kind::PropertyAccess:
2843+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2844+
diag::async_access_without_await, 1);
2845+
continue;
2846+
2847+
case PotentialEffectReason::Kind::SubscriptAccess:
2848+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2849+
diag::async_access_without_await, 2);
2850+
continue;
2851+
2852+
case PotentialEffectReason::Kind::ByClosure:
2853+
case PotentialEffectReason::Kind::ByDefaultClosure:
2854+
case PotentialEffectReason::Kind::ByConformance:
2855+
case PotentialEffectReason::Kind::Apply: {
2856+
if (auto autoclosure = dyn_cast<AutoClosureExpr>(anchor)) {
2857+
switch(autoclosure->getThunkKind()) {
2858+
case AutoClosureExpr::Kind::None:
2859+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2860+
diag::async_call_without_await_in_autoclosure);
2861+
break;
2862+
case AutoClosureExpr::Kind::AsyncLet:
2863+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2864+
diag::async_call_without_await_in_async_let);
2865+
break;
2866+
case AutoClosureExpr::Kind::SingleCurryThunk:
2867+
case AutoClosureExpr::Kind::DoubleCurryThunk:
2868+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2869+
diag::async_access_without_await, 0);
2870+
break;
2871+
}
2872+
continue;
2873+
}
2874+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2875+
diag::async_access_without_await, 0);
2876+
2877+
continue;
2878+
}
2879+
}
2880+
}
2881+
}
27762882
};
27772883

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

test/ClangImporter/objc_async.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ func testSlowServer(slowServer: SlowServer) async throws {
2929

3030
// still async version...
3131
let _: Int = slowServer.doSomethingConflicted("thinking")
32-
// expected-error@-1{{call is 'async' but is not marked with 'await'}}{{16-16=await }}
32+
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{16-16=await }}
33+
// expected-note@-2{{call is 'async'}}
3334

3435
let _: String? = try await slowServer.fortune()
3536
let _: Int = try await slowServer.magicNumber(withSeed: 42)

0 commit comments

Comments
 (0)