Skip to content

Commit 865e3ba

Browse files
authored
Merge pull request #1554 from DougGregor/parent-context-of-macro
2 parents 92dbcbc + 3a5367e commit 865e3ba

File tree

14 files changed

+605
-72
lines changed

14 files changed

+605
-72
lines changed

Release Notes/511.md

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
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
3838

39+
- `SyntaxProtocol.asMacroLexicalContext()` and `allMacroLexicalContexts(enclosingSyntax:)`
40+
- 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.
41+
- Pull request: https://github.com/apple/swift-syntax/pull/1554
42+
3943
## API Behavior Changes
4044

4145
## Deprecations
@@ -93,6 +97,11 @@
9397
- The new cases cover the newly introduced `ThrowsClauseSyntax`
9498
- Pull request: https://github.com/apple/swift-syntax/pull/2379
9599
- Migration steps: In exhaustive switches over `SyntaxEnum` and `SyntaxKind`, cover the new case.
100+
101+
- `MacroExpansionContext` now requires a property `lexicalContext`:
102+
- Description: The new property provides the lexical context in which the macro is expanded, and has several paired API changes. Types that conform to `MacroExpansionContext` will need to implement this property. Additionally, the `HostToPluginMessage` cases `expandFreestandingMacro` and `expandAttachedMacro` now include an optional `lexicalContext`. Finally, the `SyntaxProtocol.expand(macros:in:indentationWidth:)` syntactic expansion operation has been deprecated in favor of a new version `expand(macros:contextGenerator:indentationWidth:)` that takes a function produces a new macro expansion context for each expansion.
103+
- Pull request: https://github.com/apple/swift-syntax/pull/1554
104+
- Migration steps: Add the new property `lexicalContext` to any `MacroExpansionContext`-conforming types. If implementing the host-to-plugin message protocol, add support for `lexicalContext`. For macro expansion operations going through `SyntaxProtocol.expand`, provide a context generator that creates a fresh context including the lexical context.
96105

97106

98107
## Template

Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift

+13-4
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,19 @@ extension CompilerPluginMessageHandler {
114114
)
115115
try self.sendMessage(.getCapabilityResult(capability: capability))
116116

117-
case .expandFreestandingMacro(let macro, let macroRole, let discriminator, let expandingSyntax):
117+
case .expandFreestandingMacro(
118+
let macro,
119+
let macroRole,
120+
let discriminator,
121+
let expandingSyntax,
122+
let lexicalContext
123+
):
118124
try expandFreestandingMacro(
119125
macro: macro,
120126
macroRole: macroRole,
121127
discriminator: discriminator,
122-
expandingSyntax: expandingSyntax
128+
expandingSyntax: expandingSyntax,
129+
lexicalContext: lexicalContext
123130
)
124131

125132
case .expandAttachedMacro(
@@ -130,7 +137,8 @@ extension CompilerPluginMessageHandler {
130137
let declSyntax,
131138
let parentDeclSyntax,
132139
let extendedTypeSyntax,
133-
let conformanceListSyntax
140+
let conformanceListSyntax,
141+
let lexicalContext
134142
):
135143
try expandAttachedMacro(
136144
macro: macro,
@@ -140,7 +148,8 @@ extension CompilerPluginMessageHandler {
140148
declSyntax: declSyntax,
141149
parentDeclSyntax: parentDeclSyntax,
142150
extendedTypeSyntax: extendedTypeSyntax,
143-
conformanceListSyntax: conformanceListSyntax
151+
conformanceListSyntax: conformanceListSyntax,
152+
lexicalContext: lexicalContext
144153
)
145154

146155
case .loadPluginLibrary(let libraryPath, let moduleName):

Sources/SwiftCompilerPluginMessageHandling/Macros.swift

+37-7
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,41 @@ extension CompilerPluginMessageHandler {
2323
try provider.resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
2424
}
2525

26+
/// Resolve the lexical context
27+
private static func resolveLexicalContext(
28+
_ lexicalContext: [PluginMessage.Syntax]?,
29+
sourceManager: SourceManager,
30+
operatorTable: OperatorTable,
31+
fallbackSyntax: some SyntaxProtocol
32+
) -> [Syntax] {
33+
// If we weren't provided with a lexical context, retrieve it from the
34+
// syntax node we were given. This is for dealing with older compilers.
35+
guard let lexicalContext else {
36+
return fallbackSyntax.allMacroLexicalContexts()
37+
}
38+
39+
return lexicalContext.map { sourceManager.add($0, foldingWith: operatorTable) }
40+
}
41+
2642
/// Expand `@freestainding(XXX)` macros.
2743
func expandFreestandingMacro(
2844
macro: PluginMessage.MacroReference,
2945
macroRole pluginMacroRole: PluginMessage.MacroRole?,
3046
discriminator: String,
31-
expandingSyntax: PluginMessage.Syntax
47+
expandingSyntax: PluginMessage.Syntax,
48+
lexicalContext: [PluginMessage.Syntax]?
3249
) throws {
3350
let sourceManager = SourceManager()
3451
let syntax = sourceManager.add(expandingSyntax, foldingWith: .standardOperators)
3552

3653
let context = PluginMacroExpansionContext(
3754
sourceManager: sourceManager,
55+
lexicalContext: Self.resolveLexicalContext(
56+
lexicalContext,
57+
sourceManager: sourceManager,
58+
operatorTable: .standardOperators,
59+
fallbackSyntax: syntax
60+
),
3861
expansionDiscriminator: discriminator
3962
)
4063

@@ -85,14 +108,10 @@ extension CompilerPluginMessageHandler {
85108
declSyntax: PluginMessage.Syntax,
86109
parentDeclSyntax: PluginMessage.Syntax?,
87110
extendedTypeSyntax: PluginMessage.Syntax?,
88-
conformanceListSyntax: PluginMessage.Syntax?
111+
conformanceListSyntax: PluginMessage.Syntax?,
112+
lexicalContext: [PluginMessage.Syntax]?
89113
) throws {
90114
let sourceManager = SourceManager()
91-
let context = PluginMacroExpansionContext(
92-
sourceManager: sourceManager,
93-
expansionDiscriminator: discriminator
94-
)
95-
96115
let attributeNode = sourceManager.add(
97116
attributeSyntax,
98117
foldingWith: .standardOperators
@@ -107,6 +126,17 @@ extension CompilerPluginMessageHandler {
107126
return placeholderStruct.inheritanceClause!.inheritedTypes
108127
}
109128

129+
let context = PluginMacroExpansionContext(
130+
sourceManager: sourceManager,
131+
lexicalContext: Self.resolveLexicalContext(
132+
lexicalContext,
133+
sourceManager: sourceManager,
134+
operatorTable: .standardOperators,
135+
fallbackSyntax: declarationNode
136+
),
137+
expansionDiscriminator: discriminator
138+
)
139+
110140
// TODO: Make this a 'String?' and remove non-'hasExpandMacroResult' branches.
111141
let expandedSources: [String]?
112142
do {

Sources/SwiftCompilerPluginMessageHandling/PluginMacroExpansionContext.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ fileprivate extension Syntax {
192192
class PluginMacroExpansionContext {
193193
private var sourceManger: SourceManager
194194

195+
/// The lexical context of the macro expansion described by this context.
196+
let lexicalContext: [Syntax]
197+
195198
/// The macro expansion discriminator, which is used to form unique names
196199
/// when requested.
197200
///
@@ -208,8 +211,9 @@ class PluginMacroExpansionContext {
208211
/// macro.
209212
internal private(set) var diagnostics: [Diagnostic] = []
210213

211-
init(sourceManager: SourceManager, expansionDiscriminator: String = "") {
214+
init(sourceManager: SourceManager, lexicalContext: [Syntax], expansionDiscriminator: String = "") {
212215
self.sourceManger = sourceManager
216+
self.lexicalContext = lexicalContext
213217
self.expansionDiscriminator = expansionDiscriminator
214218
}
215219
}

Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public enum HostToPluginMessage: Codable {
2323
macro: PluginMessage.MacroReference,
2424
macroRole: PluginMessage.MacroRole? = nil,
2525
discriminator: String,
26-
syntax: PluginMessage.Syntax
26+
syntax: PluginMessage.Syntax,
27+
lexicalContext: [PluginMessage.Syntax]? = nil
2728
)
2829

2930
/// Expand an '@attached' macro.
@@ -35,7 +36,8 @@ public enum HostToPluginMessage: Codable {
3536
declSyntax: PluginMessage.Syntax,
3637
parentDeclSyntax: PluginMessage.Syntax?,
3738
extendedTypeSyntax: PluginMessage.Syntax?,
38-
conformanceListSyntax: PluginMessage.Syntax?
39+
conformanceListSyntax: PluginMessage.Syntax?,
40+
lexicalContext: [PluginMessage.Syntax]? = nil
3941
)
4042

4143
/// Optionally implemented message to load a dynamic link library.

Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift

+62-32
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,37 @@ public class BasicMacroExpansionContext {
3232
}
3333
}
3434

35-
/// Create a new macro evaluation context.
36-
public init(
37-
expansionDiscriminator: String = "__macro_local_",
38-
sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
39-
) {
40-
self.expansionDiscriminator = expansionDiscriminator
41-
self.sourceFiles = sourceFiles
35+
/// Describes state that is shared amongst all instances of the basic
36+
/// macro expansion context.
37+
private class SharedState {
38+
/// The set of diagnostics that were emitted as part of expanding the
39+
/// macro.
40+
var diagnostics: [Diagnostic] = []
41+
42+
/// Mapping from the root source file syntax nodes to the known source-file
43+
/// information about that source file.
44+
var sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
45+
46+
/// Mapping from intentionally-disconnected syntax nodes to the corresponding
47+
/// nodes in the original source file.
48+
///
49+
/// This is used to establish the link between a node that been intentionally
50+
/// disconnected from a source file to hide information from the macro
51+
/// implementation.
52+
var detachedNodes: [Syntax: Syntax] = [:]
53+
54+
/// Counter for each of the uniqued names.
55+
///
56+
/// Used in conjunction with `expansionDiscriminator`.
57+
var uniqueNames: [String: Int] = [:]
4258
}
4359

44-
/// The set of diagnostics that were emitted as part of expanding the
45-
/// macro.
46-
public private(set) var diagnostics: [Diagnostic] = []
47-
48-
/// Mapping from the root source file syntax nodes to the known source-file
49-
/// information about that source file.
50-
private var sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
60+
/// State shared by different instances of the macro expansion context,
61+
/// which includes information about detached nodes and source file names.
62+
private var sharedState: SharedState
5163

52-
/// Mapping from intentionally-disconnected syntax nodes to the corresponding
53-
/// nodes in the original source file.
54-
///
55-
/// This is used to establish the link between a node that been intentionally
56-
/// disconnected from a source file to hide information from the macro
57-
/// implementation.
58-
private var detachedNodes: [Syntax: Syntax] = [:]
64+
/// The lexical context of the macro expansion described by this context.
65+
public let lexicalContext: [Syntax]
5966

6067
/// The macro expansion discriminator, which is used to form unique names
6168
/// when requested.
@@ -64,18 +71,41 @@ public class BasicMacroExpansionContext {
6471
/// to produce unique names.
6572
private var expansionDiscriminator: String = ""
6673

67-
/// Counter for each of the uniqued names.
68-
///
69-
/// Used in conjunction with `expansionDiscriminator`.
70-
private var uniqueNames: [String: Int] = [:]
74+
/// Create a new macro evaluation context.
75+
public init(
76+
lexicalContext: [Syntax] = [],
77+
expansionDiscriminator: String = "__macro_local_",
78+
sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:]
79+
) {
80+
self.sharedState = SharedState()
81+
self.lexicalContext = lexicalContext
82+
self.expansionDiscriminator = expansionDiscriminator
83+
self.sharedState.sourceFiles = sourceFiles
84+
}
85+
86+
/// Create a new macro evaluation context that shares most of its global
87+
/// state (detached nodes, diagnostics, etc.) with the given context.
88+
public init(sharingWith context: BasicMacroExpansionContext, lexicalContext: [Syntax]) {
89+
self.sharedState = context.sharedState
90+
self.lexicalContext = lexicalContext
91+
self.expansionDiscriminator = context.expansionDiscriminator
92+
}
93+
}
7194

95+
extension BasicMacroExpansionContext {
96+
/// The set of diagnostics that were emitted as part of expanding the
97+
/// macro.
98+
public private(set) var diagnostics: [Diagnostic] {
99+
get { sharedState.diagnostics }
100+
set { sharedState.diagnostics = newValue }
101+
}
72102
}
73103

74104
extension BasicMacroExpansionContext {
75105
/// Detach the given node, and record where it came from.
76106
public func detach<Node: SyntaxProtocol>(_ node: Node) -> Node {
77107
let detached = node.detached
78-
detachedNodes[Syntax(detached)] = Syntax(node)
108+
sharedState.detachedNodes[Syntax(detached)] = Syntax(node)
79109
return detached
80110
}
81111

@@ -88,7 +118,7 @@ extension BasicMacroExpansionContext {
88118
{
89119
// Folding operators doesn't change the source file and its associated locations
90120
// Record the `KnownSourceFile` information for the folded tree.
91-
sourceFiles[newSourceFile] = sourceFiles[originalSourceFile]
121+
sharedState.sourceFiles[newSourceFile] = sharedState.sourceFiles[originalSourceFile]
92122
}
93123
return folded
94124
}
@@ -113,8 +143,8 @@ extension BasicMacroExpansionContext: MacroExpansionContext {
113143
let name = providedName.isEmpty ? "__local" : providedName
114144

115145
// Grab a unique index value for this name.
116-
let uniqueIndex = uniqueNames[name, default: 0]
117-
uniqueNames[name] = uniqueIndex + 1
146+
let uniqueIndex = sharedState.uniqueNames[name, default: 0]
147+
sharedState.uniqueNames[name] = uniqueIndex + 1
118148

119149
// Start with the expansion discriminator.
120150
var resultString = expansionDiscriminator
@@ -153,7 +183,7 @@ extension BasicMacroExpansionContext: MacroExpansionContext {
153183
anchoredAt node: Syntax,
154184
fileName: String
155185
) -> SourceLocation {
156-
guard let nodeInOriginalTree = detachedNodes[node.root] else {
186+
guard let nodeInOriginalTree = sharedState.detachedNodes[node.root] else {
157187
return SourceLocationConverter(fileName: fileName, tree: node.root).location(for: position)
158188
}
159189
let adjustedPosition = position + SourceLength(utf8Length: nodeInOriginalTree.position.utf8Offset)
@@ -173,15 +203,15 @@ extension BasicMacroExpansionContext: MacroExpansionContext {
173203
// The syntax node came from the source file itself.
174204
rootSourceFile = directRootSourceFile
175205
offsetAdjustment = .zero
176-
} else if let nodeInOriginalTree = detachedNodes[Syntax(node)] {
206+
} else if let nodeInOriginalTree = sharedState.detachedNodes[Syntax(node)] {
177207
// The syntax node came from a disconnected root, so adjust for that.
178208
rootSourceFile = nodeInOriginalTree.root.as(SourceFileSyntax.self)
179209
offsetAdjustment = SourceLength(utf8Length: nodeInOriginalTree.position.utf8Offset)
180210
} else {
181211
return nil
182212
}
183213

184-
guard let rootSourceFile, let knownRoot = sourceFiles[rootSourceFile] else {
214+
guard let rootSourceFile, let knownRoot = sharedState.sourceFiles[rootSourceFile] else {
185215
return nil
186216
}
187217

0 commit comments

Comments
 (0)