Skip to content

Commit 166fdb7

Browse files
committed
Merge branch 'identifier-based-lookup' into guard-and-implicit-name-lookup
2 parents 0e5009a + 32fec70 commit 166fdb7

File tree

9 files changed

+125
-54
lines changed

9 files changed

+125
-54
lines changed

Sources/SwiftLexicalLookup/IntroducingToSequentialParentScopeSyntax.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ protocol IntroducingToSequentialParentScopeSyntax: ScopeSyntax {
1616
/// Returns names matching lookup that should be
1717
/// handled by it's parent sequential scope.
1818
func introducesToSequentialParent(
19-
for name: String?,
19+
for identifier: Identifier?,
2020
at syntax: SyntaxProtocol,
2121
with config: LookupConfig,
2222
state: LookupState

Sources/SwiftLexicalLookup/LookupName.swift

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ import SwiftSyntax
4040
}
4141
}
4242

43-
/// Used for name comparison.
44-
var name: String {
43+
/// Name associated with implicit name kind.
44+
private var name: String {
4545
switch self {
4646
case .self:
4747
"self"
@@ -55,6 +55,11 @@ import SwiftSyntax
5555
"oldValue"
5656
}
5757
}
58+
59+
/// Identifier used for name comparison.
60+
var identifier: Identifier {
61+
Identifier(name)
62+
}
5863
}
5964

6065
@_spi(Experimental) public enum LookupName {
@@ -79,15 +84,15 @@ import SwiftSyntax
7984
}
8085
}
8186

82-
/// Introduced name.
87+
/// Identifier used for name comparison.
8388
@_spi(Experimental) public var identifier: Identifier? {
8489
switch self {
8590
case .identifier(let syntax, _):
8691
Identifier(syntax.identifier)
8792
case .declaration(let syntax):
8893
Identifier(syntax.name)
89-
default:
90-
nil
94+
case .implicit(let kind):
95+
kind.identifier
9196
}
9297
}
9398

@@ -102,26 +107,16 @@ import SwiftSyntax
102107
}
103108
}
104109

105-
/// Used for name comparison.
106-
var name: String? {
107-
switch self {
108-
case .identifier, .declaration:
109-
identifier?.name
110-
case .implicit(let implicitName):
111-
implicitName.name
112-
}
113-
}
114-
115110
/// Checks if this name was introduced before the syntax used for lookup.
116111
func isAccessible(at lookedUpSyntax: SyntaxProtocol) -> Bool {
117112
guard let accessibleAfter else { return true }
118113
return accessibleAfter <= lookedUpSyntax.position
119114
}
120115

121116
/// Checks if this name refers to the looked up phrase.
122-
func refersTo(_ lookedUpName: String) -> Bool {
123-
guard let name else { return false }
124-
return name == lookedUpName
117+
func refersTo(_ lookedUpIdentifier: Identifier) -> Bool {
118+
guard let identifier else { return false }
119+
return identifier == lookedUpIdentifier
125120
}
126121

127122
/// Extracts names introduced by the given `syntax` structure.

Sources/SwiftLexicalLookup/ScopeImplementations.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ import SwiftSyntax
9797
/// - for `memberBlock` - a, b, c, d, e, f
9898
/// - for `codeBlock` - a
9999
@_spi(Experimental) public func lookup(
100-
for name: String?,
100+
for identifier: Identifier?,
101101
at syntax: SyntaxProtocol,
102102
with config: LookupConfig,
103103
state: LookupState
@@ -134,7 +134,7 @@ import SwiftSyntax
134134

135135
let sequentialNames = sequentialLookup(
136136
in: sequentialItems,
137-
for: name,
137+
for: identifier,
138138
at: syntax,
139139
with: config,
140140
state: state,
@@ -156,14 +156,14 @@ import SwiftSyntax
156156
}
157157

158158
@_spi(Experimental) public func lookup(
159-
for name: String?,
159+
for identifier: Identifier?,
160160
at syntax: SyntaxProtocol,
161161
with config: LookupConfig,
162162
state: LookupState
163163
) -> [LookupResult] {
164164
sequentialLookup(
165165
in: statements,
166-
for: name,
166+
for: identifier,
167167
at: syntax,
168168
with: config,
169169
state: state,
@@ -278,7 +278,7 @@ import SwiftSyntax
278278
/// }
279279
/// ```
280280
@_spi(Experimental) public func lookup(
281-
for name: String?,
281+
for identifier: Identifier?,
282282
at syntax: SyntaxProtocol,
283283
with config: LookupConfig,
284284
state: LookupState
@@ -302,7 +302,7 @@ import SwiftSyntax
302302

303303
@_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax {
304304
@_spi(Experimental) public func introducesToSequentialParent(
305-
for name: String?,
305+
for identifier: Identifier?,
306306
at syntax: SwiftSyntax.SyntaxProtocol,
307307
with config: LookupConfig,
308308
state: LookupState
@@ -332,17 +332,17 @@ import SwiftSyntax
332332
/// // a is visible here
333333
/// ```
334334
@_spi(Experimental) public func lookup(
335-
for name: String?,
335+
for identifier: Identifier?,
336336
at syntax: SyntaxProtocol,
337337
with config: LookupConfig,
338338
state: LookupState
339339
) -> [LookupResult] {
340340
if body.position <= syntax.position && body.endPosition >= syntax.position {
341341
var newState = state
342342
newState.skipSequentialIntroductionFrom = self
343-
return lookupInParent(for: name, at: syntax, with: config, state: newState)
343+
return lookupInParent(for: identifier, at: syntax, with: config, state: newState)
344344
} else {
345-
return defaultLookupImplementation(for: name, at: syntax, with: config, state: state)
345+
return defaultLookupImplementation(for: identifier, at: syntax, with: config, state: state)
346346
}
347347
}
348348
}

Sources/SwiftLexicalLookup/ScopeSyntax.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ extension SyntaxProtocol {
1616
/// Returns all names that `for` refers to at this syntax node.
1717
/// Optional configuration can be passed as `config` to customize the lookup behavior.
1818
///
19-
/// - Returns: An array of `LookupResult` for `name` at this syntax node,
20-
/// ordered by visibility. If `name` is set to `nil`, returns all available names ordered by visibility.
19+
/// - Returns: An array of `LookupResult` for `identifier` at this syntax node,
20+
/// ordered by visibility. If `identifier` is set to `nil`, returns all available names ordered by visibility.
2121
/// The order is from the innermost to the outermost scope,
2222
/// and within each result, names are ordered by their introduction
2323
/// in the source code.
@@ -44,10 +44,10 @@ extension SyntaxProtocol {
4444
/// in this exact order. The constant declaration within the function body is omitted
4545
/// due to the ordering rules that prioritize visibility within the function body.
4646
@_spi(Experimental) public func lookup(
47-
for name: String?,
47+
for identifier: Identifier?,
4848
with config: LookupConfig = LookupConfig()
4949
) -> [LookupResult] {
50-
scope?.lookup(for: name, at: self, with: config, state: LookupState()) ?? []
50+
scope?.lookup(for: identifier, at: self, with: config, state: LookupState()) ?? []
5151
}
5252
}
5353

@@ -59,7 +59,7 @@ extension SyntaxProtocol {
5959
/// Finds all declarations `name` refers to. `syntax` specifies the node lookup was triggered with.
6060
/// If `name` set to `nil`, returns all available names at the given node.
6161
func lookup(
62-
for name: String?,
62+
for identifier: Identifier?,
6363
at syntax: SyntaxProtocol,
6464
with config: LookupConfig,
6565
state: LookupState
@@ -75,19 +75,19 @@ extension SyntaxProtocol {
7575
/// refers to and is accessible at given syntax node then passes lookup to the parent.
7676
/// If `name` set to `nil`, returns all available names at the given node.
7777
@_spi(Experimental) public func lookup(
78-
for name: String?,
78+
for identifier: Identifier?,
7979
at syntax: SyntaxProtocol,
8080
with config: LookupConfig,
8181
state: LookupState
8282
) -> [LookupResult] {
83-
defaultLookupImplementation(for: name, at: syntax, with: config, state: state)
83+
defaultLookupImplementation(for: identifier, at: syntax, with: config, state: state)
8484
}
8585

8686
/// Returns `LookupResult` of all names introduced in this scope that `name`
8787
/// refers to and is accessible at given syntax node then passes lookup to the parent.
8888
/// If `name` set to `nil`, returns all available names at the given node.
8989
func defaultLookupImplementation(
90-
for name: String?,
90+
for identifier: Identifier?,
9191
at syntax: SyntaxProtocol,
9292
with config: LookupConfig,
9393
state: LookupState
@@ -99,21 +99,21 @@ extension SyntaxProtocol {
9999
}
100100

101101
if filteredNames.isEmpty {
102-
return lookupInParent(for: name, at: syntax, with: config, state: state)
102+
return lookupInParent(for: identifier, at: syntax, with: config, state: state)
103103
} else {
104104
return [.fromScope(self, withNames: filteredNames)]
105-
+ lookupInParent(for: name, at: syntax, with: config, state: state)
105+
+ lookupInParent(for: identifier, at: syntax, with: config, state: state)
106106
}
107107
}
108108

109109
/// Looks up in parent scope.
110110
func lookupInParent(
111-
for name: String?,
111+
for identifier: Identifier?,
112112
at syntax: SyntaxProtocol,
113113
with config: LookupConfig,
114114
state: LookupState
115115
) -> [LookupResult] {
116-
parentScope?.lookup(for: name, at: syntax, with: config, state: state) ?? []
116+
parentScope?.lookup(for: identifier, at: syntax, with: config, state: state) ?? []
117117
}
118118

119119
func checkName(_ name: String?, refersTo introducedName: LookupName, at syntax: SyntaxProtocol) -> Bool {

Sources/SwiftLexicalLookup/SequentialScopeSyntax.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protocol SequentialScopeSyntax: ScopeSyntax {
2121
/// scopes that match the lookup.
2222
func sequentialLookup(
2323
in codeBlockItems: any Collection<CodeBlockItemSyntax>,
24-
for name: String?,
24+
for identifier: Identifier?,
2525
at syntax: SyntaxProtocol,
2626
with config: LookupConfig,
2727
state: LookupState,
@@ -32,7 +32,7 @@ protocol SequentialScopeSyntax: ScopeSyntax {
3232
extension SequentialScopeSyntax {
3333
func sequentialLookup(
3434
in codeBlockItems: any Collection<CodeBlockItemSyntax>,
35-
for name: String?,
35+
for identifier: Identifier?,
3636
at syntax: SyntaxProtocol,
3737
with config: LookupConfig,
3838
state: LookupState,
@@ -61,7 +61,7 @@ extension SequentialScopeSyntax {
6161
// Add names introduced by the encountered scope.
6262
result.append(
6363
contentsOf: introducingToParentScope.introducesToSequentialParent(
64-
for: name,
64+
for: identifier,
6565
at: syntax,
6666
with: config,
6767
state: state
@@ -87,6 +87,6 @@ extension SequentialScopeSyntax {
8787
currentChunk = []
8888
}
8989

90-
return (result.isEmpty ? [] : result.reversed()) + lookupInParent(for: name, at: syntax, with: config, state: state)
90+
return (result.isEmpty ? [] : result.reversed()) + lookupInParent(for: identifier, at: syntax, with: config, state: state)
9191
}
9292
}

Sources/SwiftSyntax/Identifier.swift

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,58 @@
1212

1313
/// A canonicalized representation of an identifier that strips away backticks.
1414
public struct Identifier: Equatable, Hashable, Sendable {
15-
/// The sanitized `text` of a token.
15+
enum IdentifierKind: Hashable {
16+
case token(raw: RawIdentifier, arena: SyntaxArenaRef)
17+
case string(String)
18+
19+
static func sanitize(string: String) -> IdentifierKind {
20+
let backtick = "`"
21+
if string.count > 2 && string.hasPrefix(backtick) && string.hasSuffix(backtick) {
22+
let startIndex = string.index(after: string.startIndex)
23+
let endIndex = string.index(before: string.endIndex)
24+
return .string(String(string[startIndex..<endIndex]))
25+
} else {
26+
return .string(string)
27+
}
28+
}
29+
}
30+
31+
/// The sanitized name of the identifier.
1632
public var name: String {
17-
String(syntaxText: raw.name)
33+
switch identifier {
34+
case .token(let raw, _):
35+
String(syntaxText: raw.name)
36+
case .string(let string):
37+
string
38+
}
1839
}
19-
40+
2041
@_spi(RawSyntax)
21-
public let raw: RawIdentifier
42+
public var raw: RawIdentifier? {
43+
switch identifier {
44+
case .token(let raw, _):
45+
raw
46+
default:
47+
nil
48+
}
49+
}
2250

23-
private let arena: SyntaxArenaRef
51+
let identifier: IdentifierKind
2452

2553
public init?(_ token: TokenSyntax) {
2654
guard case .identifier = token.tokenKind else {
2755
return nil
2856
}
29-
30-
self.raw = RawIdentifier(token.tokenView)
31-
self.arena = token.tokenView.raw.arenaReference
57+
58+
self.identifier = .token(raw: RawIdentifier(token.tokenView), arena: token.tokenView.raw.arenaReference)
59+
}
60+
61+
public init(_ string: String) {
62+
self.identifier = IdentifierKind.sanitize(string: string)
63+
}
64+
65+
public static func == (lhs: Self, rhs: Self) -> Bool {
66+
lhs.name == rhs.name
3267
}
3368
}
3469

Tests/SwiftLexicalLookupTest/Assertions.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ func assertLexicalNameLookup(
9292
assertLexicalScopeQuery(
9393
source: source,
9494
methodUnderTest: { marker, tokenAtMarker in
95-
let result = tokenAtMarker.lookup(for: useNilAsTheParameter ? nil : tokenAtMarker.text, with: config)
95+
let lookupIdentifier = Identifier(tokenAtMarker) ?? Identifier(tokenAtMarker.text)
96+
97+
let result = tokenAtMarker.lookup(for: useNilAsTheParameter ? nil : lookupIdentifier, with: config)
9698

9799
guard let expectedValues = references[marker] else {
98100
XCTFail("For marker \(marker), couldn't find result expectation")

Tests/SwiftLexicalLookupTest/NameLookupTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,4 +668,43 @@ final class testNameLookup: XCTestCase {
668668
]
669669
)
670670
}
671+
672+
func testBacktickCompatibility() {
673+
assertLexicalNameLookup(
674+
source: """
675+
1️⃣struct Foo {
676+
func test() {
677+
let 2️⃣`self` = 1
678+
print(3️⃣self)
679+
print(4️⃣`self`)
680+
}
681+
}
682+
683+
5️⃣struct Bar {
684+
func test() {
685+
print(6️⃣self)
686+
let 7️⃣`self` = 1
687+
print(8️⃣`self`)
688+
}
689+
}
690+
""",
691+
references: [
692+
"3️⃣": [
693+
.fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]),
694+
.fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))])
695+
],
696+
"4️⃣": [
697+
.fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]),
698+
.fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))])
699+
],
700+
"6️⃣": [
701+
.fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))])
702+
],
703+
"8️⃣": [
704+
.fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("7️⃣")]),
705+
.fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))])
706+
],
707+
]
708+
)
709+
}
671710
}

0 commit comments

Comments
 (0)