Skip to content

Commit 50c2e87

Browse files
committed
MacroSystem should qualify nested types when expanding extension macros
Resolves rdar://119850970.
1 parent 184930a commit 50c2e87

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,19 +312,61 @@ private func expandAccessorMacroWithExistingAccessors(
312312
return "\(raw: indentedSource)"
313313
}
314314

315+
private func buildQualifiedType(_ inner: DeclSyntax) -> TypeSyntax? {
316+
if let ext = inner.as(ExtensionDeclSyntax.self) {
317+
return ext.extendedType.trimmed
318+
}
319+
320+
guard let identified = inner.asProtocol(NamedDeclSyntax.self) else {
321+
return nil
322+
}
323+
324+
var typeNames: [TokenSyntax] = []
325+
var parentExtension: TypeSyntax? = nil
326+
var possibleParent = inner.parent
327+
while let parent = possibleParent {
328+
possibleParent = parent.parent
329+
if let parent = parent.asProtocol(NamedDeclSyntax.self) {
330+
typeNames.append(parent.name.trimmed)
331+
} else if let parent = parent.as(ExtensionDeclSyntax.self) {
332+
// Extensions must be top level
333+
if parentExtension != nil {
334+
return nil
335+
}
336+
parentExtension = parent.extendedType.trimmed
337+
}
338+
}
339+
340+
// Attached type is within an extension
341+
if let parentExtension {
342+
// Extensions must be top level
343+
if !typeNames.isEmpty {
344+
return nil
345+
}
346+
return TypeSyntax(MemberTypeSyntax(baseType: parentExtension, name: identified.name.trimmed))
347+
}
348+
349+
// Attached type is top level
350+
if typeNames.isEmpty {
351+
return TypeSyntax(IdentifierTypeSyntax(name: identified.name.trimmed))
352+
}
353+
354+
// Attached type is nested in another type(s)
355+
var baseType: TypeSyntax = TypeSyntax(IdentifierTypeSyntax(name: typeNames.popLast()!))
356+
while let typeName = typeNames.popLast() {
357+
baseType = TypeSyntax(MemberTypeSyntax(baseType: baseType, name: typeName))
358+
}
359+
return TypeSyntax(MemberTypeSyntax(baseType: baseType, name: identified.name.trimmed))
360+
}
361+
315362
private func expandExtensionMacro(
316363
definition: ExtensionMacro.Type,
317364
attributeNode: AttributeSyntax,
318365
attachedTo: DeclSyntax,
319366
in context: some MacroExpansionContext,
320367
indentationWidth: Trivia
321368
) 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 {
369+
guard let extendedType = buildQualifiedType(attachedTo) else {
328370
return nil
329371
}
330372

Tests/SwiftSyntaxMacroExpansionTest/ExtensionMacroTests.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,34 @@ final class ExtensionMacroTests: XCTestCase {
9999
}
100100
}
101101
102-
extension MyType: Sendable {
102+
extension Wrapper.MyType: Sendable {
103+
}
104+
""",
105+
macros: ["AddSendableExtension": SendableExtensionMacro.self],
106+
indentationWidth: indentationWidth
107+
)
108+
109+
assertMacroExpansion(
110+
"""
111+
struct Wrapper {
112+
struct AnotherWrapper {}
113+
}
114+
extension Wrapper.AnotherWrapper {
115+
@AddSendableExtension
116+
struct MyType {
117+
}
118+
}
119+
""",
120+
expandedSource: """
121+
struct Wrapper {
122+
struct AnotherWrapper {}
123+
}
124+
extension Wrapper.AnotherWrapper {
125+
struct MyType {
126+
}
127+
}
128+
129+
extension Wrapper.AnotherWrapper.MyType: Sendable {
103130
}
104131
""",
105132
macros: ["AddSendableExtension": SendableExtensionMacro.self],

0 commit comments

Comments
 (0)