Skip to content

Commit fbd615b

Browse files
authored
Merge pull request #1403 from rintaro/plugin-abstract
[CompilerPlugin] Factor out message handling logic to a module
2 parents 8212815 + db609d6 commit fbd615b

File tree

7 files changed

+142
-67
lines changed

7 files changed

+142
-67
lines changed

Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ let package = Package(
4444
.library(name: "SwiftSyntaxBuilder", type: .static, targets: ["SwiftSyntaxBuilder"]),
4545
.library(name: "SwiftSyntaxMacros", type: .static, targets: ["SwiftSyntaxMacros"]),
4646
.library(name: "SwiftCompilerPlugin", type: .static, targets: ["SwiftCompilerPlugin"]),
47+
.library(name: "SwiftCompilerPluginMessageHandling", type: .static, targets: ["SwiftCompilerPluginMessageHandling"]),
4748
.library(name: "SwiftRefactor", type: .static, targets: ["SwiftRefactor"]),
4849
],
4950
targets: [
@@ -124,6 +125,12 @@ let package = Package(
124125
),
125126
.target(
126127
name: "SwiftCompilerPlugin",
128+
dependencies: [
129+
"SwiftCompilerPluginMessageHandling", "SwiftSyntaxMacros",
130+
]
131+
),
132+
.target(
133+
name: "SwiftCompilerPluginMessageHandling",
127134
dependencies: [
128135
"SwiftSyntax", "SwiftParser", "SwiftDiagnostics", "SwiftSyntaxMacros", "SwiftOperators",
129136
]

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 SwiftCompilerPluginMessageHandling
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 = CompilerPluginMessageHandler(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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
13+
import SwiftSyntaxMacros
14+
15+
/// A type that provides the actual plugin functions.
16+
public protocol PluginProvider {
17+
func resolveMacro(moduleName: String, typeName: String) -> Macro.Type?
18+
}
19+
20+
/// Low level message connection to the plugin host.
21+
/// This encapsulates the connection and the message serialization.
22+
public protocol MessageConnection {
23+
/// Send a message to the peer.
24+
func sendMessage<TX: Encodable>(_ message: TX) throws
25+
/// Wait until receiving a message from the peer, and return it.
26+
func waitForNextMessage<RX: Decodable>(_ type: RX.Type) throws -> RX?
27+
}
28+
29+
/// 'CompilerPluginMessageHandler' is a type that listens to the message
30+
/// connection and dispatches them to the actual plugin provider, then send back
31+
/// the response.
32+
///
33+
/// The low level connection and the provider is injected by the client.
34+
public class CompilerPluginMessageHandler<Connection: MessageConnection, Provider: PluginProvider> {
35+
/// Message channel for bidirectional communication with the plugin host.
36+
let connection: Connection
37+
38+
/// Object to provide actual plugin functions.
39+
let provider: Provider
40+
41+
public init(connection: Connection, provider: Provider) {
42+
self.connection = connection
43+
self.provider = provider
44+
}
45+
}
46+
47+
extension CompilerPluginMessageHandler {
48+
func sendMessage(_ message: PluginToHostMessage) throws {
49+
try connection.sendMessage(message)
50+
}
51+
52+
func waitForNextMessage() throws -> HostToPluginMessage? {
53+
try connection.waitForNextMessage(HostToPluginMessage.self)
54+
}
55+
56+
/// Run the main message listener loop.
57+
/// Returns when the message connection was closed.
58+
/// Throws an error when it failed to send/receive the message, or failed
59+
/// to serialize/deserialize the message.
60+
public func main() throws {
61+
while let message = try self.waitForNextMessage() {
62+
try handleMessage(message)
63+
}
64+
}
65+
66+
/// Handles a single message received from the plugin host.
67+
fileprivate func handleMessage(_ message: HostToPluginMessage) throws {
68+
switch message {
69+
case .getCapability:
70+
try self.sendMessage(
71+
.getCapabilityResult(capability: PluginMessage.capability)
72+
)
73+
74+
case .expandFreestandingMacro(let macro, let discriminator, let expandingSyntax):
75+
try expandFreestandingMacro(
76+
macro: macro,
77+
discriminator: discriminator,
78+
expandingSyntax: expandingSyntax
79+
)
80+
81+
case .expandAttachedMacro(let macro, let macroRole, let discriminator, let attributeSyntax, let declSyntax, let parentDeclSyntax):
82+
try expandAttachedMacro(
83+
macro: macro,
84+
macroRole: macroRole,
85+
discriminator: discriminator,
86+
attributeSyntax: attributeSyntax,
87+
declSyntax: declSyntax,
88+
parentDeclSyntax: parentDeclSyntax
89+
)
90+
}
91+
}
92+
}

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

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,10 @@ 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-
}
32-
17+
extension CompilerPluginMessageHandler {
3318
/// Get concrete macro type from a pair of module name and type name.
3419
private func resolveMacro(_ ref: PluginMessage.MacroReference) -> Macro.Type? {
35-
resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
20+
provider.resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
3621
}
3722

3823
/// Expand `@freestainding(XXX)` macros.
@@ -90,7 +75,7 @@ extension CompilerPlugin {
9075
let diagnostics = context.diagnostics.map {
9176
PluginMessage.Diagnostic(from: $0, in: sourceManager)
9277
}
93-
try pluginHostConnection.sendMessage(
78+
try self.sendMessage(
9479
.expandFreestandingMacroResult(expandedSource: expandedSource, diagnostics: diagnostics)
9580
)
9681
}
@@ -249,19 +234,12 @@ extension CompilerPlugin {
249234
let diagnostics = context.diagnostics.map {
250235
PluginMessage.Diagnostic(from: $0, in: sourceManager)
251236
}
252-
try pluginHostConnection.sendMessage(
237+
try self.sendMessage(
253238
.expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
254239
)
255240
}
256241
}
257242

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

0 commit comments

Comments
 (0)