Skip to content

[CodeCompletion] Provide basic code completion support for Swift KeyPath. rdar://31768743 #9467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/swift/IDE/CodeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ enum class CompletionKind {
ReturnStmtExpr,
AfterPound,
GenericParams,
SwiftKeyPath,
};

/// \brief A single code completion result.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class Parser {
bool InPoundLineEnvironment = false;
bool InPoundIfEnvironment = false;
bool InSwiftKeyPath = false;
Expr* SwiftKeyPathRoot = nullptr;
SourceLoc SwiftKeyPathSlashLoc = SourceLoc();

LocalContext *CurLocalContext = nullptr;

Expand Down
58 changes: 49 additions & 9 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,16 @@ static bool KeyPathFilter(ValueDecl* decl, DeclVisibilityKind) {
(isa<VarDecl>(decl) && decl->getDeclContext()->isTypeContext());
}

static bool SwiftKeyPathFilter(ValueDecl* decl, DeclVisibilityKind) {
switch(decl->getKind()){
case DeclKind::Var:
case DeclKind::Subscript:
return true;
default:
return false;
}
}

std::string swift::ide::removeCodeCompletionTokens(
StringRef Input, StringRef TokenName, unsigned *CompletionOffset) {
assert(TokenName.size() >= 1);
Expand Down Expand Up @@ -1600,6 +1610,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
bool IsSuperRefExpr = false;
bool IsSelfRefExpr = false;
bool IsKeyPathExpr = false;
bool IsSwiftKeyPathExpr = false;
bool IsDynamicLookup = false;
bool PreferFunctionReferencesToCalls = false;
bool HaveLeadingSpace = false;
Expand Down Expand Up @@ -1765,6 +1776,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
IsKeyPathExpr = true;
}

void setIsSwiftKeyPathExpr() {
IsSwiftKeyPathExpr = true;
}

void setIsDynamicLookup() {
IsDynamicLookup = true;
}
Expand Down Expand Up @@ -2599,8 +2614,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
}
}

bool shouldAddSubscriptCall() {
if (IsSwiftKeyPathExpr)
return true;
return !HaveDot;
}

void addSubscriptCall(const SubscriptDecl *SD, DeclVisibilityKind Reason) {
assert(!HaveDot && "cannot add a subscript after a dot");
assert(shouldAddSubscriptCall() && "cannot add a subscript after a dot");
CommandWordsPairs Pairs;
CodeCompletionResultBuilder Builder(
Sink,
Expand Down Expand Up @@ -2798,6 +2819,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {

if (IsKeyPathExpr && !KeyPathFilter(D, Reason))
return;

if (IsSwiftKeyPathExpr && !SwiftKeyPathFilter(D, Reason))
return;

if (!D->hasInterfaceType())
TypeResolver->resolveDeclSignature(D);
Expand Down Expand Up @@ -2916,14 +2940,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
addEnumElementRef(EED, Reason, /*HasTypeContext=*/false);
}

if (HaveDot)
return;

if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
if (ExprType->is<AnyMetatypeType>())
return;
addSubscriptCall(SD, Reason);
return;
// Swift key path allows .[0]
if (shouldAddSubscriptCall()) {
if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
if (ExprType->is<AnyMetatypeType>())
return;
addSubscriptCall(SD, Reason);
}
}
return;

Expand Down Expand Up @@ -4392,6 +4415,8 @@ void CodeCompletionCallbacksImpl::completeDotExpr(Expr *E, SourceLoc DotLoc) {
return;

Kind = CompletionKind::DotExpr;
if (E->getKind() == ExprKind::KeyPath)
Kind = CompletionKind::SwiftKeyPath;
if (ParseExprSelectorContext != ObjCSelectorContext::None) {
PreferFunctionReferencesToCalls = true;
CompleteExprSelectorContext = ParseExprSelectorContext;
Expand Down Expand Up @@ -4784,6 +4809,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
case CompletionKind::GenericParams:
case CompletionKind::KeyPathExpr:
case CompletionKind::KeyPathExprDot:
case CompletionKind::SwiftKeyPath:
break;

case CompletionKind::StmtOrExpr:
Expand Down Expand Up @@ -5126,6 +5152,20 @@ void CodeCompletionCallbacksImpl::doneParsing() {
break;
}

case CompletionKind::SwiftKeyPath: {
Lookup.setHaveDot(DotLoc);
Lookup.setIsSwiftKeyPathExpr();
if (auto BGT = (*ExprType)->getAs<BoundGenericType>()) {
auto AllArgs = BGT->getGenericArgs();
if (AllArgs.size() == 2) {
// The second generic type argument of KeyPath<Root, Value> should be
// the value we pull code completion results from.
Lookup.getValueExprCompletions(AllArgs[1]);
}
}
break;
}

case CompletionKind::StmtOrExpr:
DoPostfixExprBeginning();
break;
Expand Down
15 changes: 14 additions & 1 deletion lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ ParserResult<Expr> Parser::parseExprUnary(Diag<> Message, bool isExprBasic) {
ParserResult<Expr> Parser::parseExprKeyPath() {
// Consume '\'.
SourceLoc backslashLoc = consumeToken(tok::backslash);
llvm::SaveAndRestore<SourceLoc> slashLoc(SwiftKeyPathSlashLoc, backslashLoc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use backslashLoc directly? It's not being modified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This basically to save SwiftKeyPathSlashLoc in the parser context so parseExprPostfixSuffix can use it to create an instance of KeyPathExpr.


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

if (startsWithSymbol(Tok, '.')) {
llvm::SaveAndRestore<Expr*> S(SwiftKeyPathRoot, rootResult.getPtrOrNull());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question for rootResult

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are not using SaveAndRestore, we are not able to use this part in parseExprPostfixSuffix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry I misread this. For some reason I didn't notice they were in completely different functions ,etc.


// For uniformity, \.foo is parsed as if it were MAGIC.foo, so we need to
// make sure the . is there, but parsing the ? in \.? as .? doesn't make
// sense. This is all made more complicated by .?. being considered an
Expand Down Expand Up @@ -1136,8 +1139,18 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> Result, bool isExprBasic,

// Handle "x.<tab>" for code completion.
if (Tok.is(tok::code_complete)) {
if (CodeCompletion && Result.isNonNull())
if (CodeCompletion && Result.isNonNull()) {
if (InSwiftKeyPath) {
Result = makeParserResult(
new (Context) KeyPathExpr(SwiftKeyPathSlashLoc, Result.get(),
nullptr));
} else if (SwiftKeyPathRoot) {
Result = makeParserResult(
new (Context) KeyPathExpr(SwiftKeyPathSlashLoc, SwiftKeyPathRoot,
Result.get()));
}
CodeCompletion->completeDotExpr(Result.get(), /*DotLoc=*/TokLoc);
}
// Eat the code completion token because we handled it.
consumeToken(tok::code_complete);
Result.setHasCodeCompletion();
Expand Down
45 changes: 45 additions & 0 deletions test/IDE/complete_swift_key_path.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_1 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_2 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_3 | %FileCheck %s -check-prefix=PERSON-MEMBER

// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_5 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_6 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_7 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_8 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_9 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_10 | %FileCheck %s -check-prefix=PERSON-MEMBER
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=PART_11 | %FileCheck %s -check-prefix=PERSON-MEMBER

class Person {
var name: String
var friends: [Person] = []
var bestFriend: Person? = nil
init(name: String) {
self.name = name
}
func getName() -> String { return name }
subscript(_ index: Int) -> Int { get { return 1} }
}

let keyPath1 = \Person.#^PART_1^#
let keyPath2 = \Person.friends[0].#^PART_2^#
let keyPath3 = \Person.friends[0].friends[0].friends[0].#^PART_3^#

// FIXME: the optionality keypath should work after our compiler is ready.
let keyPath4 = \Person.bestFriend?.#^PART_4^#
let keyPath5 = \Person.friends.[0].friends[0].friends[0].#^PART_5^#
let keyPath6 = \[Person].[0].#^PART_6^#
let keyPath7 = \[Person].[0].friends[0].#^PART_7^#

func foo1(_ p : Person) {
_ = p[keyPath:\Person.#^PART_8^#]
_ = p[keyPath:\Person.friends[0].#^PART_9^#]
_ = p[keyPath:\[Person].[0].#^PART_10^#]
_ = p[keyPath:\Person.friends.[0].friends[0].friends[0].#^PART_11^#]
}

// PERSON-MEMBER: Begin completions, 4 items
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: name[#String#]; name=name
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: friends[#[Person]#]; name=friends
// PERSON-MEMBER-NEXT: Decl[InstanceVar]/CurrNominal: bestFriend[#Person?#]; name=bestFriend
// PERSON-MEMBER-NEXT: Decl[Subscript]/CurrNominal: [{#Int#}][#Int#]; name=[Int]