Skip to content

Commit 59ed009

Browse files
authored
Merge pull request #1873 from hborla/5.9-refine-extension-macros
[5.9][Macros] Pass a list of protocols to conform to when expanding an `extension` macro.
2 parents 2a5fe53 + dac330b commit 59ed009

File tree

11 files changed

+102
-97
lines changed

11 files changed

+102
-97
lines changed

Examples/Sources/ExamplePlugin/Macros.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,13 @@ public struct SendableExtensionMacro: ExtensionMacro {
9191
of node: AttributeSyntax,
9292
attachedTo: some DeclGroupSyntax,
9393
providingExtensionsOf type: some TypeSyntaxProtocol,
94+
conformingTo protocols: [TypeSyntax],
9495
in context: some MacroExpansionContext
9596
) throws -> [ExtensionDeclSyntax] {
97+
if protocols.isEmpty {
98+
return []
99+
}
100+
96101
let sendableExtension: DeclSyntax =
97102
"""
98103
extension \(type.trimmed): Sendable {}

Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,25 @@ extension CompilerPluginMessageHandler {
122122
expandingSyntax: expandingSyntax
123123
)
124124

125-
case .expandAttachedMacro(let macro, let macroRole, let discriminator, let attributeSyntax, let declSyntax, let parentDeclSyntax, let extendedTypeSyntax):
125+
case .expandAttachedMacro(
126+
let macro,
127+
let macroRole,
128+
let discriminator,
129+
let attributeSyntax,
130+
let declSyntax,
131+
let parentDeclSyntax,
132+
let extendedTypeSyntax,
133+
let conformanceListSyntax
134+
):
126135
try expandAttachedMacro(
127136
macro: macro,
128137
macroRole: macroRole,
129138
discriminator: discriminator,
130139
attributeSyntax: attributeSyntax,
131140
declSyntax: declSyntax,
132141
parentDeclSyntax: parentDeclSyntax,
133-
extendedTypeSyntax: extendedTypeSyntax
142+
extendedTypeSyntax: extendedTypeSyntax,
143+
conformanceListSyntax: conformanceListSyntax
134144
)
135145

136146
case .loadPluginLibrary(let libraryPath, let moduleName):

Sources/SwiftCompilerPluginMessageHandling/Macros.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ extension CompilerPluginMessageHandler {
8787
attributeSyntax: PluginMessage.Syntax,
8888
declSyntax: PluginMessage.Syntax,
8989
parentDeclSyntax: PluginMessage.Syntax?,
90-
extendedTypeSyntax: PluginMessage.Syntax?
90+
extendedTypeSyntax: PluginMessage.Syntax?,
91+
conformanceListSyntax: PluginMessage.Syntax?
9192
) throws {
9293
let sourceManager = SourceManager()
9394
let context = PluginMacroExpansionContext(
@@ -104,6 +105,10 @@ extension CompilerPluginMessageHandler {
104105
let extendedType = extendedTypeSyntax.map {
105106
sourceManager.add($0).cast(TypeSyntax.self)
106107
}
108+
let conformanceList = conformanceListSyntax.map {
109+
let placeholderStruct = sourceManager.add($0).cast(StructDeclSyntax.self)
110+
return placeholderStruct.inheritanceClause!.inheritedTypeCollection
111+
}
107112

108113
// TODO: Make this a 'String?' and remove non-'hasExpandMacroResult' branches.
109114
let expandedSources: [String]?
@@ -120,6 +125,7 @@ extension CompilerPluginMessageHandler {
120125
declarationNode: declarationNode,
121126
parentDeclNode: parentDeclNode,
122127
extendedType: extendedType,
128+
conformanceList: conformanceList,
123129
in: context
124130
)
125131
if let expansions, hostCapability.hasExpandMacroResult {

Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
attributeSyntax: PluginMessage.Syntax,
3535
declSyntax: PluginMessage.Syntax,
3636
parentDeclSyntax: PluginMessage.Syntax?,
37-
extendedTypeSyntax: PluginMessage.Syntax?
37+
extendedTypeSyntax: PluginMessage.Syntax?,
38+
conformanceListSyntax: PluginMessage.Syntax?
3839
)
3940

4041
/// Optionally implemented message to load a dynamic link library.
@@ -76,7 +77,7 @@
7677
}
7778

7879
@_spi(PluginMessage) public enum PluginMessage {
79-
public static var PROTOCOL_VERSION_NUMBER: Int { 6 } // Added 'expandMacroResult'.
80+
public static var PROTOCOL_VERSION_NUMBER: Int { 7 } // Pass extension protocol list
8081

8182
public struct HostCapability: Codable {
8283
var protocolVersion: Int

Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>
185185
declarationNode: DeclSyntax,
186186
parentDeclNode: DeclSyntax?,
187187
extendedType: TypeSyntax?,
188+
conformanceList: InheritedTypeListSyntax?,
188189
in context: Context
189190
) -> [String]? {
190191
do {
@@ -266,42 +267,6 @@ public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>
266267
$0.formattedExpansion(definition.formatMode)
267268
}
268269

269-
case (let attachedMacro as ConformanceMacro.Type, .conformance):
270-
guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) else {
271-
// Compiler error: type mismatch.
272-
throw MacroExpansionError.declarationNotDeclGroup
273-
}
274-
guard let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self)
275-
else {
276-
// Compiler error: type mismatch.
277-
throw MacroExpansionError.declarationNotIdentified
278-
}
279-
280-
// Local function to expand a conformance macro once we've opened up
281-
// the existential.
282-
func expandConformanceMacro(
283-
_ node: some DeclGroupSyntax
284-
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
285-
return try attachedMacro.expansion(
286-
of: attributeNode,
287-
providingConformancesOf: node,
288-
in: context
289-
)
290-
}
291-
292-
let conformances = try _openExistential(
293-
declGroup,
294-
do: expandConformanceMacro
295-
)
296-
297-
// Form a buffer of extension declarations to return to the caller.
298-
return conformances.map { typeSyntax, whereClause in
299-
let typeName = identified.identifier.trimmedDescription
300-
let protocolName = typeSyntax.trimmedDescription
301-
let whereClause = whereClause?.trimmedDescription ?? ""
302-
return "extension \(typeName) : \(protocolName) \(whereClause) {}"
303-
}
304-
305270
case (let attachedMacro as ExtensionMacro.Type, .extension):
306271
guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self) else {
307272
// Compiler error: type mismatch.
@@ -312,6 +277,8 @@ public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>
312277
throw MacroExpansionError.noExtendedTypeSyntax
313278
}
314279

280+
let protocols = conformanceList?.map(\.typeName) ?? []
281+
315282
// Local function to expand an extension macro once we've opened up
316283
// the existential.
317284
func expandExtensionMacro(
@@ -321,6 +288,7 @@ public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>
321288
of: attributeNode,
322289
attachedTo: node,
323290
providingExtensionsOf: extendedType,
291+
conformingTo: protocols,
324292
in: context
325293
)
326294
}
@@ -364,6 +332,7 @@ public func expandAttachedMacro<Context: MacroExpansionContext>(
364332
declarationNode: DeclSyntax,
365333
parentDeclNode: DeclSyntax?,
366334
extendedType: TypeSyntax?,
335+
conformanceList: InheritedTypeListSyntax?,
367336
in context: Context
368337
) -> String? {
369338
let expandedSources = expandAttachedMacroWithoutCollapsing(
@@ -373,6 +342,7 @@ public func expandAttachedMacro<Context: MacroExpansionContext>(
373342
declarationNode: declarationNode,
374343
parentDeclNode: parentDeclNode,
375344
extendedType: extendedType,
345+
conformanceList: conformanceList,
376346
in: context
377347
)
378348
return expandedSources.map {

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
136136
|| macro is MemberMacro.Type
137137
|| macro is AccessorMacro.Type
138138
|| macro is MemberAttributeMacro.Type
139-
|| macro is ConformanceMacro.Type
140139
|| macro is ExtensionMacro.Type)
141140
}
142141

@@ -213,7 +212,7 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
213212

214213
if let declGroup = decl.asProtocol(DeclGroupSyntax.self) {
215214
newItems.append(
216-
contentsOf: expandConformances(of: declGroup).map {
215+
contentsOf: expandExtensions(of: declGroup).map {
217216
newDecl in CodeBlockItemSyntax(item: .decl(newDecl))
218217
}
219218
)
@@ -429,45 +428,29 @@ extension MacroApplication {
429428
// If any of the custom attributes associated with the given declaration
430429
// refer to conformance macros, expand them and return the resulting
431430
// set of extension declarations.
432-
private func expandConformances(of decl: DeclGroupSyntax) -> [DeclSyntax] {
433-
let extendedType: Syntax
431+
private func expandExtensions(of decl: DeclGroupSyntax) -> [DeclSyntax] {
432+
let extendedType: TypeSyntax
434433
if let identified = decl.asProtocol(IdentifiedDeclSyntax.self) {
435-
extendedType = Syntax(identified.identifier.trimmed)
434+
extendedType = "\(identified.identifier.trimmed)"
436435
} else if let ext = decl.as(ExtensionDeclSyntax.self) {
437-
extendedType = Syntax(ext.extendedType.trimmed)
436+
extendedType = "\(ext.extendedType.trimmed)"
438437
} else {
439438
return []
440439
}
441440

442441
var extensions: [DeclSyntax] = []
443-
let macroAttributes = getMacroAttributes(attachedTo: decl.as(DeclSyntax.self)!, ofType: ConformanceMacro.Type.self)
444-
for (attribute, conformanceMacro) in macroAttributes {
445-
do {
446-
let newConformances = try conformanceMacro.expansion(of: attribute, providingConformancesOf: decl, in: context)
447-
448-
for (type, whereClause) in newConformances {
449-
var ext: DeclSyntax = """
450-
extension \(extendedType): \(type) { }
451-
"""
452-
if let whereClause {
453-
ext = DeclSyntax((ext.cast(ExtensionDeclSyntax.self)).with(\.genericWhereClause, whereClause))
454-
}
455-
456-
extensions.append(DeclSyntax(ext))
457-
}
458-
} catch {
459-
context.addDiagnostics(from: error, node: attribute)
460-
}
461-
}
462442

463443
let extensionMacroAttrs = getMacroAttributes(attachedTo: decl.as(DeclSyntax.self)!, ofType: ExtensionMacro.Type.self)
464-
let extendedTypeSyntax = TypeSyntax("\(extendedType.trimmed)")
465444
for (attribute, extensionMacro) in extensionMacroAttrs {
466445
do {
446+
// FIXME: We need a way for unit tests of extension macros to
447+
// specify protocols already stated in source (e.g. as arguments
448+
// to `assertMacroExpansion`).
467449
let newExtensions = try extensionMacro.expansion(
468450
of: attribute,
469451
attachedTo: decl,
470-
providingExtensionsOf: extendedTypeSyntax,
452+
providingExtensionsOf: extendedType,
453+
conformingTo: [],
471454
in: context
472455
)
473456

Sources/SwiftSyntaxMacros/MacroProtocols/ConformanceMacro.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SwiftSyntax
1414

1515
/// Describes a macro that can add conformances to the declaration it's
1616
/// attached to.
17-
public protocol ConformanceMacro: AttachedMacro {
17+
public protocol ConformanceMacro: ExtensionMacro {
1818
/// Expand an attached conformance macro to produce a set of conformances.
1919
///
2020
/// - Parameters:
@@ -31,3 +31,38 @@ public protocol ConformanceMacro: AttachedMacro {
3131
in context: some MacroExpansionContext
3232
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)]
3333
}
34+
35+
extension ConformanceMacro {
36+
public static func expansion(
37+
of node: AttributeSyntax,
38+
attachedTo declaration: some DeclGroupSyntax,
39+
providingExtensionsOf type: some TypeSyntaxProtocol,
40+
conformingTo protocols: [TypeSyntax],
41+
in context: some MacroExpansionContext
42+
) throws -> [ExtensionDeclSyntax] {
43+
44+
let newConformances = try expansion(
45+
of: node,
46+
providingConformancesOf: declaration,
47+
in: context
48+
)
49+
50+
var extensions: [ExtensionDeclSyntax] = []
51+
for (proto, whereClause) in newConformances {
52+
let decl: DeclSyntax =
53+
"""
54+
extension \(type.trimmed): \(proto) {}
55+
"""
56+
57+
var extensionDecl = decl.cast(ExtensionDeclSyntax.self)
58+
59+
if let whereClause {
60+
extensionDecl = extensionDecl.with(\.genericWhereClause, whereClause)
61+
}
62+
63+
extensions.append(extensionDecl)
64+
}
65+
66+
return extensions
67+
}
68+
}

Sources/SwiftSyntaxMacros/MacroProtocols/ExtensionMacro.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public protocol ExtensionMacro: AttachedMacro {
2121
/// - node: The custom attribute describing the attached macro.
2222
/// - declaration: The declaration the macro attribute is attached to.
2323
/// - type: The type to provide extensions of.
24+
/// - protocols: The list of protocols to add conformances to. These will
25+
/// always be protocols that `type` does not already state a conformance
26+
/// to.
2427
/// - context: The context in which to perform the macro expansion.
2528
///
2629
/// - Returns: the set of extension declarations introduced by the macro,
@@ -30,6 +33,7 @@ public protocol ExtensionMacro: AttachedMacro {
3033
of node: AttributeSyntax,
3134
attachedTo declaration: some DeclGroupSyntax,
3235
providingExtensionsOf type: some TypeSyntaxProtocol,
36+
conformingTo protocols: [TypeSyntax],
3337
in context: some MacroExpansionContext
3438
) throws -> [ExtensionDeclSyntax]
3539
}

Sources/SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
110110
return true
111111
}
112112

113-
return !(macro is PeerMacro.Type || macro is MemberMacro.Type || macro is AccessorMacro.Type || macro is MemberAttributeMacro.Type || macro is ConformanceMacro.Type || macro is ExtensionMacro.Type)
113+
return
114+
!(macro is PeerMacro.Type
115+
|| macro is MemberMacro.Type
116+
|| macro is AccessorMacro.Type
117+
|| macro is MemberAttributeMacro.Type
118+
|| macro is ExtensionMacro.Type)
114119
}
115120

116121
if newAttributes.isEmpty {
@@ -186,7 +191,7 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
186191

187192
if let declGroup = decl.asProtocol(DeclGroupSyntax.self) {
188193
newItems.append(
189-
contentsOf: expandConformances(of: declGroup).map {
194+
contentsOf: expandExtensions(of: declGroup).map {
190195
newDecl in CodeBlockItemSyntax(item: .decl(newDecl))
191196
}
192197
)
@@ -402,45 +407,29 @@ extension MacroApplication {
402407
// If any of the custom attributes associated with the given declaration
403408
// refer to conformance macros, expand them and return the resulting
404409
// set of extension declarations.
405-
private func expandConformances(of decl: DeclGroupSyntax) -> [DeclSyntax] {
406-
let extendedType: Syntax
410+
private func expandExtensions(of decl: DeclGroupSyntax) -> [DeclSyntax] {
411+
let extendedType: TypeSyntax
407412
if let identified = decl.asProtocol(IdentifiedDeclSyntax.self) {
408-
extendedType = Syntax(identified.identifier.trimmed)
413+
extendedType = "\(identified.identifier.trimmed)"
409414
} else if let ext = decl.as(ExtensionDeclSyntax.self) {
410-
extendedType = Syntax(ext.extendedType.trimmed)
415+
extendedType = "\(ext.extendedType.trimmed)"
411416
} else {
412417
return []
413418
}
414419

415420
var extensions: [DeclSyntax] = []
416-
let macroAttributes = getMacroAttributes(attachedTo: decl.as(DeclSyntax.self)!, ofType: ConformanceMacro.Type.self)
417-
for (attribute, conformanceMacro) in macroAttributes {
418-
do {
419-
let newConformances = try conformanceMacro.expansion(of: attribute, providingConformancesOf: decl, in: context)
420-
421-
for (type, whereClause) in newConformances {
422-
var ext: DeclSyntax = """
423-
extension \(extendedType): \(type) { }
424-
"""
425-
if let whereClause {
426-
ext = DeclSyntax((ext.cast(ExtensionDeclSyntax.self)).with(\.genericWhereClause, whereClause))
427-
}
428-
429-
extensions.append(DeclSyntax(ext))
430-
}
431-
} catch {
432-
context.addDiagnostics(from: error, node: attribute)
433-
}
434-
}
435421

436422
let extensionMacroAttrs = getMacroAttributes(attachedTo: decl.as(DeclSyntax.self)!, ofType: ExtensionMacro.Type.self)
437-
let extendedTypeSyntax = TypeSyntax("\(extendedType.trimmed)")
438423
for (attribute, extensionMacro) in extensionMacroAttrs {
439424
do {
425+
// FIXME: We need a way for unit tests of extension macros to
426+
// specify protocols already stated in source (e.g. as arguments
427+
// to `assertMacroExpansion`).
440428
let newExtensions = try extensionMacro.expansion(
441429
of: attribute,
442430
attachedTo: decl,
443-
providingExtensionsOf: extendedTypeSyntax,
431+
providingExtensionsOf: extendedType,
432+
conformingTo: [],
444433
in: context
445434
)
446435

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ public struct SendableExtensionMacro: ExtensionMacro {
687687
of node: AttributeSyntax,
688688
attachedTo: some DeclGroupSyntax,
689689
providingExtensionsOf type: some TypeSyntaxProtocol,
690+
conformingTo protocols: [TypeSyntax],
690691
in context: some MacroExpansionContext
691692
) throws -> [ExtensionDeclSyntax] {
692693
let sendableExtension: DeclSyntax =

0 commit comments

Comments
 (0)