Skip to content

Commit f85d6d9

Browse files
committed
[Macros] Attached macro expasnions return single string
* Move collapse(expansions:for:attachedTo:) to SwiftSyntaxMacroExpansion * SwiftSyntaxMacroExpansion.expandAttachedMacro() now perform collapsing * SwiftSyntaxMacroExpansion.expandAttachedMacroWithoutCollapsing() for to keep old behavior * IPC message 'getCapability' now send the host protocol version * Unified IPC response 'macroExpansionResult' that returns single string for both 'expandFreestandingMacro' and 'expandAttachedMacro' * Compiler accepts old 'expandFreestandingMacroResult' and 'expandAttachedMacroResult' to keep compatibility * Plugins check the compiler's protcol version to see if it suppports 'macroExpansionResult', and fall back to old behavior if necessary
1 parent 1555299 commit f85d6d9

File tree

4 files changed

+168
-14
lines changed

4 files changed

+168
-14
lines changed

Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ public protocol MessageConnection {
4040
func waitForNextMessage<RX: Decodable>(_ type: RX.Type) throws -> RX?
4141
}
4242

43+
/// Represent the capability of the plugin host (i.e. compiler).
44+
struct HostCapability {
45+
var protocolVersion: Int
46+
47+
// Create an "oldest" capability.
48+
init() {
49+
self.protocolVersion = 0
50+
}
51+
52+
init(_ message: PluginMessage.HostCapability) {
53+
self.protocolVersion = message.protocolVersion
54+
}
55+
56+
/// Compiler accept 'expandMacroResult' response message.
57+
var hasExpandMacroResult: Bool { protocolVersion >= 5 }
58+
}
59+
4360
/// 'CompilerPluginMessageHandler' is a type that listens to the message
4461
/// connection and dispatches them to the actual plugin provider, then send back
4562
/// the response.
@@ -52,9 +69,13 @@ public class CompilerPluginMessageHandler<Connection: MessageConnection, Provide
5269
/// Object to provide actual plugin functions.
5370
let provider: Provider
5471

72+
/// Plugin host capability
73+
var hostCapability: HostCapability
74+
5575
public init(connection: Connection, provider: Provider) {
5676
self.connection = connection
5777
self.provider = provider
78+
self.hostCapability = HostCapability()
5879
}
5980
}
6081

@@ -80,7 +101,13 @@ extension CompilerPluginMessageHandler {
80101
/// Handles a single message received from the plugin host.
81102
fileprivate func handleMessage(_ message: HostToPluginMessage) throws {
82103
switch message {
83-
case .getCapability:
104+
case .getCapability(let hostCapability):
105+
// Remember the peer capability if provided.
106+
if let hostCapability = hostCapability {
107+
self.hostCapability = .init(hostCapability)
108+
}
109+
110+
// Return the plugin capability.
84111
let capability = PluginMessage.PluginCapability(
85112
protocolVersion: PluginMessage.PROTOCOL_VERSION_NUMBER,
86113
features: provider.features.map({ $0.rawValue })

Sources/SwiftCompilerPluginMessageHandling/Macros.swift

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,15 @@ extension CompilerPluginMessageHandler {
6868
let diagnostics = context.diagnostics.map {
6969
PluginMessage.Diagnostic(from: $0, in: sourceManager)
7070
}
71-
try self.sendMessage(
72-
.expandFreestandingMacroResult(expandedSource: expandedSource, diagnostics: diagnostics)
73-
)
71+
72+
let response: PluginToHostMessage
73+
if (hostCapability.hasExpandMacroResult) {
74+
response = .expandMacroResult(expandedSource: expandedSource, diagnostics: diagnostics)
75+
} else {
76+
// TODO: Remove this when all compilers have 'hasExpandMacroResult'.
77+
response = .expandFreestandingMacroResult(expandedSource: expandedSource, diagnostics: diagnostics)
78+
}
79+
try self.sendMessage(response)
7480
}
7581

7682
/// Expand `@attached(XXX)` macros.
@@ -95,20 +101,36 @@ extension CompilerPluginMessageHandler {
95101
let declarationNode = sourceManager.add(declSyntax).cast(DeclSyntax.self)
96102
let parentDeclNode = parentDeclSyntax.map { sourceManager.add($0).cast(DeclSyntax.self) }
97103

104+
// TODO: Make this a 'String?' and remove non-'hasExpandMacroResult' branches.
98105
let expandedSources: [String]?
99-
do {
106+
MAIN: do {
100107
guard let macroDefinition = resolveMacro(macro) else {
101108
throw MacroExpansionError.macroTypeNotFound(macro)
102109
}
110+
let role = MacroRole(messageMacroRole: macroRole)
103111

104-
expandedSources = SwiftSyntaxMacroExpansion.expandAttachedMacro(
112+
let expansions = SwiftSyntaxMacroExpansion.expandAttachedMacroWithoutCollapsing(
105113
definition: macroDefinition,
106-
macroRole: MacroRole(messageMacroRole: macroRole),
114+
macroRole: role,
107115
attributeNode: attributeNode,
108116
declarationNode: declarationNode,
109117
parentDeclNode: parentDeclNode,
110118
in: context
111119
)
120+
guard let expansions = expansions else {
121+
expandedSources = nil
122+
break MAIN
123+
}
124+
if hostCapability.hasExpandMacroResult {
125+
// Make a single element array by collapsing the results into a string.
126+
expandedSources = [
127+
SwiftSyntaxMacroExpansion.collapse(
128+
expansions: expansions, for: role, attachedTo: declarationNode
129+
)
130+
]
131+
} else {
132+
expandedSources = expansions
133+
}
112134
} catch {
113135
context.addDiagnostics(from: error, node: attributeNode)
114136
expandedSources = nil
@@ -117,9 +139,14 @@ extension CompilerPluginMessageHandler {
117139
let diagnostics = context.diagnostics.map {
118140
PluginMessage.Diagnostic(from: $0, in: sourceManager)
119141
}
120-
try self.sendMessage(
121-
.expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
122-
)
142+
143+
let response: PluginToHostMessage
144+
if (hostCapability.hasExpandMacroResult) {
145+
response = .expandMacroResult(expandedSource: expandedSources?.first, diagnostics: diagnostics)
146+
} else {
147+
response = .expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
148+
}
149+
try self.sendMessage(response)
123150
}
124151
}
125152

Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
// NOTE: Types in this file should be self-contained and should not depend on any non-stdlib types.
1515

1616
internal enum HostToPluginMessage: Codable {
17-
/// Get capability of this plugin.
18-
case getCapability
17+
/// Send capability of the host, and get capability of the plugin.
18+
case getCapability(
19+
capability: PluginMessage.HostCapability?
20+
)
1921

2022
/// Expand a '@freestanding' macro.
2123
case expandFreestandingMacro(
@@ -49,11 +51,19 @@ internal enum PluginToHostMessage: Codable {
4951
capability: PluginMessage.PluginCapability
5052
)
5153

54+
/// Unified response for freestanding/attached macro expansion.
55+
case expandMacroResult(
56+
expandedSource: String?,
57+
diagnostics: [PluginMessage.Diagnostic]
58+
)
59+
60+
// @available(*, deprecated: "use expandMacroResult() instead")
5261
case expandFreestandingMacroResult(
5362
expandedSource: String?,
5463
diagnostics: [PluginMessage.Diagnostic]
5564
)
5665

66+
// @available(*, deprecated: "use expandMacroResult() instead")
5767
case expandAttachedMacroResult(
5868
expandedSources: [String]?,
5969
diagnostics: [PluginMessage.Diagnostic]
@@ -66,7 +76,11 @@ internal enum PluginToHostMessage: Codable {
6676
}
6777

6878
/*namespace*/ internal enum PluginMessage {
69-
static var PROTOCOL_VERSION_NUMBER: Int { 4 } // Added 'loadPluginLibrary'.
79+
static var PROTOCOL_VERSION_NUMBER: Int { 5 } // Added 'expandMacroResult'.
80+
81+
struct HostCapability: Codable {
82+
var protocolVersion: Int
83+
}
7084

7185
struct PluginCapability: Codable {
7286
var protocolVersion: Int

Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public func expandFreestandingMacro(
160160
/// - Returns: A list of expanded source text. Upon failure (i.e.
161161
/// `defintion.expansion()` throws) returns `nil`, and the diagnostics
162162
/// representing the `Error` are guaranteed to be added to context.
163-
public func expandAttachedMacro<Context: MacroExpansionContext>(
163+
public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>(
164164
definition: Macro.Type,
165165
macroRole: MacroRole,
166166
attributeNode: AttributeSyntax,
@@ -292,6 +292,40 @@ public func expandAttachedMacro<Context: MacroExpansionContext>(
292292
}
293293
}
294294

295+
/// Expand `@attached(XXX)` macros.
296+
///
297+
/// - Parameters:
298+
/// - definition: a type that conforms to one or more attached `Macro` protocols.
299+
/// - macroRole: indicates which `Macro` protocol expansion should be performed
300+
/// - attributeNode: attribute syntax node (e.g. `@macroName(argument)`).
301+
/// - declarationNode: target declaration syntax node to apply the expansion.
302+
/// - parentDeclNode: Only used for `MacroRole.memberAttribute`. The parent
303+
/// context node of `declarationNode`.
304+
/// - in: context of the expansion.
305+
/// - Returns: expanded source text. Upon failure (i.e. `defintion.expansion()`
306+
/// throws) returns `nil`, and the diagnostics representing the `Error` are
307+
/// guaranteed to be added to context.
308+
public func expandAttachedMacro<Context: MacroExpansionContext>(
309+
definition: Macro.Type,
310+
macroRole: MacroRole,
311+
attributeNode: AttributeSyntax,
312+
declarationNode: DeclSyntax,
313+
parentDeclNode: DeclSyntax?,
314+
in context: Context
315+
) -> String? {
316+
let expandedSources = expandAttachedMacroWithoutCollapsing(
317+
definition: definition,
318+
macroRole: macroRole,
319+
attributeNode: attributeNode,
320+
declarationNode: declarationNode,
321+
parentDeclNode: parentDeclNode,
322+
in: context
323+
)
324+
return expandedSources.map {
325+
collapse(expansions: $0, for: macroRole, attachedTo: declarationNode)
326+
}
327+
}
328+
295329
fileprivate extension SyntaxProtocol {
296330
/// Perform a format if required and then trim any leading/trailing
297331
/// whitespace.
@@ -306,3 +340,55 @@ fileprivate extension SyntaxProtocol {
306340
return formatted.trimmedDescription(matching: { $0.isWhitespace })
307341
}
308342
}
343+
344+
/// Join `expansions`
345+
public func collapse<Node: SyntaxProtocol>(
346+
expansions: [String],
347+
for role: MacroRole,
348+
attachedTo declarationNode: Node
349+
) -> String {
350+
if expansions.isEmpty {
351+
return ""
352+
}
353+
354+
var expansions = expansions
355+
var separator: String = "\n\n"
356+
357+
if role == .accessor,
358+
let varDecl = declarationNode.as(VariableDeclSyntax.self),
359+
let binding = varDecl.bindings.first,
360+
binding.accessor == nil {
361+
let indentation = String(repeating: " ", count: 4)
362+
363+
expansions = expansions.map({ indent($0, with: indentation) })
364+
expansions[0] = "{\n" + expansions[0]
365+
expansions[expansions.count - 1] += "\n}"
366+
} else if role == .memberAttribute {
367+
separator = " "
368+
}
369+
370+
return expansions.joined(separator: separator)
371+
}
372+
373+
fileprivate func indent(_ source: String, with indentation: String) -> String {
374+
if source.isEmpty || indentation.isEmpty {
375+
return source
376+
}
377+
378+
var indented = ""
379+
var remaining = source[...]
380+
while let nextNewline = remaining.firstIndex(where: { $0.isNewline }) {
381+
if nextNewline != remaining.startIndex {
382+
indented += indentation
383+
}
384+
indented += remaining[...nextNewline]
385+
remaining = remaining[remaining.index(after: nextNewline)...]
386+
}
387+
388+
if !remaining.isEmpty {
389+
indented += indentation
390+
indented += remaining
391+
}
392+
393+
return indented
394+
}

0 commit comments

Comments
 (0)