11import SwiftDiagnostics
22import SwiftSyntax
3-
4- /// The replacement of a parameter.
5- @_spi ( Testing)
6- public struct ParameterReplacement {
7- /// A reference to a parameter as it occurs in the macro expansion expression.
8- public let reference : IdentifierExprSyntax
9-
10- /// The index of the parameter
11- public let parameterIndex : Int
12- }
3+ import SwiftSyntaxBuilder
134
145extension FunctionParameterSyntax {
156 /// Retrieve the name of the parameter as it is used in source.
@@ -44,6 +35,7 @@ extension FunctionParameterSyntax {
4435
4536enum MacroExpanderError : DiagnosticMessage {
4637 case undefined
38+ case definitionNotMacroExpansion
4739 case nonParameterReference( TokenSyntax )
4840 case nonLiteralOrParameter( ExprSyntax )
4941
@@ -52,6 +44,9 @@ enum MacroExpanderError: DiagnosticMessage {
5244 case . undefined:
5345 return " macro expansion requires a definition "
5446
47+ case . definitionNotMacroExpansion:
48+ return " macro definition must itself by a macro expansion expression (starting with '#') "
49+
5550 case . nonParameterReference( let name) :
5651 return " reference to value ' \( name. text) ' that is not a macro parameter in expansion "
5752
@@ -69,9 +64,36 @@ enum MacroExpanderError: DiagnosticMessage {
6964 }
7065}
7166
67+ /// Provide the definition of a macro
68+ public enum MacroDefinition {
69+ /// An externally-defined macro, known by its type name and the module in
70+ /// which that type resides, which uses the deprecated syntax `A.B`.
71+ case deprecatedExternal( node: Syntax , module: String , type: String )
72+
73+ /// A macro that is defined by expansion of another macro.
74+ ///
75+ /// The definition has the macro expansion expression itself, along with
76+ /// sequence of replacements for subtrees that refer to parameters of the
77+ /// defining macro. These subtrees will need to be replaced with the text of
78+ /// the corresponding argument to the macro, which can be accomplished with
79+ /// `MacroDeclSyntax.expandDefinition`.
80+ case expansion( MacroExpansionExprSyntax , replacements: [ Replacement ] )
81+ }
82+
83+ extension MacroDefinition {
84+ /// A replacement that occurs as part of an expanded macro definition.
85+ public struct Replacement {
86+ /// A reference to a parameter as it occurs in the macro expansion expression.
87+ public let reference : IdentifierExprSyntax
88+
89+ /// The index of the parameter in the defining macro.
90+ public let parameterIndex : Int
91+ }
92+ }
93+
7294fileprivate class ParameterReplacementVisitor : SyntaxAnyVisitor {
7395 let macro : MacroDeclSyntax
74- var replacements : [ ParameterReplacement ] = [ ]
96+ var replacements : [ MacroDefinition . Replacement ] = [ ]
7597 var diagnostics : [ Diagnostic ] = [ ]
7698
7799 init ( macro: MacroDeclSyntax ) {
@@ -159,7 +181,8 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor {
159181
160182 override func visitAny( _ node: Syntax ) -> SyntaxVisitorContinueKind {
161183 if let expr = node. as ( ExprSyntax . self) {
162- // We have an expression that is not one of the allowed forms,
184+ // We have an expression that is not one of the allowed forms, so
185+ // diagnose it.
163186 diagnostics. append (
164187 Diagnostic (
165188 node: node,
@@ -176,24 +199,61 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor {
176199}
177200
178201extension MacroDeclSyntax {
202+ /// Check the definition of the given macro.
203+ ///
204+ /// Macros are defined by an expression, which must itself be a macro
205+ /// expansion. Check the definition and produce a semantic representation of
206+ /// it or one of the "builtin"
207+ ///
179208 /// Compute the sequence of parameter replacements required when expanding
180209 /// the definition of a non-external macro.
181- @_spi ( Testing)
182- public func expansionParameterReplacements( ) -> ( replacements: [ ParameterReplacement ] , diagnostics: [ Diagnostic ] ) {
210+ ///
211+ /// If there are an errors that prevent expansion, the diagnostics will be
212+ /// wrapped into a an error that prevents expansion, that error is thrown.
213+ public func checkDefinition( ) throws -> MacroDefinition {
183214 // Cannot compute replacements for an undefined macro.
184- guard let definition = definition? . value else {
215+ guard let originalDefinition = definition? . value else {
185216 let undefinedDiag = Diagnostic (
186217 node: Syntax ( self ) ,
187218 message: MacroExpanderError . undefined
188219 )
189220
190- return ( replacements: [ ] , diagnostics: [ undefinedDiag] )
221+ throw DiagnosticsError ( diagnostics: [ undefinedDiag] )
222+ }
223+
224+ /// Recognize the deprecated syntax A.B. Clients will need to
225+ /// handle this themselves.
226+ if let memberAccess = originalDefinition. as ( MemberAccessExprSyntax . self) ,
227+ let base = memberAccess. base,
228+ let baseName = base. as ( IdentifierExprSyntax . self) ? . identifier
229+ {
230+ let memberName = memberAccess. name
231+ return . deprecatedExternal(
232+ node: Syntax ( memberAccess) ,
233+ module: baseName. trimmedDescription,
234+ type: memberName. trimmedDescription
235+ )
236+ }
237+
238+ // Make sure we have a macro expansion expression.
239+ guard let definition = originalDefinition. as ( MacroExpansionExprSyntax . self) else {
240+ let badDefinitionDiag =
241+ Diagnostic (
242+ node: Syntax ( originalDefinition) ,
243+ message: MacroExpanderError . definitionNotMacroExpansion
244+ )
245+
246+ throw DiagnosticsError ( diagnostics: [ badDefinitionDiag] )
191247 }
192248
193249 let visitor = ParameterReplacementVisitor ( macro: self )
194250 visitor. walk ( definition)
195251
196- return ( replacements: visitor. replacements, diagnostics: visitor. diagnostics)
252+ if !visitor. diagnostics. isEmpty {
253+ throw DiagnosticsError ( diagnostics: visitor. diagnostics)
254+ }
255+
256+ return . expansion( definition, replacements: visitor. replacements)
197257 }
198258}
199259
@@ -219,27 +279,18 @@ private final class MacroExpansionRewriter: SyntaxRewriter {
219279}
220280
221281extension MacroDeclSyntax {
222- /// Given a freestanding macro expansion syntax node that references this
223- /// macro declaration, expand the macro by substituting the arguments from
224- /// the macro expansion into the parameters that are used in the definition.
225- ///
226- /// If there are any errors, the function will throw with all diagnostics
227- /// placed in a `DiagnosticsError`.
228- public func expandDefinition(
229- _ node: some FreestandingMacroExpansionSyntax
230- ) throws -> ExprSyntax {
231- let ( replacements, diagnostics) = expansionParameterReplacements ( )
232-
233- // If there were any diagnostics, don't allow replacement.
234- if !diagnostics. isEmpty {
235- throw DiagnosticsError ( diagnostics: diagnostics)
236- }
237-
282+ /// Expand the definition of this macro when provided with the given
283+ /// argument list.
284+ private func expand(
285+ argumentList: TupleExprElementListSyntax ? ,
286+ definition: MacroExpansionExprSyntax ,
287+ replacements: [ MacroDefinition . Replacement ]
288+ ) -> ExprSyntax {
238289 // FIXME: Do real call-argument matching between the argument list and the
239290 // macro parameter list, porting over from the compiler.
240- let arguments : [ ExprSyntax ] = node . argumentList. map { element in
291+ let arguments : [ ExprSyntax ] = argumentList? . map { element in
241292 element. expression
242- }
293+ } ?? [ ]
243294
244295 return MacroExpansionRewriter (
245296 parameterReplacements: Dictionary (
@@ -248,6 +299,44 @@ extension MacroDeclSyntax {
248299 }
249300 ) ,
250301 arguments: arguments
251- ) . visit ( definition!. value)
302+ ) . visit ( definition)
303+ }
304+
305+ /// Given a freestanding macro expansion syntax node that references this
306+ /// macro declaration, expand the macro by substituting the arguments from
307+ /// the macro expansion into the parameters that are used in the definition.
308+ public func expand(
309+ _ node: some FreestandingMacroExpansionSyntax ,
310+ definition: MacroExpansionExprSyntax ,
311+ replacements: [ MacroDefinition . Replacement ]
312+ ) -> ExprSyntax {
313+ return try expand (
314+ argumentList: node. argumentList,
315+ definition: definition,
316+ replacements: replacements
317+ )
318+ }
319+
320+ /// Given an attached macro expansion syntax node that references this
321+ /// macro declaration, expand the macro by substituting the arguments from
322+ /// the expansion into the parameters that are used in the definition.
323+ public func expand(
324+ _ node: AttributeSyntax ,
325+ definition: MacroExpansionExprSyntax ,
326+ replacements: [ MacroDefinition . Replacement ]
327+ ) -> ExprSyntax {
328+ // Dig out the argument list.
329+ let argumentList : TupleExprElementListSyntax ?
330+ if case let . argumentList( argList) = node. argument {
331+ argumentList = argList
332+ } else {
333+ argumentList = nil
334+ }
335+
336+ return try expand (
337+ argumentList: argumentList,
338+ definition: definition,
339+ replacements: replacements
340+ )
252341 }
253342}
0 commit comments