Skip to content

Commit df473f6

Browse files
committed
[CodeCompletion] Escape declaration base name if needed
There should be escaped identifiers in code completion: - As primary expression: Any keyword name except for `self` and `Self`. - After dot: Something named `init`. rdar://problem/16232627
1 parent 31e6712 commit df473f6

File tree

3 files changed

+134
-13
lines changed

3 files changed

+134
-13
lines changed

lib/IDE/CodeCompletion.cpp

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,32 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
18231823
llvm_unreachable("unhandled kind");
18241824
}
18251825

1826+
void addValueBaseName(CodeCompletionResultBuilder &Builder,
1827+
DeclBaseName Name) {
1828+
auto NameStr = Name.userFacingName();
1829+
bool shouldEscapeKeywords;
1830+
if (Name.isSpecial()) {
1831+
// Special names (i.e. 'init') are always displayed as its user facing
1832+
// name.
1833+
shouldEscapeKeywords = false;
1834+
} else if (ExprType) {
1835+
// After dot. User can write any keyword after '.' except for `init` and
1836+
// `self`. E.g. 'func `init`()' must be called by 'expr.`init`()'.
1837+
shouldEscapeKeywords = NameStr == "self" || NameStr == "init";
1838+
} else {
1839+
// As primary expresson. We have to escape almost every keywords except
1840+
// for 'self' and 'Self'.
1841+
shouldEscapeKeywords = NameStr != "self" && NameStr != "Self";
1842+
}
1843+
1844+
if (!shouldEscapeKeywords) {
1845+
Builder.addTextChunk(NameStr);
1846+
} else {
1847+
SmallString<16> buffer;
1848+
Builder.addTextChunk(Builder.escapeKeyword(NameStr, true, buffer));
1849+
}
1850+
}
1851+
18261852
void addLeadingDot(CodeCompletionResultBuilder &Builder) {
18271853
if (NeedOptionalUnwrap) {
18281854
Builder.setNumBytesToErase(NumBytesToEraseForOptionalUnwrap);
@@ -1995,7 +2021,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
19952021
VD->shouldHideFromEditor())
19962022
return;
19972023

1998-
StringRef Name = VD->getName().get();
2024+
Identifier Name = VD->getName();
19992025
assert(!Name.empty() && "name should not be empty");
20002026

20012027
CommandWordsPairs Pairs;
@@ -2005,15 +2031,15 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
20052031
getSemanticContext(VD, Reason), ExpectedTypes);
20062032
Builder.setAssociatedDecl(VD);
20072033
addLeadingDot(Builder);
2008-
Builder.addTextChunk(Name);
2034+
addValueBaseName(Builder, Name);
20092035
setClangDeclKeywords(VD, Pairs, Builder);
20102036

20112037
if (!VD->hasValidSignature())
20122038
return;
20132039

20142040
// Add a type annotation.
20152041
Type VarType = getTypeOfMember(VD);
2016-
if (VD->getName() != Ctx.Id_self && VD->isInOut()) {
2042+
if (Name != Ctx.Id_self && VD->isInOut()) {
20172043
// It is useful to show inout for function parameters.
20182044
// But for 'self' it is just noise.
20192045
VarType = InOutType::get(VarType);
@@ -2319,7 +2345,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
23192345
return;
23202346
foundFunction(FD);
23212347

2322-
StringRef Name = FD->getName().get();
2348+
Identifier Name = FD->getName();
23232349
assert(!Name.empty() && "name should not be empty");
23242350

23252351
Type FunctionType = getTypeOfMember(FD);
@@ -2350,7 +2376,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
23502376
setClangDeclKeywords(FD, Pairs, Builder);
23512377
Builder.setAssociatedDecl(FD);
23522378
addLeadingDot(Builder);
2353-
Builder.addTextChunk(Name);
2379+
addValueBaseName(Builder, Name);
23542380
if (IsDynamicLookup)
23552381
Builder.addDynamicLookupMethodCallTail();
23562382
else if (FD->getAttrs().hasAttribute<OptionalAttr>())
@@ -2680,8 +2706,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
26802706
Builder.setAssociatedDecl(EED);
26812707
setClangDeclKeywords(EED, Pairs, Builder);
26822708
addLeadingDot(Builder);
2683-
2684-
Builder.addTextChunk(EED->getName().str());
2709+
addValueBaseName(Builder, EED->getName());
26852710

26862711
// Enum element is of function type; (Self.type) -> Self or
26872712
// (Self.Type) -> (Args...) -> Self.
@@ -2759,7 +2784,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
27592784

27602785
// Base name
27612786
addLeadingDot(Builder);
2762-
Builder.addTextChunk(AFD->getBaseName().userFacingName());
2787+
addValueBaseName(Builder, AFD->getBaseName());
27632788

27642789
// Add the argument labels.
27652790
auto ArgLabels = AFD->getFullName().getArgumentNames();

lib/IDE/CodeCompletionResultBuilder.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,8 @@ class CodeCompletionResultBuilder {
267267
addTypeAnnotation(Annotation);
268268
}
269269

270-
StringRef escapeArgumentLabel(StringRef Word,
271-
bool escapeAllKeywords,
272-
llvm::SmallString<16> &EscapedKeyword) {
270+
StringRef escapeKeyword(StringRef Word, bool escapeAllKeywords,
271+
llvm::SmallString<16> &EscapedKeyword) {
273272
bool shouldEscape = false;
274273
if (escapeAllKeywords) {
275274
#define KEYWORD(kw) .Case(#kw, true)
@@ -325,15 +324,15 @@ class CodeCompletionResultBuilder {
325324
llvm::SmallString<16> EscapedKeyword;
326325
addChunkWithText(
327326
CodeCompletionString::Chunk::ChunkKind::CallParameterName,
328-
escapeArgumentLabel(Name.str(), false, EscapedKeyword));
327+
escapeKeyword(Name.str(), false, EscapedKeyword));
329328
addChunkWithTextNoCopy(
330329
CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": ");
331330
} else if (!LocalName.empty()) {
332331
// Use local (non-API) parameter name if we have nothing else.
333332
llvm::SmallString<16> EscapedKeyword;
334333
addChunkWithText(
335334
CodeCompletionString::Chunk::ChunkKind::CallParameterInternalName,
336-
escapeArgumentLabel(LocalName.str(), false, EscapedKeyword));
335+
escapeKeyword(LocalName.str(), false, EscapedKeyword));
337336
addChunkWithTextNoCopy(
338337
CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": ");
339338
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_PRIMARY | %FileCheck %s -check-prefix=STATIC_PRIMARY
2+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_SELF_NODOT | %FileCheck %s -check-prefix=STATIC_SELF_NODOT
3+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_SELF_DOT | %FileCheck %s -check-prefix=STATIC_SELF_DOT
4+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=META_NODOT | %FileCheck %s -check-prefix=STATIC_SELF_NODOT
5+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=META_DOT | %FileCheck %s -check-prefix=STATIC_SELF_DOT
6+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_PRIMARY | %FileCheck %s -check-prefix=INSTANCE_PRIMARY
7+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_SELF_NODOT | %FileCheck %s -check-prefix=INSTANCE_SELF_NODOT
8+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_SELF_DOT | %FileCheck %s -check-prefix=INSTANCE_SELF_DOT
9+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=VALUE_NODOT | %FileCheck %s -check-prefix=INSTANCE_SELF_NODOT
10+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=VALUE_DOT | %FileCheck %s -check-prefix=INSTANCE_SELF_DOT
11+
12+
13+
enum MyEnum {
14+
case `class`(struct: String)
15+
case `let`(`var`: String)
16+
17+
init(`init`: String) {}
18+
static func `public`(private: String) -> Int {}
19+
20+
func `init`(deinit: String) -> Int {}
21+
func `if`(else: String) -> Int {}
22+
23+
var `self`: Int { return 0 }
24+
25+
static func testStatic(meta: MyEnum.Type) {
26+
let _ = #^STATIC_PRIMARY^#
27+
// STATIC_PRIMARY: Begin completion
28+
// STATIC_PRIMARY-DAG: Decl[LocalVar]/Local: self[#MyEnum.Type#]; name=self
29+
// STATIC_PRIMARY-DAG: Decl[EnumElement]/CurrNominal: `class`({#struct: String#})[#MyEnum#]; name=`class`(struct: String)
30+
// STATIC_PRIMARY-DAG: Decl[EnumElement]/CurrNominal: `let`({#`var`: String#})[#MyEnum#]; name=`let`(`var`: String)
31+
// STATIC_PRIMARY-DAG: Decl[StaticMethod]/CurrNominal: `public`({#private: String#})[#Int#]; name=`public`(private: String)
32+
// STATIC_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum)
33+
// STATIC_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `if`({#(self): MyEnum#})[#(else: String) -> Int#]; name=`if`(self: MyEnum)
34+
// STATIC_PRIMARY: End completion
35+
36+
let _ = self#^STATIC_SELF_NODOT^#
37+
// STATIC_SELF_NODOT: Begin completions
38+
// STATIC_SELF_NODOT-DAG: Keyword[self]/CurrNominal: .self[#MyEnum.Type#]; name=self
39+
// STATIC_SELF_NODOT-DAG: Decl[EnumElement]/CurrNominal: .class({#struct: String#})[#MyEnum#]; name=class(struct: String)
40+
// STATIC_SELF_NODOT-DAG: Decl[EnumElement]/CurrNominal: .let({#`var`: String#})[#MyEnum#]; name=let(`var`: String)
41+
// STATIC_SELF_NODOT-DAG: Decl[Constructor]/CurrNominal: .init({#init: String#})[#MyEnum#]; name=init(init: String)
42+
// STATIC_SELF_NODOT-DAG: Decl[StaticMethod]/CurrNominal: .public({#private: String#})[#Int#]; name=public(private: String)
43+
// STATIC_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .`init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum)
44+
// STATIC_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .if({#(self): MyEnum#})[#(else: String) -> Int#]; name=if(self: MyEnum)
45+
// STATIC_SELF_NODOT: End completion
46+
47+
let _ = self.#^STATIC_SELF_DOT^#
48+
// STATIC_SELF_DOT: Begin completions
49+
// STATIC_SELF_DOT-DAG: Keyword[self]/CurrNominal: self[#MyEnum.Type#]; name=self
50+
// STATIC_SELF_DOT-DAG: Decl[EnumElement]/CurrNominal: class({#struct: String#})[#MyEnum#]; name=class(struct: String)
51+
// STATIC_SELF_DOT-DAG: Decl[EnumElement]/CurrNominal: let({#`var`: String#})[#MyEnum#]; name=let(`var`: String)
52+
// STATIC_SELF_DOT-DAG: Decl[Constructor]/CurrNominal: init({#init: String#})[#MyEnum#]; name=init(init: String)
53+
// STATIC_SELF_DOT-DAG: Decl[StaticMethod]/CurrNominal: public({#private: String#})[#Int#]; name=public(private: String)
54+
// STATIC_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum)
55+
// STATIC_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: if({#(self): MyEnum#})[#(else: String) -> Int#]; name=if(self: MyEnum)
56+
// STATIC_SELF_DOT: End completion
57+
58+
let _ = meta#^META_NODOT^#
59+
// SAME AS 'STATIC_SELF_NODOT'.
60+
61+
let _ = meta.#^META_DOT^#
62+
// SAME AS 'STATIC_SELF_DOT'.
63+
}
64+
65+
func testInstance(val: MyEnum) {
66+
let _ = #^INSTANCE_PRIMARY^#
67+
// INSTANCE_PRIMARY: Begin completion
68+
// INSTANCE_PRIMARY-DAG: Decl[LocalVar]/Local: self[#MyEnum#]; name=self
69+
// INSTANCE_PRIMARY-DAG: Decl[InstanceVar]/CurrNominal: self[#Int#]; name=self
70+
// FIXME: ^ This is shadowed. We should hide this.
71+
// INSTANCE_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#deinit: String#})[#Int#]; name=`init`(deinit: String)
72+
// INSTANCE_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `if`({#else: String#})[#Int#]; name=`if`(else: String)
73+
// INSTANCE_PRIMARY: End completion
74+
75+
let _ = self#^INSTANCE_SELF_NODOT^#
76+
// INSTANCE_SELF_NODOT: Begin completions
77+
// INSTANCE_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .`init`({#deinit: String#})[#Int#]; name=`init`(deinit: String)
78+
// INSTANCE_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .if({#else: String#})[#Int#]; name=if(else: String)
79+
// INSTANCE_SELF_NODOT-DAG: Decl[InstanceVar]/CurrNominal: .`self`[#Int#]; name=`self`
80+
// INSTANCE_SELF_NODOT-DAG: Keyword[self]/CurrNominal: .self[#MyEnum#]; name=self
81+
82+
// INSTANCE_SELF_NODOT: End completions
83+
84+
let _ = self.#^INSTANCE_SELF_DOT^#
85+
// INSTANCE_SELF_DOT: Begin completions
86+
// INSTANCE_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#deinit: String#})[#Int#]; name=`init`(deinit: String)
87+
// INSTANCE_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: if({#else: String#})[#Int#]; name=if(else: String)
88+
// INSTANCE_SELF_DOT-DAG: Decl[InstanceVar]/CurrNominal: `self`[#Int#]; name=`self`
89+
// INSTANCE_SELF_DOT-DAG: Keyword[self]/CurrNominal: self[#MyEnum#]; name=self
90+
// INSTANCE_SELF_DOT: End completions
91+
92+
let _ = val#^VALUE_NODOT^#
93+
// SAME AS 'INSTANCE_SELF_NODOT'.
94+
let _ = val.#^VALUE_DOT^#
95+
// SAME AS 'INSTANCE_SELF_DOT'.
96+
}
97+
}

0 commit comments

Comments
 (0)