Skip to content

Commit 479f43d

Browse files
committed
[CompilerPlugin] Factor out message handling logic to a module
Create a module '_SwiftCompilerPluginImpl' that abstracts the low level message connection/serilaization. 'CompilerPlugin' is now a implementation of the message connection, and adapts the concrete type to the message handler. This make it possible to make a plugin with a different messaging connection and serialization.
1 parent 17d5043 commit 479f43d

File tree

7 files changed

+147
-67
lines changed

7 files changed

+147
-67
lines changed

Package.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ let package = Package(
4343
.library(name: "SwiftSyntaxParser", type: .static, targets: ["SwiftSyntaxParser"]),
4444
.library(name: "SwiftSyntaxBuilder", type: .static, targets: ["SwiftSyntaxBuilder"]),
4545
.library(name: "SwiftSyntaxMacros", type: .static, targets: ["SwiftSyntaxMacros"]),
46+
.library(name: "_SwiftCompilerPluginBase", type: .static, targets: ["_SwiftCompilerPluginImpl"]),
4647
.library(name: "SwiftCompilerPlugin", type: .static, targets: ["SwiftCompilerPlugin"]),
4748
.library(name: "SwiftRefactor", type: .static, targets: ["SwiftRefactor"]),
4849
],
@@ -123,11 +124,17 @@ let package = Package(
123124
]
124125
),
125126
.target(
126-
name: "SwiftCompilerPlugin",
127+
name: "_SwiftCompilerPluginImpl",
127128
dependencies: [
128129
"SwiftSyntax", "SwiftParser", "SwiftDiagnostics", "SwiftSyntaxMacros", "SwiftOperators",
129130
]
130131
),
132+
.target(
133+
name: "SwiftCompilerPlugin",
134+
dependencies: [
135+
"_SwiftCompilerPluginImpl", "SwiftSyntaxMacros",
136+
]
137+
),
131138
.target(
132139
name: "SwiftRefactor",
133140
dependencies: [

Sources/SwiftCompilerPlugin/CompilerPlugin.swift

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift
1414

1515
import SwiftSyntaxMacros
16+
@_implementationOnly import _SwiftCompilerPluginImpl
1617

1718
@_implementationOnly import Foundation
1819
#if os(Windows)
@@ -61,6 +62,37 @@ public protocol CompilerPlugin {
6162
var providingMacros: [Macro.Type] { get }
6263
}
6364

65+
extension CompilerPlugin {
66+
func resolveMacro(moduleName: String, typeName: String) -> Macro.Type? {
67+
let qualifedName = "\(moduleName).\(typeName)"
68+
69+
for type in providingMacros {
70+
// FIXME: Is `String(reflecting:)` stable?
71+
// Getting the module name and type name should be more robust.
72+
let name = String(reflecting: type)
73+
if name == qualifedName {
74+
return type
75+
}
76+
}
77+
return nil
78+
}
79+
80+
// @testable
81+
public func _resolveMacro(moduleName: String, typeName: String) -> Macro.Type? {
82+
resolveMacro(moduleName: moduleName, typeName: typeName)
83+
}
84+
}
85+
86+
struct MacroProviderAdapter<Plugin: CompilerPlugin>: PluginProvider {
87+
let plugin: Plugin
88+
init(plugin: Plugin) {
89+
self.plugin = plugin
90+
}
91+
func resolveMacro(moduleName: String, typeName: String) -> Macro.Type? {
92+
plugin.resolveMacro(moduleName: moduleName, typeName: typeName)
93+
}
94+
}
95+
6496
extension CompilerPlugin {
6597

6698
/// Main entry point of the plugin — sets up a communication channel with
@@ -105,18 +137,17 @@ extension CompilerPlugin {
105137
#endif
106138

107139
// Open a message channel for communicating with the plugin host.
108-
pluginHostConnection = PluginHostConnection(
140+
let connection = PluginHostConnection(
109141
inputStream: FileHandle(fileDescriptor: inputFD),
110142
outputStream: FileHandle(fileDescriptor: outputFD)
111143
)
112144

113145
// Handle messages from the host until the input stream is closed,
114146
// indicating that we're done.
115-
let instance = Self()
147+
let provider = MacroProviderAdapter(plugin: Self())
148+
let impl = CompilerPluginImpl(connection: connection, provider: provider)
116149
do {
117-
while let message = try pluginHostConnection.waitForNextMessage() {
118-
try instance.handleMessage(message)
119-
}
150+
try impl.main()
120151
} catch {
121152
// Emit a diagnostic and indicate failure to the plugin host,
122153
// and exit with an error code.
@@ -135,46 +166,13 @@ extension CompilerPlugin {
135166
if let cStr = strerror(errno) { return String(cString: cStr) }
136167
return String(describing: errno)
137168
}
138-
139-
/// Handles a single message received from the plugin host.
140-
fileprivate func handleMessage(_ message: HostToPluginMessage) throws {
141-
switch message {
142-
case .getCapability:
143-
try pluginHostConnection.sendMessage(
144-
.getCapabilityResult(capability: PluginMessage.capability)
145-
)
146-
break
147-
148-
case .expandFreestandingMacro(let macro, let discriminator, let expandingSyntax):
149-
try expandFreestandingMacro(
150-
macro: macro,
151-
discriminator: discriminator,
152-
expandingSyntax: expandingSyntax
153-
)
154-
155-
case .expandAttachedMacro(let macro, let macroRole, let discriminator, let attributeSyntax, let declSyntax, let parentDeclSyntax):
156-
try expandAttachedMacro(
157-
macro: macro,
158-
macroRole: macroRole,
159-
discriminator: discriminator,
160-
attributeSyntax: attributeSyntax,
161-
declSyntax: declSyntax,
162-
parentDeclSyntax: parentDeclSyntax
163-
)
164-
}
165-
}
166169
}
167170

168-
/// Message channel for bidirectional communication with the plugin host.
169-
internal fileprivate(set) var pluginHostConnection: PluginHostConnection!
170-
171-
typealias PluginHostConnection = MessageConnection<PluginToHostMessage, HostToPluginMessage>
172-
173-
internal struct MessageConnection<TX, RX> where TX: Encodable, RX: Decodable {
171+
internal struct PluginHostConnection: MessageConnection {
174172
let inputStream: FileHandle
175173
let outputStream: FileHandle
176174

177-
func sendMessage(_ message: TX) throws {
175+
func sendMessage<TX: Encodable>(_ message: TX) throws {
178176
// Encode the message as JSON.
179177
let payload = try JSONEncoder().encode(message)
180178

@@ -188,7 +186,7 @@ internal struct MessageConnection<TX, RX> where TX: Encodable, RX: Decodable {
188186
try outputStream._write(contentsOf: payload)
189187
}
190188

191-
func waitForNextMessage() throws -> RX? {
189+
func waitForNextMessage<RX: Decodable>(_ ty: RX.Type) throws -> RX? {
192190
// Read the header (a 64-bit length field in little endian byte order).
193191
guard
194192
let header = try inputStream._read(upToCount: 8),
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
// NOTE: This basic plugin mechanism is mostly copied from
13+
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackagePlugin/Plugin.swift
14+
15+
import SwiftSyntaxMacros
16+
17+
/// A type that provides the actual plugin functions.
18+
public protocol PluginProvider {
19+
func resolveMacro(moduleName: String, typeName: String) -> Macro.Type?
20+
}
21+
22+
/// Low level message connection to the plugin host.
23+
/// This encapsulates the connection and the message serialization.
24+
public protocol MessageConnection {
25+
/// Send a message to the peer.
26+
func sendMessage<TX: Encodable>(_ message: TX) throws
27+
/// Wait until receiving a message from the peer, and return it.
28+
func waitForNextMessage<RX: Decodable>(_ type: RX.Type) throws -> RX?
29+
}
30+
31+
/// 'CompilerPluginImpl' is a type that listens to the message connection and
32+
/// dispatches them to the actual plugin provider, then send back the response.
33+
///
34+
/// The low level connection and the provider is injected by the client.
35+
public class CompilerPluginImpl<Connection: MessageConnection, Provider: PluginProvider> {
36+
/// Message channel for bidirectional communication with the plugin host.
37+
let connection: Connection
38+
39+
/// Object to provide actual plugin functions.
40+
let provider: Provider
41+
42+
public init(connection: Connection, provider: Provider) {
43+
self.connection = connection
44+
self.provider = provider
45+
}
46+
}
47+
48+
extension CompilerPluginImpl {
49+
func sendMessage(_ message: PluginToHostMessage) throws {
50+
try connection.sendMessage(message)
51+
}
52+
53+
func waitForNextMessage() throws -> HostToPluginMessage? {
54+
try connection.waitForNextMessage(HostToPluginMessage.self)
55+
}
56+
57+
public func main() throws {
58+
/// Run the main message listener loop.
59+
/// Returns when the message connection was closed.
60+
/// Throws an error when it failed to send/receive the message, or failed
61+
/// to serialize/deserialize the message.
62+
while let message = try self.waitForNextMessage() {
63+
try handleMessage(message)
64+
}
65+
}
66+
67+
/// Handles a single message received from the plugin host.
68+
fileprivate func handleMessage(_ message: HostToPluginMessage) throws {
69+
switch message {
70+
case .getCapability:
71+
try self.sendMessage(
72+
.getCapabilityResult(capability: PluginMessage.capability)
73+
)
74+
break
75+
76+
case .expandFreestandingMacro(let macro, let discriminator, let expandingSyntax):
77+
try expandFreestandingMacro(
78+
macro: macro,
79+
discriminator: discriminator,
80+
expandingSyntax: expandingSyntax
81+
)
82+
83+
case .expandAttachedMacro(let macro, let macroRole, let discriminator, let attributeSyntax, let declSyntax, let parentDeclSyntax):
84+
try expandAttachedMacro(
85+
macro: macro,
86+
macroRole: macroRole,
87+
discriminator: discriminator,
88+
attributeSyntax: attributeSyntax,
89+
declSyntax: declSyntax,
90+
parentDeclSyntax: parentDeclSyntax
91+
)
92+
}
93+
}
94+
}
95+

Sources/SwiftCompilerPlugin/Macros.swift renamed to Sources/_SwiftCompilerPluginImpl/Macros.swift

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,12 @@ import SwiftDiagnostics
1414
import SwiftSyntax
1515
import SwiftSyntaxMacros
1616

17-
/// Implementation for `CompilerPlugin` macro related request processing.
18-
extension CompilerPlugin {
19-
private func resolveMacro(moduleName: String, typeName: String) -> Macro.Type? {
20-
let qualifedName = "\(moduleName).\(typeName)"
21-
22-
for type in self.providingMacros {
23-
// FIXME: Is `String(reflecting:)` stable?
24-
// Getting the module name and type name should be more robust.
25-
let name = String(reflecting: type)
26-
if name == qualifedName {
27-
return type
28-
}
29-
}
30-
return nil
31-
}
3217

18+
19+
extension CompilerPluginImpl {
3320
/// Get concrete macro type from a pair of module name and type name.
3421
private func resolveMacro(_ ref: PluginMessage.MacroReference) -> Macro.Type? {
35-
resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
22+
provider.resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
3623
}
3724

3825
/// Expand `@freestainding(XXX)` macros.
@@ -90,7 +77,7 @@ extension CompilerPlugin {
9077
let diagnostics = context.diagnostics.map {
9178
PluginMessage.Diagnostic(from: $0, in: sourceManager)
9279
}
93-
try pluginHostConnection.sendMessage(
80+
try self.sendMessage(
9481
.expandFreestandingMacroResult(expandedSource: expandedSource, diagnostics: diagnostics)
9582
)
9683
}
@@ -249,19 +236,12 @@ extension CompilerPlugin {
249236
let diagnostics = context.diagnostics.map {
250237
PluginMessage.Diagnostic(from: $0, in: sourceManager)
251238
}
252-
try pluginHostConnection.sendMessage(
239+
try self.sendMessage(
253240
.expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
254241
)
255242
}
256243
}
257244

258-
extension CompilerPlugin {
259-
// @testable
260-
public func _resolveMacro(moduleName: String, typeName: String) -> Macro.Type? {
261-
resolveMacro(moduleName: moduleName, typeName: typeName)
262-
}
263-
}
264-
265245
/// Diagnostic message used for thrown errors.
266246
fileprivate struct ThrownErrorDiagnostic: DiagnosticMessage {
267247
let message: String

0 commit comments

Comments
 (0)