Skip to content

Commit d67b65b

Browse files
authored
Merge pull request #63407 from DougGregor/macros-source-manager-expansion-context
[Macros] Create macro expansion contexts based on the SourceManager
2 parents 26dd150 + 8ad7601 commit d67b65b

File tree

6 files changed

+215
-55
lines changed

6 files changed

+215
-55
lines changed

lib/ASTGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ if (SWIFT_SWIFT_PARSER)
2929
Sources/ASTGen/Misc.swift
3030
Sources/ASTGen/SourceFile.swift
3131
Sources/ASTGen/SourceManager.swift
32+
Sources/ASTGen/SourceManager+MacroExpansionContext.swift
3233
Sources/ASTGen/Stmts.swift
3334
Sources/ASTGen/Types.swift
3435
)

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func expandFreestandingMacro(
181181
) throws -> ExprSyntax {
182182
return try exprMacro.expansion(
183183
of: sourceManager.detach(
184-
node, in: context,
184+
node,
185185
foldingWith: OperatorTable.standardOperators
186186
),
187187
in: context
@@ -201,7 +201,10 @@ func expandFreestandingMacro(
201201
}
202202
macroName = parentExpansion.macro.text
203203
let decls = try declMacro.expansion(
204-
of: sourceManager.detach(parentExpansion, in: context),
204+
of: sourceManager.detach(
205+
parentExpansion,
206+
foldingWith: OperatorTable.standardOperators
207+
),
205208
in: context
206209
)
207210
evaluatedSyntax = Syntax(CodeBlockItemListSyntax(
@@ -348,8 +351,11 @@ func expandAttachedMacro(
348351
switch (macro, macroRole) {
349352
case (let attachedMacro as AccessorMacro.Type, .Accessor):
350353
let accessors = try attachedMacro.expansion(
351-
of: context.detach(customAttrNode),
352-
providingAccessorsOf: context.detach(declarationNode),
354+
of: sourceManager.detach(
355+
customAttrNode,
356+
foldingWith: OperatorTable.standardOperators
357+
),
358+
providingAccessorsOf: sourceManager.detach(declarationNode),
353359
in: context
354360
)
355361

@@ -377,11 +383,12 @@ func expandAttachedMacro(
377383
_ node: Node
378384
) throws -> [AttributeSyntax] {
379385
return try attachedMacro.expansion(
380-
of: context.detach(customAttrNode),
381-
attachedTo: sourceManager.detach(node, in: context),
382-
providingAttributesFor: sourceManager.detach(
383-
declarationNode, in: context
386+
of: sourceManager.detach(
387+
customAttrNode,
388+
foldingWith: OperatorTable.standardOperators
384389
),
390+
attachedTo: sourceManager.detach(node),
391+
providingAttributesFor: sourceManager.detach(declarationNode),
385392
in: context
386393
)
387394
}
@@ -407,8 +414,11 @@ func expandAttachedMacro(
407414
_ node: Node
408415
) throws -> [DeclSyntax] {
409416
return try attachedMacro.expansion(
410-
of: sourceManager.detach(customAttrNode, in: context),
411-
providingMembersOf: sourceManager.detach(node, in: context),
417+
of: sourceManager.detach(
418+
customAttrNode,
419+
foldingWith: OperatorTable.standardOperators
420+
),
421+
providingMembersOf: sourceManager.detach(node),
412422
in: context
413423
)
414424
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import SwiftDiagnostics
2+
import SwiftSyntax
3+
import SwiftSyntaxMacros
4+
5+
extension SourceManager {
6+
class MacroExpansionContext {
7+
/// The source manager.
8+
private let sourceManager: SourceManager
9+
10+
/// The set of diagnostics that were emitted as part of expanding the
11+
/// macro.
12+
var diagnostics: [Diagnostic] = []
13+
14+
/// The macro expansion discriminator, which is used to form unique names
15+
/// when requested.
16+
///
17+
/// The expansion discriminator is combined with the `uniqueNames` counters
18+
/// to produce unique names.
19+
private var discriminator: String
20+
21+
/// Counter for each of the uniqued names.
22+
///
23+
/// Used in conjunction with `expansionDiscriminator`.
24+
private var uniqueNames: [String: Int] = [:]
25+
26+
init(sourceManager: SourceManager, discriminator: String) {
27+
self.sourceManager = sourceManager
28+
self.discriminator = discriminator
29+
}
30+
}
31+
32+
/// Create a new macro expansion context
33+
func createMacroExpansionContext(
34+
discriminator: String = ""
35+
) -> MacroExpansionContext {
36+
return MacroExpansionContext(
37+
sourceManager: self, discriminator: discriminator
38+
)
39+
}
40+
}
41+
42+
extension String {
43+
/// Retrieve the base name of a string that represents a path, removing the
44+
/// directory.
45+
fileprivate var basename: String {
46+
guard let lastSlash = lastIndex(of: "/") else {
47+
return self
48+
}
49+
50+
return String(self[index(after: lastSlash)...])
51+
}
52+
}
53+
54+
extension SourceManager.MacroExpansionContext: MacroExpansionContext {
55+
/// Generate a unique name for use in the macro.
56+
public func createUniqueName(_ providedName: String) -> TokenSyntax {
57+
// If provided with an empty name, substitute in something.
58+
let name = providedName.isEmpty ? "__local" : providedName
59+
60+
// Grab a unique index value for this name.
61+
let uniqueIndex = uniqueNames[name, default: 0]
62+
uniqueNames[name] = uniqueIndex + 1
63+
64+
// Start with the discriminator.
65+
var resultString = discriminator
66+
67+
// Mangle the name
68+
resultString += "\(name.count)\(name)"
69+
70+
// Mangle the operator for unique macro names.
71+
resultString += "fMu"
72+
73+
// Mangle the index.
74+
if uniqueIndex > 0 {
75+
resultString += "\(uniqueIndex - 1)"
76+
}
77+
resultString += "_"
78+
79+
return TokenSyntax(.identifier(resultString), presence: .present)
80+
}
81+
82+
/// Produce a diagnostic while expanding the macro.
83+
public func diagnose(_ diagnostic: Diagnostic) {
84+
diagnostics.append(diagnostic)
85+
}
86+
87+
public func location<Node: SyntaxProtocol>(
88+
of node: Node,
89+
at position: PositionInSyntaxNode,
90+
filePathMode: SourceLocationFilePathMode
91+
) -> SourceLocation? {
92+
guard let (sourceFile, rootPosition) = sourceManager.rootSourceFile(of: node),
93+
let exportedSourceFile =
94+
sourceManager.exportedSourceFilesBySyntax[sourceFile]?.pointee
95+
else {
96+
return nil
97+
}
98+
99+
// Determine the filename to use in the resulting location.
100+
let fileName: String
101+
switch filePathMode {
102+
case .fileID:
103+
fileName = "\(exportedSourceFile.moduleName)/\(exportedSourceFile.fileName.basename)"
104+
105+
case .filePath:
106+
fileName = exportedSourceFile.fileName
107+
}
108+
109+
// Find the node's offset relative to its root.
110+
let rawPosition: AbsolutePosition
111+
switch position {
112+
case .beforeLeadingTrivia:
113+
rawPosition = node.position
114+
115+
case .afterLeadingTrivia:
116+
rawPosition = node.positionAfterSkippingLeadingTrivia
117+
118+
case .beforeTrailingTrivia:
119+
rawPosition = node.endPositionBeforeTrailingTrivia
120+
121+
case .afterTrailingTrivia:
122+
rawPosition = node.endPosition
123+
}
124+
125+
let offsetWithinSyntaxNode =
126+
rawPosition.utf8Offset - node.position.utf8Offset
127+
128+
// Do the location lookup.
129+
let converter = SourceLocationConverter(file: fileName, tree: sourceFile)
130+
return converter.location(for: rootPosition.advanced(by: offsetWithinSyntaxNode))
131+
}
132+
}

lib/ASTGen/Sources/ASTGen/SourceManager.swift

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,20 @@ extension SourceManager {
4949
extension SourceManager {
5050
/// Detach a given node from its parent, keeping track of where it
5151
/// occurred in the program.
52-
func detach<Node: SyntaxProtocol>(_ node: Node) -> Node {
52+
func detach<Node: SyntaxProtocol>(
53+
_ node: Node,
54+
foldingWith operatorTable: OperatorTable? = nil
55+
) -> Node {
5356
// Already detached
5457
if node.parent == nil { return node }
5558

56-
let detached = node.detach()
59+
let detached: Node
60+
if let operatorTable = operatorTable {
61+
detached = operatorTable.foldAll(node) { _ in }.as(Node.self)!.detach()
62+
} else {
63+
detached = node.detach()
64+
}
65+
5766
detachedNodes[Syntax(detached)] = (node.root, node.position.utf8Offset)
5867
return detached
5968
}
@@ -122,45 +131,3 @@ extension SourceManager {
122131
return CxxSourceLoc(mutating: address)
123132
}
124133
}
125-
126-
/// Interaction with BasicMacroExpansionContext.
127-
extension SourceManager {
128-
/// Detach a node within a macro expansion context.
129-
///
130-
/// TODO: This ties together BasicMacroExpansionContext and SourceManager
131-
/// in a rather redundant manner. We'll probably want to drop use of
132-
/// BasicMacroExpansionContext entirely.
133-
func detach<Node: SyntaxProtocol>(
134-
_ node: Node,
135-
in context: BasicMacroExpansionContext,
136-
foldingWith operatorTable: OperatorTable? = nil
137-
) -> Node {
138-
// Already detached
139-
if node.parent == nil { return node }
140-
141-
var detached = context.detach(node)
142-
143-
if let operatorTable = operatorTable {
144-
detached = operatorTable.foldAll(node) { _ in }.as(Node.self)!
145-
}
146-
147-
detachedNodes[Syntax(detached)] = (node.root, node.position.utf8Offset)
148-
return detached
149-
}
150-
151-
/// Create a new macro expansion context
152-
func createMacroExpansionContext(discriminator: String = "") -> BasicMacroExpansionContext {
153-
// Collect the set of source files for this context.
154-
var sourceFiles: [SourceFileSyntax : BasicMacroExpansionContext.KnownSourceFile] = [:]
155-
for (syntax, exported) in exportedSourceFilesBySyntax {
156-
sourceFiles[syntax] = .init(
157-
moduleName: exported.pointee.moduleName,
158-
fullFilePath: exported.pointee.fileName
159-
)
160-
}
161-
162-
return BasicMacroExpansionContext(
163-
expansionDiscriminator: discriminator, sourceFiles: sourceFiles
164-
)
165-
}
166-
}

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,39 @@ extension DeclGroupSyntax {
533533
}
534534
}
535535
}
536+
537+
public enum LeftHandOperandFinderMacro: ExpressionMacro {
538+
class Visitor<Context: MacroExpansionContext>: SyntaxVisitor {
539+
let context: Context
540+
541+
init(context: Context) {
542+
self.context = context
543+
super.init(viewMode: .sourceAccurate)
544+
}
545+
546+
override func visit(
547+
_ node: InfixOperatorExprSyntax
548+
) -> SyntaxVisitorContinueKind {
549+
guard let lhsStartLoc = context.location(of: node.leftOperand),
550+
let lhsEndLoc = context.location(
551+
of: node.leftOperand, at: .beforeTrailingTrivia, filePathMode: .fileID
552+
) else {
553+
fatalError("missing source location information")
554+
}
555+
556+
print("Source range for LHS is \(lhsStartLoc.file!): \(lhsStartLoc.line!):\(lhsStartLoc.column!)-\(lhsEndLoc.line!):\(lhsEndLoc.column!)")
557+
558+
return .visitChildren
559+
}
560+
}
561+
562+
public static func expansion(
563+
of node: some FreestandingMacroExpansionSyntax,
564+
in context: some MacroExpansionContext
565+
) -> ExprSyntax {
566+
let visitor = Visitor(context: context)
567+
visitor.walk(node)
568+
569+
return node.argumentList.first!.expression
570+
}
571+
}

test/Macros/macro_expand.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
// Diagnostics testing
66
// RUN: %target-typecheck-verify-swift -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS
77

8-
// RUN: not %target-swift-frontend -typecheck -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics
8+
// RUN: not %target-swift-frontend -typecheck -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics > %t/macro-printing.txt
99
// RUN: c-index-test -read-diagnostics %t/macro_expand.dia 2>&1 | %FileCheck -check-prefix CHECK-DIAGS %s
1010

11+
// RUN: %FileCheck %s --check-prefix CHECK-MACRO-PRINTED < %t/macro-printing.txt
12+
1113
// Debug info SIL testing
1214
// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature Macros -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir %s -module-name MacroUser -o - -g | %FileCheck --check-prefix CHECK-SIL %s
1315

@@ -117,6 +119,18 @@ func testAddBlocker(a: Int, b: Int, c: Int, oa: OnlyAdds) {
117119
#endif
118120
}
119121

122+
@freestanding(expression) macro leftHandOperandFinder<T>(_ value: T) -> T = #externalMacro(module: "MacroDefinition", type: "LeftHandOperandFinderMacro")
123+
124+
125+
// Test source location information.
126+
func testSourceLocations(x: Int, yolo: Int, zulu: Int) {
127+
// CHECK-MACRO-PRINTED: Source range for LHS is MacroUser/macro_expand.swift: [[@LINE+3]]:5-[[@LINE+3]]:13
128+
// CHECK-MACRO-PRINTED: Source range for LHS is MacroUser/macro_expand.swift: [[@LINE+2]]:5-[[@LINE+2]]:6
129+
_ = #leftHandOperandFinder(
130+
x + yolo + zulu
131+
)
132+
}
133+
120134
// Make sure we don't crash with declarations produced by expansions.
121135
@freestanding(expression) macro nestedDeclInExpr() -> () -> Void = #externalMacro(module: "MacroDefinition", type: "NestedDeclInExprMacro")
122136

0 commit comments

Comments
 (0)