Skip to content

Commit 8a5c581

Browse files
authored
[Macros] Support conformsTo in assertMacroExpansion for member macros
1 parent 51c67b9 commit 8a5c581

File tree

4 files changed

+130
-46
lines changed

4 files changed

+130
-46
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSpec.swift

Lines changed: 21 additions & 0 deletions
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) 2014 - 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
import SwiftSyntaxMacros
315

@@ -8,6 +20,15 @@ public struct MacroSpec {
820
/// The list of types macro needs to add conformances.
921
let conformances: [TypeSyntax]
1022

23+
/// The type list macro needs to add conformances.
24+
var inheritedTypeList: InheritedTypeListSyntax {
25+
return InheritedTypeListSyntax {
26+
for conformance in conformances {
27+
InheritedTypeSyntax(type: conformance)
28+
}
29+
}
30+
}
31+
1132
/// Creates a new specification from provided macro type
1233
/// and optional list of generated conformances.
1334
///

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import SwiftSyntaxBuilder
2121

2222
extension SyntaxProtocol {
2323
/// Expand all uses of the given set of macros within this syntax node.
24+
/// - SeeAlso: ``expand(macroSpecs:in:indentationWidth:)`` to also specify
25+
/// the list of conformances passed to the macro expansion.
2426
public func expand(
2527
macros: [String: Macro.Type],
2628
in context: some MacroExpansionContext,
2729
indentationWidth: Trivia? = nil
2830
) -> Syntax {
29-
let specs = Dictionary(uniqueKeysWithValues: macros.map { ($0.key, MacroSpec(type: $0.value)) })
31+
let specs = macros.mapValues { MacroSpec(type: $0) }
3032
return self.expand(macroSpecs: specs, in: context, indentationWidth: indentationWidth)
3133
}
3234

@@ -39,9 +41,7 @@ extension SyntaxProtocol {
3941
// Build the macro system.
4042
var system = MacroSystem()
4143
for (macroName, macroSpec) in macroSpecs {
42-
try! system.add(macroSpec.type, name: macroName)
43-
let conformedTypes = InheritedTypeListSyntax(macroSpec.conformances.map { InheritedTypeSyntax(type: $0) })
44-
try! system.add(conformedTypes, name: macroName)
44+
try! system.add(macroSpec, name: macroName)
4545
}
4646

4747
let applier = MacroApplication(
@@ -140,6 +140,7 @@ private func expandMemberMacro(
140140
definition: MemberMacro.Type,
141141
attributeNode: AttributeSyntax,
142142
attachedTo: DeclSyntax,
143+
conformanceList: InheritedTypeListSyntax,
143144
in context: some MacroExpansionContext,
144145
indentationWidth: Trivia
145146
) throws -> MemberBlockItemListSyntax? {
@@ -151,7 +152,7 @@ private func expandMemberMacro(
151152
declarationNode: attachedTo.detach(in: context),
152153
parentDeclNode: nil,
153154
extendedType: nil,
154-
conformanceList: nil,
155+
conformanceList: conformanceList,
155156
in: context,
156157
indentationWidth: indentationWidth
157158
)
@@ -328,7 +329,7 @@ private func expandExtensionMacro(
328329
definition: ExtensionMacro.Type,
329330
attributeNode: AttributeSyntax,
330331
attachedTo: DeclSyntax,
331-
conformanceList: InheritedTypeListSyntax?,
332+
conformanceList: InheritedTypeListSyntax,
332333
in context: some MacroExpansionContext,
333334
indentationWidth: Trivia
334335
) throws -> CodeBlockItemListSyntax? {
@@ -349,7 +350,7 @@ private func expandExtensionMacro(
349350
declarationNode: attachedTo.detach(in: context),
350351
parentDeclNode: nil,
351352
extendedType: extendedType.detach(in: context),
352-
conformanceList: conformanceList ?? [],
353+
conformanceList: conformanceList,
353354
in: context,
354355
indentationWidth: indentationWidth
355356
)
@@ -438,49 +439,30 @@ private func expandBodyMacro(
438439
enum MacroSystemError: Error {
439440
/// Indicates that a macro with the given name has already been defined.
440441
case alreadyDefined(new: Macro.Type, existing: Macro.Type)
441-
/// Indicates that protocol conformances for a macro with the given name has already been defined.
442-
case alreadyConforming(new: InheritedTypeListSyntax, existing: InheritedTypeListSyntax)
443442
}
444443

445444
/// A system of known macros that can be expanded syntactically
446445
struct MacroSystem {
447-
var macros: [String: Macro.Type] = [:]
448-
var conformanceMap: [String: InheritedTypeListSyntax] = [:]
446+
var macros: [String: MacroSpec] = [:]
449447

450448
/// Create an empty macro system.
451449
init() {}
452450

453-
/// Add a macro to the system.
451+
/// Add a macro specification to the system.
454452
///
455453
/// Throws an error if there is already a macro with this name.
456-
mutating func add(_ macro: Macro.Type, name: String) throws {
457-
if let knownMacro = macros[name] {
458-
throw MacroSystemError.alreadyDefined(new: macro, existing: knownMacro)
454+
mutating func add(_ macroSpec: MacroSpec, name: String) throws {
455+
if let knownMacroSpec = macros[name] {
456+
throw MacroSystemError.alreadyDefined(new: macroSpec.type, existing: knownMacroSpec.type)
459457
}
460458

461-
macros[name] = macro
459+
macros[name] = macroSpec
462460
}
463461

464-
/// Add protocol conformances for a macro to the system.
465-
///
466-
/// Throws an error if there is already conformances for a macro with this name.
467-
mutating func add(_ conformanceList: InheritedTypeListSyntax, name: String) throws {
468-
if let knownConformanceList = conformanceMap[name] {
469-
throw MacroSystemError.alreadyConforming(new: conformanceList, existing: knownConformanceList)
470-
}
471-
472-
conformanceMap[name] = conformanceList
473-
}
474-
475-
/// Look for a macro with the given name.
476-
func lookup(_ macroName: String) -> Macro.Type? {
462+
/// Look for a macro specification with the given name.
463+
func lookup(_ macroName: String) -> MacroSpec? {
477464
return macros[macroName]
478465
}
479-
480-
/// Look for protocol conformances of a macro with the given name.
481-
func conformaces(forMacro macroName: String) -> InheritedTypeListSyntax? {
482-
return conformanceMap[macroName]
483-
}
484466
}
485467

486468
// MARK: - MacroApplication
@@ -898,41 +880,40 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
898880
// MARK: Attached macro expansions.
899881

900882
extension MacroApplication {
901-
/// Get macro attribute, the macro definition and optional
902-
/// conformance protocols list attached to `decl`.
883+
/// Get macro attribute, the macro specification attached to `decl`.
903884
///
904885
/// The macros must be registered in `macroSystem`.
905886
private func macroAttributes(
906887
attachedTo decl: DeclSyntax
907-
) -> [(attributeNode: AttributeSyntax, definition: Macro.Type, conformanceList: InheritedTypeListSyntax?)] {
888+
) -> [(attributeNode: AttributeSyntax, spec: MacroSpec)] {
908889
guard let attributedNode = decl.asProtocol(WithAttributesSyntax.self) else {
909890
return []
910891
}
911892

912893
return attributedNode.attributes.compactMap {
913894
guard case let .attribute(attribute) = $0,
914895
let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text,
915-
let macro = macroSystem.lookup(attributeName)
896+
let macroSpec = macroSystem.lookup(attributeName)
916897
else {
917898
return nil
918899
}
919900

920-
return (attribute, macro, macroSystem.conformaces(forMacro: attributeName))
901+
return (attribute, macroSpec)
921902
}
922903
}
923904

924-
/// Get macro attribute, the macro definition and optional conformance
905+
/// Get macro attribute, the macro definition and conformance
925906
/// protocols list attached to `decl` matching `ofType` macro type.
926907
///
927908
/// The macros must be registered in `macroSystem`.
928909
private func macroAttributes<MacroType>(
929910
attachedTo decl: DeclSyntax,
930911
ofType: MacroType.Type
931-
) -> [(attributeNode: AttributeSyntax, definition: MacroType, conformanceList: InheritedTypeListSyntax?)] {
912+
) -> [(attributeNode: AttributeSyntax, definition: MacroType, conformanceList: InheritedTypeListSyntax)] {
932913
return macroAttributes(attachedTo: decl)
933-
.compactMap { (attributeNode: AttributeSyntax, definition: Macro.Type, conformanceList: InheritedTypeListSyntax?) in
934-
if let macroType = definition as? MacroType {
935-
return (attributeNode, macroType, conformanceList)
914+
.compactMap { (attributeNode: AttributeSyntax, spec: MacroSpec) in
915+
if let macroType = spec.type as? MacroType {
916+
return (attributeNode, macroType, spec.inheritedTypeList)
936917
} else {
937918
return nil
938919
}
@@ -948,7 +929,7 @@ extension MacroApplication {
948929
>(
949930
attachedTo decl: DeclSyntax,
950931
ofType: MacroType.Type,
951-
expandMacro: (_ attributeNode: AttributeSyntax, _ definition: MacroType, _ conformanceList: InheritedTypeListSyntax?) throws -> ExpanedNodeCollection?
932+
expandMacro: (_ attributeNode: AttributeSyntax, _ definition: MacroType, _ conformanceList: InheritedTypeListSyntax) throws -> ExpanedNodeCollection?
952933
) -> [ExpandedNode] {
953934
var result: [ExpandedNode] = []
954935

@@ -1026,6 +1007,7 @@ extension MacroApplication {
10261007
definition: definition,
10271008
attributeNode: attributeNode,
10281009
attachedTo: decl,
1010+
conformanceList: conformanceList,
10291011
in: context,
10301012
indentationWidth: indentationWidth
10311013
)
@@ -1147,7 +1129,7 @@ extension MacroApplication {
11471129
expandMacro: (_ macro: Macro.Type, _ node: any FreestandingMacroExpansionSyntax) throws -> ExpandedMacroType?
11481130
) -> MacroExpansionResult<ExpandedMacroType> {
11491131
guard let node,
1150-
let macro = macroSystem.lookup(node.macroName.text)
1132+
let macro = macroSystem.lookup(node.macroName.text)?.type
11511133
else {
11521134
return .notAMacro
11531135
}

Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ func assertDiagnostic(
300300
/// - testModuleName: The name of the test module to use.
301301
/// - testFileName: The name of the test file name to use.
302302
/// - indentationWidth: The indentation width used in the expansion.
303+
///
304+
/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:file:line:)``
305+
/// to also specify the list of conformances passed to the macro expansion.
303306
public func assertMacroExpansion(
304307
_ originalSource: String,
305308
expandedSource expectedExpandedSource: String,

Tests/SwiftSyntaxMacroExpansionTest/MemberMacroTests.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,82 @@ final class MemberMacroTests: XCTestCase {
299299
]
300300
)
301301
}
302+
303+
func testConditionalMemberExpansion() {
304+
struct CodableExtensionMacro: MemberMacro {
305+
static func expansion(
306+
of node: AttributeSyntax,
307+
providingMembersOf declaration: some DeclGroupSyntax,
308+
conformingTo protocols: [TypeSyntax],
309+
in context: some MacroExpansionContext
310+
) throws -> [DeclSyntax] {
311+
let decls: [DeclSyntax] = protocols.compactMap { `protocol` in
312+
return """
313+
func print(arg: some \(`protocol`)) { }
314+
""" as DeclSyntax
315+
}
316+
317+
return decls
318+
}
319+
}
320+
321+
assertMacroExpansion(
322+
"""
323+
@AddCodableExtensions
324+
class MyType {
325+
}
326+
""",
327+
expandedSource: """
328+
class MyType {
329+
330+
func print(arg: some Decodable) {
331+
}
332+
333+
func print(arg: some Encodable) {
334+
}
335+
}
336+
""",
337+
macroSpecs: ["AddCodableExtensions": MacroSpec(type: CodableExtensionMacro.self, conformances: ["Decodable", "Encodable"])],
338+
indentationWidth: indentationWidth
339+
)
340+
341+
assertMacroExpansion(
342+
"""
343+
class Wrapper {
344+
@AddCodableExtensions
345+
class MyType {
346+
}
347+
}
348+
""",
349+
expandedSource: """
350+
class Wrapper {
351+
class MyType {
352+
353+
func print(arg: some Encodable) {
354+
}
355+
}
356+
}
357+
""",
358+
macroSpecs: ["AddCodableExtensions": MacroSpec(type: CodableExtensionMacro.self, conformances: ["Encodable"])],
359+
indentationWidth: indentationWidth
360+
)
361+
362+
assertMacroExpansion(
363+
"""
364+
class Wrapper {
365+
@AddCodableExtensions
366+
class MyType {
367+
}
368+
}
369+
""",
370+
expandedSource: """
371+
class Wrapper {
372+
class MyType {
373+
}
374+
}
375+
""",
376+
macros: ["AddCodableExtensions": CodableExtensionMacro.self],
377+
indentationWidth: indentationWidth
378+
)
379+
}
302380
}

0 commit comments

Comments
 (0)