Skip to content

Commit 4265ac0

Browse files
authored
Merge pull request #2415 from bnbarham/qualify-extensions
MacroSystem should qualify nested types when expanding extension macros
2 parents 0f50f93 + 816eaef commit 4265ac0

File tree

2 files changed

+136
-54
lines changed

2 files changed

+136
-54
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,11 @@ private func expandExtensionMacro(
319319
in context: some MacroExpansionContext,
320320
indentationWidth: Trivia
321321
) throws -> CodeBlockItemListSyntax? {
322-
let extendedType: TypeSyntax
323-
if let identified = attachedTo.asProtocol(NamedDeclSyntax.self) {
324-
extendedType = "\(identified.name.trimmed)"
325-
} else if let ext = attachedTo.as(ExtensionDeclSyntax.self) {
326-
extendedType = "\(ext.extendedType.trimmed)"
327-
} else {
322+
guard attachedTo.isProtocol(DeclGroupSyntax.self) else {
323+
return nil
324+
}
325+
326+
guard let extendedType = attachedTo.syntacticQualifiedTypeContext else {
328327
return nil
329328
}
330329

@@ -1319,3 +1318,25 @@ private extension AccessorDeclSyntax {
13191318
return accessorSpecifier.tokenKind == .keyword(.get) || accessorSpecifier.tokenKind == .keyword(.set)
13201319
}
13211320
}
1321+
1322+
private extension SyntaxProtocol {
1323+
/// Retrieve the qualified type for the nearest extension or name decl.
1324+
///
1325+
/// For example, for `struct Foo { struct Bar {} }`, calling this on the
1326+
/// inner struct (`Bar`) will return `Foo.Bar`.
1327+
var syntacticQualifiedTypeContext: TypeSyntax? {
1328+
if let ext = self.as(ExtensionDeclSyntax.self) {
1329+
// Don't handle nested 'extension' cases - they are invalid anyway.
1330+
return ext.extendedType.trimmed
1331+
}
1332+
1333+
let base = self.parent?.syntacticQualifiedTypeContext
1334+
if let named = self.asProtocol(NamedDeclSyntax.self) {
1335+
if let base = base {
1336+
return TypeSyntax(MemberTypeSyntax(baseType: base, name: named.name.trimmed))
1337+
}
1338+
return TypeSyntax(IdentifierTypeSyntax(name: named.name.trimmed))
1339+
}
1340+
return base
1341+
}
1342+
}

Tests/SwiftSyntaxMacroExpansionTest/ExtensionMacroTests.swift

Lines changed: 109 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,90 +25,130 @@ import SwiftSyntaxMacros
2525
import SwiftSyntaxMacrosTestSupport
2626
import XCTest
2727

28-
fileprivate struct DeclsFromStringsMacro: DeclarationMacro {
29-
static func expansion(
30-
of node: some FreestandingMacroExpansionSyntax,
31-
in context: some MacroExpansionContext
32-
) throws -> [DeclSyntax] {
33-
var strings: [String] = []
34-
for arg in node.arguments {
35-
guard let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue else {
36-
continue
37-
}
38-
strings.append(value)
39-
}
40-
41-
return strings.map { "\(raw: $0)" }
42-
}
43-
}
44-
4528
final class ExtensionMacroTests: XCTestCase {
4629
private let indentationWidth: Trivia = .spaces(2)
4730

48-
func testExtensionExpansion() {
49-
struct SendableExtensionMacro: ExtensionMacro {
50-
static func expansion(
51-
of node: AttributeSyntax,
52-
attachedTo: some DeclGroupSyntax,
53-
providingExtensionsOf type: some TypeSyntaxProtocol,
54-
conformingTo protocols: [TypeSyntax],
55-
in context: some MacroExpansionContext
56-
) throws -> [ExtensionDeclSyntax] {
57-
let sendableExtension: DeclSyntax =
58-
"""
59-
extension \(type.trimmed): Sendable {}
60-
"""
31+
func testSimpleExpansion() {
32+
assertMacroExpansion(
33+
"""
34+
@AddSendableExtension
35+
struct MyType {}
36+
""",
37+
expandedSource: """
38+
struct MyType {}
6139
62-
guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else {
63-
return []
40+
extension MyType: Sendable {
6441
}
42+
""",
43+
macros: ["AddSendableExtension": SendableExtensionMacro.self],
44+
indentationWidth: indentationWidth
45+
)
46+
}
6547

66-
return [extensionDecl]
67-
}
68-
}
69-
48+
func testNestedExpansion() {
7049
assertMacroExpansion(
7150
"""
72-
@AddSendableExtension
73-
struct MyType {
51+
struct Wrapper {
52+
@AddSendableExtension
53+
struct MyType {}
7454
}
7555
""",
7656
expandedSource: """
77-
78-
struct MyType {
57+
struct Wrapper {
58+
struct MyType {}
7959
}
8060
81-
extension MyType: Sendable {
61+
extension Wrapper.MyType: Sendable {
8262
}
8363
""",
8464
macros: ["AddSendableExtension": SendableExtensionMacro.self],
8565
indentationWidth: indentationWidth
8666
)
67+
}
8768

69+
func testNestedInExtensionExpansion() {
8870
assertMacroExpansion(
8971
"""
9072
struct Wrapper {
73+
struct AnotherWrapper {}
74+
}
75+
extension Wrapper.AnotherWrapper {
9176
@AddSendableExtension
92-
struct MyType {
93-
}
77+
struct MyType {}
9478
}
9579
""",
9680
expandedSource: """
9781
struct Wrapper {
98-
struct MyType {
82+
struct AnotherWrapper {}
83+
}
84+
extension Wrapper.AnotherWrapper {
85+
struct MyType {}
86+
}
87+
88+
extension Wrapper.AnotherWrapper.MyType: Sendable {
89+
}
90+
""",
91+
macros: ["AddSendableExtension": SendableExtensionMacro.self],
92+
indentationWidth: indentationWidth
93+
)
94+
}
95+
96+
func testComplexNestedExpansion() {
97+
assertMacroExpansion(
98+
"""
99+
struct Wrapper {}
100+
extension Wrapper {
101+
struct AnotherWrapper {
102+
@AddSendableExtension
103+
struct MyType {}
104+
}
105+
}
106+
""",
107+
expandedSource: """
108+
struct Wrapper {}
109+
extension Wrapper {
110+
struct AnotherWrapper {
111+
struct MyType {}
99112
}
100113
}
101114
102-
extension MyType: Sendable {
115+
extension Wrapper.AnotherWrapper.MyType: Sendable {
103116
}
104117
""",
105118
macros: ["AddSendableExtension": SendableExtensionMacro.self],
106119
indentationWidth: indentationWidth
107120
)
108121
}
109122

123+
func testAttachedToInvalid() {
124+
assertMacroExpansion(
125+
"@AddSendableExtension var foo: Int",
126+
expandedSource: "var foo: Int",
127+
macros: [
128+
"AddSendableExtension": SendableExtensionMacro.self
129+
]
130+
)
131+
132+
assertMacroExpansion(
133+
"""
134+
struct Foo {
135+
@AddSendableExtension var foo: Int
136+
}
137+
""",
138+
expandedSource:
139+
"""
140+
struct Foo {
141+
var foo: Int
142+
}
143+
""",
144+
macros: [
145+
"AddSendableExtension": SendableExtensionMacro.self
146+
]
147+
)
148+
}
149+
110150
func testEmpty() {
111-
struct TestMacro: ExtensionMacro {
151+
struct EmptyExtensionMacro: ExtensionMacro {
112152
static func expansion(
113153
of node: AttributeSyntax,
114154
attachedTo declaration: some DeclGroupSyntax,
@@ -121,11 +161,32 @@ final class ExtensionMacroTests: XCTestCase {
121161
}
122162

123163
assertMacroExpansion(
124-
"@Test struct Foo {}",
164+
"@Empty struct Foo {}",
125165
expandedSource: "struct Foo {}",
126166
macros: [
127-
"Test": TestMacro.self
167+
"Empty": EmptyExtensionMacro.self
128168
]
129169
)
130170
}
131171
}
172+
173+
fileprivate struct SendableExtensionMacro: ExtensionMacro {
174+
static func expansion(
175+
of node: AttributeSyntax,
176+
attachedTo: some DeclGroupSyntax,
177+
providingExtensionsOf type: some TypeSyntaxProtocol,
178+
conformingTo protocols: [TypeSyntax],
179+
in context: some MacroExpansionContext
180+
) throws -> [ExtensionDeclSyntax] {
181+
let sendableExtension: DeclSyntax =
182+
"""
183+
extension \(type.trimmed): Sendable {}
184+
"""
185+
186+
guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else {
187+
return []
188+
}
189+
190+
return [extensionDecl]
191+
}
192+
}

0 commit comments

Comments
 (0)