diff --git a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift index 0d680ff69ad..6c78e03e845 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift @@ -74,15 +74,21 @@ struct HostCapability { /// /// The low level connection and the provider is injected by the client. @_spi(PluginMessage) -public class CompilerPluginMessageListener { +public class CompilerPluginMessageListener { /// Message channel for bidirectional communication with the plugin host. let connection: Connection - let handler: CompilerPluginMessageHandler + let handler: Handler - public init(connection: Connection, provider: Provider) { + public init(connection: Connection, messageHandler: Handler) { self.connection = connection - self.handler = CompilerPluginMessageHandler(provider: provider) + self.handler = messageHandler + } + + public init(connection: Connection, provider: Provider) + where Handler == PluginProviderMessageHandler { + self.connection = connection + self.handler = PluginProviderMessageHandler(provider: provider) } /// Run the main message listener loop. @@ -91,11 +97,26 @@ public class CompilerPluginMessageListener Bool { do { - while let message = try connection.waitForNextMessage(HostToPluginMessage.self) { - let result = handler.handleMessage(message) - try connection.sendMessage(result) + guard let message = try connection.waitForNextMessage(HostToPluginMessage.self) else { + return false } + let result = handler.handleMessage(message) + try connection.sendMessage(result) + return true } catch { // Emit a diagnostic and indicate failure to the plugin host, // and exit with an error code. @@ -105,10 +126,18 @@ public class CompilerPluginMessageListener PluginToHostMessage +} + +/// A `PluginMessageHandler` that uses a `PluginProvider`. @_spi(PluginMessage) -public class CompilerPluginMessageHandler { +public class PluginProviderMessageHandler: PluginMessageHandler { /// Object to provide actual plugin functions. let provider: Provider @@ -199,6 +228,10 @@ public class CompilerPluginMessageHandler { } } +@_spi(PluginMessage) +@available(*, deprecated, renamed: "PluginProviderMessageHandler") +public typealias CompilerPluginMessageHandler = PluginProviderMessageHandler + struct UnimplementedError: Error, CustomStringConvertible { var description: String { "unimplemented" } } @@ -216,3 +249,31 @@ extension PluginProvider { throw UnimplementedError() } } + +#if compiler(>=6) && os(WASI) + +/// A callback invoked by the Wasm Host when new data is available on `stdin`. +/// +/// This is safe to access without serialization as Wasm plugins are single-threaded. +nonisolated(unsafe) private var readabilityHandler: () -> Void = { + fatalError( + """ + CompilerPlugin.main wasn't called. Did you annotate your plugin with '@main'? + """ + ) +} + +@_expose(wasm, "swift_wasm_macro_v1_pump") +@_cdecl("swift_wasm_macro_v1_pump") +func wasmPump() { + readabilityHandler() +} + +// we can't nest the whole #if-#else in '#if os(WASI)' due to a bug where +// '#if compiler' directives have to be the top-level #if, otherwise +// the compiler doesn't skip unknown syntax. +#elseif os(WASI) + +#error("Building swift-syntax for WebAssembly requires compiler version 6.0 or higher.") + +#endif diff --git a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift index c92a36fde5f..1a558deba0d 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift @@ -26,7 +26,7 @@ import SwiftSyntax @_spi(ExperimentalLanguageFeature) import SwiftSyntaxMacros #endif -extension CompilerPluginMessageHandler { +extension PluginProviderMessageHandler { /// Get concrete macro type from a pair of module name and type name. private func resolveMacro(_ ref: PluginMessage.MacroReference) throws -> Macro.Type { try provider.resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName) diff --git a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift index aeb8e9bd9ae..37029ff6bd5 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift @@ -229,7 +229,7 @@ public enum PluginMessage { public var notes: [Note] public var fixIts: [FixIt] - internal init( + public init( message: String, severity: Severity, position: Position, diff --git a/Sources/SwiftCompilerPluginMessageHandling/StandardIOMessageConnection.swift b/Sources/SwiftCompilerPluginMessageHandling/StandardIOMessageConnection.swift index df5a10ecdd7..a4c45112c36 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/StandardIOMessageConnection.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/StandardIOMessageConnection.swift @@ -40,6 +40,16 @@ public struct StandardIOMessageConnection: MessageConnection { self.outputFileDescriptor = outputFileDescriptor } + #if os(WASI) + /// Convenience initializer for Wasm executable plugins. Connects + /// directly to `stdin` and `stdout` as WASI doesn't support + /// `dup{,2}`. + public init() throws { + let inputFD = fileno(_stdin) + let outputFD = fileno(_stdout) + self.init(inputFileDescriptor: inputFD, outputFileDescriptor: outputFD) + } + #else /// Convenience initializer for normal executable plugins. Upon creation: /// - Redirect `stdout` to `stderr` so that print statements from the plugin /// are treated as plain-text output @@ -83,6 +93,7 @@ public struct StandardIOMessageConnection: MessageConnection { self.init(inputFileDescriptor: inputFD, outputFileDescriptor: outputFD) } + #endif /// Write the buffer to the file descriptor. Throws an error on failure. private func _write(contentsOf buffer: UnsafeRawBufferPointer) throws {