Skip to content

Commit e997128

Browse files
authored
[CodeCompletion] Provide basic code completion support for Swift KeyPath. rdar://31768743 (#9467)
1 parent 5cf9716 commit e997128

File tree

5 files changed

+111
-10
lines changed

5 files changed

+111
-10
lines changed

include/swift/IDE/CodeCompletion.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ enum class CompletionKind {
503503
ReturnStmtExpr,
504504
AfterPound,
505505
GenericParams,
506+
SwiftKeyPath,
506507
};
507508

508509
/// \brief A single code completion result.

include/swift/Parse/Parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class Parser {
131131
bool InPoundLineEnvironment = false;
132132
bool InPoundIfEnvironment = false;
133133
bool InSwiftKeyPath = false;
134+
Expr* SwiftKeyPathRoot = nullptr;
135+
SourceLoc SwiftKeyPathSlashLoc = SourceLoc();
134136

135137
LocalContext *CurLocalContext = nullptr;
136138

lib/IDE/CodeCompletion.cpp

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ static bool KeyPathFilter(ValueDecl* decl, DeclVisibilityKind) {
318318
(isa<VarDecl>(decl) && decl->getDeclContext()->isTypeContext());
319319
}
320320

321+
static bool SwiftKeyPathFilter(ValueDecl* decl, DeclVisibilityKind) {
322+
switch(decl->getKind()){
323+
case DeclKind::Var:
324+
case DeclKind::Subscript:
325+
return true;
326+
default:
327+
return false;
328+
}
329+
}
330+
321331
std::string swift::ide::removeCodeCompletionTokens(
322332
StringRef Input, StringRef TokenName, unsigned *CompletionOffset) {
323333
assert(TokenName.size() >= 1);
@@ -1601,6 +1611,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
16011611
bool IsSuperRefExpr = false;
16021612
bool IsSelfRefExpr = false;
16031613
bool IsKeyPathExpr = false;
1614+
bool IsSwiftKeyPathExpr = false;
16041615
bool IsDynamicLookup = false;
16051616
bool PreferFunctionReferencesToCalls = false;
16061617
bool HaveLeadingSpace = false;
@@ -1766,6 +1777,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
17661777
IsKeyPathExpr = true;
17671778
}
17681779

1780+
void setIsSwiftKeyPathExpr() {
1781+
IsSwiftKeyPathExpr = true;
1782+
}
1783+
17691784
void setIsDynamicLookup() {
17701785
IsDynamicLookup = true;
17711786
}
@@ -2600,8 +2615,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
26002615
}
26012616
}
26022617

2618+
bool shouldAddSubscriptCall() {
2619+
if (IsSwiftKeyPathExpr)
2620+
return true;
2621+
return !HaveDot;
2622+
}
2623+
26032624
void addSubscriptCall(const SubscriptDecl *SD, DeclVisibilityKind Reason) {
2604-
assert(!HaveDot && "cannot add a subscript after a dot");
2625+
assert(shouldAddSubscriptCall() && "cannot add a subscript after a dot");
26052626
CommandWordsPairs Pairs;
26062627
CodeCompletionResultBuilder Builder(
26072628
Sink,
@@ -2799,6 +2820,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
27992820

28002821
if (IsKeyPathExpr && !KeyPathFilter(D, Reason))
28012822
return;
2823+
2824+
if (IsSwiftKeyPathExpr && !SwiftKeyPathFilter(D, Reason))
2825+
return;
28022826

28032827
if (!D->hasInterfaceType())
28042828
TypeResolver->resolveDeclSignature(D);
@@ -2917,14 +2941,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
29172941
addEnumElementRef(EED, Reason, /*HasTypeContext=*/false);
29182942
}
29192943

2920-
if (HaveDot)
2921-
return;
2922-
2923-
if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
2924-
if (ExprType->is<AnyMetatypeType>())
2925-
return;
2926-
addSubscriptCall(SD, Reason);
2927-
return;
2944+
// Swift key path allows .[0]
2945+
if (shouldAddSubscriptCall()) {
2946+
if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
2947+
if (ExprType->is<AnyMetatypeType>())
2948+
return;
2949+
addSubscriptCall(SD, Reason);
2950+
}
29282951
}
29292952
return;
29302953

@@ -4393,6 +4416,8 @@ void CodeCompletionCallbacksImpl::completeDotExpr(Expr *E, SourceLoc DotLoc) {
43934416
return;
43944417

43954418
Kind = CompletionKind::DotExpr;
4419+
if (E->getKind() == ExprKind::KeyPath)
4420+
Kind = CompletionKind::SwiftKeyPath;
43964421
if (ParseExprSelectorContext != ObjCSelectorContext::None) {
43974422
PreferFunctionReferencesToCalls = true;
43984423
CompleteExprSelectorContext = ParseExprSelectorContext;
@@ -4785,6 +4810,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
47854810
case CompletionKind::GenericParams:
47864811
case CompletionKind::KeyPathExpr:
47874812
case CompletionKind::KeyPathExprDot:
4813+
case CompletionKind::SwiftKeyPath:
47884814
break;
47894815

47904816
case CompletionKind::StmtOrExpr:
@@ -5127,6 +5153,20 @@ void CodeCompletionCallbacksImpl::doneParsing() {
51275153
break;
51285154
}
51295155

5156+
case CompletionKind::SwiftKeyPath: {
5157+
Lookup.setHaveDot(DotLoc);
5158+
Lookup.setIsSwiftKeyPathExpr();
5159+
if (auto BGT = (*ExprType)->getAs<BoundGenericType>()) {
5160+
auto AllArgs = BGT->getGenericArgs();
5161+
if (AllArgs.size() == 2) {
5162+
// The second generic type argument of KeyPath<Root, Value> should be
5163+
// the value we pull code completion results from.
5164+
Lookup.getValueExprCompletions(AllArgs[1]);
5165+
}
5166+
}
5167+
break;
5168+
}
5169+
51305170
case CompletionKind::StmtOrExpr:
51315171
DoPostfixExprBeginning();
51325172
break;

lib/Parse/ParseExpr.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ ParserResult<Expr> Parser::parseExprUnary(Diag<> Message, bool isExprBasic) {
525525
ParserResult<Expr> Parser::parseExprKeyPath() {
526526
// Consume '\'.
527527
SourceLoc backslashLoc = consumeToken(tok::backslash);
528+
llvm::SaveAndRestore<SourceLoc> slashLoc(SwiftKeyPathSlashLoc, backslashLoc);
528529

529530
// FIXME: diagnostics
530531
ParserResult<Expr> rootResult, pathResult;
@@ -538,6 +539,8 @@ ParserResult<Expr> Parser::parseExprKeyPath() {
538539
}
539540

540541
if (startsWithSymbol(Tok, '.')) {
542+
llvm::SaveAndRestore<Expr*> S(SwiftKeyPathRoot, rootResult.getPtrOrNull());
543+
541544
// For uniformity, \.foo is parsed as if it were MAGIC.foo, so we need to
542545
// make sure the . is there, but parsing the ? in \.? as .? doesn't make
543546
// sense. This is all made more complicated by .?. being considered an
@@ -1136,8 +1139,18 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,
11361139

11371140
// Handle "x.<tab>" for code completion.
11381141
if (Tok.is(tok::code_complete)) {
1139-
if (CodeCompletion && Result.isNonNull())
1142+
if (CodeCompletion && Result.isNonNull()) {
1143+
if (InSwiftKeyPath) {
1144+
Result = makeParserResult(
1145+
new (Context) KeyPathExpr(SwiftKeyPathSlashLoc, Result.get(),
1146+
nullptr));
1147+
} else if (SwiftKeyPathRoot) {
1148+
Result = makeParserResult(
1149+
new (Context) KeyPathExpr(SwiftKeyPathSlashLoc, SwiftKeyPathRoot,
1150+
Result.get()));
1151+
}
11401152
CodeCompletion->completeDotExpr(Result.get(), /*DotLoc=*/TokLoc);
1153+
}
11411154
// Eat the code completion token because we handled it.
11421155
consumeToken(tok::code_complete);
11431156
Result.setHasCodeCompletion();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_1 | %FileCheck %s -check-prefix=PERSON-MEMBER
2+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_2 | %FileCheck %s -check-prefix=PERSON-MEMBER
3+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_3 | %FileCheck %s -check-prefix=PERSON-MEMBER
4+
5+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_5 | %FileCheck %s -check-prefix=PERSON-MEMBER
6+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_6 | %FileCheck %s -check-prefix=PERSON-MEMBER
7+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_7 | %FileCheck %s -check-prefix=PERSON-MEMBER
8+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_8 | %FileCheck %s -check-prefix=PERSON-MEMBER
9+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_9 | %FileCheck %s -check-prefix=PERSON-MEMBER
10+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_10 | %FileCheck %s -check-prefix=PERSON-MEMBER
11+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_11 | %FileCheck %s -check-prefix=PERSON-MEMBER
12+
13+
class Person {
14+
var name: String
15+
var friends: [Person] = []
16+
var bestFriend: Person? = nil
17+
init(name: String) {
18+
self.name = name
19+
}
20+
func getName() -> String { return name }
21+
subscript(_ index: Int) -> Int { get { return 1} }
22+
}
23+
24+
let keyPath1 = \Person.#^PART_1^#
25+
let keyPath2 = \Person.friends[0].#^PART_2^#
26+
let keyPath3 = \Person.friends[0].friends[0].friends[0].#^PART_3^#
27+
28+
// FIXME: the optionality keypath should work after our compiler is ready.
29+
let keyPath4 = \Person.bestFriend?.#^PART_4^#
30+
let keyPath5 = \Person.friends.[0].friends[0].friends[0].#^PART_5^#
31+
let keyPath6 = \[Person].[0].#^PART_6^#
32+
let keyPath7 = \[Person].[0].friends[0].#^PART_7^#
33+
34+
func foo1(_ p : Person) {
35+
_ = p[keyPath:\Person.#^PART_8^#]
36+
_ = p[keyPath:\Person.friends[0].#^PART_9^#]
37+
_ = p[keyPath:\[Person].[0].#^PART_10^#]
38+
_ = p[keyPath:\Person.friends.[0].friends[0].friends[0].#^PART_11^#]
39+
}
40+
41+
// PERSON-MEMBER: Begin completions, 4 items
42+
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: name[#String#]; name=name
43+
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: friends[#[Person]#]; name=friends
44+
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: bestFriend[#Person?#]; name=bestFriend
45+
// PERSON-MEMBER-NEXT: Decl[Subscript]/CurrNominal: [{#Int#}][#Int#]; name=[Int]

0 commit comments

Comments
 (0)