Skip to content

Commit 73aad93

Browse files
committed
Fix a formatting issue if a member attribute macro is applied to properties that already have an attribute
If a variable already had a macro already had an attribute and we add a new attribute to it via a member macro, the produced code was invalid. For example, ```swift @wrapStoredProperties struct Foo { @available(*, deprecated) var x: Int } ``` produced ```swift struct Foo { @available(*, deprecated) @Wrappervar x: Int } ``` which is invalid because there is no space between `Wrapper` and `var`. To fix this, transfer the trailing trivia from the last attribute to the last newly inserted attribute. This way, we essentially insert the new attributes right after the last attribute in source, but before its trailing trivia, keeping the trivia that separates the attribute block from the variable itself. ```swift struct Foo { @available(*, deprecated) @wrapper var x: Int } ``` rdar://114275860
1 parent d45ffea commit 73aad93

File tree

2 files changed

+205
-6
lines changed

2 files changed

+205
-6
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,14 +573,21 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
573573
// Expand member attribute members attached to the declaration context.
574574
// Note that MemberAttribute macros are _not_ applied to generated members
575575
if let parentDeclGroup, let decl = item.decl.asProtocol(WithAttributesSyntax.self) {
576-
var newAttributes = expandAttributesFromMemberAttributeMacros(
577-
of: item.decl,
578-
parentDecl: parentDeclGroup
576+
var newAttributes = AttributeListSyntax(
577+
expandAttributesFromMemberAttributeMacros(
578+
of: item.decl,
579+
parentDecl: parentDeclGroup
580+
)
581+
.map { visit($0) }
579582
)
580-
.map { visit($0) }
581583
if !newAttributes.isEmpty {
582-
newAttributes.insert(contentsOf: decl.attributes, at: 0)
583-
item.decl = decl.with(\.attributes, AttributeListSyntax(newAttributes)).cast(DeclSyntax.self)
584+
// Transfer the trailing trivia from the old attributes to the new attributes.
585+
// This way, we essentially insert the new attributes right after the last attribute in source
586+
// but before its trailing trivia, keeping the trivia that separates the attribute block
587+
// from the variable itself.
588+
newAttributes.trailingTrivia = newAttributes.trailingTrivia + decl.attributes.trailingTrivia
589+
newAttributes.insert(contentsOf: decl.attributes.with(\.trailingTrivia, []), at: newAttributes.startIndex)
590+
item.decl = decl.with(\.attributes, newAttributes).cast(DeclSyntax.self)
584591
}
585592
}
586593

Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,198 @@ final class MacroSystemTests: XCTestCase {
12281228
)
12291229
}
12301230

1231+
func testMemberAttributeMacroOnPropertyThatAlreadyHasAttribute() {
1232+
struct TestMacro: MemberAttributeMacro {
1233+
static func expansion(
1234+
of node: AttributeSyntax,
1235+
attachedTo decl: some DeclGroupSyntax,
1236+
providingAttributesFor member: some DeclSyntaxProtocol,
1237+
in context: some MacroExpansionContext
1238+
) throws -> [AttributeSyntax] {
1239+
return [
1240+
AttributeSyntax(
1241+
attributeName: IdentifierTypeSyntax(
1242+
name: .identifier("Wrapper")
1243+
)
1244+
)
1245+
]
1246+
}
1247+
}
1248+
1249+
assertMacroExpansion(
1250+
"""
1251+
@Test
1252+
struct Foo {
1253+
@available(*, deprecated) var x: Int
1254+
}
1255+
""",
1256+
expandedSource: """
1257+
struct Foo {
1258+
@available(*, deprecated)
1259+
@Wrapper var x: Int
1260+
}
1261+
""",
1262+
macros: ["Test": TestMacro.self],
1263+
indentationWidth: indentationWidth
1264+
)
1265+
1266+
assertMacroExpansion(
1267+
"""
1268+
@Test
1269+
struct Foo {
1270+
@available(*, deprecated) /* x */ var x: Int
1271+
}
1272+
""",
1273+
expandedSource: """
1274+
struct Foo {
1275+
@available(*, deprecated)
1276+
@Wrapper /* x */ var x: Int
1277+
}
1278+
""",
1279+
macros: ["Test": TestMacro.self],
1280+
indentationWidth: indentationWidth
1281+
)
1282+
1283+
assertMacroExpansion(
1284+
"""
1285+
@Test
1286+
struct Foo {
1287+
@available(*, deprecated)
1288+
1289+
var x: Int
1290+
}
1291+
""",
1292+
expandedSource: """
1293+
struct Foo {
1294+
@available(*, deprecated)
1295+
@Wrapper
1296+
1297+
var x: Int
1298+
}
1299+
""",
1300+
macros: ["Test": TestMacro.self],
1301+
indentationWidth: indentationWidth
1302+
)
1303+
1304+
assertMacroExpansion(
1305+
"""
1306+
@Test
1307+
struct Foo {
1308+
@available(*, deprecated) // some comment
1309+
1310+
var x: Int
1311+
}
1312+
""",
1313+
expandedSource: """
1314+
struct Foo {
1315+
@available(*, deprecated)
1316+
@Wrapper // some comment
1317+
1318+
var x: Int
1319+
}
1320+
""",
1321+
macros: ["Test": TestMacro.self],
1322+
indentationWidth: indentationWidth
1323+
)
1324+
}
1325+
1326+
func testMemberAttributeWithTriviaMacroOnPropertyThatAlreadyHasAttribute() {
1327+
struct TestMacro: MemberAttributeMacro {
1328+
static func expansion(
1329+
of node: AttributeSyntax,
1330+
attachedTo decl: some DeclGroupSyntax,
1331+
providingAttributesFor member: some DeclSyntaxProtocol,
1332+
in context: some MacroExpansionContext
1333+
) throws -> [AttributeSyntax] {
1334+
return [
1335+
AttributeSyntax(
1336+
leadingTrivia: .blockComment("/* start */"),
1337+
attributeName: IdentifierTypeSyntax(
1338+
name: .identifier("Wrapper")
1339+
),
1340+
trailingTrivia: .blockComment("/* end */")
1341+
)
1342+
]
1343+
}
1344+
}
1345+
1346+
assertMacroExpansion(
1347+
"""
1348+
@Test
1349+
struct Foo {
1350+
@available(*, deprecated) var x: Int
1351+
}
1352+
""",
1353+
expandedSource: """
1354+
struct Foo {
1355+
@available(*, deprecated)
1356+
/* start */@Wrapper/* end */ var x: Int
1357+
}
1358+
""",
1359+
macros: ["Test": TestMacro.self],
1360+
indentationWidth: indentationWidth
1361+
)
1362+
1363+
assertMacroExpansion(
1364+
"""
1365+
@Test
1366+
struct Foo {
1367+
@available(*, deprecated) /* x */ var x: Int
1368+
}
1369+
""",
1370+
expandedSource: """
1371+
struct Foo {
1372+
@available(*, deprecated)
1373+
/* start */@Wrapper/* end */ /* x */ var x: Int
1374+
}
1375+
""",
1376+
macros: ["Test": TestMacro.self],
1377+
indentationWidth: indentationWidth
1378+
)
1379+
1380+
assertMacroExpansion(
1381+
"""
1382+
@Test
1383+
struct Foo {
1384+
@available(*, deprecated)
1385+
1386+
var x: Int
1387+
}
1388+
""",
1389+
expandedSource: """
1390+
struct Foo {
1391+
@available(*, deprecated)
1392+
/* start */@Wrapper/* end */
1393+
1394+
var x: Int
1395+
}
1396+
""",
1397+
macros: ["Test": TestMacro.self],
1398+
indentationWidth: indentationWidth
1399+
)
1400+
1401+
assertMacroExpansion(
1402+
"""
1403+
@Test
1404+
struct Foo {
1405+
@available(*, deprecated) // some comment
1406+
1407+
var x: Int
1408+
}
1409+
""",
1410+
expandedSource: """
1411+
struct Foo {
1412+
@available(*, deprecated)
1413+
/* start */@Wrapper/* end */ // some comment
1414+
1415+
var x: Int
1416+
}
1417+
""",
1418+
macros: ["Test": TestMacro.self],
1419+
indentationWidth: indentationWidth
1420+
)
1421+
}
1422+
12311423
func testTypeWrapperTransform() {
12321424
assertMacroExpansion(
12331425
"""

0 commit comments

Comments
 (0)