Skip to content

Commit e77b169

Browse files
authored
Merge pull request #2450 from ktoso/wip-macros-generics
[macros] prepare generic arguments for replacement in macros
2 parents 89dd2bd + 9d22af9 commit e77b169

File tree

3 files changed

+367
-24
lines changed

3 files changed

+367
-24
lines changed

Release Notes/511.md

+10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
- `String.isValidIdentifier(for:)`
3636
- Description: `SwiftParser` adds an extension on `String` to check if it can be used as an identifier in a given context.
3737
- Pull Request: https://github.com/apple/swift-syntax/pull/2434
38+
39+
- `MacroDeclSyntax.expand`
40+
- the `expand(argumentList:definition:replacements:)` method gains a new parameter 'genericReplacements:' that is defaulted to an empty array.
41+
- The method's signature is now `expand(argumentList:definition:replacements:genericReplacements:)`
42+
- Pull Request: https://github.com/apple/swift-syntax/pull/2450
3843

3944
- `SyntaxProtocol.asMacroLexicalContext()` and `allMacroLexicalContexts(enclosingSyntax:)`
4045
- Description: Produce the lexical context for a given syntax node (if it has one), or the entire stack of lexical contexts enclosing a syntax node, for use in macro expansion.
@@ -67,6 +72,11 @@
6772

6873
## API-Incompatible Changes
6974

75+
- `MacroDefinition` used for expanding macros:
76+
- Description: The `MacroDefinition/expansion` enum case used to have two values (`(MacroExpansionExprSyntax, replacements: [Replacement])`), has now gained another value in order to support generic argument replacements in macro expansions: `(MacroExpansionExprSyntax, replacements: [Replacement], genericReplacements: [GenericArgumentReplacement])`
77+
- Pull request: https://github.com/apple/swift-syntax/pull/2450
78+
- Migration steps: Code which exhaustively checked over the enum should be changed to `case .expansion(let node, let replacements, let genericReplacements):`. Creating the `.extension` gained a compatibility shim, retaining the previous syntax source compatible (`return .expansion(node, replacements: [])`).
79+
7080
- Effect specifiers:
7181
- Description: The `unexpectedAfterThrowsSpecifier` node of the various effect specifiers has been removed.
7282
- Pull request: https://github.com/apple/swift-syntax/pull/2219

Sources/SwiftSyntaxMacroExpansion/MacroReplacement.swift

+123-17
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(TokenSyntax)
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,15 @@ 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])
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+
@available(*, deprecated, message: "Use the expansion case with three associated values instead")
71+
public func expansion(_ node: MacroExpansionExprSyntax, replacements: [Replacement]) -> Self {
72+
.expansion(node, replacements: replacements, genericReplacements: [])
73+
}
6274
}
6375

6476
extension MacroDefinition {
@@ -70,11 +82,21 @@ extension MacroDefinition {
7082
/// The index of the parameter in the defining macro.
7183
public let parameterIndex: Int
7284
}
85+
86+
/// A replacement that occurs as part of an expanded macro definition.
87+
public struct GenericArgumentReplacement {
88+
/// A reference to a parameter as it occurs in the macro expansion expression.
89+
public let reference: GenericArgumentSyntax
90+
91+
/// The index of the parameter in the defining macro.
92+
public let parameterIndex: Int
93+
}
7394
}
7495

7596
fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor {
7697
let macro: MacroDeclSyntax
7798
var replacements: [MacroDefinition.Replacement] = []
99+
var genericReplacements: [MacroDefinition.GenericArgumentReplacement] = []
78100
var diagnostics: [Diagnostic] = []
79101

80102
init(macro: MacroDeclSyntax) {
@@ -156,6 +178,44 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor {
156178
return .visitChildren
157179
}
158180

181+
override func visit(_ node: GenericArgumentClauseSyntax) -> SyntaxVisitorContinueKind {
182+
return .visitChildren
183+
}
184+
185+
override func visit(_ node: GenericArgumentListSyntax) -> SyntaxVisitorContinueKind {
186+
return .visitChildren
187+
}
188+
189+
override func visit(_ node: GenericArgumentSyntax) -> SyntaxVisitorContinueKind {
190+
guard let baseName = node.argument.as(IdentifierTypeSyntax.self)?.name else {
191+
return .skipChildren
192+
}
193+
194+
guard let genericParameterClause = macro.genericParameterClause else {
195+
return .skipChildren
196+
}
197+
198+
let matchedParameter = genericParameterClause.parameters.enumerated().first { (index, parameter) in
199+
return parameter.name.text == baseName.text
200+
}
201+
202+
guard let (parameterIndex, _) = matchedParameter else {
203+
// We have a reference to something that isn't a parameter of the macro.
204+
diagnostics.append(
205+
Diagnostic(
206+
node: Syntax(baseName),
207+
message: MacroExpanderError.nonTypeReference(baseName)
208+
)
209+
)
210+
211+
return .visitChildren
212+
}
213+
214+
genericReplacements.append(.init(reference: node, parameterIndex: parameterIndex))
215+
216+
return .visitChildren
217+
}
218+
159219
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
160220
if let expr = node.as(ExprSyntax.self) {
161221
// We have an expression that is not one of the allowed forms, so
@@ -230,7 +290,7 @@ extension MacroDeclSyntax {
230290
throw DiagnosticsError(diagnostics: visitor.diagnostics)
231291
}
232292

233-
return .expansion(definition, replacements: visitor.replacements)
293+
return .expansion(definition, replacements: visitor.replacements, genericReplacements: visitor.genericReplacements)
234294
}
235295
}
236296

@@ -239,10 +299,19 @@ extension MacroDeclSyntax {
239299
private final class MacroExpansionRewriter: SyntaxRewriter {
240300
let parameterReplacements: [DeclReferenceExprSyntax: Int]
241301
let arguments: [ExprSyntax]
242-
243-
init(parameterReplacements: [DeclReferenceExprSyntax: Int], arguments: [ExprSyntax]) {
302+
let genericParameterReplacements: [GenericArgumentSyntax: Int]
303+
let genericArguments: [TypeSyntax]
304+
305+
init(
306+
parameterReplacements: [DeclReferenceExprSyntax: Int],
307+
arguments: [ExprSyntax],
308+
genericReplacements: [GenericArgumentSyntax: Int],
309+
genericArguments: [TypeSyntax]
310+
) {
244311
self.parameterReplacements = parameterReplacements
245312
self.arguments = arguments
313+
self.genericParameterReplacements = genericReplacements
314+
self.genericArguments = genericArguments
246315
super.init(viewMode: .sourceAccurate)
247316
}
248317

@@ -254,31 +323,62 @@ private final class MacroExpansionRewriter: SyntaxRewriter {
254323
// Swap in the argument for this parameter
255324
return arguments[parameterIndex].trimmed
256325
}
326+
327+
override func visit(_ node: GenericArgumentSyntax) -> GenericArgumentSyntax {
328+
guard let parameterIndex = genericParameterReplacements[node] else {
329+
return super.visit(node)
330+
}
331+
332+
guard parameterIndex < genericArguments.count else {
333+
return super.visit(node)
334+
}
335+
336+
// Swap in the argument for type parameter
337+
var node = node
338+
node.argument = genericArguments[parameterIndex].trimmed
339+
return node
340+
}
257341
}
258342

259343
extension MacroDeclSyntax {
260344
/// Expand the definition of this macro when provided with the given
261345
/// argument list.
262346
private func expand(
263347
argumentList: LabeledExprListSyntax?,
348+
genericArgumentList: GenericArgumentClauseSyntax?,
264349
definition: MacroExpansionExprSyntax,
265-
replacements: [MacroDefinition.Replacement]
350+
replacements: [MacroDefinition.Replacement],
351+
genericReplacements: [MacroDefinition.GenericArgumentReplacement] = []
266352
) -> ExprSyntax {
267353
// FIXME: Do real call-argument matching between the argument list and the
268354
// macro parameter list, porting over from the compiler.
355+
let parameterReplacements = Dictionary(
356+
replacements.map { replacement in
357+
(replacement.reference, replacement.parameterIndex)
358+
},
359+
uniquingKeysWith: { l, r in l }
360+
)
269361
let arguments: [ExprSyntax] =
270362
argumentList?.map { element in
271363
element.expression
272364
} ?? []
273365

274-
return MacroExpansionRewriter(
275-
parameterReplacements: Dictionary(
276-
uniqueKeysWithValues: replacements.map { replacement in
277-
(replacement.reference, replacement.parameterIndex)
278-
}
279-
),
280-
arguments: arguments
281-
).visit(definition)
366+
let genericReplacements = Dictionary(
367+
genericReplacements.map { replacement in
368+
(replacement.reference, replacement.parameterIndex)
369+
},
370+
uniquingKeysWith: { l, r in l }
371+
)
372+
let genericArguments: [TypeSyntax] =
373+
genericArgumentList?.arguments.map { $0.argument } ?? []
374+
375+
let rewriter = MacroExpansionRewriter(
376+
parameterReplacements: parameterReplacements,
377+
arguments: arguments,
378+
genericReplacements: genericReplacements,
379+
genericArguments: genericArguments
380+
)
381+
return rewriter.visit(definition)
282382
}
283383

284384
/// Given a freestanding macro expansion syntax node that references this
@@ -287,12 +387,15 @@ extension MacroDeclSyntax {
287387
public func expand(
288388
_ node: some FreestandingMacroExpansionSyntax,
289389
definition: MacroExpansionExprSyntax,
290-
replacements: [MacroDefinition.Replacement]
390+
replacements: [MacroDefinition.Replacement],
391+
genericReplacements: [MacroDefinition.GenericArgumentReplacement] = []
291392
) -> ExprSyntax {
292393
return expand(
293394
argumentList: node.arguments,
395+
genericArgumentList: node.genericArgumentClause,
294396
definition: definition,
295-
replacements: replacements
397+
replacements: replacements,
398+
genericReplacements: genericReplacements
296399
)
297400
}
298401

@@ -302,7 +405,8 @@ extension MacroDeclSyntax {
302405
public func expand(
303406
_ node: AttributeSyntax,
304407
definition: MacroExpansionExprSyntax,
305-
replacements: [MacroDefinition.Replacement]
408+
replacements: [MacroDefinition.Replacement],
409+
genericReplacements: [MacroDefinition.GenericArgumentReplacement] = []
306410
) -> ExprSyntax {
307411
// Dig out the argument list.
308412
let argumentList: LabeledExprListSyntax?
@@ -314,8 +418,10 @@ extension MacroDeclSyntax {
314418

315419
return expand(
316420
argumentList: argumentList,
421+
genericArgumentList: .init(arguments: []),
317422
definition: definition,
318-
replacements: replacements
423+
replacements: replacements,
424+
genericReplacements: genericReplacements
319425
)
320426
}
321427
}

0 commit comments

Comments
 (0)