Skip to content

Commit 727baf1

Browse files
authored
Merge pull request #35415 from rintaro/ide-completion-rdar72198532
[CodeCompletion] Mark async calls "NotRecommended" in non-async context
2 parents b9188a1 + b856c15 commit 727baf1

File tree

4 files changed

+139
-8
lines changed

4 files changed

+139
-8
lines changed

include/swift/IDE/CodeCompletion.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ class CodeCompletionResult {
600600
enum NotRecommendedReason {
601601
Redundant,
602602
Deprecated,
603+
InvalidContext,
603604
NoReason,
604605
};
605606

lib/IDE/CodeCompletion.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,52 @@ static bool hasTrivialTrailingClosure(const FuncDecl *FD,
17591759
return false;
17601760
}
17611761

1762+
/// Returns \c true if \p DC can handles async call.
1763+
static bool canDeclContextHandlesAsync(const DeclContext *DC) {
1764+
if (auto *func = dyn_cast<AbstractFunctionDecl>(DC))
1765+
return func->isAsyncContext();
1766+
1767+
if (auto *closure = dyn_cast<ClosureExpr>(DC)) {
1768+
// See if the closure has 'async' function type.
1769+
if (auto closureType = closure->getType())
1770+
if (auto fnType = closureType->getAs<AnyFunctionType>())
1771+
if (fnType->isAsync())
1772+
return true;
1773+
1774+
// If the closure doesn't contain any async call in the body, closure itself
1775+
// doesn't have 'async' type even if 'async' closure is expected.
1776+
// func foo(fn: () async -> Void)
1777+
// foo { <HERE> }
1778+
// In this case, the closure is wrapped with a 'FunctionConversionExpr'
1779+
// which has 'async' function type.
1780+
struct AsyncClosureChecker: public ASTWalker {
1781+
const ClosureExpr *Target;
1782+
bool Result = false;
1783+
1784+
AsyncClosureChecker(const ClosureExpr *Target) : Target(Target) {}
1785+
1786+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
1787+
if (E == Target)
1788+
return {false, E};
1789+
1790+
if (auto conversionExpr = dyn_cast<FunctionConversionExpr>(E)) {
1791+
if (conversionExpr->getSubExpr() == Target) {
1792+
if (conversionExpr->getType()->is<AnyFunctionType>() &&
1793+
conversionExpr->getType()->castTo<AnyFunctionType>()->isAsync())
1794+
Result = true;
1795+
return {false, E};
1796+
}
1797+
}
1798+
return {true, E};
1799+
}
1800+
} checker(closure);
1801+
closure->getParent()->walkContext(checker);
1802+
return checker.Result;
1803+
}
1804+
1805+
return false;
1806+
}
1807+
17621808
/// Build completions by doing visible decl lookup from a context.
17631809
class CompletionLookup final : public swift::VisibleDeclConsumer {
17641810
CodeCompletionResultSink &Sink;
@@ -2813,6 +2859,12 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
28132859
genericSig);
28142860
else
28152861
addTypeAnnotation(Builder, AFT->getResult(), genericSig);
2862+
2863+
if (AFT->isAsync() &&
2864+
!canDeclContextHandlesAsync(CurrDeclContext)) {
2865+
Builder.setNotRecommended(
2866+
CodeCompletionResult::NotRecommendedReason::InvalidContext);
2867+
}
28162868
};
28172869

28182870
if (!AFD || !AFD->getInterfaceType()->is<AnyFunctionType>()) {
@@ -3023,6 +3075,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
30233075
ResultType->isVoid()) {
30243076
Builder.setExpectedTypeRelation(CodeCompletionResult::Invalid);
30253077
}
3078+
3079+
if (!IsImplicitlyCurriedInstanceMethod &&
3080+
AFT->isAsync() &&
3081+
!canDeclContextHandlesAsync(CurrDeclContext)) {
3082+
Builder.setNotRecommended(
3083+
CodeCompletionResult::NotRecommendedReason::InvalidContext);
3084+
}
30263085
};
30273086

30283087
if (!AFT || IsImplicitlyCurriedInstanceMethod) {
@@ -3109,6 +3168,12 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
31093168
} else {
31103169
addTypeAnnotation(Builder, *Result, CD->getGenericSignatureOfContext());
31113170
}
3171+
3172+
if (ConstructorType->isAsync() &&
3173+
!canDeclContextHandlesAsync(CurrDeclContext)) {
3174+
Builder.setNotRecommended(
3175+
CodeCompletionResult::NotRecommendedReason::InvalidContext);
3176+
}
31123177
};
31133178

31143179
if (ConstructorType && hasInterestingDefaultValues(CD))

test/IDE/complete_async_context.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t -enable-experimental-concurrency
2+
3+
func funcThrows() throws {
4+
fatalError()
5+
}
6+
func asyncThrows() async throws {
7+
fatalError()
8+
}
9+
func asyncRethrows(fn : () async throws -> Int) async rethrows -> Int {
10+
fatalError()
11+
}
12+
func asyncRethrows(fn : () async throws -> String) async rethrows -> String {
13+
fatalError()
14+
}
15+
func invoke<T>(fn : () async throws -> T) async rethrows -> T {
16+
fatalError()
17+
}
18+
func invokeAuto<T>(_ val : @autoclosure () async throws -> T) async rethrows -> T {
19+
fatalError()
20+
}
21+
func normalTask() async -> Int {
22+
fatalError()
23+
}
24+
func throwingTask() async throws -> String {
25+
fatalError()
26+
}
27+
28+
// CHECK_syncContext: Begin completions
29+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule: funcThrows()[' throws'][#Void#];
30+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: asyncRethrows({#fn: () async throws -> Int##() async throws -> Int#})[' async'][' rethrows'][#Int#];
31+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: asyncRethrows({#fn: () async throws -> String##() async throws -> String#})[' async'][' rethrows'][#String#];
32+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: invokeAuto({#(val): T#})[' async'][' rethrows'][#T#];
33+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: throwingTask()[' async'][' throws'][#String#];
34+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: invoke({#fn: () async throws -> T##() async throws -> T#})[' async'][' rethrows'][#T#];
35+
// CHECK_syncContext-DAG: Decl[FreeFunction]/CurrModule/NotRecommended: normalTask()[' async'][#Int#];
36+
// CHECK_syncContext: End completions
37+
38+
// CHECK_asyncContext: Begin completions
39+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: funcThrows()[' throws'][#Void#];
40+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: asyncRethrows({#fn: () async throws -> Int##() async throws -> Int#})[' async'][' rethrows'][#Int#];
41+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: asyncRethrows({#fn: () async throws -> String##() async throws -> String#})[' async'][' rethrows'][#String#];
42+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: invokeAuto({#(val): T#})[' async'][' rethrows'][#T#];
43+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: throwingTask()[' async'][' throws'][#String#];
44+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: invoke({#fn: () async throws -> T##() async throws -> T#})[' async'][' rethrows'][#T#];
45+
// CHECK_asyncContext-DAG: Decl[FreeFunction]/CurrModule: normalTask()[' async'][#Int#];
46+
// CHECK_asyncContext: End completions
47+
48+
func syncFunc() {
49+
#^CHECK_syncFunc?check=CHECK_syncContext^#
50+
}
51+
func syncClosure() async {
52+
func handleSyncClosure<T>(_: () -> T) {}
53+
handleSyncClosure {
54+
#^CHECK_syncClosure?check=CHECK_syncContext^#
55+
}
56+
}
57+
func syncClosure() {
58+
func handleAsyncClosure<T>(_: () async -> T) async {}
59+
handleAsyncClosure {
60+
#^CHECK_asyncClosure?check=CHECK_asyncContext^#
61+
}
62+
}
63+
func asyncFunc() async {
64+
#^CHECK_asyncFunc?check=CHECK_asyncContext^#
65+
}

test/IDE/complete_asyncannotation.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,25 @@ struct HasAsyncMembers {
1414
init(withAsyncRethrows: () async throws -> Void) async rethrows {}
1515
}
1616

17-
func testGlobalFuncAsync() {
17+
func testGlobalFuncAsync() async {
1818
globalFuncAsync#^CHECK_globalFuncAsync^#
1919
// CHECK_globalFuncAsync: Begin completions
2020
// CHECK_globalFuncAsync-DAG: Decl[FreeFunction]/CurrModule: ()[' async'][#Void#]; name=() async
2121
// CHECK_globalFuncAsync: End completions
2222
}
23-
func testGlobalFuncAsyncThrows() {
23+
func testGlobalFuncAsyncThrows() async {
2424
globalFuncAsyncThrows#^CHECK_globalFuncAsyncThrows^#
2525
// CHECK_globalFuncAsyncThrows: Begin completions
2626
// CHECK_globalFuncAsyncThrows-DAG: Decl[FreeFunction]/CurrModule: ()[' async'][' throws'][#Void#]; name=() async throws
2727
// CHECK_globalFuncAsyncThrows: End completions
2828
}
29-
func testGlobalFuncAsyncRethrows() {
29+
func testGlobalFuncAsyncRethrows() async {
3030
globalFuncAsyncRethrows#^CHECK_globalFuncAsyncRethrows^#
3131
// CHECK_globalFuncAsyncRethrows: Begin completions
3232
// CHECK_globalFuncAsyncRethrows-DAG: Decl[FreeFunction]/CurrModule: ({#(x): () async throws -> ()##() async throws -> ()#})[' async'][' rethrows'][#Void#]; name=(x: () async throws -> ()) async rethrows
3333
// CHECK_globalFuncAsyncRethrows: End completions
3434
}
35-
func testAsyncMembers(_ x: HasAsyncMembers) {
35+
func testAsyncMembers(_ x: HasAsyncMembers) async {
3636
x.#^CHECK_members^#
3737
// CHECK_members: Begin completions
3838
// CHECK_members-DAG: Decl[InstanceMethod]/CurrNominal: memberAsync()[' async'][#Void#]; name=memberAsync() async
@@ -41,26 +41,26 @@ func testAsyncMembers(_ x: HasAsyncMembers) {
4141
// CHECK_members-DAG: Decl[InstanceMethod]/CurrNominal: memberAsyncRethrows({#(x): () async throws -> ()##() async throws -> ()#})[' async'][' rethrows'][#Void#]; name=memberAsyncRethrows(x: () async throws -> ()) async rethrows
4242
// CHECK_members: End completions
4343
}
44-
func testMemberAsync(_ x: HasAsyncMembers) {
44+
func testMemberAsync(_ x: HasAsyncMembers) async {
4545
x.memberAsync#^CHECK_memberAsync^#
4646
// CHECK_memberAsync: Begin completions
4747
// CHECK_memberAsync-DAG: Decl[InstanceMethod]/CurrNominal: ()[' async'][#Void#]; name=() async
4848
// CHECK_memberAsync: End completions
4949
}
50-
func testMemberAsyncThrows(_ x: HasAsyncMembers) {
50+
func testMemberAsyncThrows(_ x: HasAsyncMembers) async {
5151
x.memberAsyncThrows#^CHECK_memberAsyncThrows^#
5252
// CHECK_memberAsyncThrows: Begin completions
5353
// CHECK_memberAsyncThrows-DAG: Decl[InstanceMethod]/CurrNominal: ()[' async'][' throws'][#Void#]; name=() async throws
5454
// CHECK_memberAsyncThrows: End completions
5555
}
56-
func testMemberAsyncRethrows(_ x: HasAsyncMembers) {
56+
func testMemberAsyncRethrows(_ x: HasAsyncMembers) async {
5757
x.memberAsyncRethrows#^CHECK_memberAsyncRethrows^#
5858
// CHECK_memberAsyncRethrows: Begin completions
5959
// CHECK_memberAsyncRethrows-DAG: Decl[InstanceMethod]/CurrNominal: ({#(x): () async throws -> ()##() async throws -> ()#})[' async'][' rethrows'][#Void#]; name=(x: () async throws -> ()) async rethrows
6060
// CHECK_memberAsyncRethrows: End completions
6161
}
6262

63-
func testAsyncIntiializers() {
63+
func testAsyncIntiializers() async {
6464
HasAsyncMembers(#^CHECK_initializers^#
6565
// CHECK_initializers: Begin completions
6666
// CHECK_initializers-DAG: Decl[Constructor]/CurrNominal: ['('][')'][#HasAsyncMembers#]; name=

0 commit comments

Comments
 (0)