Skip to content

[5.1][sourcekitd] Improve CursorInfo, RelatedIdents, and local rename to better handle VarDecls involved in case fallthroughs #23468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/AST/USRGeneration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ static bool shouldUseObjCUSR(const Decl *D) {
llvm::Expected<std::string>
swift::USRGenerationRequest::evaluate(Evaluator &evaluator,
const ValueDecl *D) const {
if (auto *VD = dyn_cast<VarDecl>(D))
D = VD->getCanonicalVarDecl();

if (!D->hasName() && !isa<ParamDecl>(D) && !isa<AccessorDecl>(D))
return std::string(); // Ignore.
if (D->getModuleContext()->isBuiltinModule())
Expand Down
16 changes: 12 additions & 4 deletions lib/IDE/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,19 @@ bool CursorInfoResolver::tryResolve(ValueDecl *D, TypeDecl *CtorTyRef,
if (!D->hasName())
return false;

if (Loc == LocToResolve) {
CursorInfo.setValueRef(D, CtorTyRef, ExtTyRef, IsRef, Ty, ContainerType);
return true;
if (Loc != LocToResolve)
return false;

if (auto *VD = dyn_cast<VarDecl>(D)) {
// Handle references to the implicitly generated vars in case statements
// matching multiple patterns
if (VD->isImplicit()) {
if (auto * Parent = VD->getParentVarDecl())
D = Parent;
}
}
return false;
CursorInfo.setValueRef(D, CtorTyRef, ExtTyRef, IsRef, Ty, ContainerType);
return true;
}

bool CursorInfoResolver::tryResolve(ModuleEntity Mod, SourceLoc Loc) {
Expand Down
11 changes: 11 additions & 0 deletions lib/Index/Index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,12 @@ class IndexSwiftASTWalker : public SourceEntityWalker {
}

bool shouldIndex(ValueDecl *D, bool IsRef) const {
if (D->isImplicit() && isa<VarDecl>(D) && IsRef) {
// Bypass the implicit VarDecls introduced in CaseStmt bodies by using the
// canonical VarDecl for these checks instead.
D = cast<VarDecl>(D)->getCanonicalVarDecl();
}

if (D->isImplicit() && !isa<ConstructorDecl>(D))
return false;

Expand Down Expand Up @@ -1151,6 +1157,11 @@ bool IndexSwiftASTWalker::reportImplicitConformance(ValueDecl *witness, ValueDec
bool IndexSwiftASTWalker::initIndexSymbol(ValueDecl *D, SourceLoc Loc,
bool IsRef, IndexSymbol &Info) {
assert(D);
if (auto *VD = dyn_cast<VarDecl>(D)) {
// Always base the symbol information on the canonical VarDecl
D = VD->getCanonicalVarDecl();
}

Info.decl = D;
Info.symInfo = getSymbolInfoForDecl(D);
if (Info.symInfo.Kind == SymbolKind::Unknown)
Expand Down
1 change: 0 additions & 1 deletion lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,6 @@ static void parseGuardedPattern(Parser &P, GuardedPattern &result,

for (auto VD : repeatedDecls) {
VD->setHasNonPatternBindingInit();
VD->setImplicit();
}

// Parse the optional 'where' guard, with this particular pattern's bound
Expand Down
14 changes: 14 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,20 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
auto *var = elt.first;
unsigned access = elt.second;

if (auto *CS = dyn_cast_or_null<CaseStmt>(var->getRecursiveParentPatternStmt())) {
// Only diagnose VarDecls from the first CaseLabelItem in CaseStmts, as
// the remaining items must match it anyway.
auto CaseItems = CS->getCaseLabelItems();
if (!CaseItems.empty()) {
bool InFirstCaseLabelItem = false;
CaseItems.front().getPattern()->forEachVariable([&](VarDecl *D) {
InFirstCaseLabelItem |= var == D;
});
if (!InFirstCaseLabelItem)
continue;
}
}

// If this is a 'let' value, any stores to it are actually initializations,
// not mutations.
auto isWrittenLet = false;
Expand Down
40 changes: 39 additions & 1 deletion test/Index/local.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,47 @@ func foo(a: Int, b: Int, c: Int) {
let _ = LocalEnum.foo(x: LocalStruct())
// LOCAL: [[@LINE-1]]:13 | enum(local)/Swift | LocalEnum | [[LocalEnum_USR]] | Ref,RelCont | rel: 1
// CHECK-NOT: [[@LINE-2]]:13 | enum(local)/Swift | LocalEnum | [[LocalEnum_USR]] | Ref,RelCont | rel: 1
// LOCAL: [[@LINE-3]]:23 | enumerator(local)/Swift | foo(x:) | [[LocalEnum_foo_USR]] | Ref,RelCont | rel: 1
// LOCAL: [[@LINE-3]]:23 | enumerator(local)/Swift | foo(x:) | [[LocalEnum_foo_USR]] | Ref,RelCont | rel: 1
// CHECK-NOT: [[@LINE-4]]:23 | enumerator(local)/Swift | foo(x:) | [[LocalEnum_foo_USR]] | Ref,RelCont | rel: 1
// LOCAL: [[@LINE-5]]:30 | struct(local)/Swift | LocalStruct | [[LocalStruct_USR]] | Ref,RelCont | rel: 1
// CHECK-NOT: [[@LINE-6]]:30 | struct(local)/Swift | LocalStruct | [[LocalStruct_USR]] | Ref,RelCont | rel: 1

}

func bar(arg: Int?) {
switch arg {
case let .some(x) where x == 0:
// LOCAL: [[@LINE-1]]:18 | variable(local)/Swift | x | [[x_USR:.*]] | Def,RelChild | rel: 1
// LOCAL: [[@LINE-2]]:27 | variable(local)/Swift | x | [[x_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-3]]:18 | variable(local)/Swift | x | [[x_USR:.*]] | Def,RelChild | rel: 1
// CHECK-NOT: [[@LINE-4]]:27 | variable(local)/Swift | x | [[x_USR]] | Ref,Read,RelCont | rel: 1
print(x)
// LOCAL: [[@LINE-1]]:11 | variable(local)/Swift | x | [[x_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-2]]:11 | variable(local)/Swift | x | [[x_USR]] | Ref,Read,RelCont | rel: 1

case let .some(x) where x == 1,
// LOCAL: [[@LINE-1]]:18 | variable(local)/Swift | x | [[x2_USR:.*]] | Def,RelChild | rel: 1
// LOCAL: [[@LINE-2]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-3]]:18 | variable(local)/Swift | x | [[x2_USR:.*]] | Def,RelChild | rel: 1
// CHECK-NOT: [[@LINE-4]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
let .some(x) where x == 2:
// LOCAL: [[@LINE-1]]:18 | variable(local)/Swift | x | [[x2_USR]] | Def,RelChild | rel: 1
// LOCAL: [[@LINE-2]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-3]]:18 | variable(local)/Swift | x | [[x2_USR]] | Def,RelChild | rel: 1
// CHECK-NOT: [[@LINE-4]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
print(x)
// LOCAL: [[@LINE-1]]:11 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-2]]:11 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
fallthrough
case let .some(x) where x == 3:
// LOCAL: [[@LINE-1]]:18 | variable(local)/Swift | x | [[x2_USR]] | Def,RelChild | rel: 1
// LOCAL: [[@LINE-2]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-1]]:18 | variable(local)/Swift | x | [[x2_USR]] | Def,RelChild | rel: 1
// CHECK-NOT: [[@LINE-2]]:27 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
print(x)
// LOCAL: [[@LINE-1]]:11 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
// CHECK-NOT: [[@LINE-1]]:11 | variable(local)/Swift | x | [[x2_USR]] | Ref,Read,RelCont | rel: 1
default:
break
}
}
57 changes: 57 additions & 0 deletions test/SourceKit/CursorInfo/cursor_vardecl_across_fallthrough.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

enum X {
case first(Int, String)
case second(Int, String)
case third(Int, String)
case fourth(Int, String)
case fifth(Int, String)
}

let p = X.first(3, "hello")

switch p {
case .first(let x, let y)
print("foo \(x) \(y)")
fallthrough
case .second(let x, let y), .third(let x, let y):
print("bar \(x) \(y)")
default:
print("other")
}

// RUN: %sourcekitd-test -req=cursor -pos=13:19 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKX,CHECK1DECL %s
// RUN: %sourcekitd-test -req=cursor -pos=14:18 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKX,CHECK1REF %s

// CHECK1DECL: source.lang.swift.decl.var.local (13:19-13:20)
// CHECK1REF: source.lang.swift.ref.var.local (13:19-13:20)

// RUN: %sourcekitd-test -req=cursor -pos=16:20 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKX,CHECK2DECL %s
// RUN: %sourcekitd-test -req=cursor -pos=16:42 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKX,CHECK2DECL2 %s
// RUN: %sourcekitd-test -req=cursor -pos=17:18 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKX,CHECK2REF %s

// CHECK2DECL: source.lang.swift.decl.var.local (16:20-16:21)
// CHECK2DECL2: source.lang.swift.decl.var.local (16:42-16:43)
// CHECK2REF: source.lang.swift.ref.var.local (16:20-16:21)

// CHECKX: x
// CHECKX: s:33cursor_vardecl_across_fallthrough1xL_Sivp
// CHECKX: Int


// RUN: %sourcekitd-test -req=cursor -pos=13:26 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKY,CHECK3DECL %s
// RUN: %sourcekitd-test -req=cursor -pos=14:23 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKY,CHECK3REF %s

// CHECK3DECL: source.lang.swift.decl.var.local (13:26-13:27)
// CHECK3REF: source.lang.swift.ref.var.local (13:26-13:27)

// RUN: %sourcekitd-test -req=cursor -pos=16:27 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKY,CHECK4DECL %s
// RUN: %sourcekitd-test -req=cursor -pos=16:49 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKY,CHECK4DECL2 %s
// RUN: %sourcekitd-test -req=cursor -pos=17:23 %s -- %mcp_opt %s | %FileCheck -check-prefixes=CHECKY,CHECK4REF %s

// CHECK4DECL: source.lang.swift.decl.var.local (16:27-16:28)
// CHECK4DECL2: source.lang.swift.decl.var.local (16:49-16:50)
// CHECK4REF: source.lang.swift.ref.var.local (16:27-16:28)

// CHECKY: y
// CHECKY: s:33cursor_vardecl_across_fallthrough1yL_SSvp
// CHECKY: String
55 changes: 55 additions & 0 deletions test/SourceKit/RelatedIdents/related_idents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ class C2<Param> {
func f(t : Param) -> Param {return t}
}

enum X {
case first(Int, String)
case second(Int, String)
case third(Int, String)
case fourth(Int, String)
}

switch X.first(2, "") {
case .first(let x, let y):
print(y)
fallthrough
case .second(let x, _):
print(x)
case .third(let x, let y):
fallthrough
case .fourth(let x, let y):
print(y)
print(x)
break
}

// RUN: %sourcekitd-test -req=related-idents -pos=6:17 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK1 %s
// CHECK1: START RANGES
// CHECK1-NEXT: 1:7 - 2
Expand Down Expand Up @@ -53,3 +74,37 @@ class C2<Param> {
// CHECK5-NEXT: 23:13 - 5
// CHECK5-NEXT: 23:23 - 5
// CHECK5-NEXT: END RANGES

// RUN: %sourcekitd-test -req=related-idents -pos=34:19 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK6 %s
// RUN: %sourcekitd-test -req=related-idents -pos=37:20 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK6 %s
// RUN: %sourcekitd-test -req=related-idents -pos=38:11 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK6 %s
// CHECK6: START RANGES
// CHECK6-NEXT: 34:19 - 1
// CHECK6-NEXT: 37:20 - 1
// CHECK6-NEXT: 38:11 - 1
// CHECK6-NEXT: END RANGES

// RUN: %sourcekitd-test -req=related-idents -pos=34:26 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK7 %s
// RUN: %sourcekitd-test -req=related-idents -pos=35:11 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK7 %s
// CHECK7: START RANGES
// CHECK7-NEXT: 34:26 - 1
// CHECK7-NEXT: 35:11 - 1
// CHECK7-NEXT: END RANGES

// RUN: %sourcekitd-test -req=related-idents -pos=39:26 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK8 %s
// RUN: %sourcekitd-test -req=related-idents -pos=41:27 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK8 %s
// RUN: %sourcekitd-test -req=related-idents -pos=42:11 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK8 %s
// CHECK8: START RANGES
// CHECK8-NEXT: 39:26 - 1
// CHECK8-NEXT: 41:27 - 1
// CHECK8-NEXT: 42:11 - 1
// CHECK8-NEXT: END RANGES

// RUN: %sourcekitd-test -req=related-idents -pos=39:19 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK9 %s
// RUN: %sourcekitd-test -req=related-idents -pos=41:20 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK9 %s
// RUN: %sourcekitd-test -req=related-idents -pos=43:11 %s -- -module-name related_idents %s | %FileCheck -check-prefix=CHECK9 %s
// CHECK9: START RANGES
// CHECK9-NEXT: 39:19 - 1
// CHECK9-NEXT: 41:20 - 1
// CHECK9-NEXT: 43:11 - 1
// CHECK9-NEXT: END RANGES
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(xRenamed) where xRenamed == 0:
print(xRenamed)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(xRenamed) where xRenamed == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(xRenamed) where xRenamed == 2:
print(xRenamed)
fallthrough
case let .some(xRenamed) where xRenamed == 3:
print(xRenamed)
default:
break
Expand Down
5 changes: 4 additions & 1 deletion test/refactoring/rename/Outputs/local/catch_1.swift.expected
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
5 changes: 4 additions & 1 deletion test/refactoring/rename/Outputs/local/catch_2.swift.expected
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
5 changes: 4 additions & 1 deletion test/refactoring/rename/Outputs/local/param_1.swift.expected
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
5 changes: 4 additions & 1 deletion test/refactoring/rename/Outputs/local/param_2.swift.expected
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func test3(arg: Int?) {
case let .some(x) where x == 0:
print(x)
case let .some(x) where x == 1,
let .some(x) where x == 2: // FIXME: This 'x' in '.some(x)' isn't properly renamed in 'casebind_2' case.
let .some(x) where x == 2:
print(x)
fallthrough
case let .some(x) where x == 3:
print(x)
default:
break
Expand Down
Loading