Skip to content

Commit a2b5968

Browse files
committed
[CodeCompletion] Add decl context dependent 'Flair' to decl keywords
* 'super' in a overriding decl is "common". * type decl introducers (e.g. 'struct', 'enum') at top-level in library files are "common" * type decl introducers in 'protocol' are invalid, hence "rare" * top-level only decl introducer (e.g. 'import', 'extension') are invalid at non-top-level, hence "rare" * nested types in function bodies are "rare" * member only decls (e.g. 'subscript', 'deinit') are invalid in function body, hence "rare" * some modifiers (e.g. 'public', 'private', 'override') are invalid for local decls, hence "rare" rdar://77934651
1 parent 154cd88 commit a2b5968

File tree

8 files changed

+477
-53
lines changed

8 files changed

+477
-53
lines changed

include/swift/IDE/CodeCompletion.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,13 @@ enum class CodeCompletionFlairBit: uint8_t {
417417

418418
/// Argument label and type. i.e. 'label: <#Ty#>'.
419419
ArgumentLabels = 1 << 2,
420+
421+
/// E.g. decl introducer or modifiers ('enum', 'protocol', 'public', etc.) at
422+
/// top-level.
423+
CommonKeywordAtCurrentPosition = 1 << 3,
424+
425+
/// E.g. type decl introducer ('enum', 'class', etc.) in a function body.
426+
RareKeywordAtCurrentPosition = 1 << 4,
420427
};
421428

422429
using CodeCompletionFlair = OptionSet<CodeCompletionFlairBit>;

lib/IDE/CodeCompletion.cpp

Lines changed: 201 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,8 @@ void CodeCompletionResult::printPrefix(raw_ostream &OS) const {
741741
PRINT_FLAIR(ExpressionSpecific, "ExprSpecific");
742742
PRINT_FLAIR(SuperChain, "SuperChain");
743743
PRINT_FLAIR(ArgumentLabels, "ArgLabels");
744+
PRINT_FLAIR(CommonKeywordAtCurrentPosition, "CommonKeyword")
745+
PRINT_FLAIR(RareKeywordAtCurrentPosition, "RareKeyword")
744746
Prefix.append("]");
745747
}
746748
if (NotRecommended)
@@ -1540,6 +1542,12 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
15401542
CodeCompletionResult::ResultKind::Keyword,
15411543
SemanticContextKind::CurrentNominal,
15421544
{});
1545+
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(CurDeclContext)) {
1546+
if (AFD->getOverriddenDecl() != nullptr) {
1547+
Builder.addFlair(CodeCompletionFlairBit::CommonKeywordAtCurrentPosition);
1548+
}
1549+
}
1550+
15431551
Builder.setKeywordKind(CodeCompletionKeywordKind::kw_super);
15441552
Builder.addKeyword("super");
15451553
Builder.addTypeAnnotation(ST, PrintOptions());
@@ -1822,6 +1830,71 @@ static bool canDeclContextHandleAsync(const DeclContext *DC) {
18221830
return false;
18231831
}
18241832

1833+
/// Returns \c true only if the completion is happening for top-level
1834+
/// declrarations. i.e.:
1835+
///
1836+
/// if condition {
1837+
/// #false#
1838+
/// }
1839+
/// expr.#false#
1840+
///
1841+
/// #true#
1842+
///
1843+
/// struct S {
1844+
/// #false#
1845+
/// func foo() {
1846+
/// #false#
1847+
/// }
1848+
/// }
1849+
static bool isCodeCompletionAtTopLevel(DeclContext *DC) {
1850+
if (DC->isModuleScopeContext())
1851+
return true;
1852+
1853+
// CC token at top-level is parsed as an expression. If the only element
1854+
// body of the TopLevelCodeDecl is a CodeCompletionExpr without a base
1855+
// expression, the user might be writing a top-level declaration.
1856+
if (TopLevelCodeDecl *TLCD = dyn_cast<TopLevelCodeDecl>(DC)) {
1857+
auto body = TLCD->getBody();
1858+
if (!body || body->empty())
1859+
return true;
1860+
if (body->getElements().size() > 1)
1861+
return false;
1862+
auto expr = body->getFirstElement().dyn_cast<Expr *>();
1863+
if (!expr)
1864+
return false;
1865+
if (CodeCompletionExpr *CCExpr = dyn_cast<CodeCompletionExpr>(expr)) {
1866+
if (CCExpr->getBase() == nullptr)
1867+
return true;
1868+
}
1869+
}
1870+
1871+
return false;
1872+
}
1873+
1874+
/// Returns \c true if the completion is happening in local context such as
1875+
/// inside function bodies. i.e.:
1876+
///
1877+
/// if condition {
1878+
/// #true#
1879+
/// }
1880+
/// expr.#true#
1881+
///
1882+
/// #false#
1883+
///
1884+
/// struct S {
1885+
/// #false#
1886+
/// func foo() {
1887+
/// #true#
1888+
/// }
1889+
/// }
1890+
static bool isCompletionDeclContextLocalContext(DeclContext *DC) {
1891+
if (!DC->isLocalContext())
1892+
return false;
1893+
if (isCodeCompletionAtTopLevel(DC))
1894+
return false;
1895+
return true;
1896+
}
1897+
18251898
/// Build completions by doing visible decl lookup from a context.
18261899
class CompletionLookup final : public swift::VisibleDeclConsumer {
18271900
CodeCompletionResultSink &Sink;
@@ -5925,22 +5998,127 @@ static void
59255998
addKeyword(CodeCompletionResultSink &Sink, StringRef Name,
59265999
CodeCompletionKeywordKind Kind, StringRef TypeAnnotation = "",
59276000
CodeCompletionResult::ExpectedTypeRelation TypeRelation =
5928-
CodeCompletionResult::ExpectedTypeRelation::NotApplicable) {
6001+
CodeCompletionResult::ExpectedTypeRelation::NotApplicable,
6002+
CodeCompletionFlair Flair = {}) {
59296003
CodeCompletionResultBuilder Builder(Sink,
59306004
CodeCompletionResult::ResultKind::Keyword,
59316005
SemanticContextKind::None, {});
59326006
Builder.setKeywordKind(Kind);
59336007
Builder.addKeyword(Name);
6008+
Builder.addFlair(Flair);
59346009
if (!TypeAnnotation.empty())
59356010
Builder.addTypeAnnotation(TypeAnnotation);
59366011
Builder.setExpectedTypeRelation(TypeRelation);
59376012
}
59386013

5939-
static void addDeclKeywords(CodeCompletionResultSink &Sink,
6014+
static void addDeclKeywords(CodeCompletionResultSink &Sink, DeclContext *DC,
59406015
bool IsConcurrencyEnabled,
59416016
bool IsDistributedEnabled) {
6017+
auto isTypeDeclIntroducer = [](CodeCompletionKeywordKind Kind,
6018+
Optional<DeclAttrKind> DAK) -> bool {
6019+
switch (Kind) {
6020+
case CodeCompletionKeywordKind::kw_protocol:
6021+
case CodeCompletionKeywordKind::kw_class:
6022+
case CodeCompletionKeywordKind::kw_struct:
6023+
case CodeCompletionKeywordKind::kw_enum:
6024+
case CodeCompletionKeywordKind::kw_extension:
6025+
return true;
6026+
case CodeCompletionKeywordKind::None:
6027+
if (DAK && *DAK == DeclAttrKind::DAK_Actor) {
6028+
return true;
6029+
}
6030+
break;
6031+
default:
6032+
break;
6033+
}
6034+
return false;
6035+
};
6036+
auto isTopLevelOnlyDeclIntroducer = [](CodeCompletionKeywordKind Kind,
6037+
Optional<DeclAttrKind> DAK) -> bool {
6038+
switch (Kind) {
6039+
case CodeCompletionKeywordKind::kw_operator:
6040+
case CodeCompletionKeywordKind::kw_precedencegroup:
6041+
case CodeCompletionKeywordKind::kw_import:
6042+
case CodeCompletionKeywordKind::kw_protocol:
6043+
case CodeCompletionKeywordKind::kw_extension:
6044+
return true;
6045+
default:
6046+
return false;
6047+
}
6048+
};
6049+
6050+
auto getFlair = [&](CodeCompletionKeywordKind Kind,
6051+
Optional<DeclAttrKind> DAK) -> CodeCompletionFlair {
6052+
if (isCodeCompletionAtTopLevel(DC) &&
6053+
!DC->getParentSourceFile()->isScriptMode()) {
6054+
// Type decls are common in library file top-level.
6055+
if (isTypeDeclIntroducer(Kind, DAK))
6056+
return CodeCompletionFlairBit::CommonKeywordAtCurrentPosition;
6057+
}
6058+
if (isa<ProtocolDecl>(DC)) {
6059+
// Protocols cannot have nested type decls (other than 'typealias').
6060+
if (isTypeDeclIntroducer(Kind, DAK))
6061+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6062+
}
6063+
if (DC->isTypeContext()) {
6064+
// Top-level only decls are invalid in type context.
6065+
if (isTopLevelOnlyDeclIntroducer(Kind, DAK))
6066+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6067+
}
6068+
if (isCompletionDeclContextLocalContext(DC)) {
6069+
// Local type decl are valid, but not common.
6070+
if (isTypeDeclIntroducer(Kind, DAK))
6071+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6072+
6073+
// Top-level only decls are invalid in function body.
6074+
if (isTopLevelOnlyDeclIntroducer(Kind, DAK))
6075+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6076+
6077+
// 'init', 'deinit' and 'subscript' are invalid in function body.
6078+
// Access control modifiers are invalid in function body.
6079+
switch (Kind) {
6080+
case CodeCompletionKeywordKind::kw_init:
6081+
case CodeCompletionKeywordKind::kw_deinit:
6082+
case CodeCompletionKeywordKind::kw_subscript:
6083+
case CodeCompletionKeywordKind::kw_private:
6084+
case CodeCompletionKeywordKind::kw_fileprivate:
6085+
case CodeCompletionKeywordKind::kw_internal:
6086+
case CodeCompletionKeywordKind::kw_public:
6087+
case CodeCompletionKeywordKind::kw_static:
6088+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6089+
6090+
default:
6091+
break;
6092+
}
6093+
6094+
// These modifiers are invalid for decls in function body.
6095+
if (DAK) {
6096+
switch (*DAK) {
6097+
case DeclAttrKind::DAK_Lazy:
6098+
case DeclAttrKind::DAK_Final:
6099+
case DeclAttrKind::DAK_Infix:
6100+
case DeclAttrKind::DAK_Frozen:
6101+
case DeclAttrKind::DAK_Prefix:
6102+
case DeclAttrKind::DAK_Postfix:
6103+
case DeclAttrKind::DAK_Dynamic:
6104+
case DeclAttrKind::DAK_Override:
6105+
case DeclAttrKind::DAK_Optional:
6106+
case DeclAttrKind::DAK_Required:
6107+
case DeclAttrKind::DAK_Convenience:
6108+
case DeclAttrKind::DAK_AccessControl:
6109+
case DeclAttrKind::DAK_Nonisolated:
6110+
return CodeCompletionFlairBit::RareKeywordAtCurrentPosition;
6111+
6112+
default:
6113+
break;
6114+
}
6115+
}
6116+
}
6117+
return None;
6118+
};
6119+
59426120
auto AddDeclKeyword = [&](StringRef Name, CodeCompletionKeywordKind Kind,
5943-
Optional<DeclAttrKind> DAK) {
6121+
Optional<DeclAttrKind> DAK) {
59446122
if (Name == "let" || Name == "var") {
59456123
// Treat keywords that could be the start of a pattern specially.
59466124
return;
@@ -5949,7 +6127,8 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink,
59496127
// FIXME: This should use canUseAttributeOnDecl.
59506128

59516129
// Remove user inaccessible keywords.
5952-
if (DAK.hasValue() && DeclAttribute::isUserInaccessible(*DAK)) return;
6130+
if (DAK.hasValue() && DeclAttribute::isUserInaccessible(*DAK))
6131+
return;
59536132

59546133
// Remove keywords only available when concurrency is enabled.
59556134
if (DAK.hasValue() && !IsConcurrencyEnabled &&
@@ -5961,10 +6140,14 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink,
59616140
DeclAttribute::isDistributedOnly(*DAK))
59626141
return;
59636142

5964-
addKeyword(Sink, Name, Kind);
6143+
addKeyword(
6144+
Sink, Name, Kind, /*TypeAnnotation=*/"",
6145+
/*TypeRelation=*/CodeCompletionResult::ExpectedTypeRelation::NotApplicable,
6146+
getFlair(Kind, DAK));
59656147
};
59666148

5967-
#define DECL_KEYWORD(kw) AddDeclKeyword(#kw, CodeCompletionKeywordKind::kw_##kw, None);
6149+
#define DECL_KEYWORD(kw) \
6150+
AddDeclKeyword(#kw, CodeCompletionKeywordKind::kw_##kw, None);
59686151
#include "swift/Syntax/TokenKinds.def"
59696152

59706153
// Context-sensitive keywords.
@@ -5978,7 +6161,6 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink,
59786161
#define CONTEXTUAL_SIMPLE_DECL_ATTR(KW, CLASS, ...) CONTEXTUAL_CASE(KW, CLASS)
59796162
#include <swift/AST/Attr.def>
59806163
#undef CONTEXTUAL_CASE
5981-
59826164
}
59836165

59846166
static void addStmtKeywords(CodeCompletionResultSink &Sink, bool MaybeFuncBody) {
@@ -6082,7 +6264,9 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
60826264
LLVM_FALLTHROUGH;
60836265
}
60846266
case CompletionKind::StmtOrExpr:
6085-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency, Context.LangOpts.EnableExperimentalDistributed);
6267+
addDeclKeywords(Sink, CurDeclContext,
6268+
Context.LangOpts.EnableExperimentalConcurrency,
6269+
Context.LangOpts.EnableExperimentalDistributed);
60866270
addStmtKeywords(Sink, MaybeFuncBody);
60876271
LLVM_FALLTHROUGH;
60886272
case CompletionKind::ReturnStmtExpr:
@@ -6150,7 +6334,9 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
61506334
.Default(false);
61516335
}) != ParsedKeywords.end();
61526336
if (!HasDeclIntroducer) {
6153-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency, Context.LangOpts.EnableExperimentalDistributed);
6337+
addDeclKeywords(Sink, CurDeclContext,
6338+
Context.LangOpts.EnableExperimentalConcurrency,
6339+
Context.LangOpts.EnableExperimentalDistributed);
61546340
addLetVarKeywords(Sink);
61556341
}
61566342
break;
@@ -6966,15 +7152,19 @@ void CodeCompletionCallbacksImpl::doneParsing() {
69667152

69677153
if (CurDeclContext->isTypeContext()) {
69687154
// Override completion (CompletionKind::NominalMemberBeginning).
6969-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency, Context.LangOpts.EnableExperimentalDistributed);
7155+
addDeclKeywords(Sink, CurDeclContext,
7156+
Context.LangOpts.EnableExperimentalConcurrency,
7157+
Context.LangOpts.EnableExperimentalDistributed);
69707158
addLetVarKeywords(Sink);
69717159
SmallVector<StringRef, 0> ParsedKeywords;
69727160
CompletionOverrideLookup OverrideLookup(Sink, Context, CurDeclContext,
69737161
ParsedKeywords, SourceLoc());
69747162
OverrideLookup.getOverrideCompletions(SourceLoc());
69757163
} else {
69767164
// Global completion (CompletionKind::PostfixExprBeginning).
6977-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency, Context.LangOpts.EnableExperimentalDistributed);
7165+
addDeclKeywords(Sink, CurDeclContext,
7166+
Context.LangOpts.EnableExperimentalConcurrency,
7167+
Context.LangOpts.EnableExperimentalDistributed);
69787168
addStmtKeywords(Sink, MaybeFuncBody);
69797169
addSuperKeyword(Sink);
69807170
addLetVarKeywords(Sink);

test/IDE/complete_annotation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func testGlobal() {
3131
}
3232
// GLOBAL_EXPR: Begin completions
3333
// GLOBAL_EXPR-DAG: Decl[Struct]/CurrModule: <name>MyStruct</name>; typename=<typeid.user>MyStruct</typeid.user>;
34-
// GLOBAL_EXPR-DAG: Keyword[class]/None: <keyword>class</keyword>; typename=;
35-
// GLOBAL_EXPR-DAG: Keyword[enum]/None: <keyword>enum</keyword>; typename=;
34+
// GLOBAL_EXPR-DAG: Keyword[class]/None/Flair[RareKeyword]: <keyword>class</keyword>; typename=;
35+
// GLOBAL_EXPR-DAG: Keyword[enum]/None/Flair[RareKeyword]: <keyword>enum</keyword>; typename=;
3636
// GLOBAL_EXPR-DAG: Keyword[if]/None: <keyword>if</keyword>; typename=;
3737
// GLOBAL_EXPR-DAG: Keyword[guard]/None: <keyword>guard</keyword>; typename=;
3838
// GLOBAL_EXPR-DAG: Keyword[try]/None: <keyword>try</keyword>; typename=;

test/IDE/complete_concurrency_keyword.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ enum Namespace {
2222
func testFunc() {
2323
#^STMT^#
2424
// STMT: Begin completions
25-
// STMT-DAG: Keyword/None: actor; name=actor
25+
// STMT-DAG: Keyword/None/Flair[RareKeyword]: actor; name=actor
2626
// STMT-DAG: Keyword/None: await; name=await
2727
// STMT: End completion
2828
}

0 commit comments

Comments
 (0)