Skip to content

Commit 5d5beb9

Browse files
authored
Merge pull request #2327 from soumyamahunt/extension-macro-assert-fix
[Macros] Introduce macro specification in assertMacroExpansion
2 parents 6291aff + b866edb commit 5d5beb9

File tree

7 files changed

+308
-34
lines changed

7 files changed

+308
-34
lines changed

Release Notes/511.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
- Description: `TriviaPiece` now has a computed property `isComment` that returns `true` if the trivia piece is a comment.
5050
- Pull Request: https://github.com/apple/swift-syntax/pull/2469
5151

52+
- New `assertMacroExpansion` API with option to specify macro specifications with `macroSpecs` argument
53+
- Description: `macroSpecs` can have additional specifications like conformances provided by member or extension macro that can be used for macro expansion.
54+
- Issue: https://github.com/apple/swift-syntax/issues/2031
55+
- Pull Request: https://github.com/apple/swift-syntax/pull/2327
56+
5257
## API Behavior Changes
5358

5459
## Deprecations

Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_swift_syntax_library(SwiftSyntaxMacroExpansion
55
MacroExpansion.swift
66
MacroExpansionDiagnosticMessages.swift
77
MacroReplacement.swift
8+
MacroSpec.swift
89
MacroSystem.swift
910
)
1011

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
13+
import SwiftSyntax
14+
import SwiftSyntaxMacros
15+
16+
/// The information of a macro declaration, to be used with `assertMacroExpansion`.
17+
///
18+
/// In addition to specifying the macro’s type, this allows the specification of conformances that will be passed to the macro’s `expansion` function.
19+
public struct MacroSpec {
20+
/// The type of macro.
21+
let type: Macro.Type
22+
/// The list of types for which the macro needs to add conformances.
23+
let conformances: [TypeSyntax]
24+
25+
/// An `InheritedTypeListSytnax` containing all the types for which the macro needs to add conformances.
26+
var inheritedTypeList: InheritedTypeListSyntax {
27+
return InheritedTypeListSyntax {
28+
for conformance in conformances {
29+
InheritedTypeSyntax(type: conformance)
30+
}
31+
}
32+
}
33+
34+
/// Creates a new specification from provided macro type
35+
/// and optional list of generated conformances.
36+
///
37+
/// - Parameters:
38+
/// - type: The type of macro.
39+
/// - conformances: The list of types that will be passed to the macro’s `expansion` function.
40+
public init(type: Macro.Type, conformances: [TypeSyntax] = []) {
41+
self.type = type
42+
self.conformances = conformances
43+
}
44+
}

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,30 @@ extension SyntaxProtocol {
3535
}
3636

3737
/// Expand all uses of the given set of macros within this syntax node.
38+
/// - SeeAlso: ``expand(macroSpecs:contextGenerator:indentationWidth:)``
39+
/// to also specify the list of conformances passed to the macro expansion.
3840
public func expand<Context: MacroExpansionContext>(
3941
macros: [String: Macro.Type],
4042
contextGenerator: @escaping (Syntax) -> Context,
4143
indentationWidth: Trivia? = nil
44+
) -> Syntax {
45+
return expand(
46+
macroSpecs: macros.mapValues { MacroSpec(type: $0) },
47+
contextGenerator: contextGenerator,
48+
indentationWidth: indentationWidth
49+
)
50+
}
51+
52+
/// Expand all uses of the given set of macros with specifications within this syntax node.
53+
public func expand<Context: MacroExpansionContext>(
54+
macroSpecs: [String: MacroSpec],
55+
contextGenerator: @escaping (Syntax) -> Context,
56+
indentationWidth: Trivia? = nil
4257
) -> Syntax {
4358
// Build the macro system.
4459
var system = MacroSystem()
45-
for (macroName, macroType) in macros {
46-
try! system.add(macroType, name: macroName)
60+
for (macroName, macroSpec) in macroSpecs {
61+
try! system.add(macroSpec, name: macroName)
4762
}
4863

4964
let applier = MacroApplication(
@@ -142,6 +157,7 @@ private func expandMemberMacro(
142157
definition: MemberMacro.Type,
143158
attributeNode: AttributeSyntax,
144159
attachedTo: DeclSyntax,
160+
conformanceList: InheritedTypeListSyntax,
145161
in context: some MacroExpansionContext,
146162
indentationWidth: Trivia
147163
) throws -> MemberBlockItemListSyntax? {
@@ -153,7 +169,7 @@ private func expandMemberMacro(
153169
declarationNode: attachedTo.detach(in: context),
154170
parentDeclNode: nil,
155171
extendedType: nil,
156-
conformanceList: nil,
172+
conformanceList: conformanceList,
157173
in: context,
158174
indentationWidth: indentationWidth
159175
)
@@ -330,6 +346,7 @@ private func expandExtensionMacro(
330346
definition: ExtensionMacro.Type,
331347
attributeNode: AttributeSyntax,
332348
attachedTo: DeclSyntax,
349+
conformanceList: InheritedTypeListSyntax,
333350
in context: some MacroExpansionContext,
334351
indentationWidth: Trivia
335352
) throws -> CodeBlockItemListSyntax? {
@@ -349,7 +366,7 @@ private func expandExtensionMacro(
349366
declarationNode: attachedTo.detach(in: context),
350367
parentDeclNode: nil,
351368
extendedType: extendedType.detach(in: context),
352-
conformanceList: [],
369+
conformanceList: conformanceList,
353370
in: context,
354371
indentationWidth: indentationWidth
355372
)
@@ -442,24 +459,24 @@ enum MacroSystemError: Error {
442459

443460
/// A system of known macros that can be expanded syntactically
444461
struct MacroSystem {
445-
var macros: [String: Macro.Type] = [:]
462+
var macros: [String: MacroSpec] = [:]
446463

447464
/// Create an empty macro system.
448465
init() {}
449466

450-
/// Add a macro to the system.
467+
/// Add a macro specification to the system.
451468
///
452469
/// Throws an error if there is already a macro with this name.
453-
mutating func add(_ macro: Macro.Type, name: String) throws {
454-
if let knownMacro = macros[name] {
455-
throw MacroSystemError.alreadyDefined(new: macro, existing: knownMacro)
470+
mutating func add(_ macroSpec: MacroSpec, name: String) throws {
471+
if let knownMacroSpec = macros[name] {
472+
throw MacroSystemError.alreadyDefined(new: macroSpec.type, existing: knownMacroSpec.type)
456473
}
457474

458-
macros[name] = macro
475+
macros[name] = macroSpec
459476
}
460477

461-
/// Look for a macro with the given name.
462-
func lookup(_ macroName: String) -> Macro.Type? {
478+
/// Look for a macro specification with the given name.
479+
func lookup(_ macroName: String) -> MacroSpec? {
463480
return macros[macroName]
464481
}
465482
}
@@ -680,7 +697,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
680697
_ node: Node
681698
) -> Node {
682699
// Expand preamble macros into a set of code items.
683-
let preamble = expandMacros(attachedTo: DeclSyntax(node), ofType: PreambleMacro.Type.self) { attributeNode, definition in
700+
let preamble = expandMacros(attachedTo: DeclSyntax(node), ofType: PreambleMacro.Type.self) { attributeNode, definition, _ in
684701
expandPreambleMacro(
685702
definition: definition,
686703
attributeNode: attributeNode,
@@ -691,7 +708,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
691708
}
692709

693710
// Expand body macro.
694-
let expandedBodies = expandMacros(attachedTo: DeclSyntax(node), ofType: BodyMacro.Type.self) { attributeNode, definition in
711+
let expandedBodies = expandMacros(attachedTo: DeclSyntax(node), ofType: BodyMacro.Type.self) { attributeNode, definition, _ in
695712
expandBodyMacro(
696713
definition: definition,
697714
attributeNode: attributeNode,
@@ -900,40 +917,40 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
900917
// MARK: Attached macro expansions.
901918

902919
extension MacroApplication {
903-
/// Get pairs of a macro attribute and the macro definition attached to `decl`.
920+
/// Get pairs of a macro attribute and the macro specification attached to `decl`.
904921
///
905922
/// The macros must be registered in `macroSystem`.
906923
private func macroAttributes(
907924
attachedTo decl: DeclSyntax
908-
) -> [(attributeNode: AttributeSyntax, definition: Macro.Type)] {
925+
) -> [(attributeNode: AttributeSyntax, spec: MacroSpec)] {
909926
guard let attributedNode = decl.asProtocol(WithAttributesSyntax.self) else {
910927
return []
911928
}
912929

913930
return attributedNode.attributes.compactMap {
914931
guard case let .attribute(attribute) = $0,
915932
let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text,
916-
let macro = macroSystem.lookup(attributeName)
933+
let macroSpec = macroSystem.lookup(attributeName)
917934
else {
918935
return nil
919936
}
920937

921-
return (attribute, macro)
938+
return (attribute, macroSpec)
922939
}
923940
}
924941

925-
/// Get pairs of a macro attribute and the macro definition attached to `decl`
926-
/// matching `ofType` macro type.
942+
/// Get a list of the macro attribute, the macro definition and the conformance
943+
/// protocols list attached to `decl` matching `ofType` macro type.
927944
///
928945
/// The macros must be registered in `macroSystem`.
929946
private func macroAttributes<MacroType>(
930947
attachedTo decl: DeclSyntax,
931948
ofType: MacroType.Type
932-
) -> [(attributeNode: AttributeSyntax, definition: MacroType)] {
949+
) -> [(attributeNode: AttributeSyntax, definition: MacroType, conformanceList: InheritedTypeListSyntax)] {
933950
return macroAttributes(attachedTo: decl)
934-
.compactMap { (attributeNode: AttributeSyntax, definition: Macro.Type) in
935-
if let macroType = definition as? MacroType {
936-
return (attributeNode, macroType)
951+
.compactMap { (attributeNode: AttributeSyntax, spec: MacroSpec) in
952+
if let macroType = spec.type as? MacroType {
953+
return (attributeNode, macroType, spec.inheritedTypeList)
937954
} else {
938955
return nil
939956
}
@@ -949,13 +966,13 @@ extension MacroApplication {
949966
>(
950967
attachedTo decl: DeclSyntax,
951968
ofType: MacroType.Type,
952-
expandMacro: (_ attributeNode: AttributeSyntax, _ definition: MacroType) throws -> ExpanedNodeCollection?
969+
expandMacro: (_ attributeNode: AttributeSyntax, _ definition: MacroType, _ conformanceList: InheritedTypeListSyntax) throws -> ExpanedNodeCollection?
953970
) -> [ExpandedNode] {
954971
var result: [ExpandedNode] = []
955972

956973
for macroAttribute in macroAttributes(attachedTo: decl, ofType: ofType) {
957974
do {
958-
if let expanded = try expandMacro(macroAttribute.attributeNode, macroAttribute.definition) {
975+
if let expanded = try expandMacro(macroAttribute.attributeNode, macroAttribute.definition, macroAttribute.conformanceList) {
959976
result += expanded
960977
}
961978
} catch {
@@ -973,7 +990,7 @@ extension MacroApplication {
973990
///
974991
/// - Returns: The macro-synthesized peers
975992
private func expandMemberDeclPeers(of decl: DeclSyntax) -> [MemberBlockItemSyntax] {
976-
return expandMacros(attachedTo: decl, ofType: PeerMacro.Type.self) { attributeNode, definition in
993+
return expandMacros(attachedTo: decl, ofType: PeerMacro.Type.self) { attributeNode, definition, conformanceList in
977994
return try expandPeerMacroMember(
978995
definition: definition,
979996
attributeNode: attributeNode,
@@ -993,7 +1010,7 @@ extension MacroApplication {
9931010
///
9941011
/// - Returns: The macro-synthesized peers
9951012
private func expandCodeBlockPeers(of decl: DeclSyntax) -> [CodeBlockItemSyntax] {
996-
return expandMacros(attachedTo: decl, ofType: PeerMacro.Type.self) { attributeNode, definition in
1013+
return expandMacros(attachedTo: decl, ofType: PeerMacro.Type.self) { attributeNode, definition, conformanceList in
9971014
return try expandPeerMacroCodeItem(
9981015
definition: definition,
9991016
attributeNode: attributeNode,
@@ -1008,11 +1025,12 @@ extension MacroApplication {
10081025
///
10091026
/// - Returns: The macro-synthesized extensions
10101027
private func expandExtensions(of decl: DeclSyntax) -> [CodeBlockItemSyntax] {
1011-
return expandMacros(attachedTo: decl, ofType: ExtensionMacro.Type.self) { attributeNode, definition in
1028+
return expandMacros(attachedTo: decl, ofType: ExtensionMacro.Type.self) { attributeNode, definition, conformanceList in
10121029
return try expandExtensionMacro(
10131030
definition: definition,
10141031
attributeNode: attributeNode,
10151032
attachedTo: decl,
1033+
conformanceList: conformanceList,
10161034
in: contextGenerator(Syntax(decl)),
10171035
indentationWidth: indentationWidth
10181036
)
@@ -1021,11 +1039,12 @@ extension MacroApplication {
10211039

10221040
/// Expand all 'member' macros attached to `decl`.
10231041
private func expandMembers(of decl: DeclSyntax) -> [MemberBlockItemSyntax] {
1024-
return expandMacros(attachedTo: decl, ofType: MemberMacro.Type.self) { attributeNode, definition in
1042+
return expandMacros(attachedTo: decl, ofType: MemberMacro.Type.self) { attributeNode, definition, conformanceList in
10251043
return try expandMemberMacro(
10261044
definition: definition,
10271045
attributeNode: attributeNode,
10281046
attachedTo: decl,
1047+
conformanceList: conformanceList,
10291048
in: contextGenerator(Syntax(decl)),
10301049
indentationWidth: indentationWidth
10311050
)
@@ -1041,7 +1060,7 @@ extension MacroApplication {
10411060
of decl: DeclSyntax,
10421061
parentDecl: DeclSyntax
10431062
) -> [AttributeListSyntax.Element] {
1044-
return expandMacros(attachedTo: parentDecl, ofType: MemberAttributeMacro.Type.self) { attributeNode, definition in
1063+
return expandMacros(attachedTo: parentDecl, ofType: MemberAttributeMacro.Type.self) { attributeNode, definition, conformanceList in
10451064
return try expandMemberAttributeMacro(
10461065
definition: definition,
10471066
attributeNode: attributeNode,
@@ -1147,7 +1166,7 @@ extension MacroApplication {
11471166
expandMacro: (_ macro: Macro.Type, _ node: any FreestandingMacroExpansionSyntax) throws -> ExpandedMacroType?
11481167
) -> MacroExpansionResult<ExpandedMacroType> {
11491168
guard let node,
1150-
let macro = macroSystem.lookup(node.macroName.text)
1169+
let macro = macroSystem.lookup(node.macroName.text)?.type
11511170
else {
11521171
return .notAMacro
11531172
}

Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,52 @@ func assertDiagnostic(
299299
/// - macros: The macros that should be expanded, provided as a dictionary
300300
/// mapping macro names (e.g., `"stringify"`) to implementation types
301301
/// (e.g., `StringifyMacro.self`).
302+
/// - testModuleName: The name of the test module to use.
303+
/// - testFileName: The name of the test file name to use.
304+
/// - indentationWidth: The indentation width used in the expansion.
305+
///
306+
/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:file:line:)``
307+
/// to also specify the list of conformances passed to the macro expansion.
308+
public func assertMacroExpansion(
309+
_ originalSource: String,
310+
expandedSource expectedExpandedSource: String,
311+
diagnostics: [DiagnosticSpec] = [],
312+
macros: [String: Macro.Type],
313+
applyFixIts: [String]? = nil,
314+
fixedSource expectedFixedSource: String? = nil,
315+
testModuleName: String = "TestModule",
316+
testFileName: String = "test.swift",
317+
indentationWidth: Trivia = .spaces(4),
318+
file: StaticString = #file,
319+
line: UInt = #line
320+
) {
321+
let specs = macros.mapValues { MacroSpec(type: $0) }
322+
assertMacroExpansion(
323+
originalSource,
324+
expandedSource: expectedExpandedSource,
325+
diagnostics: diagnostics,
326+
macroSpecs: specs,
327+
applyFixIts: applyFixIts,
328+
fixedSource: expectedFixedSource,
329+
testModuleName: testModuleName,
330+
testFileName: testFileName,
331+
indentationWidth: indentationWidth
332+
)
333+
}
334+
335+
/// Assert that expanding the given macros in the original source produces
336+
/// the given expanded source code.
337+
///
338+
/// - Parameters:
339+
/// - originalSource: The original source code, which is expected to contain
340+
/// macros in various places (e.g., `#stringify(x + y)`).
341+
/// - expectedExpandedSource: The source code that we expect to see after
342+
/// performing macro expansion on the original source.
343+
/// - diagnostics: The diagnostics when expanding any macro
344+
/// - macroSpecs: The macros that should be expanded, provided as a dictionary
345+
/// mapping macro names (e.g., `"CodableMacro"`) to specification with macro type
346+
/// (e.g., `CodableMacro.self`) and a list of conformances macro provides
347+
/// (e.g., `["Decodable", "Encodable"]`).
302348
/// - applyFixIts: If specified, filters the Fix-Its that are applied to generate `fixedSource` to only those whose message occurs in this array. If `nil`, all Fix-Its from the diagnostics are applied.
303349
/// - fixedSource: If specified, asserts that the source code after applying Fix-Its matches this string.
304350
/// - testModuleName: The name of the test module to use.
@@ -308,7 +354,7 @@ public func assertMacroExpansion(
308354
_ originalSource: String,
309355
expandedSource expectedExpandedSource: String,
310356
diagnostics: [DiagnosticSpec] = [],
311-
macros: [String: Macro.Type],
357+
macroSpecs: [String: MacroSpec],
312358
applyFixIts: [String]? = nil,
313359
fixedSource expectedFixedSource: String? = nil,
314360
testModuleName: String = "TestModule",
@@ -329,7 +375,7 @@ public func assertMacroExpansion(
329375
return BasicMacroExpansionContext(sharingWith: context, lexicalContext: syntax.allMacroLexicalContexts())
330376
}
331377

332-
let expandedSourceFile = origSourceFile.expand(macros: macros, contextGenerator: contextGenerator, indentationWidth: indentationWidth)
378+
let expandedSourceFile = origSourceFile.expand(macroSpecs: macroSpecs, contextGenerator: contextGenerator, indentationWidth: indentationWidth)
333379
let diags = ParseDiagnosticsGenerator.diagnostics(for: expandedSourceFile)
334380
if !diags.isEmpty {
335381
XCTFail(

0 commit comments

Comments
 (0)