From 47c846c8a3a6f49432c9ee70925dcf3d1e1885b8 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 13 Sep 2023 14:09:24 -0700 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20add=20any=20accessors=20if=20ac?= =?UTF-8?q?cessor=20macro=20returned=20an=20empty=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If an accessor macro returns an empty array, MacroSystem was failing with internal error messages because it tried ot parse an `AccessorBlockSyntax` from an empty string. To fix this, check if the expanded source is empty before trying to parse the `AccessorBlockSyntax`. --- .../MacroSystem.swift | 3 +- .../MacroSystemTests.swift | 226 ++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index 0f9f627dc33..e6b12263a53 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -259,7 +259,8 @@ private func expandAccessorMacroWithoutExistingAccessors( conformanceList: nil, in: context, indentationWidth: indentationWidth - ) + ), + !expanded.isEmpty else { return nil } diff --git a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift index f6e413a554d..60bb18be223 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift @@ -2088,4 +2088,230 @@ final class MacroSystemTests: XCTestCase { macros: ["Test": DiagnoseFirstArgument.self] ) } + + func testEmptyAccessorMacro() { + struct TestMacro: AccessorMacro { + static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + return [] + } + } + + // The compiler will reject this with + // 'Expansion of macro 'Test()' did not produce a non-observing accessor' + // We consider this a semantic error because swift-syntax doesn't have + // knowledge about which accessors are observing and which ones aren't. + assertMacroExpansion( + "@Test var x: Int", + expandedSource: "var x: Int", + macros: ["Test": TestMacro.self] + ) + + assertMacroExpansion( + "@Test var x: Int { 1 }", + expandedSource: "var x: Int { 1 }", + macros: ["Test": TestMacro.self] + ) + } + + func testEmitErrorFromAccessorMacro() { + struct TestMacro: AccessorMacro { + static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + context.diagnose(Diagnostic(node: node, message: MacroExpansionErrorMessage("test"))) + return [] + } + } + + assertMacroExpansion( + "@Test var x: Int", + expandedSource: "var x: Int", + diagnostics: [ + DiagnosticSpec(message: "test", line: 1, column: 1) + ], + macros: ["Test": TestMacro.self] + ) + + assertMacroExpansion( + "@Test var x: Int { 1 }", + expandedSource: "var x: Int { 1 }", + diagnostics: [DiagnosticSpec(message: "test", line: 1, column: 1)], + macros: ["Test": TestMacro.self] + ) + } + + func testEmptyCodeItemMacro() { + struct TestMacro: CodeItemMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + return [] + } + } + + assertMacroExpansion( + "#test", + expandedSource: "", + macros: [ + "test": TestMacro.self + ] + ) + } + + func testEmptyDeclarationMacro() { + struct TestMacro: DeclarationMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [] + } + } + + assertMacroExpansion( + "#test", + expandedSource: "", + macros: [ + "test": TestMacro.self + ] + ) + } + + func testEmptyExtensionMacro() { + struct TestMacro: ExtensionMacro { + static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + return [] + } + } + + assertMacroExpansion( + "@Test struct Foo {}", + expandedSource: "struct Foo {}", + macros: [ + "Test": TestMacro.self + ] + ) + } + + func testEmptyMemberAttributeMacro() { + struct TestMacro: MemberAttributeMacro { + static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + return [] + } + } + + assertMacroExpansion( + """ + @Test + struct Foo { + var x: Int + } + """, + expandedSource: """ + struct Foo { + var x: Int + } + """, + macros: [ + "Test": TestMacro.self + ] + ) + + assertMacroExpansion( + """ + @Test + struct Foo { + } + """, + expandedSource: """ + struct Foo { + } + """, + macros: [ + "Test": TestMacro.self + ] + ) + } + + func testEmptyMemberMacro() { + struct TestMacro: MemberMacro { + static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [] + } + } + + assertMacroExpansion( + """ + @Test + struct Foo { + var x: Int + } + """, + expandedSource: """ + struct Foo { + var x: Int + } + """, + macros: [ + "Test": TestMacro.self + ] + ) + + assertMacroExpansion( + """ + @Test + struct Foo { + } + """, + expandedSource: """ + struct Foo { + } + """, + macros: [ + "Test": TestMacro.self + ] + ) + } + + func testEmptyPeerMacro() { + struct TestMacro: PeerMacro { + static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [] + } + } + + assertMacroExpansion( + "@Test var x: Int", + expandedSource: "var x: Int", + macros: [ + "Test": TestMacro.self + ] + ) + } }