Skip to content

Commit 95b1176

Browse files
authored
Merge pull request #58893 from ahoppen/pr-5.7/shorthand-closure-info
[5.7][CursorInfo] Report values shadowed using shorthand syntax as secondary results
2 parents 8b19bae + 897ceec commit 95b1176

File tree

7 files changed

+317
-51
lines changed

7 files changed

+317
-51
lines changed

include/swift/IDE/Utils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ struct ResolvedCursorInfo {
151151
ValueDecl *ValueD = nullptr;
152152
TypeDecl *CtorTyRef = nullptr;
153153
ExtensionDecl *ExtTyRef = nullptr;
154+
/// Declarations that were shadowed by \c ValueD using a shorthand syntax that
155+
/// names both the newly declared variable and the referenced variable by the
156+
/// same identifier in the source text. This includes shorthand closure
157+
/// captures (`[foo]`) and shorthand if captures
158+
/// (`if let foo {`).
159+
/// Decls that are shadowed using shorthand syntax should be reported as
160+
/// additional cursor info results.
161+
SmallVector<ValueDecl *, 2> ShorthandShadowedDecls;
154162
ModuleEntity Mod;
155163
bool IsRef = true;
156164
bool IsKeywordArgument = false;

include/swift/Sema/IDETypeChecking.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace swift {
2828
class AbstractFunctionDecl;
2929
class ASTContext;
30+
class CaptureListExpr;
3031
class ConcreteDeclRef;
3132
class Decl;
3233
class DeclContext;
@@ -35,6 +36,7 @@ namespace swift {
3536
class Expr;
3637
class ExtensionDecl;
3738
class FunctionType;
39+
class LabeledConditionalStmt;
3840
class LookupResult;
3941
class NominalTypeDecl;
4042
class PatternBindingDecl;
@@ -319,6 +321,19 @@ namespace swift {
319321

320322
/// Just a proxy to swift::contextUsesConcurrencyFeatures() from lib/IDE code.
321323
bool completionContextUsesConcurrencyFeatures(const DeclContext *dc);
324+
325+
/// If the capture list shadows any declarations using shorthand syntax, i.e.
326+
/// syntax that names both the newly declared variable and the referenced
327+
/// variable by the same identifier in the source text, i.e. `[foo]`, return
328+
/// these shorthand shadows.
329+
/// The first element in the pair is the implicitly declared variable and the
330+
/// second variable is the shadowed one.
331+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1>
332+
getShorthandShadows(CaptureListExpr *CaptureList);
333+
334+
/// Same as above but for shorthand `if let foo {` syntax.
335+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1>
336+
getShorthandShadows(LabeledConditionalStmt *CondStmt);
322337
}
323338

324339
#endif

lib/IDE/IDERequests.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ class CursorInfoResolver : public SourceEntityWalker {
6262
Type ContainerType;
6363
Expr *OutermostCursorExpr;
6464
llvm::SmallVector<Expr*, 8> ExprStack;
65+
/// If a decl shadows another decl using shorthand syntax (`[foo]` or
66+
/// `if let foo {`), this maps the re-declared variable to the one that is
67+
/// being shadowed.
68+
/// The transitive closure of shorthand shadowed decls should be reported as
69+
/// additional results in cursor info.
70+
llvm::DenseMap<ValueDecl *, ValueDecl *> ShorthandShadowedDecls;
6571

6672
public:
6773
explicit CursorInfoResolver(SourceFile &SrcFile) :
@@ -167,6 +173,11 @@ ResolvedCursorInfo CursorInfoResolver::resolve(SourceLoc Loc) {
167173
LocToResolve = Loc;
168174
CursorInfo.Loc = Loc;
169175
walk(SrcFile);
176+
auto ShorthandShadowedDecl = ShorthandShadowedDecls[CursorInfo.ValueD];
177+
while (ShorthandShadowedDecl) {
178+
CursorInfo.ShorthandShadowedDecls.push_back(ShorthandShadowedDecl);
179+
ShorthandShadowedDecl = ShorthandShadowedDecls[ShorthandShadowedDecl];
180+
}
170181
return CursorInfo;
171182
}
172183

@@ -199,6 +210,14 @@ bool CursorInfoResolver::walkToStmtPre(Stmt *S) {
199210
// there is a token location inside the string, it will seem as if it is out
200211
// of the source range, unless we convert to character range.
201212

213+
if (auto CondStmt = dyn_cast<LabeledConditionalStmt>(S)) {
214+
for (auto ShorthandShadow : getShorthandShadows(CondStmt)) {
215+
assert(ShorthandShadowedDecls.count(ShorthandShadow.first) == 0);
216+
ShorthandShadowedDecls[ShorthandShadow.first] =
217+
ShorthandShadow.second;
218+
}
219+
}
220+
202221
// FIXME: Even implicit Stmts should have proper ranges that include any
203222
// non-implicit Stmts (fix Stmts created for lazy vars).
204223
if (!S->isImplicit() &&
@@ -250,6 +269,14 @@ bool CursorInfoResolver::walkToExprPre(Expr *E) {
250269
if (isDone())
251270
return true;
252271

272+
if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
273+
for (auto ShorthandShadows : getShorthandShadows(CaptureList)) {
274+
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0);
275+
ShorthandShadowedDecls[ShorthandShadows.first] =
276+
ShorthandShadows.second;
277+
}
278+
}
279+
253280
if (auto SAE = dyn_cast<SelfApplyExpr>(E)) {
254281
if (SAE->getFn()->getStartLoc() == LocToResolve) {
255282
ContainerType = SAE->getBase()->getType();

lib/IDE/IDETypeChecking.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,3 +906,61 @@ Type swift::getResultTypeOfKeypathDynamicMember(SubscriptDecl *SD) {
906906
RootAndResultTypeOfKeypathDynamicMemberRequest{SD}, TypePair()).
907907
SecondTy;
908908
}
909+
910+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1>
911+
swift::getShorthandShadows(CaptureListExpr *CaptureList) {
912+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1> Result;
913+
for (auto Capture : CaptureList->getCaptureList()) {
914+
if (Capture.PBD->getPatternList().size() != 1) {
915+
continue;
916+
}
917+
auto *DRE = dyn_cast_or_null<DeclRefExpr>(Capture.PBD->getInit(0));
918+
if (!DRE) {
919+
continue;
920+
}
921+
922+
auto DeclaredVar = Capture.getVar();
923+
if (DeclaredVar->getLoc() != DRE->getLoc()) {
924+
// We have a capture like `[foo]` if the declared var and the
925+
// reference share the same location.
926+
continue;
927+
}
928+
929+
auto *ReferencedVar = dyn_cast_or_null<VarDecl>(DRE->getDecl());
930+
if (!ReferencedVar) {
931+
continue;
932+
}
933+
934+
assert(DeclaredVar->getName() == ReferencedVar->getName());
935+
936+
Result.emplace_back(std::make_pair(DeclaredVar, ReferencedVar));
937+
}
938+
return Result;
939+
}
940+
941+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1>
942+
swift::getShorthandShadows(LabeledConditionalStmt *CondStmt) {
943+
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1> Result;
944+
for (const StmtConditionElement &Cond : CondStmt->getCond()) {
945+
if (Cond.getKind() != StmtConditionElement::CK_PatternBinding) {
946+
continue;
947+
}
948+
auto Init = dyn_cast<DeclRefExpr>(Cond.getInitializer());
949+
if (!Init) {
950+
continue;
951+
}
952+
auto ReferencedVar = dyn_cast_or_null<VarDecl>(Init->getDecl());
953+
if (!ReferencedVar) {
954+
continue;
955+
}
956+
957+
Cond.getPattern()->forEachVariable([&](VarDecl *DeclaredVar) {
958+
if (DeclaredVar->getLoc() != Init->getLoc()) {
959+
return;
960+
}
961+
assert(DeclaredVar->getName() == ReferencedVar->getName());
962+
Result.emplace_back(std::make_pair(DeclaredVar, ReferencedVar));
963+
});
964+
}
965+
return Result;
966+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
func takeClosure(_ closure: () -> Void) {}
2+
3+
func simple(bar: Int) {
4+
takeClosure { [bar] in
5+
print(bar)
6+
}
7+
}
8+
9+
// RUN: %sourcekitd-test -req=cursor -pos=3:13 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_PARAM
10+
// SIMPLE_PARAM: source.lang.swift.decl.var.parameter (3:13-3:16)
11+
// SIMPLE_PARAM: SECONDARY SYMBOLS BEGIN
12+
// There should be no secondary symbols
13+
// SIMPLE_PARAM-NEXT: SECONDARY SYMBOLS END
14+
15+
// RUN: %sourcekitd-test -req=cursor -pos=4:18 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_CAPTURE
16+
// SIMPLE_CAPTURE: source.lang.swift.decl.var.local (4:18-4:21)
17+
// SIMPLE_CAPTURE: SECONDARY SYMBOLS BEGIN
18+
// SIMPLE_CAPTURE: source.lang.swift.ref.var.local (3:13-3:16)
19+
20+
// RUN: %sourcekitd-test -req=cursor -pos=5:11 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_REF
21+
// SIMPLE_REF: source.lang.swift.ref.var.local (4:18-4:21)
22+
// SIMPLE_REF: SECONDARY SYMBOLS BEGIN
23+
// SIMPLE_REF: source.lang.swift.ref.var.local (3:13-3:16)
24+
25+
func doubleNested(bar: Int) {
26+
takeClosure { [bar] in
27+
takeClosure { [bar] in
28+
print(bar)
29+
}
30+
}
31+
}
32+
33+
// RUN: %sourcekitd-test -req=cursor -pos=26:18 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_FIRST_CAPTURE
34+
// DOUBLE_NESTED_FIRST_CAPTURE: source.lang.swift.decl.var.local (26:18-26:21)
35+
// DOUBLE_NESTED_FIRST_CAPTURE: SECONDARY SYMBOLS BEGIN
36+
// DOUBLE_NESTED_FIRST_CAPTURE: source.lang.swift.ref.var.local (25:19-25:22)
37+
38+
// RUN: %sourcekitd-test -req=cursor -pos=27:20 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_SECOND_CAPTURE
39+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.decl.var.local (27:20-27:23)
40+
// DOUBLE_NESTED_SECOND_CAPTURE: SECONDARY SYMBOLS BEGIN
41+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.ref.var.local (26:18-26:21)
42+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.ref.var.local (25:19-25:22)
43+
44+
// RUN: %sourcekitd-test -req=cursor -pos=28:13 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_REF
45+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (27:20-27:23)
46+
// DOUBLE_NESTED_REF: SECONDARY SYMBOLS BEGIN
47+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (26:18-26:21)
48+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (25:19-25:22)
49+
50+
// Make sure we don't report secondary symbols if the variable is captured explicitly using '='
51+
func explicitCapture(bar: Int) {
52+
takeClosure { [bar = bar] in
53+
print(bar)
54+
}
55+
}
56+
57+
// RUN: %sourcekitd-test -req=cursor -pos=53:11 %s -- %s | %FileCheck %s --check-prefix=EXPLICIT_CAPTURE_REF
58+
// EXPLICIT_CAPTURE_REF: source.lang.swift.ref.var.local (52:18-52:21)
59+
// EXPLICIT_CAPTURE_REF: SECONDARY SYMBOLS BEGIN
60+
// There should be no secondary symbols
61+
// EXPLICIT_CAPTURE_REF-NEXT: SECONDARY SYMBOLS END
62+
63+
func multipleCaptures(bar: Int, baz: Int) {
64+
takeClosure { [bar, baz] in
65+
print(bar)
66+
print(baz)
67+
}
68+
}
69+
70+
// RUN: %sourcekitd-test -req=cursor -pos=65:11 %s -- %s | %FileCheck %s --check-prefix=MULTIPLE_CAPTURES_BAR
71+
// MULTIPLE_CAPTURES_BAR: source.lang.swift.ref.var.local (64:18-64:21)
72+
// MULTIPLE_CAPTURES_BAR: SECONDARY SYMBOLS BEGIN
73+
// MULTIPLE_CAPTURES_BAR.lang.swift.ref.var.local (63:23-63:26)
74+
75+
// RUN: %sourcekitd-test -req=cursor -pos=66:11 %s -- %s | %FileCheck %s --check-prefix=MULTIPLE_CAPTURES_BAZ
76+
// MULTIPLE_CAPTURES_BAZ: source.lang.swift.ref.var.local (64:23-64:26)
77+
// MULTIPLE_CAPTURES_BAZ: SECONDARY SYMBOLS BEGIN
78+
// MULTIPLE_CAPTURES_BAZ.lang.swift.ref.var.local (63:33-63:36)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
func simple(bar: Int?) {
2+
if let bar {
3+
print(bar)
4+
}
5+
}
6+
7+
// RUN: %sourcekitd-test -req=cursor -pos=1:13 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_PARAM
8+
// SIMPLE_PARAM: source.lang.swift.decl.var.parameter (1:13-1:16)
9+
// SIMPLE_PARAM: Int
10+
// SIMPLE_PARAM: SECONDARY SYMBOLS BEGIN
11+
// There should be no secondary symbols
12+
// SIMPLE_PARAM-NEXT: SECONDARY SYMBOLS END
13+
14+
// RUN: %sourcekitd-test -req=cursor -pos=2:10 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_CAPTURE
15+
// SIMPLE_CAPTURE: source.lang.swift.decl.var.local (2:10-2:13)
16+
// SIMPLE_CAPTURE: Int
17+
// SIMPLE_CAPTURE: SECONDARY SYMBOLS BEGIN
18+
// SIMPLE_CAPTURE: source.lang.swift.ref.var.local (1:13-1:16)
19+
// SIMPLE_CAPTURE: Int?
20+
21+
// RUN: %sourcekitd-test -req=cursor -pos=3:11 %s -- %s | %FileCheck %s --check-prefix=SIMPLE_REF
22+
// SIMPLE_REF: source.lang.swift.ref.var.local (2:10-2:13)
23+
// SIMPLE_REF: Int
24+
// SIMPLE_REF: SECONDARY SYMBOLS BEGIN
25+
// SIMPLE_REF: source.lang.swift.ref.var.local (1:13-1:16)
26+
// SIMPLE_REF: Int?
27+
28+
func doubleNested(bar: Int??) {
29+
if let bar {
30+
if let bar {
31+
print(bar)
32+
}
33+
}
34+
}
35+
36+
// RUN: %sourcekitd-test -req=cursor -pos=29:10 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_FIRST_CAPTURE
37+
// DOUBLE_NESTED_FIRST_CAPTURE: source.lang.swift.decl.var.local (29:10-29:13)
38+
// DOUBLE_NESTED_FIRST_CAPTURE: Int?
39+
// DOUBLE_NESTED_FIRST_CAPTURE: SECONDARY SYMBOLS BEGIN
40+
// DOUBLE_NESTED_FIRST_CAPTURE: source.lang.swift.ref.var.local (28:19-28:22)
41+
// DOUBLE_NESTED_FIRST_CAPTURE: Int??
42+
43+
// RUN: %sourcekitd-test -req=cursor -pos=30:12 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_SECOND_CAPTURE
44+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.decl.var.local (30:12-30:15)
45+
// DOUBLE_NESTED_SECOND_CAPTURE: Int
46+
// DOUBLE_NESTED_SECOND_CAPTURE: SECONDARY SYMBOLS BEGIN
47+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.ref.var.local (29:10-29:13)
48+
// DOUBLE_NESTED_SECOND_CAPTURE: Int?
49+
// DOUBLE_NESTED_SECOND_CAPTURE: source.lang.swift.ref.var.local (28:19-28:22)
50+
// DOUBLE_NESTED_SECOND_CAPTURE: Int??
51+
52+
// RUN: %sourcekitd-test -req=cursor -pos=31:13 %s -- %s | %FileCheck %s --check-prefix=DOUBLE_NESTED_REF
53+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (30:12-30:15)
54+
// DOUBLE_NESTED_REF: Int
55+
// DOUBLE_NESTED_REF: SECONDARY SYMBOLS BEGIN
56+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (29:10-29:13)
57+
// DOUBLE_NESTED_REF: Int?
58+
// DOUBLE_NESTED_REF: source.lang.swift.ref.var.local (28:19-28:22)
59+
// DOUBLE_NESTED_REF: Int??
60+
61+
// Make sure we don't report secondary symbols if the variable is captured explicitly using '='
62+
func explicitCapture(bar: Int?) {
63+
if let bar = bar {
64+
print(bar)
65+
}
66+
}
67+
68+
// RUN: %sourcekitd-test -req=cursor -pos=64:11 %s -- %s | %FileCheck %s --check-prefix=EXPLICIT_CAPTURE_REF
69+
// EXPLICIT_CAPTURE_REF: source.lang.swift.ref.var.local (63:10-63:13)
70+
// EXPLICIT_CAPTURE_REF: Int
71+
// EXPLICIT_CAPTURE_REF: SECONDARY SYMBOLS BEGIN
72+
// There should be no secondary symbols
73+
// EXPLICIT_CAPTURE_REF-NEXT: SECONDARY SYMBOLS END
74+
75+
func multipleShorthand(bar: Int?, baz: Int?) {
76+
if let bar, let baz {
77+
print(bar)
78+
print(baz)
79+
}
80+
}
81+
82+
// RUN: %sourcekitd-test -req=cursor -pos=77:11 %s -- %s | %FileCheck %s --check-prefix=MULTIPLE_SHORTHAND_BAR
83+
// MULTIPLE_SHORTHAND_BAR: source.lang.swift.ref.var.local (76:10-76:13)
84+
// MULTIPLE_SHORTHAND_BAR: Int
85+
// MULTIPLE_SHORTHAND_BAR: SECONDARY SYMBOLS BEGIN
86+
// MULTIPLE_SHORTHAND_BAR.lang.swift.ref.var.local (75:23-75:26)
87+
// MULTIPLE_SHORTHAND_BAR: Int?
88+
89+
// RUN: %sourcekitd-test -req=cursor -pos=78:11 %s -- %s | %FileCheck %s --check-prefix=MULTIPLE_SHORTHAND_BAZ
90+
// MULTIPLE_SHORTHAND_BAZ: source.lang.swift.ref.var.local (76:19-76:22)
91+
// MULTIPLE_SHORTHAND_BAZ: Int
92+
// MULTIPLE_SHORTHAND_BAZ: SECONDARY SYMBOLS BEGIN
93+
// MULTIPLE_SHORTHAND_BAZ.lang.swift.ref.var.local (63:33-63:36)
94+
// MULTIPLE_SHORTHAND_BAZ: Int?
95+
96+
func guardLet(bar: Int?) {
97+
guard let bar else {
98+
return
99+
}
100+
print(bar)
101+
}
102+
103+
// RUN: %sourcekitd-test -req=cursor -pos=100:9 %s -- %s | %FileCheck %s --check-prefix=GUARD_LET
104+
// GUARD_LET: source.lang.swift.ref.var.local (97:13-97:16)
105+
// GUARD_LET: Int
106+
// GUARD_LET: SECONDARY SYMBOLS BEGIN
107+
// GUARD_LET.lang.swift.ref.var.local (96:15-96:18)
108+
// GUARD_LET: Int?

0 commit comments

Comments
 (0)