Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ let diagnosticDomain: String = "SwiftSyntaxMacroExpansion"
private enum MacroApplicationError: DiagnosticMessage, Error {
case accessorMacroOnVariableWithMultipleBindings
case peerMacroOnVariableWithMultipleBindings
case memberMacroOnInvalidDecl(macroName: String)
case malformedAccessor

var diagnosticID: MessageID {
Expand All @@ -637,6 +638,8 @@ private enum MacroApplicationError: DiagnosticMessage, Error {
return "accessor macro can only be applied to a single variable"
case .peerMacroOnVariableWithMultipleBindings:
return "peer macro can only be applied to a single variable"
case .memberMacroOnInvalidDecl(let macroName):
return "macro '\(macroName)' can only be applied to a struct, enum, class, extension, or actor"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say member macro here for clarity, also I'm not sure it's worth listing every type decl kind (note you missed protocol), maybe it would be better just to say type declaration or extension instead?

case .malformedAccessor:
return """
macro returned a malformed accessor. Accessors should start with an introducer like 'get' or 'set'.
Expand Down Expand Up @@ -702,6 +705,18 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
let attributedNode = node.asProtocol(WithAttributesSyntax.self),
!attributedNode.attributes.isEmpty
{
// Check if there are any member macros generated on a non-group decl.
if !(node.isProtocol(DeclGroupSyntax.self) || node.is(ExtensionDeclSyntax.self)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An ExtensionDeclSyntax is a DeclGroupSyntax so you should only need the former check I think

let memberMacros = self.macroAttributes(attachedTo: declSyntax, ofType: MemberMacro.Type.self)
for (attribute, _, _) in memberMacros {
let macroName = attribute.attributeName.trimmedDescription
contextGenerator(Syntax(node)).addDiagnostics(
from: MacroApplicationError.memberMacroOnInvalidDecl(macroName: macroName),
node: attribute
)
}
}

// Apply body and preamble macros.
if let nodeWithBody = node.asProtocol(WithOptionalCodeBlockSyntax.self),
let declNodeWithBody = nodeWithBody as? any DeclSyntaxProtocol & WithOptionalCodeBlockSyntax
Expand Down
41 changes: 41 additions & 0 deletions Tests/SwiftSyntaxMacroExpansionTest/Issue2206Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacroExpansion
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest

private struct NoOpMemberMacro: MemberMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return []
}
}

final class Issue2206Tests: XCTestCase {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be folded into the existing tests we have for member macros in MemberMacroTests.swift

private let indentationWidth: Trivia = .spaces(2)

func testMemberMacroOnVariable() {
// Issue #2206: assertMacroExpansion should emit an error if member macro is applied to declaration that can’t have members
// Currently this is expected to FAIL because the diagnostic is swallowed.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated comment?

assertMacroExpansion(
"""
@Test
var x: Int
""",
expandedSource: """
var x: Int
""",
diagnostics: [
DiagnosticSpec(message: "macro 'Test' can only be applied to a struct, enum, class, extension, or actor", line: 1, column: 1)
],
macros: ["Test": NoOpMemberMacro.self],
indentationWidth: indentationWidth
)
}
}