Skip to content

Commit a9908ab

Browse files
committed
Add composite name handling in switch cases.
1 parent cdd8614 commit a9908ab

File tree

5 files changed

+133
-15
lines changed

5 files changed

+133
-15
lines changed

Sources/SwiftLexicalLookup/LookupName.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ import SwiftSyntax
139139
case implicit(ImplicitDecl)
140140
/// Dollar identifier introduced by a closure without parameters.
141141
case dollarIdentifier(ClosureExprSyntax, strRepresentation: String)
142+
/// Equivalent identifiers grouped together.
143+
/// The associated array of names is always non-empty.
144+
///
145+
/// ### Example:
146+
/// ```swift
147+
/// switch x {
148+
/// case .a(let smth), .b(let smth):
149+
/// print(smth) // <-- lookup here
150+
/// }
151+
/// ```
152+
/// For lookup at the given position, the result
153+
/// contains only one (composite) name
154+
case compositeName([LookupName])
142155

143156
/// Syntax associated with this name.
144157
@_spi(Experimental) public var syntax: SyntaxProtocol {
@@ -151,6 +164,8 @@ import SwiftSyntax
151164
return implicitName.syntax
152165
case .dollarIdentifier(let closureExpr, _):
153166
return closureExpr
167+
case .compositeName(let names):
168+
return names.first!.syntax
154169
}
155170
}
156171

@@ -165,6 +180,8 @@ import SwiftSyntax
165180
return kind.identifier
166181
case .dollarIdentifier(_, strRepresentation: _):
167182
return nil
183+
case .compositeName(let names):
184+
return names.first!.identifier
168185
}
169186
}
170187

@@ -185,6 +202,8 @@ import SwiftSyntax
185202
return implicitName.position
186203
case .dollarIdentifier(let closureExpr, _):
187204
return closureExpr.positionAfterSkippingLeadingTrivia
205+
case .compositeName(let names):
206+
return names.first!.position
188207
}
189208
}
190209

@@ -319,6 +338,20 @@ import SwiftSyntax
319338
return "implicit: \(strName)"
320339
case .dollarIdentifier(_, strRepresentation: let str):
321340
return "dollarIdentifier: \(str)"
341+
case .compositeName(let names):
342+
var result = "Composite name: [ "
343+
344+
for (index, name) in names.enumerated() {
345+
result += name.debugDescription
346+
347+
if index < names.count - 1 {
348+
result += ", "
349+
} else {
350+
result += " ]"
351+
}
352+
}
353+
354+
return result
322355
}
323356
}
324357
}

Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,13 +574,44 @@ import SwiftSyntax
574574
@_spi(Experimental) extension SwitchCaseSyntax: SequentialScopeSyntax {
575575
/// Names introduced within `case` items.
576576
var namesFromLabel: [LookupName] {
577-
label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in
577+
guard let switchCaseItemList = label.as(SwitchCaseLabelSyntax.self)?.caseItems else { return [] }
578+
579+
let extractedNames = switchCaseItemList.flatMap { child in
578580
if let exprPattern = child.pattern.as(ExpressionPatternSyntax.self) {
579581
return LookupName.getNames(from: exprPattern.expression)
580582
} else {
581583
return LookupName.getNames(from: child.pattern)
582584
}
583-
} ?? []
585+
}
586+
587+
if switchCaseItemList.count <= 1 {
588+
return extractedNames
589+
}
590+
591+
var orderedKeys: [Identifier] = []
592+
var partitioned: [Identifier: [LookupName]] = [:]
593+
594+
for extractedName in extractedNames {
595+
guard let identifier = extractedName.identifier else { continue }
596+
597+
if !partitioned.keys.contains(identifier) {
598+
orderedKeys.append(identifier)
599+
}
600+
601+
if partitioned[identifier] == nil {
602+
partitioned[identifier] = [extractedName]
603+
} else {
604+
partitioned[identifier]?.append(extractedName)
605+
}
606+
}
607+
608+
return
609+
orderedKeys
610+
.compactMap { key in
611+
guard let names = partitioned[key] else { return nil }
612+
613+
return .compositeName(names)
614+
}
584615
}
585616

586617
/// Names introduced within `case` items

Tests/SwiftLexicalLookupTest/Assertions.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,18 @@ func assertLexicalNameLookup(
105105
ResultExpectation.assertResult(marker: marker, result: result, expectedValues: expectedValues)
106106

107107
return result.flatMap { lookUpResult in
108-
lookUpResult.names.map { lookupName in
109-
lookupName.syntax
108+
lookUpResult.names.flatMap { lookupName in
109+
if case .compositeName(let names) = lookupName {
110+
return names.map(\.syntax)
111+
} else {
112+
return [lookupName.syntax]
113+
}
110114
}
111115
}
112116
},
113117
expected: references.mapValues { expectations in
114118
expectations.flatMap { expectation in
115-
expectation.expectedNames.map { expectedName in
119+
expectation.expectedNames.flatMap { expectedName in
116120
expectedName.marker
117121
}
118122
}

Tests/SwiftLexicalLookupTest/ExpectedName.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import XCTest
1616

1717
/// Used to define lookup name assertion.
1818
protocol ExpectedName {
19-
var marker: String { get }
19+
var marker: [String] { get }
2020
}
2121

2222
extension String: ExpectedName {
23-
var marker: String {
24-
self
23+
var marker: [String] {
24+
[self]
2525
}
2626
}
2727

@@ -63,15 +63,22 @@ enum NameExpectation: ExpectedName {
6363
case declaration(String)
6464
case implicit(ImplicitNameExpectation)
6565
case dollarIdentifier(String, String)
66+
case compositeName([NameExpectation])
6667

67-
var marker: String {
68+
var marker: [String] {
6869
switch self {
6970
case .identifier(let marker),
7071
.declaration(let marker),
7172
.dollarIdentifier(let marker, _):
72-
return marker
73+
return [marker]
7374
case .implicit(let implicitName):
74-
return implicitName.marker
75+
return [implicitName.marker]
76+
case .compositeName(let expectedNames):
77+
return
78+
expectedNames
79+
.flatMap { expectedName in
80+
expectedName.marker
81+
}
7582
}
7683
}
7784

@@ -86,6 +93,15 @@ enum NameExpectation: ExpectedName {
8693
actualStr == expectedStr,
8794
"For marker \(marker), actual identifier \(actualStr) doesn't match expected \(expectedStr)"
8895
)
96+
case (.compositeName(let actualNames), .compositeName(let expectedNames)):
97+
XCTAssert(
98+
actualNames.count == expectedNames.count,
99+
"For marker \(marker), actual composite name count \(actualNames.count) doesn't match expected \(expectedNames.count)"
100+
)
101+
102+
for (actualName, expectedName) in zip(actualNames, expectedNames) {
103+
expectedName.assertExpectation(marker: marker, for: actualName)
104+
}
89105
default:
90106
XCTFail("For marker \(marker), actual name kind \(name) doesn't match expected \(self)")
91107
}

Tests/SwiftLexicalLookupTest/NameLookupTests.swift

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -791,9 +791,11 @@ final class testNameLookup: XCTestCase {
791791
assertLexicalNameLookup(
792792
source: """
793793
switch {
794-
case .x(let 1️⃣a, let 2️⃣b), .y(.c(let 3️⃣c), .z):
795-
print(4️⃣a, 5️⃣b, 6️⃣c)
796-
case .z(let 7️⃣a), .smth(let 8️⃣a)
794+
case .x(let 1️⃣a, let 2️⃣b):
795+
print(4️⃣a, 5️⃣b)
796+
case .y(.c(let 3️⃣c), .z):
797+
print(6️⃣c)
798+
case .z(let 7️⃣a)
797799
print(9️⃣a)
798800
default:
799801
print(0️⃣a)
@@ -803,13 +805,45 @@ final class testNameLookup: XCTestCase {
803805
"4️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["1️⃣"])],
804806
"5️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["2️⃣"])],
805807
"6️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["3️⃣"])],
806-
"9️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["7️⃣", "8️⃣"])],
808+
"9️⃣": [.fromScope(SwitchCaseSyntax.self, expectedNames: ["7️⃣"])],
807809
"0️⃣": [],
808810
],
809811
expectedResultTypes: .all(IdentifierPatternSyntax.self)
810812
)
811813
}
812814

815+
func testCompositeNames() {
816+
assertLexicalNameLookup(
817+
source: """
818+
switch X {
819+
case .x(let 1️⃣a, let 2️⃣b), .y(let 3️⃣a, let 4️⃣b):
820+
print(5️⃣x)
821+
case .z(let 7️⃣a), .smth(let 8️⃣a):
822+
print(9️⃣x)
823+
}
824+
""",
825+
references: [
826+
"5️⃣": [
827+
.fromScope(
828+
SwitchCaseSyntax.self,
829+
expectedNames: [
830+
NameExpectation.compositeName([.identifier("1️⃣"), .identifier("3️⃣")]),
831+
NameExpectation.compositeName([.identifier("2️⃣"), .identifier("4️⃣")]),
832+
]
833+
)
834+
],
835+
"9️⃣": [
836+
.fromScope(
837+
SwitchCaseSyntax.self,
838+
expectedNames: [NameExpectation.compositeName([.identifier("7️⃣"), .identifier("8️⃣")])]
839+
)
840+
],
841+
],
842+
expectedResultTypes: .all(IdentifierPatternSyntax.self),
843+
useNilAsTheParameter: true
844+
)
845+
}
846+
813847
func testSimpleGenericParameterScope() {
814848
assertLexicalNameLookup(
815849
source: """

0 commit comments

Comments
 (0)