Skip to content

Commit 5c47c90

Browse files
committed
Add "lexical context" information to the macro expansion context
The lexical context of a particular macro expansion involves all of the enclosing contexts, including functions, types and extensions, properties and subscripts, and so on. The lexical context stack can be used to, for example, determine which type a macro was expanded within, or gather the parameters of the enclosing function for an assertion, etc.
1 parent 114a6a1 commit 5c47c90

File tree

13 files changed

+572
-72
lines changed

13 files changed

+572
-72
lines changed

Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift

Lines changed: 13 additions & 4 deletions
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

Lines changed: 37 additions & 7 deletions
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

Lines changed: 5 additions & 1 deletion
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

Lines changed: 4 additions & 2 deletions
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]?
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]?
3941
)
4042

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

Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift

Lines changed: 62 additions & 32 deletions
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)