Skip to content

Commit 217af0e

Browse files
committed
[Macros] "Disconnect" macro expansion expression syntax from its parent.
Providing the macro expansion syntax node directly to the macro implementation allowed those implementations to chase its parent pointer, which indirectly exposed the full source code of the enclosing file to the macro implementation. "Disconnect" the syntax node from its parent so the syntax node is independent of all other source code in the file. Another sample macro implementation, `#function`, is now unusable---but this is a better model for incremental builds.
1 parent 8df91a8 commit 217af0e

File tree

3 files changed

+56
-187
lines changed

3 files changed

+56
-187
lines changed

Sources/_SwiftSyntaxMacros/MacroSystem+Builtin.swift

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -13,121 +13,6 @@
1313
import SwiftSyntax
1414
import SwiftSyntaxBuilder
1515

16-
extension PatternBindingSyntax {
17-
/// When the variable is declaring a single binding, produce the name of
18-
/// that binding.
19-
fileprivate var singleBindingName: String? {
20-
if let identifierPattern = pattern.as(IdentifierPatternSyntax.self) {
21-
return identifierPattern.identifier.text
22-
}
23-
24-
return nil
25-
}
26-
}
27-
28-
public struct FunctionMacro: ExpressionMacro {
29-
/// Form a function name.
30-
private static func formFunctionName(
31-
_ baseName: String, _ parameters: ParameterClauseSyntax?,
32-
isSubscript: Bool = false
33-
) -> String {
34-
let argumentNames: [String] = parameters?.parameterList.map { param in
35-
if let argumentName = param.firstName?.text,
36-
!isSubscript || param.secondName != nil {
37-
return "\(argumentName):"
38-
}
39-
40-
return "_:"
41-
} ?? []
42-
43-
return "\(baseName)(\(argumentNames.joined(separator: "")))"
44-
}
45-
46-
private static func findEnclosingName(
47-
_ macro: MacroExpansionExprSyntax
48-
) -> String? {
49-
var currentNode = Syntax(macro)
50-
while let parent = currentNode.parent {
51-
switch parent.as(SyntaxEnum.self) {
52-
case .accessorDecl(let accessor):
53-
if let accessorList = accessor.parent?.as(AccessorListSyntax.self),
54-
let accessorBlock = accessorList.parent?.as(AccessorBlockSyntax.self),
55-
let binding = accessorBlock.parent?.as(PatternBindingSyntax.self),
56-
let varName = binding.singleBindingName {
57-
return varName
58-
}
59-
60-
break
61-
62-
case .functionDecl(let function):
63-
return formFunctionName(
64-
function.identifier.text, function.signature.input
65-
)
66-
67-
case .initializerDecl(let initializer):
68-
return formFunctionName("init", initializer.signature.input)
69-
70-
case .subscriptDecl(let subscriptDecl):
71-
return formFunctionName(
72-
"subscript", subscriptDecl.indices, isSubscript: true
73-
)
74-
75-
case .enumCaseElement(let enumCase):
76-
return formFunctionName(
77-
enumCase.identifier.text, enumCase.associatedValue
78-
)
79-
80-
case .structDecl(let structDecl):
81-
return structDecl.identifier.text
82-
83-
case .enumDecl(let enumDecl):
84-
return enumDecl.identifier.text
85-
86-
case .classDecl(let classDecl):
87-
return classDecl.identifier.text
88-
89-
case .actorDecl(let actorDecl):
90-
return actorDecl.identifier.text
91-
92-
case .protocolDecl(let protocolDecl):
93-
return protocolDecl.identifier.text
94-
95-
case .extensionDecl(let extensionDecl):
96-
// FIXME: It would be nice to be able to switch on type syntax...
97-
let extendedType = extensionDecl.extendedType
98-
if let simple = extendedType.as(SimpleTypeIdentifierSyntax.self) {
99-
return simple.name.text
100-
}
101-
102-
if let member = extendedType.as(MemberTypeIdentifierSyntax.self) {
103-
return member.name.text
104-
}
105-
106-
return nil
107-
108-
default:
109-
break
110-
}
111-
112-
currentNode = parent
113-
}
114-
115-
return nil
116-
}
117-
118-
public static func apply(
119-
_ macro: MacroExpansionExprSyntax, in context: inout MacroExpansionContext
120-
) -> MacroResult<ExprSyntax> {
121-
let name = findEnclosingName(macro) ?? context.moduleName
122-
let literal: ExprSyntax = "\(literal: name)"
123-
if let leadingTrivia = macro.leadingTrivia {
124-
return .init(literal.withLeadingTrivia(leadingTrivia))
125-
}
126-
127-
return .init(literal)
128-
}
129-
}
130-
13116
/// Replace the label of the first element in the tuple with the given
13217
/// new label.
13318
private func replaceFirstLabel(
@@ -205,7 +90,6 @@ extension MacroSystem {
20590
var macroSystem = MacroSystem()
20691
try! macroSystem.add(ColorLiteralMacro.self, name: "colorLiteral")
20792
try! macroSystem.add(FileIDMacro.self, name: "fileID")
208-
try! macroSystem.add(FunctionMacro.self, name: "function")
20993
try! macroSystem.add(ImageLiteralMacro.self, name: "imageLiteral")
21094
return macroSystem
21195
}()

Sources/_SwiftSyntaxMacros/Syntax+MacroEvaluation.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ import SwiftDiagnostics
1414
import SwiftSyntax
1515

1616
extension MacroExpansionExprSyntax {
17+
private func disconnectedCopy() -> MacroExpansionExprSyntax {
18+
MacroExpansionExprSyntax(
19+
unexpectedBeforePoundToken, poundToken: poundToken,
20+
unexpectedBetweenPoundTokenAndMacro, macro: macro,
21+
genericArguments: genericArguments,
22+
unexpectedBetweenGenericArgumentsAndLeftParen, leftParen: leftParen,
23+
unexpectedBetweenLeftParenAndArgumentList, argumentList: argumentList,
24+
unexpectedBetweenArgumentListAndRightParen, rightParen: rightParen,
25+
unexpectedBetweenRightParenAndTrailingClosure,
26+
trailingClosure: trailingClosure,
27+
unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures,
28+
additionalTrailingClosures: additionalTrailingClosures,
29+
unexpectedAfterAdditionalTrailingClosures
30+
)
31+
}
32+
1733
/// Evaluate the given macro for this syntax node, producing the expanded
1834
/// result and (possibly) some diagnostics.
1935
func evaluateMacro(
@@ -27,7 +43,7 @@ extension MacroExpansionExprSyntax {
2743
}
2844

2945
// Handle the rewrite.
30-
let result = exprMacro.apply(self, in: &context)
46+
let result = exprMacro.apply(disconnectedCopy(), in: &context)
3147

3248
// Report diagnostics, if there were any.
3349
if !result.diagnostics.isEmpty {

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 39 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@ import SwiftSyntaxBuilder
1616
@_spi(Testing) import _SwiftSyntaxMacros
1717
import _SwiftSyntaxTestSupport
1818

19+
/// Macro whose only purpose is to ensure that we cannot see "out" of the
20+
/// macro expansion syntax node we were given.
21+
struct CheckContextIndependenceMacro: ExpressionMacro {
22+
static func apply(
23+
_ macro: MacroExpansionExprSyntax,
24+
in context: inout MacroExpansionContext) -> MacroResult<ExprSyntax> {
25+
26+
// Should not have a parent.
27+
XCTAssertNil(macro.parent)
28+
29+
// Absolute starting position should be zero.
30+
XCTAssertEqual(macro.position.utf8Offset, 0)
31+
32+
return .init(ExprSyntax(macro))
33+
}
34+
}
35+
1936
final class MacroSystemTests: XCTestCase {
2037
func testExpressionExpansion() {
2138
let sf: SourceFileSyntax =
@@ -63,104 +80,56 @@ final class MacroSystemTests: XCTestCase {
6380
)
6481
}
6582

66-
func testPoundFunctionExpansion() {
83+
func testFileExpansions() {
6784
let sf: SourceFileSyntax =
6885
"""
69-
func f(a: Int, _: Double, c: Int) {
70-
print(#function)
71-
}
72-
73-
struct X {
74-
var computed: String {
75-
get {
76-
#function
77-
}
78-
}
79-
80-
init(from: String) {
81-
#function
82-
}
83-
84-
subscript(a: Int) -> String {
85-
#function
86-
}
87-
88-
subscript(a a: Int) -> String {
89-
#function
90-
}
91-
}
92-
93-
extension A {
94-
static var staticProp: String = #function
95-
}
86+
let b = #fileID
9687
"""
9788
var context = MacroExpansionContext(
98-
moduleName: "MyModule", fileName: "test.swift"
89+
moduleName: "MyModule", fileName: "taylor.swift"
9990
)
10091
let transformedSF = MacroSystem.exampleSystem.evaluateMacros(
10192
node: sf, in: &context, errorHandler: { error in }
10293
)
10394
AssertStringsEqualWithDiff(
10495
transformedSF.description,
10596
"""
106-
func f(a: Int, _: Double, c: Int) {
107-
print("f(a:_:c:)")
108-
}
109-
110-
struct X {
111-
var computed: String {
112-
get {
113-
"computed"
114-
}
115-
}
116-
117-
init(from: String) {
118-
"init(from:)"
119-
}
120-
121-
subscript(a: Int) -> String {
122-
"subscript(_:)"
123-
}
124-
125-
subscript(a a: Int) -> String {
126-
"subscript(a:)"
127-
}
128-
}
129-
130-
extension A {
131-
static var staticProp: String = "A"
132-
}
97+
let b = "MyModule/taylor.swift"
13398
"""
13499
)
135100
}
136101

137-
func testFileExpansions() {
102+
func testContextUniqueLocalNames() {
103+
var context = MacroExpansionContext(
104+
moduleName: "MyModule", fileName: "taylor.swift"
105+
)
106+
107+
let t1 = context.createUniqueLocalName()
108+
let t2 = context.createUniqueLocalName()
109+
XCTAssertNotEqual(t1.description, t2.description)
110+
XCTAssertEqual(t1.description, "__macro_local_0")
111+
}
112+
113+
func testContextIndependence() {
114+
var system = MacroSystem()
115+
try! system.add(CheckContextIndependenceMacro.self, name: "checkContext")
116+
138117
let sf: SourceFileSyntax =
139118
"""
140-
let b = #fileID
119+
let b = #checkContext
141120
"""
142121
var context = MacroExpansionContext(
143122
moduleName: "MyModule", fileName: "taylor.swift"
144123
)
145-
let transformedSF = MacroSystem.exampleSystem.evaluateMacros(
124+
let transformedSF = system.evaluateMacros(
146125
node: sf, in: &context, errorHandler: { error in }
147126
)
148127
AssertStringsEqualWithDiff(
149128
transformedSF.description,
150129
"""
151-
let b = "MyModule/taylor.swift"
130+
let b = #checkContext
152131
"""
153132
)
154-
}
155133

156-
func testContextUniqueLocalNames() {
157-
var context = MacroExpansionContext(
158-
moduleName: "MyModule", fileName: "taylor.swift"
159-
)
160-
161-
let t1 = context.createUniqueLocalName()
162-
let t2 = context.createUniqueLocalName()
163-
XCTAssertNotEqual(t1.description, t2.description)
164-
XCTAssertEqual(t1.description, "__macro_local_0")
165134
}
166135
}

0 commit comments

Comments
 (0)