Skip to content

Commit 984ec6d

Browse files
authored
Merge pull request #2782 from MAJKFL/generic-parameter-list-function-scope
[SwiftLexicalLookup][GSoC] Add switch, generic parameter and function scopes.
2 parents 801b535 + 5e8c6d4 commit 984ec6d

11 files changed

+524
-17
lines changed

Sources/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_subdirectory(_SwiftSyntaxCShims)
1111
add_subdirectory(SwiftBasicFormat)
1212
add_subdirectory(SwiftSyntax)
1313
add_subdirectory(SwiftDiagnostics)
14+
add_subdirectory(SwiftLexicalLookup)
1415
add_subdirectory(SwiftLibraryPluginProvider)
1516
add_subdirectory(SwiftParser)
1617
add_subdirectory(SwiftParserDiagnostics)
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This source file is part of the Swift.org open source project
2+
#
3+
# Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
4+
# Licensed under Apache License v2.0 with Runtime Library Exception
5+
#
6+
# See http://swift.org/LICENSE.txt for license information
7+
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
8+
9+
add_swift_syntax_library(SwiftLexicalLookup
10+
IdentifiableSyntax.swift
11+
LookupName.swift
12+
LookupResult.swift
13+
SimpleLookupQueries.swift
14+
15+
Configurations/FileScopeHandlingConfig.swift
16+
Configurations/LookupConfig.swift
17+
18+
Scopes/GenericParameterScopeSyntax.swift
19+
Scopes/IntroducingToSequentialParentScopeSyntax.swift
20+
Scopes/ScopeImplementations.swift
21+
Scopes/ScopeSyntax.swift
22+
Scopes/SequentialScopeSyntax.swift
23+
Scopes/TypeScopeSyntax.swift
24+
Scopes/WithGenericParametersScopeSyntax.swift
25+
)
26+
27+
target_link_swift_syntax_libraries(SwiftLexicalLookup PUBLIC
28+
SwiftSyntax)
29+

Sources/SwiftLexicalLookup/IdentifiableSyntax.swift

+18
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ import SwiftSyntax
2525
}
2626
}
2727

28+
@_spi(Experimental) extension FunctionParameterSyntax: IdentifiableSyntax {
29+
@_spi(Experimental) public var identifier: TokenSyntax {
30+
secondName ?? firstName
31+
}
32+
}
33+
2834
@_spi(Experimental) extension ClosureShorthandParameterSyntax: IdentifiableSyntax {
2935
@_spi(Experimental) public var identifier: TokenSyntax {
3036
name
@@ -42,3 +48,15 @@ import SwiftSyntax
4248
name
4349
}
4450
}
51+
52+
@_spi(Experimental) extension GenericParameterSyntax: IdentifiableSyntax {
53+
@_spi(Experimental) public var identifier: TokenSyntax {
54+
name
55+
}
56+
}
57+
58+
@_spi(Experimental) extension PrimaryAssociatedTypeSyntax: IdentifiableSyntax {
59+
@_spi(Experimental) public var identifier: TokenSyntax {
60+
name
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
/// Scope that introduces generic parameter names and directs
16+
/// futher lookup to its `WithGenericParametersScopeSyntax`
17+
/// parent scope's parent scope (i.e. on return, bypasses names
18+
/// introduced by its parent).
19+
@_spi(Experimental) public protocol GenericParameterScopeSyntax: ScopeSyntax {}
20+
21+
@_spi(Experimental) extension GenericParameterScopeSyntax {
22+
/// Returns names matching lookup and bypasses
23+
/// `WithGenericParametersScopeSyntax` parent scope in futher lookup.
24+
///
25+
/// ### Example
26+
/// ```swift
27+
/// let a = 23
28+
/// func foo<A>(a: A) {
29+
/// a // <-- start lookup here
30+
/// }
31+
/// ```
32+
/// When starting lookup at the `a` reference,
33+
/// lookup first visits the code block scope associated
34+
/// with the function's body. Then, it's forwarded to the
35+
/// function declaration scope and then to generic parameter
36+
/// scope (`WithGenericParametersScopeSyntax`).
37+
/// Then, to ensure there is no infinite cycle,
38+
/// this method passes lookup to function scope's parent scope
39+
/// (in this case: file scope).
40+
@_spi(Experimental) public func lookup(
41+
_ identifier: Identifier?,
42+
at lookUpPosition: AbsolutePosition,
43+
with config: LookupConfig
44+
) -> [LookupResult] {
45+
return defaultLookupImplementation(
46+
identifier,
47+
at: lookUpPosition,
48+
with: config,
49+
propagateToParent: false
50+
)
51+
+ lookupBypassingParentResults(
52+
identifier,
53+
at: lookUpPosition,
54+
with: config
55+
)
56+
}
57+
58+
/// Bypasses names introduced by `WithGenericParametersScopeSyntax` parent scope.
59+
///
60+
/// ### Example
61+
/// ```swift
62+
/// let a = 23
63+
/// func foo<A>(a: A) {
64+
/// a // <-- start lookup here
65+
/// }
66+
/// ```
67+
/// When starting lookup at the `a` reference,
68+
/// lookup first visits the code block scope associated
69+
/// with the function's body. Then, it's forwarded to the
70+
/// function declaration scope and then to generic parameter
71+
/// scope (`WithGenericParametersScopeSyntax`).
72+
/// Then, to ensure there is no infinite cycle,
73+
/// we use this method instead of the standard `lookupInParent`
74+
/// to pass lookup to the function scope's parent scope (in this case: file scope)
75+
/// and effectively bypass names already looked up before.
76+
private func lookupBypassingParentResults(
77+
_ identifier: Identifier?,
78+
at lookUpPosition: AbsolutePosition,
79+
with config: LookupConfig
80+
) -> [LookupResult] {
81+
guard let parentScope else { return [] }
82+
83+
if let parentScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self)
84+
as? WithGenericParametersScopeSyntax
85+
{
86+
return parentScope.lookupInParent(identifier, at: lookUpPosition, with: config)
87+
} else {
88+
return lookupInParent(identifier, at: lookUpPosition, with: config)
89+
}
90+
}
91+
}

Sources/SwiftLexicalLookup/ScopeImplementations.swift Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift

+115-9
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ import SwiftSyntax
176176
/// All names introduced by the closure signature.
177177
/// Could be closure captures or (shorthand) parameters.
178178
///
179-
/// Example:
179+
/// ### Example
180180
/// ```swift
181181
/// let x = { [weak self, a] b, _ in
182182
/// // <--
@@ -222,7 +222,7 @@ import SwiftSyntax
222222

223223
/// Finds parent scope, omitting ancestor `if` statements if part of their `else if` clause.
224224
///
225-
/// Example:
225+
/// ### Example
226226
/// ```swift
227227
/// func foo() {
228228
/// if let a = x {
@@ -262,7 +262,7 @@ import SwiftSyntax
262262
/// Lookup triggered from inside of `else`
263263
/// clause is immediately forwarded to parent scope.
264264
///
265-
/// Example:
265+
/// ### Example
266266
/// ```swift
267267
/// if let a = x {
268268
/// // <-- a is visible here
@@ -290,6 +290,24 @@ import SwiftSyntax
290290
LookupName.getNames(from: member.decl)
291291
}
292292
}
293+
294+
/// Creates a result from associated type declarations
295+
/// made by it's members.
296+
func lookupAssociatedTypeDeclarations(
297+
_ identifier: Identifier?,
298+
at lookUpPosition: AbsolutePosition,
299+
with config: LookupConfig
300+
) -> [LookupResult] {
301+
let filteredNames = members.flatMap { member in
302+
guard member.decl.kind == .associatedTypeDecl else { return [LookupName]() }
303+
304+
return LookupName.getNames(from: member.decl)
305+
}.filter { name in
306+
checkIdentifier(identifier, refersTo: name, at: lookUpPosition)
307+
}
308+
309+
return filteredNames.isEmpty ? [] : [.fromScope(self, withNames: filteredNames)]
310+
}
293311
}
294312

295313
@_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax {
@@ -308,7 +326,7 @@ import SwiftSyntax
308326
/// Lookup triggered from within of the `else` body
309327
/// returns no names.
310328
///
311-
/// Example:
329+
/// ### Example
312330
/// ```swift
313331
/// guard let a = x else {
314332
/// return // a is not visible here
@@ -330,10 +348,10 @@ import SwiftSyntax
330348
}
331349
}
332350

333-
@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax {}
334-
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax {}
335-
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax {}
336-
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax {}
351+
@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
352+
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
353+
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
354+
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
337355
@_spi(Experimental) extension ExtensionDeclSyntax: TypeScopeSyntax {}
338356

339357
@_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax {
@@ -356,7 +374,95 @@ import SwiftSyntax
356374

357375
@_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax {
358376
/// Implicit `error` when there are no catch items.
359-
public var introducedNames: [LookupName] {
377+
@_spi(Experimental) public var introducedNames: [LookupName] {
360378
return catchItems.isEmpty ? [.implicit(.error(self))] : []
361379
}
362380
}
381+
382+
@_spi(Experimental) extension SwitchCaseSyntax: ScopeSyntax {
383+
/// Names introduced within `case` items.
384+
@_spi(Experimental) public var introducedNames: [LookupName] {
385+
label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in
386+
LookupName.getNames(from: child.pattern)
387+
} ?? []
388+
}
389+
}
390+
391+
@_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax {
392+
/// Protocol declarations don't introduce names by themselves.
393+
@_spi(Experimental) public var introducedNames: [LookupName] {
394+
[]
395+
}
396+
397+
/// For the lookup initiated from inside primary
398+
/// associated type clause, this function also finds
399+
/// all associated type declarations made inside the
400+
/// protocol member block.
401+
///
402+
/// ### Example
403+
/// ```swift
404+
/// class A {}
405+
///
406+
/// protocol Foo<A/*<-- lookup here>*/> {
407+
/// associatedtype A
408+
/// class A {}
409+
/// }
410+
/// ```
411+
/// For the lookup started at the primary associated type `A`,
412+
/// the function returns exactly two results. First associated with the member
413+
/// block that consists of the `associatedtype A` declaration and
414+
/// the latter one from the file scope and `class A` exactly in this order.
415+
public func lookup(
416+
_ identifier: Identifier?,
417+
at lookUpPosition: AbsolutePosition,
418+
with config: LookupConfig
419+
) -> [LookupResult] {
420+
var results: [LookupResult] = []
421+
422+
if let primaryAssociatedTypeClause,
423+
primaryAssociatedTypeClause.range.contains(lookUpPosition)
424+
{
425+
results = memberBlock.lookupAssociatedTypeDeclarations(
426+
identifier,
427+
at: lookUpPosition,
428+
with: config
429+
)
430+
}
431+
432+
return results + defaultLookupImplementation(identifier, at: lookUpPosition, with: config)
433+
}
434+
}
435+
436+
@_spi(Experimental) extension GenericParameterClauseSyntax: GenericParameterScopeSyntax {
437+
/// Generic parameter names introduced by this clause.
438+
@_spi(Experimental) public var introducedNames: [LookupName] {
439+
parameters.children(viewMode: .fixedUp).flatMap { child in
440+
LookupName.getNames(from: child, accessibleAfter: child.endPosition)
441+
}
442+
}
443+
}
444+
445+
@_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersScopeSyntax {
446+
/// Function parameters introduced by this function's signature.
447+
@_spi(Experimental) public var introducedNames: [LookupName] {
448+
signature.parameterClause.parameters.flatMap { parameter in
449+
LookupName.getNames(from: parameter)
450+
}
451+
}
452+
}
453+
454+
@_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax {
455+
/// Parameters introduced by this subscript.
456+
@_spi(Experimental) public var introducedNames: [LookupName] {
457+
parameterClause.parameters.flatMap { parameter in
458+
LookupName.getNames(from: parameter)
459+
}
460+
}
461+
}
462+
463+
@_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax {
464+
/// Type alias doesn't introduce any names to it's children.
465+
@_spi(Experimental) public var introducedNames: [LookupName] {
466+
[]
467+
}
468+
}

Sources/SwiftLexicalLookup/ScopeSyntax.swift Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift

+5-7
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,18 @@ extension SyntaxProtocol {
8787
func defaultLookupImplementation(
8888
_ identifier: Identifier?,
8989
at lookUpPosition: AbsolutePosition,
90-
with config: LookupConfig
90+
with config: LookupConfig,
91+
propagateToParent: Bool = true
9192
) -> [LookupResult] {
9293
let filteredNames =
9394
introducedNames
9495
.filter { introducedName in
9596
checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition)
9697
}
9798

98-
if filteredNames.isEmpty {
99-
return lookupInParent(identifier, at: lookUpPosition, with: config)
100-
} else {
101-
return [.fromScope(self, withNames: filteredNames)]
102-
+ lookupInParent(identifier, at: lookUpPosition, with: config)
103-
}
99+
let fromThisScope = filteredNames.isEmpty ? [] : [LookupResult.fromScope(self, withNames: filteredNames)]
100+
101+
return fromThisScope + (propagateToParent ? lookupInParent(identifier, at: lookUpPosition, with: config) : [])
104102
}
105103

106104
/// Looks up in parent scope.

Sources/SwiftLexicalLookup/SequentialScopeSyntax.swift Sources/SwiftLexicalLookup/Scopes/SequentialScopeSyntax.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension SequentialScopeSyntax {
2222
/// and included `IntroducingToSequentialParentScopeSyntax` children
2323
/// scopes that match the lookup.
2424
///
25-
/// Example:
25+
/// ### Example
2626
/// ```swift
2727
/// func foo() {
2828
/// let a = 1

0 commit comments

Comments
 (0)