Skip to content

Commit b817cbb

Browse files
committed
[code-completion] Add type context for single-expression closures
When completing in the only expression of closure, use the return type of the closure as the type context for the code-completion. However, since code-completion may be on an incomplete input, we only use the return type to improve the quality of the result, not to mark it invalid, since (a) we may add another statement afterwards, or (b) if the context type is Void it doesn't need to match the value.
1 parent 6e7202e commit b817cbb

11 files changed

+304
-85
lines changed

include/swift/Parse/CodeCompletionCallbacks.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class CodeCompletionCallbacks {
123123
virtual void completeDotExpr(Expr *E, SourceLoc DotLoc) {};
124124

125125
/// Complete the beginning of a statement or expression.
126-
virtual void completeStmtOrExpr() {};
126+
virtual void completeStmtOrExpr(CodeCompletionExpr *E) {};
127127

128128
/// Complete the beginning of expr-postfix -- no tokens provided
129129
/// by user.

lib/IDE/CodeCompletion.cpp

Lines changed: 91 additions & 57 deletions
Large diffs are not rendered by default.

lib/IDE/CodeCompletionResultBuilder.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class ModuleDecl;
3131

3232
namespace ide {
3333

34+
struct ExpectedTypeContext;
35+
3436
class CodeCompletionResultBuilder {
3537
CodeCompletionResultSink &Sink;
3638
CodeCompletionResult::ResultKind Kind;
@@ -43,7 +45,7 @@ class CodeCompletionResultBuilder {
4345
SmallVector<CodeCompletionString::Chunk, 4> Chunks;
4446
llvm::PointerUnion<const ModuleDecl *, const clang::Module *>
4547
CurrentModule;
46-
ArrayRef<Type> ExpectedDeclTypes;
48+
const ExpectedTypeContext &declTypeContext;
4749
CodeCompletionResult::ExpectedTypeRelation ExpectedTypeRelation =
4850
CodeCompletionResult::Unrelated;
4951
bool Cancelled = false;
@@ -78,9 +80,9 @@ class CodeCompletionResultBuilder {
7880
CodeCompletionResultBuilder(CodeCompletionResultSink &Sink,
7981
CodeCompletionResult::ResultKind Kind,
8082
SemanticContextKind SemanticContext,
81-
ArrayRef<Type> ExpectedDeclTypes)
83+
const ExpectedTypeContext &declTypeContext)
8284
: Sink(Sink), Kind(Kind), SemanticContext(SemanticContext),
83-
ExpectedDeclTypes(ExpectedDeclTypes) {}
85+
declTypeContext(declTypeContext) {}
8486

8587
~CodeCompletionResultBuilder() {
8688
finishResult();

lib/IDE/ExprContextAnalysis.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ class ExprContextAnalyzer {
510510
SmallVectorImpl<Type> &PossibleTypes;
511511
SmallVectorImpl<StringRef> &PossibleNames;
512512
SmallVectorImpl<FunctionTypeAndDecl> &PossibleCallees;
513+
bool &singleExpressionBody;
513514

514515
void recordPossibleType(Type ty) {
515516
if (!ty || ty->is<ErrorType>())
@@ -616,6 +617,13 @@ class ExprContextAnalyzer {
616617
}
617618
break;
618619
}
620+
case ExprKind::Closure: {
621+
auto *CE = cast<ClosureExpr>(Parent);
622+
assert(isSingleExpressionBodyForCodeCompletion(CE->getBody()));
623+
singleExpressionBody = true;
624+
recordPossibleType(getReturnTypeFromContext(CE));
625+
break;
626+
}
619627
default:
620628
llvm_unreachable("Unhandled expression kind.");
621629
}
@@ -705,14 +713,28 @@ class ExprContextAnalyzer {
705713
}
706714
}
707715

716+
static bool isSingleExpressionBodyForCodeCompletion(BraceStmt *body) {
717+
switch (body->getNumElements()) {
718+
case 0:
719+
// Nothing in the body except the code-completion token.
720+
return true;
721+
case 1:
722+
return body->getElements()[0].is<Expr *>();
723+
default:
724+
return false;
725+
}
726+
}
727+
708728
public:
709729
ExprContextAnalyzer(DeclContext *DC, Expr *ParsedExpr,
710730
SmallVectorImpl<Type> &PossibleTypes,
711731
SmallVectorImpl<StringRef> &PossibleNames,
712-
SmallVectorImpl<FunctionTypeAndDecl> &PossibleCallees)
732+
SmallVectorImpl<FunctionTypeAndDecl> &PossibleCallees,
733+
bool &singleExpressionBody)
713734
: DC(DC), ParsedExpr(ParsedExpr), SM(DC->getASTContext().SourceMgr),
714735
Context(DC->getASTContext()), PossibleTypes(PossibleTypes),
715-
PossibleNames(PossibleNames), PossibleCallees(PossibleCallees) {}
736+
PossibleNames(PossibleNames), PossibleCallees(PossibleCallees),
737+
singleExpressionBody(singleExpressionBody) {}
716738

717739
void Analyze() {
718740
// We cannot analyze without target.
@@ -735,6 +757,15 @@ class ExprContextAnalyzer {
735757
(!isa<CallExpr>(ParentE) && !isa<SubscriptExpr>(ParentE) &&
736758
!isa<BinaryExpr>(ParentE) && !isa<TupleShuffleExpr>(ParentE));
737759
}
760+
case ExprKind::Closure: {
761+
// Note: we cannot use hasSingleExpressionBody, because we explicitly
762+
// do not use the single-expression-body when there is code-completion
763+
// in the expression in order to avoid a base expression affecting
764+
// the type. However, now that we've typechecked, we will take the
765+
// context type into account.
766+
return isSingleExpressionBodyForCodeCompletion(
767+
cast<ClosureExpr>(E)->getBody());
768+
}
738769
default:
739770
return false;
740771
}
@@ -793,6 +824,6 @@ class ExprContextAnalyzer {
793824

794825
ExprContextInfo::ExprContextInfo(DeclContext *DC, Expr *TargetExpr) {
795826
ExprContextAnalyzer Analyzer(DC, TargetExpr, PossibleTypes, PossibleNames,
796-
PossibleCallees);
827+
PossibleCallees, singleExpressionBody);
797828
Analyzer.Analyze();
798829
}

lib/IDE/ExprContextAnalysis.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,21 @@ class ExprContextInfo {
5050
SmallVector<Type, 2> PossibleTypes;
5151
SmallVector<StringRef, 2> PossibleNames;
5252
SmallVector<FunctionTypeAndDecl, 2> PossibleCallees;
53+
bool singleExpressionBody = false;
5354

5455
public:
5556
ExprContextInfo(DeclContext *DC, Expr *TargetExpr);
5657

5758
// Returns a list of possible context types.
5859
ArrayRef<Type> getPossibleTypes() const { return PossibleTypes; }
5960

61+
/// Whether the type context comes from a single-expression body, e.g.
62+
/// `foo({ here })`.
63+
///
64+
/// If the input may be incomplete, such as in code-completion, take into
65+
/// account that the types returned by `getPossibleTypes()` are only a hint.
66+
bool isSingleExpressionBody() const { return singleExpressionBody; }
67+
6068
// Returns a list of possible argument label names.
6169
// Valid only if \c getKind() is \c CallArgument.
6270
ArrayRef<StringRef> getPossibleNames() const { return PossibleNames; }

lib/Parse/ParseStmt.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ ParserStatus Parser::parseExprOrStmt(ASTNode &Result) {
141141
CodeCompletion->setExprBeginning(getParserPosition());
142142

143143
if (Tok.is(tok::code_complete)) {
144+
auto *CCE = new (Context) CodeCompletionExpr(Tok.getLoc());
145+
Result = CCE;
144146
if (CodeCompletion)
145-
CodeCompletion->completeStmtOrExpr();
147+
CodeCompletion->completeStmtOrExpr(CCE);
146148
SyntaxParsingContext ErrorCtxt(SyntaxContext, SyntaxContextKind::Stmt);
147149
consumeToken(tok::code_complete);
148150
return makeParserCodeCompletionStatus();

test/IDE/complete_at_top_level.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ _ = {
298298
}()
299299
// TOP_LEVEL_CLOSURE_1: Begin completions
300300
// TOP_LEVEL_CLOSURE_1-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
301-
// TOP_LEVEL_CLOSURE_1-DAG: Decl[FreeFunction]/CurrModule: fooFunc1()[#Void#]{{; name=.+$}}
301+
// TOP_LEVEL_CLOSURE_1-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Identical]: fooFunc1()[#Void#]{{; name=.+$}}
302302
// TOP_LEVEL_CLOSURE_1-DAG: Decl[GlobalVar]/Local: fooObject[#FooStruct#]{{; name=.+$}}
303303
// TOP_LEVEL_CLOSURE_1: End completions
304304

test/IDE/complete_in_closures.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct FooStruct {
7070
// FOO_OBJECT_DOT: Begin completions
7171
// FOO_OBJECT_DOT-NEXT: Keyword[self]/CurrNominal: self[#FooStruct#]; name=self
7272
// FOO_OBJECT_DOT-NEXT: Decl[InstanceVar]/CurrNominal: instanceVar[#Int#]{{; name=.+$}}
73-
// FOO_OBJECT_DOT-NEXT: Decl[InstanceMethod]/CurrNominal: instanceFunc0()[#Void#]{{; name=.+$}}
73+
// FOO_OBJECT_DOT-NEXT: Decl[InstanceMethod]/CurrNominal{{(/TypeRelation\[Identical\])?}}: instanceFunc0()[#Void#]{{; name=.+$}}
7474
// FOO_OBJECT_DOT-NEXT: End completions
7575

7676
// WITH_GLOBAL_DECLS: Begin completions
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureRet | %FileCheck %s -check-prefix=TestSingleExprClosureRet
2+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureRetVoid | %FileCheck %s -check-prefix=TestSingleExprClosureRetVoid
3+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureRetUnresolved | %FileCheck %s -check-prefix=TestSingleExprClosureRetUnresolved
4+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosure | %FileCheck %s -check-prefix=TestSingleExprClosure
5+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureVoid | %FileCheck %s -check-prefix=TestSingleExprClosureRetVoid
6+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureUnresolved | %FileCheck %s -check-prefix=TestSingleExprClosureRetUnresolved
7+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureCall | %FileCheck %s -check-prefix=TestSingleExprClosure
8+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TestSingleExprClosureGlobal | %FileCheck %s -check-prefix=TestSingleExprClosureGlobal
9+
10+
struct TestSingleExprClosureRet {
11+
func void() -> Void {}
12+
func str() -> String { return "" }
13+
func int() -> Int { return 0 }
14+
15+
func test() -> Int {
16+
return { () in
17+
return self.#^TestSingleExprClosureRet^#
18+
}()
19+
}
20+
21+
// TestSingleExprClosureRet: Begin completions
22+
// TestSingleExprClosureRet-DAG: Decl[InstanceMethod]/CurrNominal: str()[#String#];
23+
// TestSingleExprClosureRet-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Identical]: int()[#Int#];
24+
// TestSingleExprClosureRet-DAG: Decl[InstanceMethod]/CurrNominal/NotRecommended/TypeRelation[Invalid]: void()[#Void#];
25+
// TestSingleExprClosureRet: End completions
26+
}
27+
28+
struct TestSingleExprClosureRetVoid {
29+
func void() -> Void {}
30+
func str() -> String { return "" }
31+
func int() -> Int { return 0 }
32+
33+
func test() {
34+
return { () in
35+
return self.#^TestSingleExprClosureRetVoid^#
36+
}()
37+
}
38+
39+
// TestSingleExprClosureRetVoid: Begin completions
40+
// TestSingleExprClosureRetVoid-DAG: Decl[InstanceMethod]/CurrNominal: str()[#String#];
41+
// TestSingleExprClosureRetVoid-DAG: Decl[InstanceMethod]/CurrNominal: int()[#Int#];
42+
// TestSingleExprClosureRetVoid-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Identical]: void()[#Void#];
43+
// TestSingleExprClosureRetVoid: End completions
44+
}
45+
46+
struct TestSingleExprClosureRetUnresolved {
47+
enum MyEnum { case myEnum }
48+
enum NotMine { case notMine }
49+
func mine() -> MyEnum { return .myEnum }
50+
func notMine() -> NotMine { return .notMine }
51+
52+
func test() -> MyEnum {
53+
return { () in
54+
return .#^TestSingleExprClosureRetUnresolved^#
55+
}()
56+
}
57+
58+
// TestSingleExprClosureRetUnresolved: Begin completions
59+
// TestSingleExprClosureRetUnresolved-NOT: notMine
60+
// TestSingleExprClosureRetUnresolved: Decl[EnumElement]/ExprSpecific: myEnum[#TestSingleExprClosure{{(Ret)?}}Unresolved.MyEnum#];
61+
// TestSingleExprClosureRetUnresolved-NOT: notMine
62+
// TestSingleExprClosureRetUnresolved: End completions
63+
}
64+
65+
struct TestSingleExprClosure {
66+
func void() -> Void {}
67+
func str() -> String { return "" }
68+
func int() -> Int { return 0 }
69+
70+
func test() -> Int {
71+
return { () in
72+
self.#^TestSingleExprClosure^#
73+
}()
74+
}
75+
// TestSingleExprClosure: Begin completions
76+
// TestSingleExprClosure-DAG: Decl[InstanceMethod]/CurrNominal: str()[#String#];
77+
// TestSingleExprClosure-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Identical]: int()[#Int#];
78+
// NOTE: this differs from the one using a return keyword.
79+
// TestSingleExprClosure-DAG: Decl[InstanceMethod]/CurrNominal: void()[#Void#];
80+
// TestSingleExprClosure: End completions
81+
}
82+
83+
struct TestSingleExprClosureVoid {
84+
func void() -> Void {}
85+
func str() -> String { return "" }
86+
func int() -> Int { return 0 }
87+
88+
func test() {
89+
return { () in
90+
self.#^TestSingleExprClosureVoid^#
91+
}()
92+
}
93+
}
94+
95+
struct TestSingleExprClosureUnresolved {
96+
enum MyEnum { case myEnum }
97+
enum NotMine { case notMine }
98+
func mine() -> MyEnum { return .myEnum }
99+
func notMine() -> NotMine { return .notMine }
100+
101+
func test() -> MyEnum {
102+
return { () in
103+
.#^TestSingleExprClosureUnresolved^#
104+
}()
105+
}
106+
}
107+
108+
struct TestSingleExprClosureCall {
109+
func void() -> Void {}
110+
func str() -> String { return "" }
111+
func int() -> Int { return 0 }
112+
113+
func take(_: () -> Int) {}
114+
115+
func test() {
116+
take {
117+
self.#^TestSingleExprClosureCall^#
118+
}
119+
}
120+
}
121+
122+
struct TestSingleExprClosureGlobal {
123+
func void() -> Void {}
124+
func str() -> String { return "" }
125+
func int() -> Int { return 0 }
126+
127+
func test() -> Int {
128+
return { () -> Int in
129+
#^TestSingleExprClosureGlobal^#
130+
}()
131+
}
132+
// TestSingleExprClosureGlobal: Begin completions
133+
// TestSingleExprClosureGlobal-DAG: Decl[InstanceMethod]/OutNominal: str()[#String#];
134+
// TestSingleExprClosureGlobal-DAG: Decl[InstanceMethod]/OutNominal/TypeRelation[Identical]: int()[#Int#];
135+
// TestSingleExprClosureGlobal-DAG: Decl[InstanceMethod]/OutNominal: void()[#Void#];
136+
// TestSingleExprClosureGlobal: End completions
137+
}

test/IDE/complete_unresolved_members.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_20 | %FileCheck %s -check-prefix=UNRESOLVED_3
2929
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_21 | %FileCheck %s -check-prefix=UNRESOLVED_1
3030
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_22 | %FileCheck %s -check-prefix=UNRESOLVED_1
31+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_22_noreturn | %FileCheck %s -check-prefix=UNRESOLVED_1
3132
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_23 | %FileCheck %s -check-prefix=UNRESOLVED_1
3233
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_24 | %FileCheck %s -check-prefix=UNRESOLVED_3
3334
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_25 | %FileCheck %s -check-prefix=UNRESOLVED_3
@@ -303,6 +304,10 @@ var c1 = {() -> SomeOptions1 in
303304
return .#^UNRESOLVED_22^#
304305
}
305306

307+
var c1_noreturn = {() -> SomeOptions1 in
308+
.#^UNRESOLVED_22_noreturn^#
309+
}
310+
306311
class C6 {
307312
func f1() -> SomeOptions1 {
308313
return .#^UNRESOLVED_23^#

0 commit comments

Comments
 (0)