Skip to content

Commit b9c4f51

Browse files
committed
Add initial name lookup functionality
1 parent 67e2608 commit b9c4f51

File tree

6 files changed

+672
-15
lines changed

6 files changed

+672
-15
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
@_spi(Experimental) public enum LookupName {
16+
/// Identifier associated with the name.
17+
/// Could be an identifier of a variable, function or closure parameter and more
18+
case identifier(String, SyntaxProtocol)
19+
/// Declaration associated with the name.
20+
/// Could be class, struct, actor, protocol, function and more
21+
case declaration(String, DeclSyntaxProtocol)
22+
23+
/// Syntax associated with this name.
24+
@_spi(Experimental) public var syntax: SyntaxProtocol {
25+
switch self {
26+
case .identifier(_, let syntax):
27+
syntax
28+
case .declaration(_, let syntax):
29+
syntax
30+
}
31+
}
32+
33+
/// Introduced name.
34+
@_spi(Experimental) public var name: String {
35+
switch self {
36+
case .identifier(let name, _):
37+
name
38+
case .declaration(let name, _):
39+
name
40+
}
41+
}
42+
43+
/// Checks if this name was introduced before the syntax used for lookup.
44+
func isBefore(_ lookedUpSyntax: SyntaxProtocol) -> Bool {
45+
syntax.position < lookedUpSyntax.position
46+
}
47+
48+
/// Checks if this name refers to the looked up phrase.
49+
func refersTo(_ lookedUpName: String) -> Bool {
50+
name == lookedUpName
51+
}
52+
53+
/// Extracts names introduced by the given `from` structure.
54+
static func getNames(from syntax: SyntaxProtocol) -> [LookupName] {
55+
switch Syntax(syntax).as(SyntaxEnum.self) {
56+
case .variableDecl(let variableDecl):
57+
variableDecl.bindings.flatMap { binding in
58+
getNames(from: binding.pattern)
59+
}
60+
case .tuplePattern(let tuplePattern):
61+
tuplePattern.elements.flatMap { tupleElement in
62+
getNames(from: tupleElement.pattern)
63+
}
64+
case .valueBindingPattern(let valueBindingPattern):
65+
getNames(from: valueBindingPattern.pattern)
66+
case .expressionPattern(let expressionPattern):
67+
getNames(from: expressionPattern.expression)
68+
case .sequenceExpr(let sequenceExpr):
69+
sequenceExpr.elements.flatMap { expression in
70+
getNames(from: expression)
71+
}
72+
case .patternExpr(let patternExpr):
73+
getNames(from: patternExpr.pattern)
74+
case .optionalBindingCondition(let optionalBinding):
75+
getNames(from: optionalBinding.pattern)
76+
case .identifierPattern(let identifierPattern):
77+
handle(identifierPattern: identifierPattern)
78+
case .closureShorthandParameter(let closureShorthandParameter):
79+
handle(closureShorthandParameter: closureShorthandParameter)
80+
case .closureParameter(let closureParameter):
81+
handle(closureParameter: closureParameter)
82+
case .functionDecl(let functionDecl):
83+
handle(functionDecl: functionDecl)
84+
case .classDecl(let classDecl):
85+
handle(classDecl: classDecl)
86+
case .structDecl(let structDecl):
87+
handle(structDecl: structDecl)
88+
case .actorDecl(let actorDecl):
89+
handle(actorDecl: actorDecl)
90+
case .protocolDecl(let protocolDecl):
91+
handle(protocolDecl: protocolDecl)
92+
default:
93+
[]
94+
}
95+
}
96+
97+
/// Extracts name introduced by `identifierPattern`.
98+
private static func handle(identifierPattern: IdentifierPatternSyntax) -> [LookupName] {
99+
[.identifier(identifierPattern.identifier.text, identifierPattern)]
100+
}
101+
102+
/// Extracts name introduced by `closureParameter`.
103+
private static func handle(closureParameter: ClosureParameterSyntax) -> [LookupName] {
104+
[.identifier(closureParameter.secondName?.text ?? closureParameter.firstName.text, closureParameter)]
105+
}
106+
107+
/// Extracts name introduced by `closureShorthandParameter`.
108+
private static func handle(closureShorthandParameter: ClosureShorthandParameterSyntax) -> [LookupName] {
109+
let name = closureShorthandParameter.name.text
110+
if name != "_" {
111+
return [.identifier(name, closureShorthandParameter)]
112+
} else {
113+
return []
114+
}
115+
}
116+
117+
/// Extracts name introduced by `functionDecl`.
118+
private static func handle(functionDecl: FunctionDeclSyntax) -> [LookupName] {
119+
[.declaration(functionDecl.name.text, functionDecl)]
120+
}
121+
122+
/// Extracts name introduced by `classDecl`.
123+
private static func handle(classDecl: ClassDeclSyntax) -> [LookupName] {
124+
[.declaration(classDecl.name.text, classDecl)]
125+
}
126+
127+
/// Extracts name introduced by `structDecl`.
128+
private static func handle(structDecl: StructDeclSyntax) -> [LookupName] {
129+
[.declaration(structDecl.name.text, structDecl)]
130+
}
131+
132+
/// Extracts name introduced by `actorDecl`.
133+
private static func handle(actorDecl: ActorDeclSyntax) -> [LookupName] {
134+
[.declaration(actorDecl.name.text, actorDecl)]
135+
}
136+
137+
/// Extracts name introduced by `protocolDecl`.
138+
private static func handle(protocolDecl: ProtocolDeclSyntax) -> [LookupName] {
139+
[.declaration(protocolDecl.name.text, protocolDecl)]
140+
}
141+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
extension SyntaxProtocol {
16+
/// Parent scope of this syntax node, or scope introduced by this syntax node.
17+
var scope: ScopeSyntax? {
18+
switch Syntax(self).as(SyntaxEnum.self) {
19+
case .sourceFile(let sourceFile):
20+
sourceFile
21+
case .codeBlock(let codeBlock):
22+
codeBlock
23+
case .forStmt(let forStmt):
24+
forStmt
25+
case .closureExpr(let closureExpr):
26+
closureExpr
27+
case .whileStmt(let whileStmt):
28+
whileStmt
29+
case .ifExpr(let ifExpr):
30+
ifExpr
31+
case .memberBlock(let memberBlock):
32+
memberBlock
33+
default:
34+
self.parent?.scope
35+
}
36+
}
37+
}
38+
39+
extension SourceFileSyntax: ScopeSyntax {
40+
var parentScope: ScopeSyntax? {
41+
nil
42+
}
43+
44+
var introducedNames: [LookupName] {
45+
[]
46+
}
47+
48+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
49+
[]
50+
}
51+
}
52+
53+
extension CodeBlockSyntax: ScopeSyntax {
54+
var introducedNames: [LookupName] {
55+
statements.flatMap { codeBlockItem in
56+
LookupName.getNames(from: codeBlockItem.item)
57+
}
58+
}
59+
60+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
61+
defaultLookupImplementation(for: name, at: syntax, positionSensitive: true)
62+
}
63+
}
64+
65+
extension ForStmtSyntax: ScopeSyntax {
66+
var introducedNames: [LookupName] {
67+
LookupName.getNames(from: pattern)
68+
}
69+
70+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
71+
defaultLookupImplementation(for: name, at: syntax)
72+
}
73+
}
74+
75+
extension ClosureExprSyntax: ScopeSyntax {
76+
var introducedNames: [LookupName] {
77+
signature?.parameterClause?.children(viewMode: .sourceAccurate).flatMap { parameter in
78+
if let parameterList = parameter.as(ClosureParameterListSyntax.self) {
79+
parameterList.children(viewMode: .sourceAccurate).flatMap { parameter in
80+
LookupName.getNames(from: parameter)
81+
}
82+
} else {
83+
LookupName.getNames(from: parameter)
84+
}
85+
} ?? []
86+
}
87+
88+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
89+
defaultLookupImplementation(for: name, at: syntax)
90+
}
91+
}
92+
93+
extension WhileStmtSyntax: ScopeSyntax {
94+
var introducedNames: [LookupName] {
95+
conditions.flatMap { element in
96+
LookupName.getNames(from: element.condition)
97+
}
98+
}
99+
100+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
101+
defaultLookupImplementation(for: name, at: syntax)
102+
}
103+
}
104+
105+
extension IfExprSyntax: ScopeSyntax {
106+
var parentScope: ScopeSyntax? {
107+
getParent(for: self.parent, previousIfElse: self.elseKeyword == nil)
108+
}
109+
110+
/// Finds the parent scope, omitting parent `if` statements if part of their `else if` clause.
111+
private func getParent(for syntax: Syntax?, previousIfElse: Bool) -> ScopeSyntax? {
112+
guard let syntax else { return nil }
113+
114+
if let lookedUpScope = syntax.scope, lookedUpScope.id != self.id {
115+
if let currentIfExpr = lookedUpScope.as(IfExprSyntax.self), previousIfElse {
116+
return getParent(for: syntax.parent, previousIfElse: currentIfExpr.elseKeyword == nil)
117+
} else {
118+
return lookedUpScope
119+
}
120+
} else {
121+
return getParent(for: syntax.parent, previousIfElse: previousIfElse)
122+
}
123+
}
124+
125+
var introducedNames: [LookupName] {
126+
conditions.flatMap { element in
127+
LookupName.getNames(from: element.condition)
128+
}
129+
}
130+
131+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
132+
if let elseBody, elseBody.position <= syntax.position, elseBody.endPosition >= syntax.position {
133+
parentScope?.lookup(for: name, at: syntax) ?? []
134+
} else {
135+
defaultLookupImplementation(for: name, at: syntax, positionSensitive: true)
136+
}
137+
}
138+
}
139+
140+
extension MemberBlockSyntax: ScopeSyntax {
141+
var introducedNames: [LookupName] {
142+
members.flatMap { member in
143+
LookupName.getNames(from: member.decl)
144+
}
145+
}
146+
147+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName] {
148+
defaultLookupImplementation(for: name, at: syntax)
149+
}
150+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
extension SyntaxProtocol {
16+
/// Returns all names that `for` refers to at this syntax node.
17+
///
18+
/// - Returns: An array of names referred to by `for` at this syntax node,
19+
/// ordered by visibility. The order is from the innermost to the outermost
20+
/// scope, and within each scope, names are ordered by their introduction
21+
/// in the source code.
22+
///
23+
/// Example usage:
24+
/// ```swift
25+
/// class C {
26+
/// var a = 42
27+
///
28+
/// func a(a: Int) {
29+
/// a // <--- lookup here
30+
///
31+
/// let a = 0
32+
/// }
33+
///
34+
/// func a() {
35+
/// // ...
36+
/// }
37+
/// }
38+
/// ```
39+
/// When calling this function on the declaration reference `a` within its name,
40+
/// the function returns the parameter first, then the identifier of the variable
41+
/// declaration, followed by the first function name, and then the second function name,
42+
/// in this exact order. The constant declaration within the function body is omitted
43+
/// due to the ordering rules that prioritize visibility within the function body.
44+
@_spi(Experimental) public func lookup(for name: String) -> [LookupName] {
45+
scope?.lookup(for: name, at: self) ?? []
46+
}
47+
}
48+
49+
protocol ScopeSyntax: SyntaxProtocol {
50+
/// Parent of this scope, or `nil` if it is the root.
51+
var parentScope: ScopeSyntax? { get }
52+
/// Names found in this scope. Ordered from first to last introduced.
53+
var introducedNames: [LookupName] { get }
54+
/// Finds all declarations `name` refers to. `at` specifies the node lookup was triggered with.
55+
func lookup(for name: String, at syntax: SyntaxProtocol) -> [LookupName]
56+
}
57+
58+
extension ScopeSyntax {
59+
var parentScope: ScopeSyntax? {
60+
self.parent?.scope
61+
}
62+
63+
/// Returns all names introduced in this scope that `name` refers to and then
64+
/// passes lookup to the parent. Optionally, if `positionSensitive` is set to `true`,
65+
/// the method filters names that were introduced in this scope after `syntax`.
66+
func defaultLookupImplementation(
67+
for name: String,
68+
at syntax: SyntaxProtocol,
69+
positionSensitive: Bool = false
70+
) -> [LookupName] {
71+
introducedNames
72+
.filter { introducedName in
73+
(!positionSensitive || introducedName.isBefore(syntax)) && introducedName.refersTo(name)
74+
} + (parentScope?.lookup(for: name, at: syntax) ?? [])
75+
}
76+
}

Sources/SwiftLexicalLookup/SimpleLookupQueries.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ extension SyntaxProtocol {
6868

6969
// MARK: - lookupCatchNode
7070

71-
/// Given syntax node location, finds where an error could be caught. If `traverseCatchClause` is set to `true` lookup will skip the next do statement.
71+
/// Given syntax node location, finds where an error could be caught.
72+
/// If `traverseCatchClause` is set to `true` lookup will skip the next do statement.
7273
private func lookupCatchNodeHelper(traversedCatchClause: Bool) -> Syntax? {
7374
guard let parent else { return nil }
7475

@@ -108,7 +109,8 @@ extension SyntaxProtocol {
108109
collectNodesOfTypeUpToFunctionBoundary(type, stopWithFirstMatch: true).first
109110
}
110111

111-
/// Collect syntax nodes matching the collection type up until encountering one of the specified syntax nodes. The nodes in the array are inside out, with the innermost node being the first.
112+
/// Collect syntax nodes matching the collection type up until encountering one of the specified syntax nodes.
113+
/// The nodes in the array are inside out, with the innermost node being the first.
112114
fileprivate func collectNodesOfTypeUpToFunctionBoundary<T: SyntaxProtocol>(
113115
_ type: T.Type,
114116
stopWithFirstMatch: Bool = false
@@ -128,7 +130,7 @@ extension SyntaxProtocol {
128130
)
129131
}
130132

131-
/// Callect syntax nodes matching the collection type up until encountering one of the specified syntax nodes.
133+
/// Collect syntax nodes matching the collection type up until encountering one of the specified syntax nodes.
132134
private func collectNodes<T: SyntaxProtocol>(
133135
ofType type: T.Type,
134136
upTo stopAt: [SyntaxProtocol.Type],

0 commit comments

Comments
 (0)