Skip to content

Commit 8fff8dd

Browse files
committed
Switch MacroExpansionContext to an protocol
The macros design has moved to having `MacroExpansionContext` be a class-bound protocol. Adjust the implementation accordingly, and add a `TestingMacroExpansionContext` for a concrete implementation to use in test cases. And since I didn't want to break the `#fileID` macro that used to work, implement a general scheme for getting the location of a syntax node :).
1 parent a09c4fc commit 8fff8dd

12 files changed

+321
-104
lines changed

Sources/_SwiftSyntaxMacros/MacroExpansionContext.swift

Lines changed: 72 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,85 @@
1313
import SwiftSyntax
1414
import SwiftDiagnostics
1515

16-
/// System-supplied structure that provides information about the context in
17-
/// which a given macro is being expanded.
18-
public struct MacroExpansionContext {
19-
/// The name of the module in which the macro is being evaluated.
20-
public let moduleName: String
21-
22-
/// The name of the source file in which the macro is being evaluated,
23-
/// without any path information.
16+
/// Interface to extract information about the context in which a given
17+
/// macro is expanded.
18+
public protocol MacroExpansionContext: AnyObject {
19+
/// Generate a unique name for use in the macro.
20+
///
21+
/// - Parameters:
22+
/// - name: The name to use as a basis for the uniquely-generated name,
23+
/// which will appear in the unique name that's produced here.
2424
///
25-
/// Swift prevents two files within the same module from having the same
26-
/// name, so the combination of file and module name is unique.
27-
public let fileName: String
25+
/// - Returns: an identifier token containing a unique name that will not
26+
/// conflict with any other name in a well-formed program.
27+
func createUniqueName(_ name: String) -> TokenSyntax
2828

29-
/// Counter used to generate names local to the macro.
30-
var localNameCounter = 0
29+
/// Produce a diagnostic while expanding the macro.
30+
func diagnose(_ diagnostic: Diagnostic)
3131

32-
/// The set of diagnostics that were emitted as part of expanding the
33-
/// macro.
34-
public private(set) var diagnostics: [Diagnostic] = []
32+
/// Retrieve a source location for the given syntax node.
33+
///
34+
/// - Parameters:
35+
/// - node: The syntax node whose source location to produce.
36+
/// - position: The position within the syntax node for the resulting
37+
/// location.
38+
/// - filePathMode: How the file name contained in the source location is
39+
/// formed.
40+
///
41+
/// - Returns: the source location within the given node, or `nil` if the
42+
/// given syntax node is not rooted in a source file that the macro
43+
/// expansion context knows about.
44+
func location<Node: SyntaxProtocol> (
45+
of node: Node,
46+
at position: PositionInSyntaxNode,
47+
filePathMode: SourceLocationFilePathMode
48+
) -> SourceLocation?
49+
}
3550

36-
/// Create a new macro evaluation context.
37-
public init(moduleName: String, fileName: String) {
38-
self.moduleName = moduleName
39-
self.fileName = fileName
51+
extension MacroExpansionContext {
52+
/// Retrieve a source location for the given syntax node's starting token
53+
/// (after leading trivia) using file naming according to `#fileID`.
54+
///
55+
/// - Parameters:
56+
/// - node: The syntax node whose source location to produce.
57+
///
58+
/// - Returns: the source location within the given node, or `nil` if the
59+
/// given syntax node is not rooted in a source file that the macro
60+
/// expansion context knows about.
61+
public func location<Node: SyntaxProtocol> (
62+
of node: Node
63+
) -> SourceLocation? {
64+
return location(of: node, at: .afterLeadingTrivia, filePathMode: .fileID)
4065
}
66+
}
4167

42-
/// Generate a unique local name for use in the macro.
43-
public mutating func createUniqueLocalName() -> TokenSyntax {
44-
let name = "__macro_local_\(localNameCounter)"
45-
let token = TokenSyntax(.identifier(name), presence: .present)
46-
localNameCounter += 1
47-
return token
48-
}
68+
/// Describe the position within a syntax node that can be used to compute
69+
/// source locations.
70+
public enum PositionInSyntaxNode {
71+
/// Refers to the start of the syntax node's leading trivia, which is
72+
/// the first source location covered by the syntax node.
73+
case beforeLeadingTrivia
4974

50-
/// Create a new macro evaluation context.
51-
@available(*, deprecated, message: "Use init(moduleName:fileName:)")
52-
public init(
53-
moduleName: String,
54-
sourceLocationConverter: SourceLocationConverter
55-
) {
56-
let fileName =
57-
sourceLocationConverter.location(
58-
for: AbsolutePosition(utf8Offset: -1)
59-
).file ?? "unknown.swift"
75+
/// Refers to the start of the syntax node's first token, which
76+
/// immediately follows the leading trivia.
77+
case afterLeadingTrivia
6078

61-
self.init(moduleName: moduleName, fileName: fileName)
62-
}
79+
/// Refers to the end of the syntax node's last token, right before the
80+
/// trailing trivia.
81+
case beforeTrailingTrivia
6382

64-
/// Produce a diagnostic while expanding the macro.
65-
public mutating func diagnose(_ diagnostic: Diagnostic) {
66-
diagnostics.append(diagnostic)
67-
}
83+
/// Refers just past the end of the source text that is covered by the
84+
/// syntax node, after all trailing trivia.
85+
case afterTrailingTrivia
86+
}
87+
88+
/// Describes how the a source location file path
89+
public enum SourceLocationFilePathMode {
90+
/// A file ID consisting of the module name and file name (without full path),
91+
/// as would be generated by the macro expansion `#fileID`.
92+
case fileID
93+
94+
/// A full path name as would be generated by the macro expansion `#filePath`,
95+
/// e.g., `/home/taylor/alison.swift`.
96+
case filePath
6897
}

Sources/_SwiftSyntaxMacros/MacroProtocols/AccessorMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public protocol AccessorMacro: AttachedMacro {
1717
static func expansion(
1818
of node: AttributeSyntax,
1919
attachedTo declaration: DeclSyntax,
20-
in context: inout MacroExpansionContext
20+
in context: any MacroExpansionContext
2121
) throws -> [AccessorDeclSyntax]
2222
}
2323

Sources/_SwiftSyntaxMacros/MacroProtocols/CodeItemMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ public protocol CodeItemMacro: FreestandingMacro {
1515
/// declaration within the given context to produce a set of declarations.
1616
static func expansion(
1717
of node: MacroExpansionDeclSyntax,
18-
in context: inout MacroExpansionContext
18+
in context: any MacroExpansionContext
1919
) throws -> [CodeBlockItemSyntax]
2020
}

Sources/_SwiftSyntaxMacros/MacroProtocols/DeclarationMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public protocol DeclarationMacro: FreestandingMacro {
1515
/// declaration within the given context to produce a set of declarations.
1616
static func expansion(
1717
of node: MacroExpansionDeclSyntax,
18-
in context: inout MacroExpansionContext
18+
in context: any MacroExpansionContext
1919
) throws -> [DeclSyntax]
2020
}
2121

Sources/_SwiftSyntaxMacros/MacroProtocols/ExpressionMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ public protocol ExpressionMacro: FreestandingMacro {
1818
/// within the given context to produce a replacement expression.
1919
static func expansion(
2020
of node: MacroExpansionExprSyntax,
21-
in context: inout MacroExpansionContext
21+
in context: any MacroExpansionContext
2222
) throws -> ExprSyntax
2323
}

Sources/_SwiftSyntaxMacros/MacroProtocols/MemberAttributeMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ public protocol MemberAttributeMacro: AttachedMacro {
2929
of node: AttributeSyntax,
3030
attachedTo declaration: DeclSyntax,
3131
annotating member: DeclSyntax,
32-
in context: inout MacroExpansionContext
32+
in context: any MacroExpansionContext
3333
) throws -> [AttributeSyntax]
3434
}

Sources/_SwiftSyntaxMacros/MacroProtocols/MemberMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public protocol MemberMacro: AttachedMacro {
2626
static func expansion(
2727
of node: AttributeSyntax,
2828
attachedTo declaration: DeclSyntax,
29-
in context: inout MacroExpansionContext
29+
in context: any MacroExpansionContext
3030
) throws -> [DeclSyntax]
3131
}
3232

Sources/_SwiftSyntaxMacros/MacroProtocols/PeerMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public protocol PeerMacro: AttachedMacro {
1919
static func expansion(
2020
of node: AttributeSyntax,
2121
attachedTo declaration: DeclSyntax,
22-
in context: inout MacroExpansionContext
22+
in context: any MacroExpansionContext
2323
) throws -> [DeclSyntax]
2424
}
2525

Sources/_SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class MacroApplication: SyntaxRewriter {
8484
if node.evaluatedMacroName != nil {
8585
return node.evaluateMacro(
8686
with: macroSystem,
87-
context: &context
87+
context: context
8888
)
8989
}
9090

@@ -135,7 +135,7 @@ class MacroApplication: SyntaxRewriter {
135135
if let macro = macro as? DeclarationMacro.Type {
136136
let expandedItemList = try macro.expansion(
137137
of: declExpansion,
138-
in: &context
138+
in: context
139139
)
140140
newItems.append(
141141
contentsOf: expandedItemList.map {
@@ -145,7 +145,7 @@ class MacroApplication: SyntaxRewriter {
145145
} else if let macro = macro as? ExpressionMacro.Type {
146146
let expandedExpr = try macro.expansion(
147147
of: declExpansion.asMacroExpansionExpr(),
148-
in: &context
148+
in: context
149149
)
150150
newItems.append(CodeBlockItemSyntax(item: .init(expandedExpr)))
151151
}
@@ -191,7 +191,7 @@ class MacroApplication: SyntaxRewriter {
191191
do {
192192
let expandedList = try freestandingMacro.expansion(
193193
of: declExpansion,
194-
in: &context
194+
in: context
195195
)
196196

197197
newItems.append(
@@ -305,7 +305,7 @@ class MacroApplication: SyntaxRewriter {
305305
let newAccessors = try accessorMacro.expansion(
306306
of: accessorAttr,
307307
attachedTo: DeclSyntax(visitedNode),
308-
in: &context
308+
in: context
309309
)
310310

311311
accessors.append(contentsOf: newAccessors)
@@ -373,7 +373,7 @@ extension MacroApplication {
373373
let macroAttributes = getMacroAttributes(attachedTo: decl, ofType: PeerMacro.Type.self)
374374
for (attribute, peerMacro) in macroAttributes {
375375
do {
376-
let newPeers = try peerMacro.expansion(of: attribute, attachedTo: decl, in: &context)
376+
let newPeers = try peerMacro.expansion(of: attribute, attachedTo: decl, in: context)
377377
peers.append(contentsOf: newPeers)
378378
} catch {
379379
// Record the error
@@ -402,7 +402,7 @@ extension MacroApplication {
402402
contentsOf: memberMacro.expansion(
403403
of: attribute,
404404
attachedTo: DeclSyntax(decl),
405-
in: &context
405+
in: context
406406
)
407407
)
408408
} catch {
@@ -442,7 +442,7 @@ extension MacroApplication {
442442
of: attribute,
443443
attachedTo: DeclSyntax(decl),
444444
annotating: member.decl,
445-
in: &context
445+
in: context
446446
)
447447
)
448448
} catch {
@@ -470,7 +470,7 @@ extension SyntaxProtocol {
470470
/// node.
471471
public func expand(
472472
macros: [String: Macro.Type],
473-
in context: inout MacroExpansionContext
473+
in context: any MacroExpansionContext
474474
) -> Syntax {
475475
// Build the macro system.
476476
var system = MacroSystem()
@@ -483,10 +483,6 @@ extension SyntaxProtocol {
483483
context: context
484484
)
485485

486-
defer {
487-
context = applier.context
488-
}
489-
490486
return applier.visit(Syntax(self))
491487
}
492488
}

Sources/_SwiftSyntaxMacros/Syntax+MacroEvaluation.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,41 @@ extension MacroExpansionDeclSyntax {
4949
}
5050
}
5151

52+
extension SyntaxProtocol {
53+
/// Detach the current node and inform the macro expansion context,
54+
/// if it needs to know.
55+
fileprivate func detach(in context: MacroExpansionContext) -> Self {
56+
let detached = detach()
57+
58+
// Testing contexts want to know where the detach occurred so they can
59+
// track it.
60+
//
61+
// TODO: Should this be generalized?
62+
if let testingContext = context as? TestingMacroExpansionContext,
63+
let parentSourceFile = root.as(SourceFileSyntax.self) {
64+
testingContext.addDisconnected(
65+
Syntax(detached), at: position, in: parentSourceFile
66+
)
67+
}
68+
69+
return detached
70+
}
71+
}
72+
5273
extension MacroExpansionExprSyntax {
5374
/// Evaluate the given macro for this syntax node, producing the expanded
5475
/// result and (possibly) some diagnostics.
5576
func evaluateMacro(
5677
_ macro: Macro.Type,
57-
in context: inout MacroExpansionContext
78+
in context: any MacroExpansionContext
5879
) -> ExprSyntax {
5980
guard let exprMacro = macro as? ExpressionMacro.Type else {
6081
return ExprSyntax(self)
6182
}
6283

6384
// Handle the rewrite.
6485
do {
65-
return try exprMacro.expansion(of: detach(), in: &context)
86+
return try exprMacro.expansion(of: detach(in: context), in: context)
6687
} catch {
6788
// Record the error
6889
context.diagnose(
@@ -82,7 +103,7 @@ extension MacroExpansionDeclSyntax {
82103
/// result and (possibly) some diagnostics.
83104
func evaluateMacro(
84105
_ macro: Macro.Type,
85-
in context: inout MacroExpansionContext
106+
in context: any MacroExpansionContext
86107
) -> Syntax {
87108
// TODO: declaration/statement macros
88109

@@ -115,7 +136,7 @@ extension Syntax {
115136
/// some kind.
116137
func evaluateMacro(
117138
with macroSystem: MacroSystem,
118-
context: inout MacroExpansionContext
139+
context: any MacroExpansionContext
119140
) -> Syntax {
120141
// If this isn't a macro evaluation node, do nothing.
121142
guard let macroName = evaluatedMacroName else {
@@ -129,11 +150,11 @@ extension Syntax {
129150

130151
switch self.as(SyntaxEnum.self) {
131152
case .macroExpansionDecl(let expansion):
132-
return expansion.evaluateMacro(macro, in: &context)
153+
return expansion.evaluateMacro(macro, in: context)
133154

134155
case .macroExpansionExpr(let expansion):
135156
return Syntax(
136-
expansion.evaluateMacro(macro, in: &context)
157+
expansion.evaluateMacro(macro, in: context)
137158
)
138159

139160
default:

0 commit comments

Comments
 (0)