Skip to content

Commit 03d819f

Browse files
committed
[CodeCompletion] Check whether surrounding context supports async in all solver-based completion kinds
1 parent 17eb6ea commit 03d819f

13 files changed

+167
-95
lines changed

include/swift/IDE/ArgumentCompletion.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,14 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
4545
Type BaseType;
4646
/// True if an argument label precedes the completion location.
4747
bool HasLabel;
48+
/// Whether the surrounding context is async and thus calling async
49+
/// functions is supported.
50+
bool IsInAsyncContext;
4851
};
4952

5053
CodeCompletionExpr *CompletionExpr;
54+
DeclContext *DC;
55+
5156
SmallVector<Result, 4> Results;
5257

5358
/// Populates a vector of parameters to suggest along with a vector of types
@@ -59,8 +64,9 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
5964
SmallVectorImpl<Type> &Types);
6065

6166
public:
62-
ArgumentTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr)
63-
: CompletionExpr(CompletionExpr) {}
67+
ArgumentTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr,
68+
DeclContext *DC)
69+
: CompletionExpr(CompletionExpr), DC(DC) {}
6470

6571
void sawSolution(const constraints::Solution &solution) override;
6672

include/swift/IDE/CompletionLookup.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ bool isCodeCompletionAtTopLevel(const DeclContext *DC);
7676
/// }
7777
bool isCompletionDeclContextLocalContext(DeclContext *DC);
7878

79+
/// Returns \c true if \p DC can handles async call.
80+
bool canDeclContextHandleAsync(const DeclContext *DC);
81+
7982
/// Return \c true if the completion happens at top-level of a library file.
8083
bool isCodeCompletionAtTopLevelOfLibraryFile(const DeclContext *DC);
8184

include/swift/IDE/DotExprCompletion.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,22 @@ class DotExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
3131
bool ExpectsNonVoid;
3232
bool BaseIsStaticMetaType;
3333
bool IsImplicitSingleExpressionReturn;
34+
35+
/// Whether the surrounding context is async and thus calling async
36+
/// functions is supported.
37+
bool IsInAsyncContext;
3438
};
3539

3640
CodeCompletionExpr *CompletionExpr;
41+
DeclContext *DC;
42+
3743
SmallVector<Result, 4> Results;
3844
llvm::DenseMap<std::pair<Type, Decl *>, size_t> BaseToSolutionIdx;
3945

4046
public:
41-
DotExprTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr)
42-
: CompletionExpr(CompletionExpr) {}
47+
DotExprTypeCheckCompletionCallback(CodeCompletionExpr *CompletionExpr,
48+
DeclContext *DC)
49+
: CompletionExpr(CompletionExpr), DC(DC) {}
4350

4451
/// Typecheck the code completion expression in isolation, calling
4552
/// \c sawSolution for each solution formed.

include/swift/IDE/TypeCheckCompletionCallback.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ Type getTypeForCompletion(const constraints::Solution &S, Expr *E);
7575
bool isImplicitSingleExpressionReturn(constraints::ConstraintSystem &CS,
7676
Expr *CompletionExpr);
7777

78+
/// Returns \c true iff the decl context \p DC allows calling async functions.
79+
bool isContextAsync(const constraints::Solution &S, DeclContext *DC);
80+
7881
} // namespace ide
7982
} // namespace swift
8083

include/swift/IDE/UnresolvedMemberCompletion.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,25 @@ namespace ide {
2525
/// formed during expression type-checking.
2626
class UnresolvedMemberTypeCheckCompletionCallback
2727
: public TypeCheckCompletionCallback {
28-
struct ExprResult {
28+
struct Result {
2929
Type ExpectedTy;
3030
bool IsImplicitSingleExpressionReturn;
31+
32+
/// Whether the surrounding context is async and thus calling async
33+
/// functions is supported.
34+
bool IsInAsyncContext;
3135
};
3236

3337
CodeCompletionExpr *CompletionExpr;
34-
SmallVector<ExprResult, 4> ExprResults;
35-
SmallVector<Type, 1> EnumPatternTypes;
38+
DeclContext *DC;
39+
40+
SmallVector<Result, 4> ExprResults;
41+
SmallVector<Result, 1> EnumPatternTypes;
3642

3743
public:
3844
UnresolvedMemberTypeCheckCompletionCallback(
39-
CodeCompletionExpr *CompletionExpr)
40-
: CompletionExpr(CompletionExpr) {}
45+
CodeCompletionExpr *CompletionExpr, DeclContext *DC)
46+
: CompletionExpr(CompletionExpr), DC(DC) {}
4147

4248
void sawSolution(const constraints::Solution &solution) override;
4349

lib/IDE/ArgumentCompletion.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ void ArgumentTypeCheckCompletionCallback::sawSolution(const Solution &S) {
195195
}
196196
}
197197

198+
bool IsAsync = isContextAsync(S, DC);
199+
198200
// If this is a duplicate of any other result, ignore this solution.
199201
if (llvm::any_of(Results, [&](const Result &R) {
200202
return R.FuncD == FuncD && nullableTypesEqual(R.FuncTy, FuncTy) &&
@@ -207,7 +209,7 @@ void ArgumentTypeCheckCompletionCallback::sawSolution(const Solution &S) {
207209

208210
Results.push_back({ExpectedTy, isa<SubscriptExpr>(ParentCall), FuncD, FuncTy,
209211
ArgIdx, ParamIdx, std::move(ClaimedParams),
210-
IsNoninitialVariadic, CallBaseTy, HasLabel});
212+
IsNoninitialVariadic, CallBaseTy, HasLabel, IsAsync});
211213
}
212214

213215
void ArgumentTypeCheckCompletionCallback::deliverResults(
@@ -272,6 +274,9 @@ void ArgumentTypeCheckCompletionCallback::deliverResults(
272274
ExpectedTypes.push_back(Result.ExpectedType);
273275
}
274276
Lookup.setExpectedTypes(ExpectedTypes, false);
277+
bool IsInAsyncContext = llvm::any_of(
278+
Results, [](const Result &Res) { return Res.IsInAsyncContext; });
279+
Lookup.setCanCurrDeclContextHandleAsync(IsInAsyncContext);
275280
Lookup.getValueCompletionsInDeclContext(Loc);
276281
Lookup.getSelfTypeCompletionInDeclContext(Loc, /*isForDeclResult=*/false);
277282

lib/IDE/CodeCompletion.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,7 +1327,8 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13271327
assert(CodeCompleteTokenExpr);
13281328
assert(CurDeclContext);
13291329

1330-
DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr);
1330+
DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
1331+
CurDeclContext);
13311332
llvm::SaveAndRestore<TypeCheckCompletionCallback*>
13321333
CompletionCollector(Context.CompletionCallback, &Lookup);
13331334
typeCheckContextAt(CurDeclContext, CompletionLoc);
@@ -1351,7 +1352,8 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13511352
assert(CodeCompleteTokenExpr);
13521353
assert(CurDeclContext);
13531354

1354-
UnresolvedMemberTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr);
1355+
UnresolvedMemberTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
1356+
CurDeclContext);
13551357
llvm::SaveAndRestore<TypeCheckCompletionCallback*>
13561358
CompletionCollector(Context.CompletionCallback, &Lookup);
13571359
typeCheckContextAt(CurDeclContext, CompletionLoc);
@@ -1380,7 +1382,8 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13801382
case CompletionKind::CallArg: {
13811383
assert(CodeCompleteTokenExpr);
13821384
assert(CurDeclContext);
1383-
ArgumentTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr);
1385+
ArgumentTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
1386+
CurDeclContext);
13841387
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
13851388
Context.CompletionCallback, &Lookup);
13861389
typeCheckContextAt(CurDeclContext, CompletionLoc);

lib/IDE/CompletionLookup.cpp

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,52 +34,6 @@ static bool SwiftKeyPathFilter(ValueDecl *decl, DeclVisibilityKind) {
3434
}
3535
}
3636

37-
/// Returns \c true if \p DC can handles async call.
38-
static bool canDeclContextHandleAsync(const DeclContext *DC) {
39-
if (auto *func = dyn_cast<AbstractFunctionDecl>(DC))
40-
return func->isAsyncContext();
41-
42-
if (auto *closure = dyn_cast<ClosureExpr>(DC)) {
43-
// See if the closure has 'async' function type.
44-
if (auto closureType = closure->getType())
45-
if (auto fnType = closureType->getAs<AnyFunctionType>())
46-
if (fnType->isAsync())
47-
return true;
48-
49-
// If the closure doesn't contain any async call in the body, closure itself
50-
// doesn't have 'async' type even if 'async' closure is expected.
51-
// func foo(fn: () async -> Void)
52-
// foo { <HERE> }
53-
// In this case, the closure is wrapped with a 'FunctionConversionExpr'
54-
// which has 'async' function type.
55-
struct AsyncClosureChecker : public ASTWalker {
56-
const ClosureExpr *Target;
57-
bool Result = false;
58-
59-
AsyncClosureChecker(const ClosureExpr *Target) : Target(Target) {}
60-
61-
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
62-
if (E == Target)
63-
return {false, E};
64-
65-
if (auto conversionExpr = dyn_cast<FunctionConversionExpr>(E)) {
66-
if (conversionExpr->getSubExpr() == Target) {
67-
if (conversionExpr->getType()->is<AnyFunctionType>() &&
68-
conversionExpr->getType()->castTo<AnyFunctionType>()->isAsync())
69-
Result = true;
70-
return {false, E};
71-
}
72-
}
73-
return {true, E};
74-
}
75-
} checker(closure);
76-
closure->getParent()->walkContext(checker);
77-
return checker.Result;
78-
}
79-
80-
return false;
81-
}
82-
8337
static bool isTopLevelSubcontext(const DeclContext *DC) {
8438
for (; DC && DC->isLocalContext(); DC = DC->getParent()) {
8539
switch (DC->getContextKind()) {
@@ -207,6 +161,52 @@ bool swift::ide::isCompletionDeclContextLocalContext(DeclContext *DC) {
207161
return true;
208162
}
209163

164+
/// Returns \c true if \p DC can handles async call.
165+
bool swift::ide::canDeclContextHandleAsync(const DeclContext *DC) {
166+
if (auto *func = dyn_cast<AbstractFunctionDecl>(DC))
167+
return func->isAsyncContext();
168+
169+
if (auto *closure = dyn_cast<ClosureExpr>(DC)) {
170+
// See if the closure has 'async' function type.
171+
if (auto closureType = closure->getType())
172+
if (auto fnType = closureType->getAs<AnyFunctionType>())
173+
if (fnType->isAsync())
174+
return true;
175+
176+
// If the closure doesn't contain any async call in the body, closure itself
177+
// doesn't have 'async' type even if 'async' closure is expected.
178+
// func foo(fn: () async -> Void)
179+
// foo { <HERE> }
180+
// In this case, the closure is wrapped with a 'FunctionConversionExpr'
181+
// which has 'async' function type.
182+
struct AsyncClosureChecker : public ASTWalker {
183+
const ClosureExpr *Target;
184+
bool Result = false;
185+
186+
AsyncClosureChecker(const ClosureExpr *Target) : Target(Target) {}
187+
188+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
189+
if (E == Target)
190+
return {false, E};
191+
192+
if (auto conversionExpr = dyn_cast<FunctionConversionExpr>(E)) {
193+
if (conversionExpr->getSubExpr() == Target) {
194+
if (conversionExpr->getType()->is<AnyFunctionType>() &&
195+
conversionExpr->getType()->castTo<AnyFunctionType>()->isAsync())
196+
Result = true;
197+
return {false, E};
198+
}
199+
}
200+
return {true, E};
201+
}
202+
} checker(closure);
203+
closure->getParent()->walkContext(checker);
204+
return checker.Result;
205+
}
206+
207+
return false;
208+
}
209+
210210
/// Return \c true if the completion happens at top-level of a library file.
211211
bool swift::ide::isCodeCompletionAtTopLevelOfLibraryFile(
212212
const DeclContext *DC) {

lib/IDE/DotExprCompletion.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ void DotExprTypeCheckCompletionCallback::sawSolution(
6969
if (auto SelectedOverload = S.getOverloadChoiceIfAvailable(CalleeLocator))
7070
ReferencedDecl = SelectedOverload->choice.getDeclOrNull();
7171

72+
bool IsAsync = isContextAsync(S, DC);
73+
7274
auto Key = std::make_pair(BaseTy, ReferencedDecl);
7375
auto Ret = BaseToSolutionIdx.insert({Key, Results.size()});
7476
if (Ret.second) {
@@ -79,15 +81,19 @@ void DotExprTypeCheckCompletionCallback::sawSolution(
7981
: !ParentExpr && CS.getContextualTypePurpose(
8082
CompletionExpr) != CTP_Unused;
8183

82-
Results.push_back(
83-
{BaseTy, ReferencedDecl, {}, DisallowVoid, ISDMT, ImplicitReturn});
84-
if (ExpectedTy)
84+
Results.push_back({BaseTy, ReferencedDecl,
85+
/*ExpectedTypes=*/{}, DisallowVoid, ISDMT,
86+
ImplicitReturn, IsAsync});
87+
if (ExpectedTy) {
8588
Results.back().ExpectedTypes.push_back(ExpectedTy);
89+
}
8690
} else if (ExpectedTy) {
87-
auto &ExpectedTys = Results[Ret.first->getSecond()].ExpectedTypes;
91+
auto &ExistingResult = Results[Ret.first->getSecond()];
92+
ExistingResult.IsInAsyncContext |= IsAsync;
8893
auto IsEqual = [&](Type Ty) { return ExpectedTy->isEqual(Ty); };
89-
if (!llvm::any_of(ExpectedTys, IsEqual))
90-
ExpectedTys.push_back(ExpectedTy);
94+
if (!llvm::any_of(ExistingResult.ExpectedTypes, IsEqual)) {
95+
ExistingResult.ExpectedTypes.push_back(ExpectedTy);
96+
}
9197
}
9298
}
9399

@@ -116,6 +122,7 @@ void DotExprTypeCheckCompletionCallback::deliverResults(
116122

117123
Lookup.shouldCheckForDuplicates(Results.size() > 1);
118124
for (auto &Result : Results) {
125+
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);
119126
Lookup.setIsStaticMetatype(Result.BaseIsStaticMetaType);
120127
Lookup.getPostfixKeywordCompletions(Result.BaseTy, BaseExpr);
121128
Lookup.setExpectedTypes(Result.ExpectedTypes,

lib/IDE/ExprCompletion.cpp

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,7 @@ void ExprTypeCheckCompletionCallback::sawSolution(
2929

3030
bool ImplicitReturn = isImplicitSingleExpressionReturn(CS, CompletionExpr);
3131

32-
// We are in an async context if
33-
// - the decl context is async or
34-
// - the decl context is sync but it's used in a context that expectes an
35-
// async function. This happens if the code completion token is in a
36-
// closure that doesn't contain any async calles. Thus the closure is
37-
// type-checked as non-async, but it might get converted to an async
38-
// closure based on its contextual type.
39-
bool isAsync = CS.isAsynchronousContext(DC);
40-
if (!isAsync) {
41-
auto target = S.solutionApplicationTargets.find(dyn_cast<ClosureExpr>(DC));
42-
if (target != S.solutionApplicationTargets.end()) {
43-
if (auto ContextTy = target->second.getClosureContextualType()) {
44-
if (auto ContextFuncTy =
45-
S.simplifyType(ContextTy)->getAs<AnyFunctionType>()) {
46-
isAsync = ContextFuncTy->isAsync();
47-
}
48-
}
49-
}
50-
}
32+
bool IsAsync = isContextAsync(S, DC);
5133

5234
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
5335
for (auto NT : S.nodeTypes) {
@@ -57,7 +39,7 @@ void ExprTypeCheckCompletionCallback::sawSolution(
5739
}
5840

5941
Results.push_back(
60-
{ExpectedTy, ImplicitReturn, isAsync, SolutionSpecificVarTypes});
42+
{ExpectedTy, ImplicitReturn, IsAsync, SolutionSpecificVarTypes});
6143
}
6244

6345
void ExprTypeCheckCompletionCallback::deliverResults(

lib/IDE/TypeCheckCompletionCallback.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/IDE/TypeCheckCompletionCallback.h"
14+
#include "swift/IDE/CompletionLookup.h"
1415
#include "swift/Sema/CompletionContextFinder.h"
1516
#include "swift/Sema/ConstraintSystem.h"
1617
#include "swift/Sema/IDETypeChecking.h"
@@ -118,3 +119,32 @@ bool swift::ide::isImplicitSingleExpressionReturn(ConstraintSystem &CS,
118119
}
119120
return false;
120121
}
122+
123+
bool swift::ide::isContextAsync(const constraints::Solution &S,
124+
DeclContext *DC) {
125+
// We are in an async context if
126+
// - the decl context is async
127+
if (S.getConstraintSystem().isAsynchronousContext(DC)) {
128+
return true;
129+
}
130+
131+
// - the decl context is sync but it's used in a context that expectes an
132+
// async function. This happens if the code completion token is in a
133+
// closure that doesn't contain any async calles. Thus the closure is
134+
// type-checked as non-async, but it might get converted to an async
135+
// closure based on its contextual type
136+
auto target = S.solutionApplicationTargets.find(dyn_cast<ClosureExpr>(DC));
137+
if (target != S.solutionApplicationTargets.end()) {
138+
if (auto ContextTy = target->second.getClosureContextualType()) {
139+
if (auto ContextFuncTy =
140+
S.simplifyType(ContextTy)->getAs<AnyFunctionType>()) {
141+
return ContextFuncTy->isAsync();
142+
}
143+
}
144+
}
145+
146+
// - we did not record any information about async-ness of the context in the
147+
// solution, but the type information recorded AST declares the context as
148+
// async.
149+
return canDeclContextHandleAsync(DC);
150+
}

0 commit comments

Comments
 (0)