Skip to content

Commit 814a08a

Browse files
committed
[IDE] Code completion for Objective-C #keyPath expressions.
Implement code completion support for Objective-C #keyPath expressions, using semantic analysis of the partially-typed keypath argument to provide an appropriate set of results (i.e., just properties and types). This implements all of the necessary parts of SE-0062 / SR-1237 / rdar://problem/25710611, although at some point I'd like to follow it up with some warnings to help migrate existing string literals to
1 parent 690d98a commit 814a08a

File tree

9 files changed

+231
-31
lines changed

9 files changed

+231
-31
lines changed

include/swift/IDE/CodeCompletion.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ enum class CompletionKind {
487487
PostfixExprParen,
488488
SuperExpr,
489489
SuperExprDot,
490+
KeyPathExpr,
491+
KeyPathExprDot,
490492
TypeSimpleBeginning,
491493
TypeIdentifierWithDot,
492494
TypeIdentifierWithoutDot,

include/swift/Parse/CodeCompletionCallbacks.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ class CodeCompletionCallbacks {
165165
/// a dot.
166166
virtual void completeExprSuperDot(SuperRefExpr *SRE) = 0;
167167

168+
/// \brief Complete the argument to an Objective-C #keyPath
169+
/// expression.
170+
///
171+
/// \param KPE A partial #keyPath expression that can be used to
172+
/// provide context. This will be \c NULL if no components of the
173+
/// #keyPath argument have been parsed yet.
174+
virtual void completeExprKeyPath(ObjCKeyPathExpr *KPE, bool HasDot) = 0;
175+
168176
/// \brief Complete the beginning of type-simple -- no tokens provided
169177
/// by user.
170178
virtual void completeTypeSimpleBeginning() = 0;

include/swift/Sema/IDETypeChecking.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,20 @@ namespace swift {
7777
/// \returns True on applied, false on not applied.
7878
bool isExtensionApplied(DeclContext &DC, Type Ty, const ExtensionDecl *ED);
7979

80+
/// The kind of type checking to perform for code completion.
81+
enum class CompletionTypeCheckKind {
82+
/// Type check the expression as normal.
83+
Normal,
84+
85+
/// Type check the argument to an Objective-C #keyPath.
86+
ObjCKeyPath,
87+
};
88+
8089
/// \brief Return the type of an expression parsed during code completion, or
8190
/// None on error.
8291
Optional<Type> getTypeOfCompletionContextExpr(ASTContext &Ctx,
8392
DeclContext *DC,
93+
CompletionTypeCheckKind kind,
8494
Expr *&parsedExpr);
8595

8696
/// Typecheck the sequence expression \p parsedExpr for code completion.

lib/IDE/CodeCompletion.cpp

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ void getSwiftDocKeyword(const Decl* D, CommandWordsPairs &Words) {
280280

281281
typedef llvm::function_ref<bool(ValueDecl*, DeclVisibilityKind)> DeclFilter;
282282
DeclFilter DefaultFilter = [] (ValueDecl* VD, DeclVisibilityKind Kind) {return true;};
283+
DeclFilter KeyPathFilter = [](ValueDecl* decl, DeclVisibilityKind) -> bool {
284+
return isa<TypeDecl>(decl) ||
285+
(isa<VarDecl>(decl) && decl->getDeclContext()->isTypeContext());
286+
};
283287

284288
std::string swift::ide::removeCodeCompletionTokens(
285289
StringRef Input, StringRef TokenName, unsigned *CompletionOffset) {
@@ -1281,16 +1285,24 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
12811285

12821286
Optional<Type> getTypeOfParsedExpr() {
12831287
assert(ParsedExpr && "should have an expression");
1288+
1289+
// Figure out the kind of type-check we'll be performing.
1290+
auto CheckKind = CompletionTypeCheckKind::Normal;
1291+
if (Kind == CompletionKind::KeyPathExpr ||
1292+
Kind == CompletionKind::KeyPathExprDot)
1293+
CheckKind = CompletionTypeCheckKind::ObjCKeyPath;
1294+
12841295
// If we've already successfully type-checked the expression for some
12851296
// reason, just return the type.
12861297
// FIXME: if it's ErrorType but we've already typechecked we shouldn't
12871298
// typecheck again. rdar://21466394
1288-
if (ParsedExpr->getType() && !ParsedExpr->getType()->is<ErrorType>())
1299+
if (CheckKind == CompletionTypeCheckKind::Normal &&
1300+
ParsedExpr->getType() && !ParsedExpr->getType()->is<ErrorType>())
12891301
return ParsedExpr->getType();
12901302

12911303
Expr *ModifiedExpr = ParsedExpr;
12921304
if (auto T = getTypeOfCompletionContextExpr(P.Context, CurDeclContext,
1293-
ModifiedExpr)) {
1305+
CheckKind, ModifiedExpr)) {
12941306
// FIXME: even though we don't apply the solution, the type checker may
12951307
// modify the original expression. We should understand what effect that
12961308
// may have on code completion.
@@ -1323,6 +1335,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
13231335
void completePostfixExprParen(Expr *E, Expr *CodeCompletionE) override;
13241336
void completeExprSuper(SuperRefExpr *SRE) override;
13251337
void completeExprSuperDot(SuperRefExpr *SRE) override;
1338+
void completeExprKeyPath(ObjCKeyPathExpr *KPE, bool HasDot) override;
13261339

13271340
void completeTypeSimpleBeginning() override;
13281341
void completeTypeIdentifierWithDot(IdentTypeRepr *ITR) override;
@@ -1525,6 +1538,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
15251538
bool HaveLParen = false;
15261539
bool HaveRParen = false;
15271540
bool IsSuperRefExpr = false;
1541+
bool IsKeyPathExpr = false;
15281542
bool IsDynamicLookup = false;
15291543
bool PreferFunctionReferencesToCalls = false;
15301544
bool HaveLeadingSpace = false;
@@ -1677,6 +1691,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
16771691
IsSuperRefExpr = true;
16781692
}
16791693

1694+
void setIsKeyPathExpr() {
1695+
IsKeyPathExpr = true;
1696+
}
1697+
16801698
void setIsDynamicLookup() {
16811699
IsDynamicLookup = true;
16821700
}
@@ -2147,6 +2165,25 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
21472165
Builder.addRightParen();
21482166
}
21492167

2168+
void addPoundKeyPath(bool needPound) {
2169+
// #keyPath is only available when the Objective-C runtime is.
2170+
if (!Ctx.LangOpts.EnableObjCInterop) return;
2171+
2172+
CodeCompletionResultBuilder Builder(
2173+
Sink,
2174+
CodeCompletionResult::ResultKind::Keyword,
2175+
SemanticContextKind::ExpressionSpecific,
2176+
ExpectedTypes);
2177+
if (needPound)
2178+
Builder.addTextChunk("#keyPath");
2179+
else
2180+
Builder.addTextChunk("keyPath");
2181+
Builder.addLeftParen();
2182+
Builder.addSimpleTypedParameter("@objc property sequence",
2183+
/*isVarArg=*/false);
2184+
Builder.addRightParen();
2185+
}
2186+
21502187
void addFunctionCallPattern(const AnyFunctionType *AFT,
21512188
const AbstractFunctionDecl *AFD = nullptr) {
21522189
foundFunction(AFT);
@@ -2596,6 +2633,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
25962633
if (D->getName().isEditorPlaceholder())
25972634
return;
25982635

2636+
if (IsKeyPathExpr && !KeyPathFilter(D, Reason))
2637+
return;
2638+
25992639
if (!D->hasType())
26002640
TypeResolver->resolveDeclSignature(D);
26012641
else if (isa<TypeAliasDecl>(D)) {
@@ -3046,7 +3086,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
30463086

30473087
if (auto T = getTypeOfCompletionContextExpr(
30483088
CurrDeclContext->getASTContext(),
3049-
const_cast<DeclContext *>(CurrDeclContext), tempExpr))
3089+
const_cast<DeclContext *>(CurrDeclContext),
3090+
CompletionTypeCheckKind::Normal,
3091+
tempExpr))
30503092
addPostfixOperatorCompletion(op, *T);
30513093
}
30523094

@@ -3371,7 +3413,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
33713413
void getValueCompletionsInDeclContext(SourceLoc Loc,
33723414
DeclFilter Filter = DefaultFilter,
33733415
bool IncludeTopLevel = false,
3374-
bool RequestCache = true) {
3416+
bool RequestCache = true,
3417+
bool LiteralCompletions = true) {
33753418
Kind = LookupKind::ValueInDeclContext;
33763419
NeedLeadingDot = false;
33773420
FilteredDeclConsumer Consumer(*this, Filter);
@@ -3397,19 +3440,33 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
33973440
}
33983441
}
33993442

3400-
addValueLiteralCompletions();
3443+
if (LiteralCompletions)
3444+
addValueLiteralCompletions();
34013445

3402-
// If the expected type is ObjectiveC.Selector, add #selector.
3446+
// If the expected type is ObjectiveC.Selector, add #selector. If
3447+
// it's String, add #keyPath.
34033448
if (Ctx.LangOpts.EnableObjCInterop) {
3449+
bool addedSelector = false;
3450+
bool addedKeyPath = false;
34043451
for (auto T : ExpectedTypes) {
34053452
T = T->lookThroughAllAnyOptionalTypes();
34063453
if (auto structDecl = T->getStructOrBoundGenericStruct()) {
3407-
if (structDecl->getName() == Ctx.Id_Selector &&
3454+
if (!addedSelector &&
3455+
structDecl->getName() == Ctx.Id_Selector &&
34083456
structDecl->getParentModule()->getName() == Ctx.Id_ObjectiveC) {
34093457
addPoundSelector(/*needPound=*/true);
3410-
break;
3458+
if (addedKeyPath) break;
3459+
addedSelector = true;
3460+
continue;
34113461
}
34123462
}
3463+
3464+
if (!addedKeyPath && T->getAnyNominal() == Ctx.getStringDecl()) {
3465+
addPoundKeyPath(/*needPound=*/true);
3466+
if (addedSelector) break;
3467+
addedKeyPath = true;
3468+
continue;
3469+
}
34133470
}
34143471
}
34153472
}
@@ -4192,6 +4249,13 @@ void CodeCompletionCallbacksImpl::completeExprSuperDot(SuperRefExpr *SRE) {
41924249
CurDeclContext = P.CurDeclContext;
41934250
}
41944251

4252+
void CodeCompletionCallbacksImpl::completeExprKeyPath(ObjCKeyPathExpr *KPE,
4253+
bool HasDot) {
4254+
Kind = HasDot ? CompletionKind::KeyPathExprDot : CompletionKind::KeyPathExpr;
4255+
ParsedExpr = KPE;
4256+
CurDeclContext = P.CurDeclContext;
4257+
}
4258+
41954259
void CodeCompletionCallbacksImpl::completePoundAvailablePlatform() {
41964260
Kind = CompletionKind::PoundAvailablePlatform;
41974261
CurDeclContext = P.CurDeclContext;
@@ -4447,6 +4511,8 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink) {
44474511
case CompletionKind::CallArg:
44484512
case CompletionKind::AfterPound:
44494513
case CompletionKind::GenericParams:
4514+
case CompletionKind::KeyPathExpr:
4515+
case CompletionKind::KeyPathExprDot:
44504516
break;
44514517

44524518
case CompletionKind::StmtOrExpr:
@@ -4722,7 +4788,9 @@ void CodeCompletionCallbacksImpl::doneParsing() {
47224788
if (ParsedExpr) {
47234789
ExprType = getTypeOfParsedExpr();
47244790
if (!ExprType && Kind != CompletionKind::PostfixExprParen &&
4725-
Kind != CompletionKind::CallArg)
4791+
Kind != CompletionKind::CallArg &&
4792+
Kind != CompletionKind::KeyPathExpr &&
4793+
Kind != CompletionKind::KeyPathExprDot)
47264794
return;
47274795
if (ExprType)
47284796
ParsedExpr->setType(*ExprType);
@@ -4856,6 +4924,27 @@ void CodeCompletionCallbacksImpl::doneParsing() {
48564924
break;
48574925
}
48584926

4927+
case CompletionKind::KeyPathExprDot:
4928+
Lookup.setHaveDot(SourceLoc());
4929+
SWIFT_FALLTHROUGH;
4930+
4931+
case CompletionKind::KeyPathExpr: {
4932+
Lookup.setIsKeyPathExpr();
4933+
Lookup.includeInstanceMembers();
4934+
4935+
if (ExprType) {
4936+
if (isDynamicLookup(*ExprType))
4937+
Lookup.setIsDynamicLookup();
4938+
4939+
Lookup.getValueExprCompletions(*ExprType);
4940+
} else {
4941+
SourceLoc Loc = P.Context.SourceMgr.getCodeCompletionLoc();
4942+
Lookup.getValueCompletionsInDeclContext(Loc, KeyPathFilter,
4943+
false, true, false);
4944+
}
4945+
break;
4946+
}
4947+
48594948
case CompletionKind::TypeSimpleBeginning: {
48604949
Lookup.getTypeCompletionsInDeclContext(
48614950
P.Context.SourceMgr.getCodeCompletionLoc());
@@ -4965,6 +5054,7 @@ void CodeCompletionCallbacksImpl::doneParsing() {
49655054
case CompletionKind::AfterPound: {
49665055
Lookup.addPoundAvailable(ParentStmtKind);
49675056
Lookup.addPoundSelector(/*needPound=*/false);
5057+
Lookup.addPoundKeyPath(/*needPound=*/false);
49685058
break;
49695059
}
49705060

lib/Parse/ParseExpr.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,12 +566,30 @@ ParserResult<Expr> Parser::parseExprKeyPath() {
566566
}
567567
SourceLoc lParenLoc = consumeToken(tok::l_paren);
568568

569-
// Parse the sequence of unqualified-names.
569+
// Handle code completion.
570570
SmallVector<Identifier, 4> names;
571571
SmallVector<SourceLoc, 4> nameLocs;
572+
auto handleCodeCompletion = [&](bool hasDot) -> ParserResult<Expr> {
573+
ObjCKeyPathExpr *expr = nullptr;
574+
if (!names.empty()) {
575+
expr = ObjCKeyPathExpr::create(Context, keywordLoc, lParenLoc, names,
576+
nameLocs, Tok.getLoc());
577+
}
578+
579+
if (CodeCompletion)
580+
CodeCompletion->completeExprKeyPath(expr, hasDot);
581+
582+
// Eat the code completion token because we handled it.
583+
consumeToken(tok::code_complete);
584+
return makeParserCodeCompletionResult(expr);
585+
};
586+
587+
// Parse the sequence of unqualified-names.
572588
ParserStatus status;
573589
while (true) {
574-
// FIXME: Code completion.
590+
// Handle code completion.
591+
if (Tok.is(tok::code_complete))
592+
return handleCodeCompletion(!names.empty());
575593

576594
// Parse the next name.
577595
DeclNameLoc nameLoc;
@@ -595,6 +613,10 @@ ParserResult<Expr> Parser::parseExprKeyPath() {
595613
names.push_back(name.getBaseName());
596614
nameLocs.push_back(nameLoc.getBaseNameLoc());
597615

616+
// Handle code completion.
617+
if (Tok.is(tok::code_complete))
618+
return handleCodeCompletion(false);
619+
598620
// Parse the next period to continue the path.
599621
if (consumeIf(tok::period))
600622
continue;

lib/Sema/TypeCheckExprObjC.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
#include "swift/Basic/Range.h"
1919
using namespace swift;
2020

21-
ObjCKeyPathExpr *TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
22-
ObjCKeyPathExpr *expr) {
21+
Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
22+
ObjCKeyPathExpr *expr,
23+
bool requireResultType) {
2324
// If there is already a semantic expression, do nothing.
24-
if (expr->getSemanticExpr()) return expr;
25+
if (expr->getSemanticExpr() && !requireResultType) return None;
2526

2627
// #keyPath only makes sense when we have the Objective-C runtime.
2728
if (!Context.LangOpts.EnableObjCInterop) {
@@ -30,7 +31,7 @@ ObjCKeyPathExpr *TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
3031
expr->setSemanticExpr(
3132
new (Context) StringLiteralExpr("", expr->getSourceRange(),
3233
/*Implicit=*/true));
33-
return expr;
34+
return None;
3435
}
3536

3637
// The key path string we're forming.
@@ -70,7 +71,7 @@ ObjCKeyPathExpr *TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
7071
anyObjectType = anyObject->getDeclaredInterfaceType();
7172
} else {
7273
diagnose(expr->getLoc(), diag::stdlib_anyobject_not_found);
73-
return expr;
74+
return None;
7475
}
7576

7677
// Local function to update the state after we've resolved a
@@ -332,10 +333,13 @@ ObjCKeyPathExpr *TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
332333
diagnose(expr->getLoc(), diag::expr_keypath_empty);
333334

334335
// Set the semantic expression.
335-
expr->setSemanticExpr(
336-
new (Context) StringLiteralExpr(Context.AllocateCopy(keyPathString),
337-
expr->getSourceRange(),
338-
/*Implicit=*/true));
339-
return expr;
340-
}
336+
if (!expr->getSemanticExpr()) {
337+
expr->setSemanticExpr(
338+
new (Context) StringLiteralExpr(Context.AllocateCopy(keyPathString),
339+
expr->getSourceRange(),
340+
/*Implicit=*/true));
341+
}
341342

343+
if (!currentType) return None;
344+
return currentType;
345+
}

0 commit comments

Comments
 (0)