Skip to content

Commit 4cac8a8

Browse files
committed
Fix primary associated type handling.
1 parent adf11eb commit 4cac8a8

File tree

4 files changed

+95
-52
lines changed

4 files changed

+95
-52
lines changed

Sources/SwiftLexicalLookup/Scopes/GenericParameterOrAssociatedTypeScopeSyntax.swift renamed to Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift

+9-10
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@
1212

1313
import SwiftSyntax
1414

15-
/// Scope that introduces generic parameter or
16-
/// primary associated type names and directs
17-
/// futher lookup to it's `WithGenericParametersOrAssociatedTypesScopeSyntax`
15+
/// Scope that introduces generic parameter names and directs
16+
/// futher lookup to it's `WithGenericParametersScopeSyntax`
1817
/// parent scope's parent scope (i.e. on return, bypasses names
1918
/// introduced by it's parent).
20-
@_spi(Experimental) public protocol GenericParameterOrAssociatedTypeScopeSyntax: ScopeSyntax {}
19+
@_spi(Experimental) public protocol GenericParameterScopeSyntax: ScopeSyntax {}
2120

22-
@_spi(Experimental) extension GenericParameterOrAssociatedTypeScopeSyntax {
21+
@_spi(Experimental) extension GenericParameterScopeSyntax {
2322
/// Returns names matching lookup and bypasses
24-
/// `WithGenericParametersOrAssociatedTypesScopeSyntax` parent scope in futher lookup.
23+
/// `WithGenericParametersScopeSyntax` parent scope in futher lookup.
2524
///
2625
/// example:
2726
/// ```swift
@@ -34,7 +33,7 @@ import SwiftSyntax
3433
/// lookup first visits the code block scope associated
3534
/// with the function's body. Then, it's forwarded to the
3635
/// function declaration scope and then to generic parameter
37-
/// scope (`WithGenericParametersOrAssociatedTypesScopeSyntax`).
36+
/// scope (`WithGenericParametersScopeSyntax`).
3837
/// Then, to ensure there is no infinite cycle,
3938
/// this method passes lookup to function scope's parent scope
4039
/// (in this case: file scope).
@@ -56,7 +55,7 @@ import SwiftSyntax
5655
)
5756
}
5857

59-
/// Bypasses names introduced by `WithGenericParametersOrAssociatedTypesScopeSyntax` parent scope.
58+
/// Bypasses names introduced by `WithGenericParametersScopeSyntax` parent scope.
6059
///
6160
/// example:
6261
/// ```swift
@@ -69,7 +68,7 @@ import SwiftSyntax
6968
/// lookup first visits the code block scope associated
7069
/// with the function's body. Then, it's forwarded to the
7170
/// function declaration scope and then to generic parameter
72-
/// scope (`WithGenericParametersOrAssociatedTypesScopeSyntax`).
71+
/// scope (`WithGenericParametersScopeSyntax`).
7372
/// Then, to ensure there is no infinite cycle,
7473
/// we use this method instead of the standard `lookupInParent`
7574
/// to pass lookup to the function scope's parent scope (in this case: file scope)
@@ -82,7 +81,7 @@ import SwiftSyntax
8281
guard let parentScope else { return [] }
8382

8483
if let parentScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self)
85-
as? WithGenericParametersOrAssociatedTypesScopeSyntax
84+
as? WithGenericParametersScopeSyntax
8685
{
8786
return parentScope.lookupInParent(identifier, at: lookUpPosition, with: config)
8887
} else {

Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift

+66-19
Original file line numberDiff line numberDiff line change
@@ -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 {
@@ -330,10 +348,10 @@ import SwiftSyntax
330348
}
331349
}
332350

333-
@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax, WithGenericParametersOrAssociatedTypesScopeSyntax {}
334-
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax, WithGenericParametersOrAssociatedTypesScopeSyntax {}
335-
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax, WithGenericParametersOrAssociatedTypesScopeSyntax {}
336-
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax, WithGenericParametersOrAssociatedTypesScopeSyntax {}
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 {
@@ -370,32 +388,61 @@ import SwiftSyntax
370388
}
371389
}
372390

373-
@_spi(Experimental) extension GenericParameterClauseSyntax: GenericParameterOrAssociatedTypeScopeSyntax {
374-
/// Generic parameter names introduced by this clause.
391+
@_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax {
392+
/// Protocol declarations don't introduce names by themselves.
375393
@_spi(Experimental) public var introducedNames: [LookupName] {
376-
parameters.children(viewMode: .sourceAccurate).flatMap { child in
377-
LookupName.getNames(from: child, accessibleAfter: child.endPosition)
378-
}
394+
[]
379395
}
380-
}
381396

382-
@_spi(Experimental) extension PrimaryAssociatedTypeClauseSyntax: GenericParameterOrAssociatedTypeScopeSyntax {
383-
/// Primary associated type names introduced by this clause.
384-
@_spi(Experimental) public var introducedNames: [LookupName] {
385-
primaryAssociatedTypes.children(viewMode: .sourceAccurate).flatMap { child in
386-
LookupName.getNames(from: child, accessibleAfter: child.endPosition)
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+
)
387430
}
431+
432+
return results + defaultLookupImplementation(identifier, at: lookUpPosition, with: config)
388433
}
389434
}
390435

391-
@_spi(Experimental) extension ProtocolDeclSyntax: WithGenericParametersOrAssociatedTypesScopeSyntax {
392-
/// Protocol declarations don't introduce names by themselves.
436+
@_spi(Experimental) extension GenericParameterClauseSyntax: GenericParameterScopeSyntax {
437+
/// Generic parameter names introduced by this clause.
393438
@_spi(Experimental) public var introducedNames: [LookupName] {
394-
[]
439+
parameters.children(viewMode: .sourceAccurate).flatMap { child in
440+
LookupName.getNames(from: child, accessibleAfter: child.endPosition)
441+
}
395442
}
396443
}
397444

398-
@_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersOrAssociatedTypesScopeSyntax {
445+
@_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersScopeSyntax {
399446
/// Function parameters introduced by this function's signature.
400447
@_spi(Experimental) public var introducedNames: [LookupName] {
401448
signature.parameterClause.parameters.flatMap { parameter in
+6-12
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,13 @@
1212

1313
import SwiftSyntax
1414

15-
@_spi(Experimental) public protocol WithGenericParametersOrAssociatedTypesScopeSyntax: ScopeSyntax {
15+
@_spi(Experimental) public protocol WithGenericParametersScopeSyntax: ScopeSyntax {
1616
var genericParameterClause: GenericParameterClauseSyntax? { get }
17-
var primaryAssociatedTypeClause: PrimaryAssociatedTypeClauseSyntax? { get }
1817
}
1918

20-
@_spi(Experimental) extension WithGenericParametersOrAssociatedTypesScopeSyntax {
21-
@_spi(Experimental) public var genericParameterClause: GenericParameterClauseSyntax? { nil }
22-
@_spi(Experimental) public var primaryAssociatedTypeClause: PrimaryAssociatedTypeClauseSyntax? { nil }
23-
19+
@_spi(Experimental) extension WithGenericParametersScopeSyntax {
2420
/// Returns names matching lookup and passes lookup to
25-
/// the generic parameter or primary associated type clause scopes.
21+
/// the generic parameter clause scopes.
2622
///
2723
/// example:
2824
/// ```swift
@@ -35,7 +31,7 @@ import SwiftSyntax
3531
/// lookup first visits the code block scope associated
3632
/// with the function's body. Then, it's forwarded to the
3733
/// function declaration scope and then to generic parameter
38-
/// scope (`WithGenericParametersOrAssociatedTypesScopeSyntax`)
34+
/// scope (`WithGenericParametersScopeSyntax`)
3935
/// instead of it's actual parent scope (in this case: file scope).
4036
@_spi(Experimental) public func lookup(
4137
_ identifier: Identifier?,
@@ -56,7 +52,7 @@ import SwiftSyntax
5652
}
5753

5854
/// Passes lookup to this scope's generic parameter or
59-
/// primary associated type clause scope (`WithGenericParametersOrAssociatedTypesScopeSyntax`).
55+
/// primary associated type clause scope (`WithGenericParametersScopeSyntax`).
6056
///
6157
/// example:
6258
/// ```swift
@@ -69,7 +65,7 @@ import SwiftSyntax
6965
/// lookup first visits the code block scope associated
7066
/// with the function's body. Then, it's forwarded to the
7167
/// function declaration scope and then to generic parameter
72-
/// scope (`WithGenericParametersOrAssociatedTypesScopeSyntax`)
68+
/// scope (`WithGenericParametersScopeSyntax`)
7369
/// with this method (instead of using standard `lookupInParent`).
7470
private func lookupThroughGenericParameterScope(
7571
_ identifier: Identifier?,
@@ -78,8 +74,6 @@ import SwiftSyntax
7874
) -> [LookupResult] {
7975
if let genericParameterClause {
8076
return genericParameterClause.lookup(identifier, at: lookUpPosition, with: config)
81-
} else if let primaryAssociatedTypeClause {
82-
return primaryAssociatedTypeClause.lookup(identifier, at: lookUpPosition, with: config)
8377
} else {
8478
return lookupInParent(identifier, at: lookUpPosition, with: config)
8579
}

Tests/SwiftLexicalLookupTest/NameLookupTests.swift

+14-11
Original file line numberDiff line numberDiff line change
@@ -906,26 +906,29 @@ final class testNameLookup: XCTestCase {
906906
func testPrimaryAssociatedTypes() {
907907
assertLexicalNameLookup(
908908
source: """
909+
0️⃣class A {}
910+
909911
protocol Foo<1️⃣A, 2️⃣B> {
910-
5️⃣associatedtype 3️⃣A
911-
6️⃣associatedtype 4️⃣B
912+
3️⃣associatedtype A
913+
4️⃣associatedtype B
914+
915+
class A {}
916+
class B {}
912917
}
913918
""",
914919
references: [
915-
"3️⃣": [
916-
.fromScope(MemberBlockSyntax.self, expectedNames: ["5️⃣"]), // Conceptually, should associated type be visible at it's declaration? It's a reference and declaration at the same time and all members' names are available inside their bodies, but at the same time it doesn't seem quite right...
917-
.fromScope(PrimaryAssociatedTypeClauseSyntax.self, expectedNames: ["1️⃣"]),
920+
"1️⃣": [
921+
.fromScope(MemberBlockSyntax.self, expectedNames: ["3️⃣"]),
922+
.fromFileScope(expectedNames: ["0️⃣"]),
918923
],
919-
"4️⃣": [
920-
.fromScope(MemberBlockSyntax.self, expectedNames: ["6️⃣"]),
921-
.fromScope(PrimaryAssociatedTypeClauseSyntax.self, expectedNames: ["2️⃣"]),
924+
"2️⃣": [
925+
.fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"])
922926
],
923927
],
924928
expectedResultTypes: .all(
925-
PrimaryAssociatedTypeSyntax.self,
929+
AssociatedTypeDeclSyntax.self,
926930
except: [
927-
"5️⃣": AssociatedTypeDeclSyntax.self,
928-
"6️⃣": AssociatedTypeDeclSyntax.self,
931+
"0️⃣": ClassDeclSyntax.self
929932
]
930933
)
931934
)

0 commit comments

Comments
 (0)