Skip to content

Commit af44f0e

Browse files
committed
MacroSystem should remove the initializer for an accessor macro
SE-0389 specifies that a macro returning either a getter or setter should remove the initializer, if one exists. Resolves rdar://117442713 (#2310) (cherry picked from commit b3a3f1f)
1 parent a34f554 commit af44f0e

File tree

2 files changed

+146
-23
lines changed

2 files changed

+146
-23
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ private func expandAccessorMacroWithExistingAccessors(
307307
return nil
308308
}
309309

310-
// Separate the accessor from any existing accessors by two spaces
310+
// Separate the accessor from any existing accessors by an empty line
311311
let indentedSource = "\n" + expanded.indented(by: attachedTo.indentationOfFirstLine + indentationWidth)
312312
return "\(raw: indentedSource)"
313313
}
@@ -636,13 +636,26 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
636636
context.addDiagnostics(from: MacroApplicationError.accessorMacroOnVariableWithMultipleBindings, node: node)
637637
return DeclSyntax(node)
638638
}
639-
node.bindings[node.bindings.startIndex].accessorBlock = expandAccessors(of: node, existingAccessors: binding.accessorBlock)
639+
640+
let expansion = expandAccessors(of: node, existingAccessors: binding.accessorBlock)
641+
if expansion.accessors != binding.accessorBlock {
642+
if binding.initializer != nil, expansion.expandsGetSet {
643+
// The accessor block will have a leading space, but there will already be a
644+
// space between the variable and the to-be-removed initializer. Remove the
645+
// leading trivia on the accessor block so we don't double up.
646+
node.bindings[node.bindings.startIndex].accessorBlock = expansion.accessors?.with(\.leadingTrivia, [])
647+
node.bindings[node.bindings.startIndex].initializer = nil
648+
} else {
649+
node.bindings[node.bindings.startIndex].accessorBlock = expansion.accessors
650+
}
651+
}
652+
640653
return DeclSyntax(node)
641654
}
642655

643656
override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax {
644657
var node = super.visit(node).cast(SubscriptDeclSyntax.self)
645-
node.accessorBlock = expandAccessors(of: node, existingAccessors: node.accessorBlock)
658+
node.accessorBlock = expandAccessors(of: node, existingAccessors: node.accessorBlock).accessors
646659
return DeclSyntax(node)
647660
}
648661
}
@@ -803,14 +816,23 @@ extension MacroApplication {
803816
}
804817
}
805818

806-
/// Expand all 'accessor' macros attached to `storage` and return the `storage`
807-
/// node.
819+
/// Expand all 'accessor' macros attached to `storage`.
808820
///
809-
/// - Returns: The storage node with all macro-synthesized accessors applied.
810-
private func expandAccessors(of storage: some DeclSyntaxProtocol, existingAccessors: AccessorBlockSyntax?) -> AccessorBlockSyntax? {
821+
/// - Returns: The final accessors block that includes both the existing
822+
/// and expanded accessors, as well as whether any `get`/`set` were
823+
/// expanded (in which case any initializer on `storage` should be
824+
/// removed).
825+
private func expandAccessors(of storage: some DeclSyntaxProtocol, existingAccessors: AccessorBlockSyntax?) -> (
826+
accessors: AccessorBlockSyntax?, expandsGetSet: Bool
827+
) {
811828
let accessorMacros = macroAttributes(attachedTo: DeclSyntax(storage), ofType: AccessorMacro.Type.self)
812829

813830
var newAccessorsBlock = existingAccessors
831+
var expandsGetSet = false
832+
func checkExpansions(_ accessors: AccessorDeclListSyntax?) {
833+
guard let accessors else { return }
834+
expandsGetSet = expandsGetSet || accessors.contains(where: \.isGetOrSet)
835+
}
814836

815837
for macro in accessorMacros {
816838
do {
@@ -828,6 +850,8 @@ extension MacroApplication {
828850
in: context,
829851
indentationWidth: indentationWidth
830852
) {
853+
checkExpansions(newAccessors)
854+
831855
// If existingAccessors is not `nil`, then we also set
832856
// `newAccessorBlock` above to a a non-nil value, so
833857
// `newAccessorsBlock` also isn’t `nil`.
@@ -836,31 +860,33 @@ extension MacroApplication {
836860
indentationWidth: self.indentationWidth
837861
)
838862
}
839-
} else {
840-
let newAccessors = try expandAccessorMacroWithoutExistingAccessors(
841-
definition: macro.definition,
842-
attributeNode: macro.attributeNode,
843-
attachedTo: DeclSyntax(storage),
844-
in: context,
845-
indentationWidth: indentationWidth
846-
)
847-
if newAccessorsBlock == nil {
848-
newAccessorsBlock = newAccessors
849-
} else if let newAccessors = newAccessors {
850-
guard case .accessors(let accessorList) = newAccessors.accessors else {
851-
throw MacroApplicationError.malformedAccessor
852-
}
853-
newAccessorsBlock = newAccessorsBlock!.addingAccessors(
863+
} else if let newAccessors = try expandAccessorMacroWithoutExistingAccessors(
864+
definition: macro.definition,
865+
attributeNode: macro.attributeNode,
866+
attachedTo: DeclSyntax(storage),
867+
in: context,
868+
indentationWidth: indentationWidth
869+
) {
870+
guard case .accessors(let accessorList) = newAccessors.accessors else {
871+
throw MacroApplicationError.malformedAccessor
872+
}
873+
874+
checkExpansions(accessorList)
875+
876+
if let oldBlock = newAccessorsBlock {
877+
newAccessorsBlock = oldBlock.addingAccessors(
854878
from: accessorList,
855879
indentationWidth: self.indentationWidth
856880
)
881+
} else {
882+
newAccessorsBlock = newAccessors
857883
}
858884
}
859885
} catch {
860886
context.addDiagnostics(from: error, node: macro.attributeNode)
861887
}
862888
}
863-
return newAccessorsBlock
889+
return (newAccessorsBlock, expandsGetSet)
864890
}
865891
}
866892

@@ -1064,3 +1090,9 @@ private extension AttributeSyntax {
10641090
return (detach(in: context, foldingWith: operatorTable) as Syntax).cast(Self.self)
10651091
}
10661092
}
1093+
1094+
private extension AccessorDeclSyntax {
1095+
var isGetOrSet: Bool {
1096+
return accessorSpecifier.tokenKind == .keyword(.get) || accessorSpecifier.tokenKind == .keyword(.set)
1097+
}
1098+
}

Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,95 @@ final class AccessorMacroTests: XCTestCase {
320320
macros: ["Test": TestMacro.self]
321321
)
322322
}
323+
324+
func testInitializerRemovedForGetSet() {
325+
assertMacroExpansion(
326+
"""
327+
@constantOne
328+
var x: Int = 1
329+
""",
330+
expandedSource: """
331+
var x: Int {
332+
get {
333+
return 1
334+
}
335+
}
336+
""",
337+
macros: ["constantOne": ConstantOneGetter.self],
338+
indentationWidth: indentationWidth
339+
)
340+
341+
// Bit of an odd case, compiler has the type but we don't know it in `MacroSystem`
342+
assertMacroExpansion(
343+
"""
344+
@constantOne
345+
var x = 1
346+
""",
347+
expandedSource: """
348+
var x {
349+
get {
350+
return 1
351+
}
352+
}
353+
""",
354+
macros: ["constantOne": ConstantOneGetter.self],
355+
indentationWidth: indentationWidth
356+
)
357+
}
358+
359+
func testInitializerRemainsForObserver() {
360+
struct DidSetAdder: AccessorMacro {
361+
static func expansion(
362+
of node: AttributeSyntax,
363+
providingAccessorsOf declaration: some DeclSyntaxProtocol,
364+
in context: some MacroExpansionContext
365+
) throws -> [AccessorDeclSyntax] {
366+
return [
367+
"""
368+
didSet {
369+
}
370+
"""
371+
]
372+
}
373+
}
374+
375+
assertMacroExpansion(
376+
"""
377+
@addDidSet
378+
var x = 1
379+
""",
380+
expandedSource: """
381+
var x = 1 {
382+
didSet {
383+
}
384+
}
385+
""",
386+
macros: ["addDidSet": DidSetAdder.self],
387+
indentationWidth: indentationWidth
388+
)
389+
390+
// Invalid semantically, but we shouldn't remove the initializer as the
391+
// macro did not produce a getter/setter
392+
assertMacroExpansion(
393+
"""
394+
@addDidSet
395+
var x = 1 {
396+
get {
397+
return 1
398+
}
399+
}
400+
""",
401+
expandedSource: """
402+
var x = 1 {
403+
get {
404+
return 1
405+
}
406+
didSet {
407+
}
408+
}
409+
""",
410+
macros: ["addDidSet": DidSetAdder.self],
411+
indentationWidth: indentationWidth
412+
)
413+
}
323414
}

0 commit comments

Comments
 (0)