Skip to content

Commit 084005f

Browse files
authored
Merge pull request #2852 from MAJKFL/astscope-fixes-generics-lookmembers
[SwiftLexicalLookup] Add ASTScope related fixes and lookInMembers result kind.
2 parents 3dea07c + fc1ab88 commit 084005f

15 files changed

+1013
-148
lines changed

Sources/SwiftLexicalLookup/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ add_swift_syntax_library(SwiftLexicalLookup
1515
Configurations/FileScopeHandlingConfig.swift
1616
Configurations/LookupConfig.swift
1717

18+
Scopes/CanInterleaveResultsLaterScopeSyntax.swift
19+
Scopes/FunctionScopeSyntax.swift
1820
Scopes/GenericParameterScopeSyntax.swift
1921
Scopes/IntroducingToSequentialParentScopeSyntax.swift
22+
Scopes/LookInMembersScopeSyntax.swift
23+
Scopes/NominalTypeDeclSyntax.swift
2024
Scopes/ScopeImplementations.swift
2125
Scopes/ScopeSyntax.swift
2226
Scopes/SequentialScopeSyntax.swift
23-
Scopes/TypeScopeSyntax.swift
2427
Scopes/WithGenericParametersScopeSyntax.swift
2528
)
2629

Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,61 @@
1313
@_spi(Experimental) public struct LookupConfig {
1414
/// Specifies behavior of file scope.
1515
@_spi(Experimental) public var fileScopeHandling: FileScopeHandlingConfig
16+
/// Specifies whether lookup should finish in the closest sequential scope.
17+
///
18+
/// ### Example
19+
/// ```swift
20+
/// class X {
21+
/// let a = 42
22+
///
23+
/// func (a: Int) {
24+
/// let a = 123
25+
///
26+
/// a // <-- lookup here
27+
/// }
28+
/// }
29+
/// ```
30+
/// When looking up at the specified position with `finishInSequentialScope`
31+
/// set to `false`, lookup will return declaration from inside function body,
32+
/// function parameter and the `a` declaration from `class X` member block.
33+
/// If `finishInSequentialScope` would be set to `false`, the only name
34+
/// returned by lookup would be the `a` declaration from inside function body.
35+
@_spi(Experimental) public var finishInSequentialScope: Bool
36+
/// Specifies whether to include results generated in file and member block scopes.
37+
///
38+
/// ### Example
39+
/// ```swift
40+
/// class X {
41+
/// let a = 42
42+
///
43+
/// func (a: Int) {
44+
/// let a = 123
45+
///
46+
/// a // <-- lookup here
47+
/// }
48+
/// }
49+
/// ```
50+
/// When looking up at the specified position with `includeMembers`
51+
/// set to `true`, lookup will return declaration from inside function body,
52+
/// function parameter and the `a` declaration from `class X` member block.
53+
/// If `includeMembers` would be set to `false`, the latter name would be omitted.
54+
@_spi(Experimental) public var includeMembers: Bool
1655

1756
/// Creates a new lookup configuration.
1857
///
1958
/// - `fileScopeHandling` - specifies behavior of file scope.
2059
/// `memberBlockUpToLastDecl` by default.
60+
/// - `finishInSequentialScope` - specifies whether lookup should finish
61+
/// in the closest sequential scope. `false` by default.
62+
/// - `includeMembers` - specifies whether to include results generated
63+
/// in file and member block scopes. `true` by default.
2164
@_spi(Experimental) public init(
22-
fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl
65+
fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl,
66+
finishInSequentialScope: Bool = false,
67+
includeMembers: Bool = true
2368
) {
2469
self.fileScopeHandling = fileScopeHandling
70+
self.finishInSequentialScope = finishInSequentialScope
71+
self.includeMembers = includeMembers
2572
}
2673
}

Sources/SwiftLexicalLookup/LookupName.swift

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SwiftSyntax
1616
@_spi(Experimental) public enum ImplicitDecl {
1717
/// `self` keyword representing object instance.
1818
/// Could be associated with type declaration, extension,
19-
/// or closure captures.
19+
/// or closure captures. Introduced at function edge.
2020
case `self`(DeclSyntaxProtocol)
2121
/// `Self` keyword representing object type.
2222
/// Could be associated with type declaration or extension.
@@ -135,6 +135,51 @@ import SwiftSyntax
135135
}
136136
}
137137

138+
/// Position of this name.
139+
///
140+
/// For some syntax nodes, their position doesn't reflect
141+
/// the position at which a particular name was introduced at.
142+
/// Such cases are function parameters (as they can
143+
/// contain two identifiers) and function declarations (where name
144+
/// is precided by access modifiers and `func` keyword).
145+
@_spi(Experimental) public var position: AbsolutePosition {
146+
switch self {
147+
case .identifier(let syntax, _):
148+
return syntax.identifier.positionAfterSkippingLeadingTrivia
149+
case .declaration(let syntax):
150+
return syntax.name.positionAfterSkippingLeadingTrivia
151+
case .implicit(let implicitName):
152+
switch implicitName {
153+
case .self(let declSyntax):
154+
switch Syntax(declSyntax).as(SyntaxEnum.self) {
155+
case .functionDecl(let functionDecl):
156+
return functionDecl.name.positionAfterSkippingLeadingTrivia
157+
case .initializerDecl(let initializerDecl):
158+
return initializerDecl.initKeyword.positionAfterSkippingLeadingTrivia
159+
case .subscriptDecl(let subscriptDecl):
160+
return subscriptDecl.accessorBlock?.positionAfterSkippingLeadingTrivia
161+
?? subscriptDecl.endPositionBeforeTrailingTrivia
162+
case .variableDecl(let variableDecl):
163+
return variableDecl.bindings.first?.accessorBlock?.positionAfterSkippingLeadingTrivia
164+
?? variableDecl.endPosition
165+
default:
166+
return declSyntax.positionAfterSkippingLeadingTrivia
167+
}
168+
case .Self(let declSyntax):
169+
switch Syntax(declSyntax).as(SyntaxEnum.self) {
170+
case .protocolDecl(let protocolDecl):
171+
return protocolDecl.name.positionAfterSkippingLeadingTrivia
172+
default:
173+
return declSyntax.positionAfterSkippingLeadingTrivia
174+
}
175+
case .error(let catchClause):
176+
return catchClause.body.positionAfterSkippingLeadingTrivia
177+
default:
178+
return implicitName.syntax.positionAfterSkippingLeadingTrivia
179+
}
180+
}
181+
}
182+
138183
/// Point, after which the name is available in scope.
139184
/// If set to `nil`, the name is available at any point in scope.
140185
var accessibleAfter: AbsolutePosition? {
@@ -166,7 +211,7 @@ import SwiftSyntax
166211
) -> [LookupName] {
167212
switch Syntax(syntax).as(SyntaxEnum.self) {
168213
case .variableDecl(let variableDecl):
169-
return variableDecl.bindings.flatMap { binding in
214+
return variableDecl.bindings.reversed().flatMap { binding in
170215
getNames(
171216
from: binding.pattern,
172217
accessibleAfter: accessibleAfter != nil ? binding.endPositionBeforeTrailingTrivia : nil
@@ -194,6 +239,8 @@ import SwiftSyntax
194239
return functionCallExpr.arguments.flatMap { argument in
195240
getNames(from: argument.expression, accessibleAfter: accessibleAfter)
196241
}
242+
case .optionalChainingExpr(let optionalChainingExpr):
243+
return getNames(from: optionalChainingExpr.expression, accessibleAfter: accessibleAfter)
197244
default:
198245
if let namedDecl = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? NamedDeclSyntax {
199246
return handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter)
@@ -210,12 +257,7 @@ import SwiftSyntax
210257
identifiable: IdentifiableSyntax,
211258
accessibleAfter: AbsolutePosition? = nil
212259
) -> [LookupName] {
213-
switch identifiable.identifier.tokenKind {
214-
case .wildcard:
215-
return []
216-
default:
217-
return [.identifier(identifiable, accessibleAfter: accessibleAfter)]
218-
}
260+
[.identifier(identifiable, accessibleAfter: accessibleAfter)]
219261
}
220262

221263
/// Extracts name introduced by `NamedDeclSyntax` node.
@@ -225,4 +267,27 @@ import SwiftSyntax
225267
) -> [LookupName] {
226268
[.declaration(namedDecl)]
227269
}
270+
271+
/// Debug description of this lookup name.
272+
@_spi(Experimental) public var debugDescription: String {
273+
let sourceLocationConverter = SourceLocationConverter(fileName: "", tree: syntax.root)
274+
let location = sourceLocationConverter.location(for: position)
275+
let strName = (identifier?.name ?? "NO-NAME") + " at: \(location.line):\(location.column)"
276+
277+
switch self {
278+
case .identifier:
279+
let str = "identifier: \(strName)"
280+
281+
if let accessibleAfter {
282+
let location = sourceLocationConverter.location(for: accessibleAfter)
283+
return str + " after: \(location.line):\(location.column)"
284+
} else {
285+
return str
286+
}
287+
case .declaration:
288+
return "declaration: \(strName)"
289+
case .implicit:
290+
return "implicit: \(strName)"
291+
}
292+
}
228293
}

Sources/SwiftLexicalLookup/LookupResult.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ import SwiftSyntax
1818
case fromScope(ScopeSyntax, withNames: [LookupName])
1919
/// File scope and names that matched lookup.
2020
case fromFileScope(SourceFileSyntax, withNames: [LookupName])
21+
/// Indicates where to perform member lookup.
22+
case lookInMembers(LookInMembersScopeSyntax)
2123

2224
/// Associated scope.
23-
@_spi(Experimental) public var scope: ScopeSyntax? {
25+
@_spi(Experimental) public var scope: ScopeSyntax {
2426
switch self {
2527
case .fromScope(let scopeSyntax, _):
2628
return scopeSyntax
2729
case .fromFileScope(let fileScopeSyntax, _):
2830
return fileScopeSyntax
31+
case .lookInMembers(let lookInMemb):
32+
return lookInMemb
2933
}
3034
}
3135

@@ -34,6 +38,8 @@ import SwiftSyntax
3438
switch self {
3539
case .fromScope(_, let names), .fromFileScope(_, let names):
3640
return names
41+
case .lookInMembers(_):
42+
return []
3743
}
3844
}
3945

@@ -46,4 +52,48 @@ import SwiftSyntax
4652
return .fromScope(scope, withNames: names)
4753
}
4854
}
55+
56+
/// Debug description of this lookup name.
57+
@_spi(Experimental) public var debugDescription: String {
58+
var description =
59+
resultKindDebugName + ": " + scope.scopeDebugDescription
60+
61+
switch self {
62+
case .lookInMembers:
63+
break
64+
default:
65+
if !names.isEmpty {
66+
description += "\n"
67+
}
68+
}
69+
70+
for (index, name) in names.enumerated() {
71+
if index + 1 == names.count {
72+
description += "`-" + name.debugDescription
73+
} else {
74+
description += "|-" + name.debugDescription + "\n"
75+
}
76+
}
77+
78+
return description
79+
}
80+
81+
/// Debug name of this result kind.
82+
private var resultKindDebugName: String {
83+
switch self {
84+
case .fromScope:
85+
return "fromScope"
86+
case .fromFileScope:
87+
return "fromFileScope"
88+
case .lookInMembers:
89+
return "lookInMembers"
90+
}
91+
}
92+
}
93+
94+
@_spi(Experimental) extension [LookupResult] {
95+
/// Debug description this array of lookup results.
96+
@_spi(Experimental) public var debugDescription: String {
97+
return self.map(\.debugDescription).joined(separator: "\n")
98+
}
4999
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
protocol CanInterleaveResultsLaterScopeSyntax: ScopeSyntax {
16+
/// Perform lookup in this scope and later introduce results
17+
/// passed as `resultsToInterleave`.
18+
/// The exact behavior depends on a specific scope.
19+
func lookupWithInterleavedResults(
20+
_ identifier: Identifier?,
21+
at lookUpPosition: AbsolutePosition,
22+
with config: LookupConfig,
23+
resultsToInterleave: [LookupResult]
24+
) -> [LookupResult]
25+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
protocol FunctionScopeSyntax: DeclSyntaxProtocol, WithGenericParametersScopeSyntax {
16+
var signature: FunctionSignatureSyntax { get }
17+
}
18+
19+
extension FunctionScopeSyntax {
20+
/// Function parameters introduced by this function's signature.
21+
@_spi(Experimental) public var introducedNames: [LookupName] {
22+
signature.parameterClause.parameters.flatMap { parameter in
23+
LookupName.getNames(from: parameter)
24+
} + (parentScope?.is(MemberBlockSyntax.self) ?? false ? [.implicit(.self(self))] : [])
25+
}
26+
27+
/// Lookup results from this function scope.
28+
/// Routes to generic parameter clause scope if exists.
29+
@_spi(Experimental) public func lookup(
30+
_ identifier: Identifier?,
31+
at lookUpPosition: AbsolutePosition,
32+
with config: LookupConfig
33+
) -> [LookupResult] {
34+
var thisScopeResults: [LookupResult] = []
35+
36+
if !signature.range.contains(lookUpPosition) {
37+
thisScopeResults = defaultLookupImplementation(
38+
identifier,
39+
at: position,
40+
with: config,
41+
propagateToParent: false
42+
)
43+
}
44+
45+
return thisScopeResults
46+
+ lookupThroughGenericParameterScope(
47+
identifier,
48+
at: lookUpPosition,
49+
with: config
50+
)
51+
}
52+
}

Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SwiftSyntax
1616
/// futher lookup to its `WithGenericParametersScopeSyntax`
1717
/// parent scope's parent scope (i.e. on return, bypasses names
1818
/// introduced by its parent).
19-
@_spi(Experimental) public protocol GenericParameterScopeSyntax: ScopeSyntax {}
19+
protocol GenericParameterScopeSyntax: ScopeSyntax {}
2020

2121
@_spi(Experimental) extension GenericParameterScopeSyntax {
2222
/// Returns names matching lookup and bypasses
@@ -83,7 +83,7 @@ import SwiftSyntax
8383
if let parentScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self)
8484
as? WithGenericParametersScopeSyntax
8585
{
86-
return parentScope.lookupInParent(identifier, at: lookUpPosition, with: config)
86+
return parentScope.returningLookupFromGenericParameterScope(identifier, at: lookUpPosition, with: config)
8787
} else {
8888
return lookupInParent(identifier, at: lookUpPosition, with: config)
8989
}

Sources/SwiftLexicalLookup/Scopes/TypeScopeSyntax.swift renamed to Sources/SwiftLexicalLookup/Scopes/LookInMembersScopeSyntax.swift

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,7 @@
1212

1313
import SwiftSyntax
1414

15-
@_spi(Experimental) public protocol TypeScopeSyntax: ScopeSyntax, DeclSyntaxProtocol {}
16-
17-
extension TypeScopeSyntax {
18-
@_spi(Experimental) public var implicitInstanceAndTypeNames: [LookupName] {
19-
[.implicit(.self(self)), .implicit(.Self(self))]
20-
}
21-
22-
@_spi(Experimental) public var introducedNames: [LookupName] {
23-
implicitInstanceAndTypeNames
24-
}
15+
@_spi(Experimental) public protocol LookInMembersScopeSyntax: ScopeSyntax {
16+
/// Position used for member lookup.
17+
var lookupMembersPosition: AbsolutePosition { get }
2518
}

0 commit comments

Comments
 (0)