Skip to content

Commit e861835

Browse files
committed
add test and complete impl with error handling
1 parent 92ef2db commit e861835

File tree

2 files changed

+92
-22
lines changed

2 files changed

+92
-22
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroReplacement.swift

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ enum MacroExpanderError: DiagnosticMessage {
1818
case undefined
1919
case definitionNotMacroExpansion
2020
case nonParameterReference(TokenSyntax)
21+
case nonTypeReference(TypeSyntax)
2122
case nonLiteralOrParameter(ExprSyntax)
2223

2324
var message: String {
@@ -31,6 +32,9 @@ enum MacroExpanderError: DiagnosticMessage {
3132
case .nonParameterReference(let name):
3233
return "reference to value '\(name.text)' that is not a macro parameter in expansion"
3334

35+
case .nonTypeReference(let name):
36+
return "reference to type '\(name)' that is not a macro type parameter in expansion"
37+
3438
case .nonLiteralOrParameter:
3539
return "only literals and macro parameters are permitted in expansion"
3640
}
@@ -58,7 +62,14 @@ public enum MacroDefinition {
5862
/// defining macro. These subtrees will need to be replaced with the text of
5963
/// the corresponding argument to the macro, which can be accomplished with
6064
/// `MacroDeclSyntax.expandDefinition`.
61-
case expansion(MacroExpansionExprSyntax, replacements: [Replacement], genericReplacements: [GenericArgumentReplacement]) // FIXME: do we need to evolve this without breaking? I assume so?
65+
case expansion(MacroExpansionExprSyntax, replacements: [Replacement], genericReplacements: [GenericArgumentReplacement])
66+
}
67+
68+
extension MacroDefinition {
69+
/// Best effort compatibility shim, the case has gained additional parameters.
70+
public func expansion(_ node: MacroExpansionExprSyntax, replacements: [Replacement]) -> Self {
71+
.expansion(node, replacements: replacements, genericReplacements: [])
72+
}
6273
}
6374

6475
extension MacroDefinition {
@@ -175,27 +186,23 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor {
175186
}
176187

177188
override func visit(_ node: GenericArgumentSyntax) -> SyntaxVisitorContinueKind {
178-
let baseName = "\(node)" // FIXME: where to get the name from properly
189+
let baseName = node.argument
179190
guard let genericParameterClause = macro.genericParameterClause else {
180191
return .skipChildren
181192
}
182193

183194
let matchedParameter = genericParameterClause.parameters.enumerated().first { (index, parameter) in
184-
// guard let parameterName = parameter.parameterName else {
185-
// return false
186-
// }
187-
188-
return true; // FIXME: what do we need to match here
195+
return parameter.name.text == "\(baseName)"
189196
}
190197

191198
guard let (parameterIndex, _) = matchedParameter else {
192199
// We have a reference to something that isn't a parameter of the macro.
193-
// diagnostics.append(
194-
// Diagnostic(
195-
// node: Syntax(baseName), // FIXME: error should be about type argument
196-
// message: MacroExpanderError.nonParameterReference(baseName)
197-
// )
198-
// )
200+
diagnostics.append(
201+
Diagnostic(
202+
node: Syntax(baseName),
203+
message: MacroExpanderError.nonTypeReference(baseName)
204+
)
205+
)
199206

200207
return .visitChildren
201208
}
@@ -287,10 +294,14 @@ extension MacroDeclSyntax {
287294
/// uses of macro parameters with their corresponding arguments.
288295
private final class MacroExpansionRewriter: SyntaxRewriter {
289296
let parameterReplacements: [DeclReferenceExprSyntax: Int]
297+
let genericParameterReplacements: [DeclReferenceExprSyntax: Int]
290298
let arguments: [ExprSyntax]
291299

292-
init(parameterReplacements: [DeclReferenceExprSyntax: Int], arguments: [ExprSyntax]) {
300+
init(parameterReplacements: [DeclReferenceExprSyntax: Int],
301+
genericReplacements: [DeclReferenceExprSyntax: Int],
302+
arguments: [ExprSyntax]) {
293303
self.parameterReplacements = parameterReplacements
304+
self.genericParameterReplacements = genericReplacements
294305
self.arguments = arguments
295306
super.init(viewMode: .sourceAccurate)
296307
}
@@ -311,7 +322,8 @@ extension MacroDeclSyntax {
311322
private func expand(
312323
argumentList: LabeledExprListSyntax?,
313324
definition: MacroExpansionExprSyntax,
314-
replacements: [MacroDefinition.Replacement]
325+
replacements: [MacroDefinition.Replacement],
326+
genericReplacements: [MacroDefinition.GenericArgumentReplacement]
315327
) -> ExprSyntax {
316328
// FIXME: Do real call-argument matching between the argument list and the
317329
// macro parameter list, porting over from the compiler.
@@ -326,6 +338,11 @@ extension MacroDeclSyntax {
326338
(replacement.reference, replacement.parameterIndex)
327339
}
328340
),
341+
genericReplacements: Dictionary(
342+
uniqueKeysWithValues: replacements.map { replacement in
343+
(replacement.reference, replacement.parameterIndex)
344+
}
345+
),
329346
arguments: arguments
330347
).visit(definition)
331348
}
@@ -336,12 +353,14 @@ extension MacroDeclSyntax {
336353
public func expand(
337354
_ node: some FreestandingMacroExpansionSyntax,
338355
definition: MacroExpansionExprSyntax,
339-
replacements: [MacroDefinition.Replacement]
356+
replacements: [MacroDefinition.Replacement],
357+
genericReplacements: [MacroDefinition.GenericArgumentReplacement]
340358
) -> ExprSyntax {
341359
return expand(
342360
argumentList: node.arguments,
343361
definition: definition,
344-
replacements: replacements
362+
replacements: replacements,
363+
genericReplacements: genericReplacements
345364
)
346365
}
347366

@@ -351,7 +370,8 @@ extension MacroDeclSyntax {
351370
public func expand(
352371
_ node: AttributeSyntax,
353372
definition: MacroExpansionExprSyntax,
354-
replacements: [MacroDefinition.Replacement]
373+
replacements: [MacroDefinition.Replacement],
374+
genericReplacements: [MacroDefinition.GenericArgumentReplacement]
355375
) -> ExprSyntax {
356376
// Dig out the argument list.
357377
let argumentList: LabeledExprListSyntax?
@@ -364,7 +384,8 @@ extension MacroDeclSyntax {
364384
return expand(
365385
argumentList: argumentList,
366386
definition: definition,
367-
replacements: replacements
387+
replacements: replacements,
388+
genericReplacements: genericReplacements
368389
)
369390
}
370391
}

Tests/SwiftSyntaxMacroExpansionTest/MacroReplacementTests.swift

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ final class MacroReplacementTests: XCTestCase {
2525
"""
2626

2727
let definition = try macro.as(MacroDeclSyntax.self)!.checkDefinition()
28-
guard case let .expansion(_, replacements) = definition else {
28+
guard case let .expansion(_, replacements, _) = definition else {
2929
XCTFail("not an expansion definition")
3030
fatalError()
3131
}
@@ -96,15 +96,16 @@ final class MacroReplacementTests: XCTestCase {
9696

9797
let macroDecl = macro.as(MacroDeclSyntax.self)!
9898
let definition = try macroDecl.checkDefinition()
99-
guard case let .expansion(expansion, replacements) = definition else {
99+
guard case let .expansion(expansion, replacements, genericReplacements) = definition else {
100100
XCTFail("not a normal expansion")
101101
fatalError()
102102
}
103103

104104
let expandedSyntax = macroDecl.expand(
105105
use.as(MacroExpansionExprSyntax.self)!,
106106
definition: expansion,
107-
replacements: replacements
107+
replacements: replacements,
108+
genericReplacements: genericReplacements
108109
)
109110
assertStringsEqualWithDiff(
110111
expandedSyntax.description,
@@ -113,4 +114,52 @@ final class MacroReplacementTests: XCTestCase {
113114
"""
114115
)
115116
}
117+
118+
func testMacroGenericArgumentExpansion() throws {
119+
let macro: DeclSyntax =
120+
"""
121+
macro gen<A, B>(a: A, b: B) = #otherMacro<A, B>(first: a, second: b)
122+
"""
123+
124+
let use: ExprSyntax =
125+
"""
126+
#gen<Int, String>(a: 5, b: "Hello")
127+
"""
128+
129+
let macroDecl = macro.as(MacroDeclSyntax.self)!
130+
let definition = try macroDecl.checkDefinition()
131+
guard case let .expansion(expansion, replacements, genericReplacements) = definition else {
132+
XCTFail("not a normal expansion")
133+
fatalError()
134+
}
135+
136+
guard let replacementA = genericReplacements.first else {
137+
XCTFail("Expected generic replacement for A")
138+
fatalError()
139+
}
140+
guard let replacementB = genericReplacements.dropFirst().first else {
141+
XCTFail("Expected generic replacement for A")
142+
fatalError()
143+
}
144+
XCTAssertEqual(genericReplacements.count, 2)
145+
146+
XCTAssertEqual(replacementA.parameterIndex, 0)
147+
XCTAssertEqual("\(replacementA.reference.argument)", "A")
148+
149+
XCTAssertEqual(replacementB.parameterIndex, 1)
150+
XCTAssertEqual("\(replacementB.reference.argument)", "B")
151+
152+
let expandedSyntax = macroDecl.expand(
153+
use.as(MacroExpansionExprSyntax.self)!,
154+
definition: expansion,
155+
replacements: replacements,
156+
genericReplacements: genericReplacements
157+
)
158+
assertStringsEqualWithDiff(
159+
expandedSyntax.description,
160+
"""
161+
#otherMacro<A, B>(first: 5, second: "Hello")
162+
"""
163+
)
164+
}
116165
}

0 commit comments

Comments
 (0)