Skip to content

Commit a68f00a

Browse files
committed
Merge branch 'main'
Conflicts: Sources/SwiftLexicalLookup/ScopeImplementations.swift
2 parents e15bfaa + a0ff1be commit a68f00a

File tree

10 files changed

+168
-115
lines changed

10 files changed

+168
-115
lines changed

Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift renamed to Sources/SwiftLexicalLookup/Configurations/FileScopeNameIntroductionStrategy.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import SwiftSyntax
1414

1515
/// Specifies how names should be introduced at the file scope.
16-
@_spi(Experimental) public enum FileScopeNameIntroductionStrategy: LookupConfig {
16+
@_spi(Experimental) public enum FileScopeHandlingConfig {
1717
/// Default behavior. Names introduced sequentially like in member block
1818
/// scope up to the first non-declaration after and including which,
1919
/// the declarations are treated like in code block scope.

Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212

1313
import Foundation
1414

15-
/// Used to customize lookup behavior.
16-
@_spi(Experimental) public protocol LookupConfig {}
15+
@_spi(Experimental) public struct LookupConfig {
16+
/// Specifies behaviour of file scope.
17+
/// `memberBlockUpToLastDecl` by default.
18+
public var fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl
1719

18-
extension LookupConfig {
19-
var identifier: ObjectIdentifier {
20-
ObjectIdentifier(Self.self)
20+
public init(fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl) {
21+
self.fileScopeHandling = fileScopeHandling
2122
}
2223
}

Sources/SwiftLexicalLookup/Configurations/LookupConfigDictionary.swift

Lines changed: 0 additions & 37 deletions
This file was deleted.

Sources/SwiftLexicalLookup/IdentifiableSyntax.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ extension ClosureShorthandParameterSyntax: IdentifiableSyntax {
3333

3434
extension ClosureCaptureSyntax: IdentifiableSyntax {
3535
@_spi(Experimental) public var identifier: TokenSyntax {
36+
/* Doesn't work with closures like:
37+
_ = { [y=1+2] in
38+
print(y)
39+
}
40+
*/
3641
expression.as(DeclReferenceExprSyntax.self)!.baseName
3742
}
3843
}

Sources/SwiftLexicalLookup/LookupName.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,20 @@ import SwiftSyntax
3131
}
3232

3333
/// Introduced name.
34-
@_spi(Experimental) public var name: String {
34+
@_spi(Experimental) public var identifier: Identifier? {
3535
switch self {
3636
case .identifier(let syntax, _):
37-
syntax.identifier.text
37+
Identifier(syntax.identifier)
3838
case .declaration(let syntax, _):
39-
syntax.name.text
39+
Identifier(syntax.name)
4040
}
4141
}
4242

4343
/// Point, after which the name is available in scope.
4444
/// If set to `nil`, the name is available at any point in scope.
4545
var accessibleAfter: AbsolutePosition? {
4646
switch self {
47-
case .identifier(_, let absolutePosition):
48-
absolutePosition
49-
case .declaration(_, let absolutePosition):
47+
case .identifier(_, let absolutePosition), .declaration(_, let absolutePosition):
5048
absolutePosition
5149
}
5250
}
@@ -59,7 +57,8 @@ import SwiftSyntax
5957

6058
/// Checks if this name refers to the looked up phrase.
6159
func refersTo(_ lookedUpName: String) -> Bool {
62-
name == lookedUpName
60+
guard let name = identifier?.name else { return false }
61+
return name == lookedUpName
6362
}
6463

6564
/// Extracts names introduced by the given `from` structure.

Sources/SwiftLexicalLookup/LookupResult.swift

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,23 @@ import SwiftSyntax
1616
@_spi(Experimental) public enum LookupResult {
1717
/// Scope and the names that matched lookup.
1818
case fromScope(ScopeSyntax, withNames: [LookupName])
19-
/// File scope, names that matched lookup and name introduction
20-
/// strategy used for the lookup.
21-
case fromFileScope(
22-
SourceFileSyntax,
23-
withNames: [LookupName],
24-
nameIntroductionStrategy: FileScopeNameIntroductionStrategy
25-
)
19+
/// File scope and names that matched lookup.
20+
case fromFileScope(SourceFileSyntax, withNames: [LookupName])
2621

2722
/// Associated scope.
2823
@_spi(Experimental) public var scope: ScopeSyntax? {
2924
switch self {
3025
case .fromScope(let scopeSyntax, _):
3126
scopeSyntax
32-
case .fromFileScope(let fileScopeSyntax, withNames: _, nameIntroductionStrategy: _):
27+
case .fromFileScope(let fileScopeSyntax, _):
3328
fileScopeSyntax
3429
}
3530
}
3631

3732
/// Names that matched lookup.
3833
@_spi(Experimental) public var names: [LookupName] {
3934
switch self {
40-
case .fromScope(_, let names):
41-
names
42-
case .fromFileScope(_, withNames: let names, nameIntroductionStrategy: _):
35+
case .fromScope(_, let names), .fromFileScope(_, let names):
4336
names
4437
}
4538
}

Sources/SwiftLexicalLookup/ScopeImplementations.swift

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,34 @@ extension SyntaxProtocol {
2424
}
2525

2626
@_spi(Experimental) extension SourceFileSyntax: ScopeSyntax {
27+
/// All names introduced in the file scope
28+
/// according to the default strategy: `memberBlockUpToLastDecl`.
2729
public var introducedNames: [LookupName] {
2830
introducedNames(using: .memberBlockUpToLastDecl)
2931
}
3032

31-
public func introducedNames(using nameIntroductionStrategy: FileScopeNameIntroductionStrategy) -> [LookupName] {
32-
switch nameIntroductionStrategy {
33+
/// All names introduced in the file scope
34+
/// using the provided configuration.
35+
///
36+
/// Example usage:
37+
/// ```swift
38+
/// class a {}
39+
/// class b {
40+
/// // <--
41+
/// }
42+
/// let c = 0
43+
/// class d {}
44+
/// if true {}
45+
/// class e {}
46+
/// let f = 0
47+
/// ```
48+
/// During lookup, according to different configurations,
49+
/// names available at the marked place are:
50+
/// - for `fileScopeNameIntroductionStrategy` - a, b, c, d
51+
/// - for `memberBlock` - a, b, c, d, e, f
52+
/// - for `codeBlock` - a
53+
public func introducedNames(using fileScopeHandling: FileScopeHandlingConfig) -> [LookupName] {
54+
switch fileScopeHandling {
3355
case .memberBlockUpToLastDecl:
3456
var encounteredNonDeclaration = false
3557

@@ -58,24 +80,43 @@ extension SyntaxProtocol {
5880
}
5981
}
6082

83+
/// Returns names matching lookup using provided file
84+
/// scope handling configuration (by default: `memberBlockUpToLastDecl`).
85+
///
86+
/// Example usage:
87+
/// ```swift
88+
/// class a {}
89+
/// class b {
90+
/// // <--
91+
/// }
92+
/// let c = 0
93+
/// class d {}
94+
/// if true {}
95+
/// class e {}
96+
/// let f = 0
97+
/// ```
98+
/// According to different configurations,
99+
/// names available at the marked place are:
100+
/// - for `fileScopeNameIntroductionStrategy` - a, b, c, d
101+
/// - for `memberBlock` - a, b, c, d, e, f
102+
/// - for `codeBlock` - a
61103
public func lookup(
62104
for name: String?,
63105
at syntax: SyntaxProtocol,
64-
with configDict: LookupConfigDictionary
106+
with config: LookupConfig
65107
) -> [LookupResult] {
66-
let nameIntroductionStrategy = configDict[FileScopeNameIntroductionStrategy.self] ?? .memberBlockUpToLastDecl
67-
68-
let names = introducedNames(using: nameIntroductionStrategy)
108+
let names = introducedNames(using: config.fileScopeHandling)
69109
.filter { introducedName in
70110
does(name: name, referTo: introducedName, at: syntax)
71111
}
72112

73-
return names.isEmpty
74-
? [] : [.fromFileScope(self, withNames: names, nameIntroductionStrategy: nameIntroductionStrategy)]
113+
return names.isEmpty ? [] : [.fromFileScope(self, withNames: names)]
75114
}
76115
}
77116

78117
@_spi(Experimental) extension CodeBlockSyntax: ScopeSyntax {
118+
/// Names introduced in the code block scope
119+
/// accessible after their declaration.
79120
public var introducedNames: [LookupName] {
80121
statements.flatMap { codeBlockItem in
81122
LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition)
@@ -123,12 +164,24 @@ extension SyntaxProtocol {
123164
}
124165

125166
@_spi(Experimental) extension ForStmtSyntax: ScopeSyntax {
167+
/// Names introduced in the `for` body.
126168
public var introducedNames: [LookupName] {
127169
LookupName.getNames(from: pattern)
128170
}
129171
}
130172

131173
@_spi(Experimental) extension ClosureExprSyntax: ScopeSyntax {
174+
/// All names introduced by the closure signature.
175+
/// Could be closure captures or (shorthand) parameters.
176+
///
177+
/// Example:
178+
/// ```swift
179+
/// let x = { [weak self, a] b, _ in
180+
/// // <--
181+
/// }
182+
/// ```
183+
/// During lookup, names available at the marked place are:
184+
/// `self`, a, b.
132185
public var introducedNames: [LookupName] {
133186
let captureNames =
134187
signature?.capture?.children(viewMode: .sourceAccurate).flatMap { child in
@@ -157,6 +210,7 @@ extension SyntaxProtocol {
157210
}
158211

159212
@_spi(Experimental) extension WhileStmtSyntax: ScopeSyntax {
213+
/// Names introduced by the `while` loop by its conditions.
160214
public var introducedNames: [LookupName] {
161215
conditions.flatMap { element in
162216
LookupName.getNames(from: element.condition)
@@ -165,11 +219,28 @@ extension SyntaxProtocol {
165219
}
166220

167221
@_spi(Experimental) extension IfExprSyntax: ScopeSyntax {
222+
/// Parent scope, omitting ancestor `if` statements if part of their `else if` clause.
168223
public var parentScope: ScopeSyntax? {
169224
getParent(for: self.parent, previousIfElse: self.elseKeyword == nil)
170225
}
171226

172-
/// Finds the parent scope, omitting parent `if` statements if part of their `else if` clause.
227+
/// Finds parent scope, omitting ancestor `if` statements if part of their `else if` clause.
228+
///
229+
/// Example:
230+
/// ```swift
231+
/// func foo() {
232+
/// if let a = x {
233+
/// // <--
234+
/// } else if let b {
235+
/// // <--
236+
/// } else if y == 1 {
237+
/// // <--
238+
/// }
239+
/// }
240+
/// ```
241+
/// For each of the marked scopes, resulting parent
242+
/// is the enclosing code block scope associated with
243+
/// the function body.
173244
private func getParent(for syntax: Syntax?, previousIfElse: Bool) -> ScopeSyntax? {
174245
guard let syntax else { return nil }
175246

@@ -184,26 +255,40 @@ extension SyntaxProtocol {
184255
}
185256
}
186257

258+
/// Names introduced by the `if` optional binding conditions.
187259
public var introducedNames: [LookupName] {
188260
conditions.flatMap { element in
189261
LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition)
190262
}
191263
}
192264

265+
/// Returns names matching lookup.
266+
/// Lookup triggered from inside of `else`
267+
/// clause is immediately forwarded to parent scope.
268+
///
269+
/// Example:
270+
/// ```swift
271+
/// if let a = x {
272+
/// // <-- a is visible here
273+
/// } else {
274+
/// // <-- a is not visible here
275+
/// }
276+
/// ```
193277
public func lookup(
194278
for name: String?,
195279
at syntax: SyntaxProtocol,
196-
with configDict: LookupConfigDictionary
280+
with config: LookupConfig
197281
) -> [LookupResult] {
198282
if let elseBody, elseBody.position <= syntax.position, elseBody.endPosition >= syntax.position {
199-
lookupInParent(for: name, at: syntax, with: configDict)
283+
lookupInParent(for: name, at: syntax, with: config)
200284
} else {
201-
defaultLookupImplementation(for: name, at: syntax, with: configDict)
285+
defaultLookupImplementation(for: name, at: syntax, with: config)
202286
}
203287
}
204288
}
205289

206290
@_spi(Experimental) extension MemberBlockSyntax: ScopeSyntax {
291+
/// All names introduced by members of this member scope.
207292
public var introducedNames: [LookupName] {
208293
members.flatMap { member in
209294
LookupName.getNames(from: member.decl)
@@ -230,15 +315,26 @@ extension SyntaxProtocol {
230315
[]
231316
}
232317

318+
/// Returns names matching lookup.
319+
/// Lookup triggered from inside of `else`
320+
/// clause is immediately forwarded to parent scope.
321+
///
322+
/// Example:
323+
/// ```swift
324+
/// guard let a = x else {
325+
/// return // a is not visible here
326+
/// }
327+
/// // a is visible here
328+
/// ```
233329
public func lookup(
234330
for name: String?,
235331
at syntax: SyntaxProtocol,
236-
with configDict: LookupConfigDictionary
332+
with config: LookupConfig
237333
) -> [LookupResult] {
238334
if body.position <= syntax.position && body.endPosition >= syntax.position {
239-
lookupInParent(for: name, at: self, with: configDict) // Should we add a new config that will skip certain scopes in lookup? Could be more consistent.
335+
lookupInParent(for: name, at: self, with: config)
240336
} else {
241-
defaultLookupImplementation(for: name, at: syntax, with: configDict)
337+
defaultLookupImplementation(for: name, at: syntax, with: config)
242338
}
243339
}
244340
}

0 commit comments

Comments
 (0)