Skip to content

Commit c31046a

Browse files
committed
Add implicit names and proper guard handling.
1 parent 99b5294 commit c31046a

12 files changed

+437
-116
lines changed

Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import Foundation
1919

2020
/// Specifies scopes that introduce names to their parent and
2121
/// should be skipped during lookup in sequential scopes.
22-
public var ignoreChildrenToParentIntroductionsFrom: [IntroducingToParentScopeSyntax]
22+
public var ignoreChildrenToSequentialParentIntroductionsFrom: [IntroducingToSequentialParentScopeSyntax]
2323

2424
public init(
2525
fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl,
26-
ignoreChildrenToParentIntroductionsFrom: [IntroducingToParentScopeSyntax] = []
26+
ignoreChildrenToSequentialParentIntroductionsFrom: [IntroducingToSequentialParentScopeSyntax] = []
2727
) {
2828
self.fileScopeHandling = fileScopeHandling
29-
self.ignoreChildrenToParentIntroductionsFrom = ignoreChildrenToParentIntroductionsFrom
29+
self.ignoreChildrenToSequentialParentIntroductionsFrom = ignoreChildrenToSequentialParentIntroductionsFrom
3030
}
3131
}

Sources/SwiftLexicalLookup/IdentifiableSyntax.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ extension ClosureCaptureSyntax: IdentifiableSyntax {
4141
expression.as(DeclReferenceExprSyntax.self)!.baseName
4242
}
4343
}
44+
45+
extension AccessorParametersSyntax: IdentifiableSyntax {
46+
@_spi(Experimental) public var identifier: TokenSyntax {
47+
name
48+
}
49+
}

Sources/SwiftLexicalLookup/IntroducingToParentScopeSyntax.swift renamed to Sources/SwiftLexicalLookup/IntroducingToSequentialParentScopeSyntax.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212

1313
import SwiftSyntax
1414

15-
@_spi(Experimental) public protocol IntroducingToParentScopeSyntax: ScopeSyntax {
16-
func introducedToParent(
15+
@_spi(Experimental) public protocol IntroducingToSequentialParentScopeSyntax: ScopeSyntax {
16+
/// Returns names matching lookup that should be
17+
/// handled by it's parent sequential scope.
18+
func introducesToSequentialParent(
1719
for name: String?,
1820
at syntax: SyntaxProtocol,
1921
with config: LookupConfig

Sources/SwiftLexicalLookup/LookupName.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ import SwiftSyntax
1919
/// Declaration associated with the name.
2020
/// Could be class, struct, actor, protocol, function and more
2121
case declaration(NamedDeclSyntax, accessibleAfter: AbsolutePosition?)
22+
/// `self` keyword representing object instance.
23+
case `self`(DeclSyntaxProtocol)
24+
/// `Self` keyword representing object type.
25+
case `Self`(DeclSyntaxProtocol)
26+
/// `self` captured by a closure.
27+
case selfCaptured(ClosureCaptureSyntax)
28+
/// `error` available inside `catch` clause.
29+
case error(CatchClauseSyntax)
30+
/// `newValue` available by default inside `set` and `willSet`.
31+
case newValue(AccessorDeclSyntax)
32+
/// `oldValue` available by default inside `didSet`.
33+
case oldValue(AccessorDeclSyntax)
2234

2335
/// Syntax associated with this name.
2436
@_spi(Experimental) public var syntax: SyntaxProtocol {
@@ -27,6 +39,18 @@ import SwiftSyntax
2739
syntax
2840
case .declaration(let syntax, _):
2941
syntax
42+
case .self(let syntax):
43+
syntax
44+
case .Self(let syntax):
45+
syntax
46+
case .selfCaptured(let syntax):
47+
syntax
48+
case .error(let syntax):
49+
syntax
50+
case .newValue(let syntax):
51+
syntax
52+
case .oldValue(let syntax):
53+
syntax
3054
}
3155
}
3256

@@ -37,6 +61,8 @@ import SwiftSyntax
3761
Identifier(syntax.identifier)
3862
case .declaration(let syntax, _):
3963
Identifier(syntax.name)
64+
default:
65+
nil
4066
}
4167
}
4268

@@ -46,6 +72,26 @@ import SwiftSyntax
4672
switch self {
4773
case .identifier(_, let absolutePosition), .declaration(_, let absolutePosition):
4874
absolutePosition
75+
default:
76+
nil
77+
}
78+
}
79+
80+
/// Used for name comparison.
81+
var name: String? {
82+
switch self {
83+
case .identifier, .declaration:
84+
identifier?.name
85+
case .self, .selfCaptured:
86+
"self"
87+
case .Self:
88+
"Self"
89+
case .error:
90+
"error"
91+
case .newValue:
92+
"newValue"
93+
case .oldValue:
94+
"oldValue"
4995
}
5096
}
5197

@@ -57,7 +103,7 @@ import SwiftSyntax
57103

58104
/// Checks if this name refers to the looked up phrase.
59105
func refersTo(_ lookedUpName: String) -> Bool {
60-
guard let name = identifier?.name else { return false }
106+
guard let name else { return false }
61107
return name == lookedUpName
62108
}
63109

@@ -104,7 +150,11 @@ import SwiftSyntax
104150
/// Extracts name introduced by `IdentifiableSyntax` node.
105151
private static func handle(identifiable: IdentifiableSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName]
106152
{
107-
if identifiable.identifier.text != "_" {
153+
if let closureCapture = identifiable as? ClosureCaptureSyntax,
154+
closureCapture.identifier.tokenKind == .keyword(.self)
155+
{
156+
return [.selfCaptured(closureCapture)] // Handle `self` closure capture.
157+
} else if identifiable.identifier.text != "_" {
108158
return [.identifier(identifiable, accessibleAfter: accessibleAfter)]
109159
} else {
110160
return []

Sources/SwiftLexicalLookup/ScopeImplementations.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,8 @@ extension SyntaxProtocol {
309309
}
310310
}
311311

312-
@_spi(Experimental) extension GuardStmtSyntax: IntroducingToParentScopeSyntax {
313-
public func introducedToParent(
312+
@_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax {
313+
public func introducesToSequentialParent(
314314
for name: String?,
315315
at syntax: SwiftSyntax.SyntaxProtocol,
316316
with config: LookupConfig
@@ -346,10 +346,33 @@ extension SyntaxProtocol {
346346
) -> [LookupResult] {
347347
if body.position <= syntax.position && body.endPosition >= syntax.position {
348348
var newConfig = config
349-
newConfig.ignoreChildrenToParentIntroductionsFrom.append(self)
349+
newConfig.ignoreChildrenToSequentialParentIntroductionsFrom.append(self)
350350
return lookupInParent(for: name, at: syntax, with: newConfig)
351351
} else {
352352
return defaultLookupImplementation(for: name, at: syntax, with: config)
353353
}
354354
}
355355
}
356+
357+
@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax {}
358+
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax {}
359+
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax {}
360+
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax {}
361+
@_spi(Experimental) extension ExtensionDeclSyntax: TypeScopeSyntax {}
362+
363+
@_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax {
364+
public var introducedNames: [LookupName] {
365+
if let parameters {
366+
LookupName.getNames(from: parameters)
367+
} else {
368+
switch accessorSpecifier.tokenKind {
369+
case .keyword(.set), .keyword(.willSet):
370+
[.newValue(self)]
371+
case .keyword(.didSet):
372+
[.oldValue(self)]
373+
default:
374+
[]
375+
}
376+
}
377+
}
378+
}

Sources/SwiftLexicalLookup/SequentialScopeSyntax.swift

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

1313
import SwiftSyntax
1414

15+
/// Scope that, in addition to names introduced by itself,
16+
/// also handles names introduced by
17+
/// `IntroducingToSequentialParentScopeSyntax` children scopes.
1518
@_spi(Experimental) public protocol SequentialScopeSyntax: ScopeSyntax {
19+
/// Returns names introduced by `codeBlockItems`
20+
/// and included `IntroducingToSequentialParentScopeSyntax` children
21+
/// scopes that match the lookup.
1622
func sequentialLookup(
1723
in codeBlockItems: any Collection<CodeBlockItemSyntax>,
1824
for name: String?,
@@ -35,9 +41,12 @@ import SwiftSyntax
3541

3642
for codeBlockItem in codeBlockItems {
3743
if let introducingToParentScope = Syntax(codeBlockItem.item).asProtocol(SyntaxProtocol.self)
38-
as? IntroducingToParentScopeSyntax
44+
as? IntroducingToSequentialParentScopeSyntax
3945
{
40-
guard !config.ignoreChildrenToParentIntroductionsFrom.contains(where: { $0.id == introducingToParentScope.id })
46+
guard
47+
!config.ignoreChildrenToSequentialParentIntroductionsFrom.contains(where: {
48+
$0.id == introducingToParentScope.id
49+
})
4150
else {
4251
continue
4352
}
@@ -47,7 +56,9 @@ import SwiftSyntax
4756
currentChunk = []
4857
}
4958

50-
result.append(contentsOf: introducingToParentScope.introducedToParent(for: name, at: syntax, with: config))
59+
result.append(
60+
contentsOf: introducingToParentScope.introducesToSequentialParent(for: name, at: syntax, with: config)
61+
)
5162
} else {
5263
currentChunk.append(
5364
contentsOf:
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 protocol TypeScopeSyntax: ScopeSyntax, DeclSyntaxProtocol {
16+
var implicitInstanceAndTypeNames: [LookupName] { get }
17+
}
18+
19+
@_spi(Experimental) extension TypeScopeSyntax {
20+
public var implicitInstanceAndTypeNames: [LookupName] {
21+
[.self(self), .Self(self)]
22+
}
23+
24+
public var introducedNames: [LookupName] {
25+
implicitInstanceAndTypeNames
26+
}
27+
}

Tests/SwiftLexicalLookupTest/Assertions.swift

Lines changed: 4 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -10,84 +10,12 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Foundation
1413
@_spi(Experimental) import SwiftLexicalLookup
1514
import SwiftParser
1615
import SwiftSyntax
1716
import XCTest
1817
import _SwiftSyntaxTestSupport
1918

20-
/// Used to define result type expectectations for given markers.
21-
enum MarkerExpectation {
22-
/// Specifies a separate type for each result marker.
23-
case distinct([String: SyntaxProtocol.Type])
24-
/// Specifies a common type for all results
25-
/// apart from the ones defined explicitly in `except`.
26-
case all(SyntaxProtocol.Type, except: [String: SyntaxProtocol.Type] = [:])
27-
/// Does not assert result types.
28-
case none
29-
30-
/// Assert `actual` result labeled with `marker`
31-
/// according to the rules represented by this expectation.
32-
fileprivate func assertMarkerType(marker: String, actual: SyntaxProtocol) {
33-
switch self {
34-
case .all(let expectedType, except: let dictionary):
35-
assertMarkerType(marker: marker, actual: actual, expectedType: dictionary[marker] ?? expectedType)
36-
case .distinct(let dictionary):
37-
if let expectedType = dictionary[marker] {
38-
assertMarkerType(marker: marker, actual: actual, expectedType: expectedType)
39-
} else {
40-
XCTFail("For result \(marker), could not find type expectation")
41-
}
42-
case .none:
43-
break
44-
}
45-
}
46-
47-
/// Assert whether `actual` type matches `expectedType`.
48-
private func assertMarkerType(marker: String, actual: SyntaxProtocol, expectedType: SyntaxProtocol.Type) {
49-
XCTAssert(
50-
actual.is(expectedType),
51-
"For result \(marker), expected type \(expectedType) doesn't match the actual type \(actual.syntaxNodeType)"
52-
)
53-
}
54-
}
55-
56-
/// Used to define
57-
enum ResultExpectation {
58-
case fromScope(ScopeSyntax.Type, expectedNames: [String])
59-
case fromFileScope(expectedNames: [String])
60-
61-
var expectedNames: [String] {
62-
switch self {
63-
case .fromScope(_, let expectedNames):
64-
expectedNames
65-
case .fromFileScope(expectedNames: let expectedNames):
66-
expectedNames
67-
}
68-
}
69-
70-
var debugDescription: String {
71-
switch self {
72-
case .fromScope:
73-
"fromScope"
74-
case .fromFileScope:
75-
"fromFileScope"
76-
}
77-
}
78-
}
79-
80-
extension LookupResult {
81-
var debugDescription: String {
82-
switch self {
83-
case .fromScope:
84-
"fromScope"
85-
case .fromFileScope:
86-
"fromFileScope"
87-
}
88-
}
89-
}
90-
9119
/// `methodUnderTest` is called with the token at every position marker in the keys of `expected`.
9220
/// It then asserts that the positions of the syntax nodes returned by `methodUnderTest` are the values in `expected`.
9321
/// It also checks whether result types match rules specified in `expectedResultTypes`.
@@ -185,21 +113,7 @@ func assertLexicalNameLookup(
185113
return []
186114
}
187115

188-
for (actual, expected) in zip(result, expectedValues) {
189-
switch (actual, expected) {
190-
case (.fromScope(let scope, withNames: _), .fromScope(let expectedType, expectedNames: _)):
191-
XCTAssert(
192-
scope.syntaxNodeType == expectedType,
193-
"For marker \(marker), scope result type of \(scope.syntaxNodeType) doesn't match expected \(expectedType)"
194-
)
195-
case (.fromFileScope, .fromFileScope):
196-
break
197-
default:
198-
XCTFail(
199-
"For marker \(marker), actual result kind \(actual.debugDescription) doesn't match expected \(expected.debugDescription)"
200-
)
201-
}
202-
}
116+
ResultExpectation.assertResult(marker: marker, result: result, expectedValues: expectedValues)
203117

204118
return result.flatMap { lookUpResult in
205119
lookUpResult.names.map { lookupName in
@@ -209,7 +123,9 @@ func assertLexicalNameLookup(
209123
},
210124
expected: references.mapValues { expectations in
211125
expectations.flatMap { expectation in
212-
expectation.expectedNames
126+
expectation.expectedNames.map { expectedName in
127+
expectedName.marker
128+
}
213129
}
214130
},
215131
expectedResultTypes: expectedResultTypes

0 commit comments

Comments
 (0)