Skip to content

Commit b9a389e

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 (cherry picked from commit a2b5968)
1 parent 430b422 commit b9a389e

File tree

8 files changed

+473
-53
lines changed

8 files changed

+473
-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: 197 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;
@@ -5913,21 +5986,126 @@ static void
59135986
addKeyword(CodeCompletionResultSink &Sink, StringRef Name,
59145987
CodeCompletionKeywordKind Kind, StringRef TypeAnnotation = "",
59155988
CodeCompletionResult::ExpectedTypeRelation TypeRelation =
5916-
CodeCompletionResult::ExpectedTypeRelation::NotApplicable) {
5989+
CodeCompletionResult::ExpectedTypeRelation::NotApplicable,
5990+
CodeCompletionFlair Flair = {}) {
59175991
CodeCompletionResultBuilder Builder(Sink,
59185992
CodeCompletionResult::ResultKind::Keyword,
59195993
SemanticContextKind::None, {});
59205994
Builder.setKeywordKind(Kind);
59215995
Builder.addKeyword(Name);
5996+
Builder.addFlair(Flair);
59225997
if (!TypeAnnotation.empty())
59235998
Builder.addTypeAnnotation(TypeAnnotation);
59245999
Builder.setExpectedTypeRelation(TypeRelation);
59256000
}
59266001

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

59386116
// Remove user inaccessible keywords.
5939-
if (DAK.hasValue() && DeclAttribute::isUserInaccessible(*DAK)) return;
6117+
if (DAK.hasValue() && DeclAttribute::isUserInaccessible(*DAK))
6118+
return;
59406119

59416120
// Remove keywords only available when concurrency is enabled.
59426121
if (DAK.hasValue() && !IsConcurrencyEnabled &&
59436122
DeclAttribute::isConcurrencyOnly(*DAK))
59446123
return;
59456124

5946-
addKeyword(Sink, Name, Kind);
6125+
addKeyword(
6126+
Sink, Name, Kind, /*TypeAnnotation=*/"",
6127+
/*TypeRelation=*/CodeCompletionResult::ExpectedTypeRelation::NotApplicable,
6128+
getFlair(Kind, DAK));
59476129
};
59486130

5949-
#define DECL_KEYWORD(kw) AddDeclKeyword(#kw, CodeCompletionKeywordKind::kw_##kw, None);
6131+
#define DECL_KEYWORD(kw) \
6132+
AddDeclKeyword(#kw, CodeCompletionKeywordKind::kw_##kw, None);
59506133
#include "swift/Syntax/TokenKinds.def"
59516134

59526135
// Context-sensitive keywords.
@@ -5960,7 +6143,6 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink,
59606143
#define CONTEXTUAL_SIMPLE_DECL_ATTR(KW, CLASS, ...) CONTEXTUAL_CASE(KW, CLASS)
59616144
#include <swift/AST/Attr.def>
59626145
#undef CONTEXTUAL_CASE
5963-
59646146
}
59656147

59666148
static void addStmtKeywords(CodeCompletionResultSink &Sink, bool MaybeFuncBody) {
@@ -6064,7 +6246,8 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
60646246
LLVM_FALLTHROUGH;
60656247
}
60666248
case CompletionKind::StmtOrExpr:
6067-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency);
6249+
addDeclKeywords(Sink, CurDeclContext,
6250+
Context.LangOpts.EnableExperimentalConcurrency);
60686251
addStmtKeywords(Sink, MaybeFuncBody);
60696252
LLVM_FALLTHROUGH;
60706253
case CompletionKind::ReturnStmtExpr:
@@ -6132,7 +6315,8 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
61326315
.Default(false);
61336316
}) != ParsedKeywords.end();
61346317
if (!HasDeclIntroducer) {
6135-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency);
6318+
addDeclKeywords(Sink, CurDeclContext,
6319+
Context.LangOpts.EnableExperimentalConcurrency);
61366320
addLetVarKeywords(Sink);
61376321
}
61386322
break;
@@ -6948,15 +7132,17 @@ void CodeCompletionCallbacksImpl::doneParsing() {
69487132

69497133
if (CurDeclContext->isTypeContext()) {
69507134
// Override completion (CompletionKind::NominalMemberBeginning).
6951-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency);
7135+
addDeclKeywords(Sink, CurDeclContext,
7136+
Context.LangOpts.EnableExperimentalConcurrency);
69527137
addLetVarKeywords(Sink);
69537138
SmallVector<StringRef, 0> ParsedKeywords;
69547139
CompletionOverrideLookup OverrideLookup(Sink, Context, CurDeclContext,
69557140
ParsedKeywords, SourceLoc());
69567141
OverrideLookup.getOverrideCompletions(SourceLoc());
69577142
} else {
69587143
// Global completion (CompletionKind::PostfixExprBeginning).
6959-
addDeclKeywords(Sink, Context.LangOpts.EnableExperimentalConcurrency);
7144+
addDeclKeywords(Sink, CurDeclContext,
7145+
Context.LangOpts.EnableExperimentalConcurrency);
69607146
addStmtKeywords(Sink, MaybeFuncBody);
69617147
addSuperKeyword(Sink);
69627148
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)