Skip to content

Commit 49a101e

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 49a101e

File tree

4 files changed

+183
-14
lines changed

4 files changed

+183
-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: 38 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,38 @@ 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,
129+
for: role,
130+
attachedTo: declarationNode
131+
)
132+
]
133+
} else {
134+
expandedSources = expansions
135+
}
112136
} catch {
113137
context.addDiagnostics(from: error, node: attributeNode)
114138
expandedSources = nil
@@ -117,9 +141,14 @@ extension CompilerPluginMessageHandler {
117141
let diagnostics = context.diagnostics.map {
118142
PluginMessage.Diagnostic(from: $0, in: sourceManager)
119143
}
120-
try self.sendMessage(
121-
.expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
122-
)
144+
145+
let response: PluginToHostMessage
146+
if (hostCapability.hasExpandMacroResult) {
147+
response = .expandMacroResult(expandedSource: expandedSources?.first, diagnostics: diagnostics)
148+
} else {
149+
response = .expandAttachedMacroResult(expandedSources: expandedSources, diagnostics: diagnostics)
150+
}
151+
try self.sendMessage(response)
123152
}
124153
}
125154

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: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org 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 https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
113
import SwiftSyntax
214
@_spi(MacroExpansion) import SwiftSyntaxMacros
315

@@ -160,7 +172,7 @@ public func expandFreestandingMacro(
160172
/// - Returns: A list of expanded source text. Upon failure (i.e.
161173
/// `defintion.expansion()` throws) returns `nil`, and the diagnostics
162174
/// representing the `Error` are guaranteed to be added to context.
163-
public func expandAttachedMacro<Context: MacroExpansionContext>(
175+
public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>(
164176
definition: Macro.Type,
165177
macroRole: MacroRole,
166178
attributeNode: AttributeSyntax,
@@ -292,6 +304,40 @@ public func expandAttachedMacro<Context: MacroExpansionContext>(
292304
}
293305
}
294306

307+
/// Expand `@attached(XXX)` macros.
308+
///
309+
/// - Parameters:
310+
/// - definition: a type that conforms to one or more attached `Macro` protocols.
311+
/// - macroRole: indicates which `Macro` protocol expansion should be performed
312+
/// - attributeNode: attribute syntax node (e.g. `@macroName(argument)`).
313+
/// - declarationNode: target declaration syntax node to apply the expansion.
314+
/// - parentDeclNode: Only used for `MacroRole.memberAttribute`. The parent
315+
/// context node of `declarationNode`.
316+
/// - in: context of the expansion.
317+
/// - Returns: expanded source text. Upon failure (i.e. `defintion.expansion()`
318+
/// throws) returns `nil`, and the diagnostics representing the `Error` are
319+
/// guaranteed to be added to context.
320+
public func expandAttachedMacro<Context: MacroExpansionContext>(
321+
definition: Macro.Type,
322+
macroRole: MacroRole,
323+
attributeNode: AttributeSyntax,
324+
declarationNode: DeclSyntax,
325+
parentDeclNode: DeclSyntax?,
326+
in context: Context
327+
) -> String? {
328+
let expandedSources = expandAttachedMacroWithoutCollapsing(
329+
definition: definition,
330+
macroRole: macroRole,
331+
attributeNode: attributeNode,
332+
declarationNode: declarationNode,
333+
parentDeclNode: parentDeclNode,
334+
in: context
335+
)
336+
return expandedSources.map {
337+
collapse(expansions: $0, for: macroRole, attachedTo: declarationNode)
338+
}
339+
}
340+
295341
fileprivate extension SyntaxProtocol {
296342
/// Perform a format if required and then trim any leading/trailing
297343
/// whitespace.
@@ -306,3 +352,56 @@ fileprivate extension SyntaxProtocol {
306352
return formatted.trimmedDescription(matching: { $0.isWhitespace })
307353
}
308354
}
355+
356+
/// Join `expansions`
357+
public func collapse<Node: SyntaxProtocol>(
358+
expansions: [String],
359+
for role: MacroRole,
360+
attachedTo declarationNode: Node
361+
) -> String {
362+
if expansions.isEmpty {
363+
return ""
364+
}
365+
366+
var expansions = expansions
367+
var separator: String = "\n\n"
368+
369+
if role == .accessor,
370+
let varDecl = declarationNode.as(VariableDeclSyntax.self),
371+
let binding = varDecl.bindings.first,
372+
binding.accessor == nil
373+
{
374+
let indentation = String(repeating: " ", count: 4)
375+
376+
expansions = expansions.map({ indent($0, with: indentation) })
377+
expansions[0] = "{\n" + expansions[0]
378+
expansions[expansions.count - 1] += "\n}"
379+
} else if role == .memberAttribute {
380+
separator = " "
381+
}
382+
383+
return expansions.joined(separator: separator)
384+
}
385+
386+
fileprivate func indent(_ source: String, with indentation: String) -> String {
387+
if source.isEmpty || indentation.isEmpty {
388+
return source
389+
}
390+
391+
var indented = ""
392+
var remaining = source[...]
393+
while let nextNewline = remaining.firstIndex(where: { $0.isNewline }) {
394+
if nextNewline != remaining.startIndex {
395+
indented += indentation
396+
}
397+
indented += remaining[...nextNewline]
398+
remaining = remaining[remaining.index(after: nextNewline)...]
399+
}
400+
401+
if !remaining.isEmpty {
402+
indented += indentation
403+
indented += remaining
404+
}
405+
406+
return indented
407+
}

0 commit comments

Comments
 (0)