Skip to content

Commit 9a07bd9

Browse files
authored
Fix Swift 6.3+ compatibility for generic protocol extensions (#1014)
Swift 6.3+ changed the indexstore location for protocol extensions with primary associated type constraints (e.g., 'extension Protocol<Type>'). Previously, the location was after the closing angle bracket, but now it's at the base identifier.
1 parent 52d27b0 commit 9a07bd9

File tree

10 files changed

+61
-22
lines changed

10 files changed

+61
-22
lines changed

Sources/Frontend/Scan.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ final class Scan {
7070
let indexLogger = logger.contextualized(with: "index")
7171
let plan = try driver.plan(logger: indexLogger)
7272
let syncSourceGraph = SynchronizedSourceGraph(graph: graph)
73-
let pipeline = IndexPipeline(plan: plan, graph: syncSourceGraph, logger: indexLogger, configuration: configuration)
73+
let pipeline = IndexPipeline(plan: plan, graph: syncSourceGraph, logger: indexLogger, configuration: configuration, swiftVersion: swiftVersion)
7474
try pipeline.perform()
7575
logger.endInterval(indexInterval)
7676
}

Sources/Indexer/IndexPipeline.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
import Configuration
22
import Foundation
33
import Logger
4+
import Shared
45
import SourceGraph
56

67
public struct IndexPipeline {
78
private let plan: IndexPlan
89
private let graph: SynchronizedSourceGraph
910
private let logger: ContextualLogger
1011
private let configuration: Configuration
12+
private let swiftVersion: SwiftVersion
1113

12-
public init(plan: IndexPlan, graph: SynchronizedSourceGraph, logger: ContextualLogger, configuration: Configuration) {
14+
public init(plan: IndexPlan, graph: SynchronizedSourceGraph, logger: ContextualLogger, configuration: Configuration, swiftVersion: SwiftVersion) {
1315
self.plan = plan
1416
self.graph = graph
1517
self.logger = logger
1618
self.configuration = configuration
19+
self.swiftVersion = swiftVersion
1720
}
1821

1922
public func perform() throws {
2023
try SwiftIndexer(
2124
sourceFiles: plan.sourceFiles,
2225
graph: graph,
2326
logger: logger,
24-
configuration: configuration
27+
configuration: configuration,
28+
swiftVersion: swiftVersion
2529
).perform()
2630

2731
if !plan.plistPaths.isEmpty {

Sources/Indexer/SwiftIndexer.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ final class SwiftIndexer: Indexer {
1717
private let graph: SynchronizedSourceGraph
1818
private let logger: ContextualLogger
1919
private let configuration: Configuration
20+
private let swiftVersion: SwiftVersion
2021

2122
required init(
2223
sourceFiles: [SourceFile: [IndexUnit]],
2324
graph: SynchronizedSourceGraph,
2425
logger: ContextualLogger,
25-
configuration: Configuration
26+
configuration: Configuration,
27+
swiftVersion: SwiftVersion
2628
) {
2729
self.sourceFiles = sourceFiles
2830
self.graph = graph
2931
self.logger = logger.contextualized(with: "swift")
3032
self.configuration = configuration
33+
self.swiftVersion = swiftVersion
3134
super.init(configuration: configuration)
3235
}
3336

@@ -39,7 +42,8 @@ final class SwiftIndexer: Indexer {
3942
retainAllDeclarations: isRetained(file),
4043
graph: graph,
4144
logger: logger,
42-
configuration: configuration
45+
configuration: configuration,
46+
swiftVersion: swiftVersion
4347
)
4448
}
4549

@@ -88,21 +92,24 @@ final class SwiftIndexer: Indexer {
8892
private let logger: ContextualLogger
8993
private let configuration: Configuration
9094
private var retainAllDeclarations: Bool
95+
private let swiftVersion: SwiftVersion
9196

9297
required init(
9398
sourceFile: SourceFile,
9499
units: [IndexUnit],
95100
retainAllDeclarations: Bool,
96101
graph: SynchronizedSourceGraph,
97102
logger: ContextualLogger,
98-
configuration: Configuration
103+
configuration: Configuration,
104+
swiftVersion: SwiftVersion
99105
) {
100106
self.sourceFile = sourceFile
101107
self.units = units
102108
self.retainAllDeclarations = retainAllDeclarations
103109
self.graph = graph
104110
self.logger = logger
105111
self.configuration = configuration
112+
self.swiftVersion = swiftVersion
106113
}
107114

108115
// swiftlint:disable nesting
@@ -242,7 +249,7 @@ final class SwiftIndexer: Indexer {
242249
graph.addIndexedModules(sourceFile.modules)
243250
}
244251

245-
let multiplexingSyntaxVisitor = try MultiplexingSyntaxVisitor(file: sourceFile)
252+
let multiplexingSyntaxVisitor = try MultiplexingSyntaxVisitor(file: sourceFile, swiftVersion: swiftVersion)
246253
let declarationSyntaxVisitor = multiplexingSyntaxVisitor.add(DeclarationSyntaxVisitor.self)
247254
let importSyntaxVisitor = multiplexingSyntaxVisitor.add(ImportSyntaxVisitor.self)
248255

Sources/SyntaxAnalysis/DeclarationSyntaxVisitor.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Shared
23
import SourceGraph
34
import SwiftSyntax
45

@@ -24,6 +25,7 @@ public final class DeclarationSyntaxVisitor: PeripherySyntaxVisitor {
2425

2526
private let sourceLocationBuilder: SourceLocationBuilder
2627
private let typeSyntaxInspector: TypeSyntaxInspector
28+
private let swiftVersion: SwiftVersion
2729
private(set) var results: [Result] = []
2830

2931
public var resultsByLocation: [Location: Result] {
@@ -32,8 +34,9 @@ public final class DeclarationSyntaxVisitor: PeripherySyntaxVisitor {
3234
}
3335
}
3436

35-
public init(sourceLocationBuilder: SourceLocationBuilder) {
37+
public init(sourceLocationBuilder: SourceLocationBuilder, swiftVersion: SwiftVersion) {
3638
self.sourceLocationBuilder = sourceLocationBuilder
39+
self.swiftVersion = swiftVersion
3740
typeSyntaxInspector = .init(sourceLocationBuilder: sourceLocationBuilder)
3841
}
3942

@@ -113,11 +116,17 @@ public final class DeclarationSyntaxVisitor: PeripherySyntaxVisitor {
113116

114117
if let memberType = node.extendedType.as(MemberTypeSyntax.self) {
115118
position = memberType.name.positionAfterSkippingLeadingTrivia
116-
} else if let genericArgumentClause = node.extendedType.as(IdentifierTypeSyntax.self)?.genericArgumentClause {
117-
// Generic protocol extensions in the form `extension Foo<Type>` have incorrect locations in the index store.
118-
// This results in syntax metadata not being applied to the declaration due to the location mismatch. To
119-
// workaround this, parse this node with the incorrect location.
120-
position = genericArgumentClause.rightAngle.positionAfterSkippingLeadingTrivia
119+
} else if let identifierType = node.extendedType.as(IdentifierTypeSyntax.self),
120+
let genericArgumentClause = identifierType.genericArgumentClause
121+
{
122+
if swiftVersion.version.isVersion(lessThanOrEqualTo: "6.2.3") {
123+
// Swift <= 6.2.3: Generic protocol extensions in the form `extension Foo<Type>` have incorrect locations
124+
// in the index store. This results in syntax metadata not being applied to the declaration due to the
125+
// location mismatch. To workaround this, parse this node with the incorrect location.
126+
position = genericArgumentClause.rightAngle.positionAfterSkippingLeadingTrivia
127+
} else {
128+
position = identifierType.name.positionAfterSkippingLeadingTrivia
129+
}
121130
}
122131

123132
parse(

Sources/SyntaxAnalysis/ImportSyntaxVisitor.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Shared
23
import SourceGraph
34
import SwiftSyntax
45

@@ -7,8 +8,9 @@ public final class ImportSyntaxVisitor: PeripherySyntaxVisitor {
78

89
private let sourceLocationBuilder: SourceLocationBuilder
910

10-
public init(sourceLocationBuilder: SourceLocationBuilder) {
11+
public init(sourceLocationBuilder: SourceLocationBuilder, swiftVersion _: SwiftVersion) {
1112
self.sourceLocationBuilder = sourceLocationBuilder
13+
// swiftVersion is not used in this visitor but is required by the protocol
1214
}
1315

1416
public func visit(_ node: ImportDeclSyntax) {

Sources/SyntaxAnalysis/MultiplexingSyntaxVisitor.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import Foundation
2+
import Shared
23
import SourceGraph
34
import SwiftParser
45
import SwiftSyntax
56
import SystemPackage
67

78
public protocol PeripherySyntaxVisitor {
8-
init(sourceLocationBuilder: SourceLocationBuilder)
9+
init(sourceLocationBuilder: SourceLocationBuilder, swiftVersion: SwiftVersion)
910

1011
func visit(_ node: ActorDeclSyntax)
1112
func visit(_ node: ClassDeclSyntax)
@@ -95,20 +96,22 @@ public final class MultiplexingSyntaxVisitor: SyntaxVisitor {
9596
public let syntax: SourceFileSyntax
9697
public let locationConverter: SourceLocationConverter
9798
let sourceLocationBuilder: SourceLocationBuilder
99+
let swiftVersion: SwiftVersion
98100

99101
private var visitors: [PeripherySyntaxVisitor] = []
100102

101-
public required init(file: SourceFile) throws {
103+
public required init(file: SourceFile, swiftVersion: SwiftVersion) throws {
102104
sourceFile = file
103105
let source = try String(contentsOf: file.path.url)
104106
syntax = Parser.parse(source: source)
105107
locationConverter = SourceLocationConverter(fileName: file.path.string, tree: syntax)
106108
sourceLocationBuilder = SourceLocationBuilder(file: file, locationConverter: locationConverter)
109+
self.swiftVersion = swiftVersion
107110
super.init(viewMode: .sourceAccurate)
108111
}
109112

110113
public func add<T: PeripherySyntaxVisitor>(_ visitorType: T.Type) -> T {
111-
let visitor = visitorType.init(sourceLocationBuilder: sourceLocationBuilder)
114+
let visitor = visitorType.init(sourceLocationBuilder: sourceLocationBuilder, swiftVersion: swiftVersion)
112115
visitors.append(visitor)
113116
return visitor
114117
}

Tests/PeripheryTests/Syntax/FunctionVisitTest.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import Foundation
2+
import Logger
3+
import Shared
24
@testable import SourceGraph
35
@testable import SyntaxAnalysis
46
@testable import TestShared
@@ -9,7 +11,9 @@ final class FunctionVisitTest: XCTestCase {
911

1012
override func setUpWithError() throws {
1113
try super.setUpWithError()
12-
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath)
14+
let shell = Shell(logger: Logger(quiet: true))
15+
let swiftVersion = SwiftVersion(shell: shell)
16+
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath, swiftVersion: swiftVersion)
1317
let visitor = multiplexingVisitor.add(DeclarationSyntaxVisitor.self)
1418
multiplexingVisitor.visit()
1519
results = visitor.resultsByLocation

Tests/PeripheryTests/Syntax/ImportVisitTest.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import Foundation
2+
import Logger
3+
import Shared
24
@testable import SourceGraph
35
@testable import SyntaxAnalysis
46
@testable import TestShared
@@ -9,7 +11,9 @@ final class ImportVisitTest: XCTestCase {
911

1012
override func setUpWithError() throws {
1113
try super.setUpWithError()
12-
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath)
14+
let shell = Shell(logger: Logger(quiet: true))
15+
let swiftVersion = SwiftVersion(shell: shell)
16+
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath, swiftVersion: swiftVersion)
1317
let visitor = multiplexingVisitor.add(ImportSyntaxVisitor.self)
1418
multiplexingVisitor.visit()
1519
results = visitor.importStatements

Tests/PeripheryTests/Syntax/PropertyVisitTest.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import Foundation
2+
import Logger
3+
import Shared
24
@testable import SourceGraph
35
@testable import SyntaxAnalysis
46
@testable import TestShared
@@ -9,7 +11,9 @@ final class PropertyVisitTest: XCTestCase {
911

1012
override func setUpWithError() throws {
1113
try super.setUpWithError()
12-
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath)
14+
let shell = Shell(logger: Logger(quiet: true))
15+
let swiftVersion = SwiftVersion(shell: shell)
16+
let multiplexingVisitor = try MultiplexingSyntaxVisitor(file: fixturePath, swiftVersion: swiftVersion)
1317
let visitor = multiplexingVisitor.add(DeclarationSyntaxVisitor.self)
1418
multiplexingVisitor.visit()
1519
results = visitor.resultsByLocation

Tests/Shared/SourceGraphTestCase.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ open class SourceGraphTestCase: XCTestCase {
5555
}
5656

5757
graph = SourceGraph(configuration: configuration, logger: logger)
58+
let swiftVersion = SwiftVersion(shell: shell)
5859
let pipeline = IndexPipeline(
5960
plan: newPlan,
6061
graph: SynchronizedSourceGraph(graph: graph),
6162
logger: logger.contextualized(with: "index"),
62-
configuration: configuration
63+
configuration: configuration,
64+
swiftVersion: swiftVersion
6365
)
6466
try! pipeline.perform()
6567

@@ -68,7 +70,7 @@ open class SourceGraphTestCase: XCTestCase {
6870
graph: graph,
6971
logger: logger,
7072
configuration: configuration,
71-
swiftVersion: SwiftVersion(shell: shell)
73+
swiftVersion: swiftVersion
7274
).perform()
7375
results = ScanResultBuilder.build(for: graph)
7476
}

0 commit comments

Comments
 (0)