Skip to content

Commit 3609c77

Browse files
authored
Merge pull request #1868 from rintaro/5.9-macros-macrossystem-expansion
[5.9][Macros] Copy MacroSystem and related code to SwiftSyntaxMacroExpansion
2 parents b243294 + 972f60e commit 3609c77

File tree

8 files changed

+954
-6
lines changed

8 files changed

+954
-6
lines changed

Package.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,14 @@ let package = Package(
204204

205205
.testTarget(
206206
name: "SwiftSyntaxMacroExpansionTest",
207-
dependencies: [
208-
"SwiftSyntax", "_SwiftSyntaxTestSupport", "SwiftSyntaxMacroExpansion", "SwiftSyntaxBuilder",
209-
]
207+
dependencies: ["SwiftSyntax", "_SwiftSyntaxTestSupport", "SwiftSyntaxMacroExpansion", "SwiftSyntaxBuilder"]
210208
),
211209

212210
// MARK: SwiftSyntaxMacrosTestSupport
213211

214212
.target(
215213
name: "SwiftSyntaxMacrosTestSupport",
216-
dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftParser", "SwiftSyntaxMacros"]
214+
dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftParser", "SwiftSyntaxMacros", "SwiftSyntaxMacroExpansion"]
217215
),
218216

219217
// MARK: SwiftParser
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftDiagnostics
14+
import SwiftSyntax
15+
import SwiftSyntaxMacros
16+
17+
/// An implementation of the `MacroExpansionContext` protocol that is
18+
/// suitable for testing purposes.
19+
public class BasicMacroExpansionContext {
20+
/// A single source file that is known to the macro expansion context.
21+
public struct KnownSourceFile {
22+
/// The name of the module in which this source file resides.
23+
let moduleName: String
24+
25+
/// The full path to the file.
26+
let fullFilePath: String
27+
28+
public init(moduleName: String, fullFilePath: String) {
29+
self.moduleName = moduleName
30+
self.fullFilePath = fullFilePath
31+
}
32+
}
33+
34+
/// Create a new macro evaluation context.
35+
public init(
36+
expansionDiscriminator: String = "__macro_local_",
37+
sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
38+
) {
39+
self.expansionDiscriminator = expansionDiscriminator
40+
self.sourceFiles = sourceFiles
41+
}
42+
43+
/// The set of diagnostics that were emitted as part of expanding the
44+
/// macro.
45+
public private(set) var diagnostics: [Diagnostic] = []
46+
47+
/// Mapping from the root source file syntax nodes to the known source-file
48+
/// information about that source file.
49+
private var sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
50+
51+
/// Mapping from intentionally-disconnected syntax node roots to the
52+
/// absolute offsets that have within a given source file, which is used
53+
/// to establish the link between a node that been intentionally disconnected
54+
/// from a source file to hide information from the macro implementation.
55+
private var disconnectedNodes: [Syntax: (SourceFileSyntax, Int)] = [:]
56+
57+
/// The macro expansion discriminator, which is used to form unique names
58+
/// when requested.
59+
///
60+
/// The expansion discriminator is combined with the `uniqueNames` counters
61+
/// to produce unique names.
62+
private var expansionDiscriminator: String = ""
63+
64+
/// Counter for each of the uniqued names.
65+
///
66+
/// Used in conjunction with `expansionDiscriminator`.
67+
private var uniqueNames: [String: Int] = [:]
68+
69+
}
70+
71+
extension BasicMacroExpansionContext {
72+
/// Note that the given node that was at the given position in the provided
73+
/// source file has been disconnected and is now a new root.
74+
private func addDisconnected(
75+
_ node: some SyntaxProtocol,
76+
at offset: AbsolutePosition,
77+
in sourceFile: SourceFileSyntax
78+
) {
79+
disconnectedNodes[Syntax(node)] = (sourceFile, offset.utf8Offset)
80+
}
81+
82+
/// Detach the given node, and record where it came from.
83+
public func detach<Node: SyntaxProtocol>(_ node: Node) -> Node {
84+
let detached = node.detach()
85+
86+
if let rootSourceFile = node.root.as(SourceFileSyntax.self) {
87+
addDisconnected(detached, at: node.position, in: rootSourceFile)
88+
}
89+
90+
return detached
91+
}
92+
}
93+
94+
extension String {
95+
/// Retrieve the base name of a string that represents a path, removing the
96+
/// directory.
97+
fileprivate var basename: String {
98+
guard let lastSlash = lastIndex(of: "/") else {
99+
return self
100+
}
101+
102+
return String(self[index(after: lastSlash)...])
103+
}
104+
105+
}
106+
extension BasicMacroExpansionContext: MacroExpansionContext {
107+
/// Generate a unique name for use in the macro.
108+
public func makeUniqueName(_ providedName: String) -> TokenSyntax {
109+
// If provided with an empty name, substitute in something.
110+
let name = providedName.isEmpty ? "__local" : providedName
111+
112+
// Grab a unique index value for this name.
113+
let uniqueIndex = uniqueNames[name, default: 0]
114+
uniqueNames[name] = uniqueIndex + 1
115+
116+
// Start with the expansion discriminator.
117+
var resultString = expansionDiscriminator
118+
119+
// Mangle the name
120+
resultString += "\(name.count)\(name)"
121+
122+
// Mangle the operator for unique macro names.
123+
resultString += "fMu"
124+
125+
// Mangle the index.
126+
if uniqueIndex > 0 {
127+
resultString += "\(uniqueIndex - 1)"
128+
}
129+
resultString += "_"
130+
131+
return TokenSyntax(.identifier(resultString), presence: .present)
132+
}
133+
134+
/// Produce a diagnostic while expanding the macro.
135+
public func diagnose(_ diagnostic: Diagnostic) {
136+
diagnostics.append(diagnostic)
137+
}
138+
139+
public func location(
140+
of node: some SyntaxProtocol,
141+
at position: PositionInSyntaxNode,
142+
filePathMode: SourceLocationFilePathMode
143+
) -> AbstractSourceLocation? {
144+
// Dig out the root source file and figure out how we need to adjust the
145+
// offset of the given syntax node to adjust for it.
146+
let rootSourceFile: SourceFileSyntax
147+
let offsetAdjustment: Int
148+
if let directRootSourceFile = node.root.as(SourceFileSyntax.self) {
149+
// The syntax node came from the source file itself.
150+
rootSourceFile = directRootSourceFile
151+
offsetAdjustment = 0
152+
} else if let (adjustedSourceFile, offset) = disconnectedNodes[Syntax(node)] {
153+
// The syntax node came from a disconnected root, so adjust for that.
154+
rootSourceFile = adjustedSourceFile
155+
offsetAdjustment = offset
156+
} else {
157+
return nil
158+
}
159+
160+
guard let knownRoot = sourceFiles[rootSourceFile] else {
161+
return nil
162+
}
163+
164+
// Determine the filename to use in the resulting location.
165+
let fileName: String
166+
switch filePathMode {
167+
case .fileID:
168+
fileName = "\(knownRoot.moduleName)/\(knownRoot.fullFilePath.basename)"
169+
170+
case .filePath:
171+
fileName = knownRoot.fullFilePath
172+
}
173+
174+
// Find the node's offset relative to its root.
175+
let rawPosition: AbsolutePosition
176+
switch position {
177+
case .beforeLeadingTrivia:
178+
rawPosition = node.position
179+
180+
case .afterLeadingTrivia:
181+
rawPosition = node.positionAfterSkippingLeadingTrivia
182+
183+
case .beforeTrailingTrivia:
184+
rawPosition = node.endPositionBeforeTrailingTrivia
185+
186+
case .afterTrailingTrivia:
187+
rawPosition = node.endPosition
188+
}
189+
190+
// Do the location lookup.
191+
let converter = SourceLocationConverter(file: fileName, tree: rootSourceFile)
192+
return AbstractSourceLocation(converter.location(for: rawPosition.advanced(by: offsetAdjustment)))
193+
}
194+
}

Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
add_swift_host_library(SwiftSyntaxMacroExpansion
2+
BasicMacroExpansionContext.swift
23
FunctionParameterUtils.swift
34
MacroExpansion.swift
45
MacroReplacement.swift
6+
MacroSystem.swift
7+
Syntax+MacroEvaluation.swift
58
)
69

710
target_link_libraries(SwiftSyntaxMacroExpansion PUBLIC

0 commit comments

Comments
 (0)