I write a public memberwise init macro of my own version (it's similar to those availbale on the net). It works well in Xcode, but when I add a test for it, assertMacroExpansion()
reports a syntax error (not a diff error). Below are the details and my analysis.
The test:
func testMemberwiseInit() {
assertMacroExpansion(
"""
@mInit
public struct Foo {
var x: Int
var y: String
}
""",
expandedSource:
"""
public struct Foo {
var x: Int
var y: String
public init(x: Int, y: String) {
self.x = x
self.y = y
}
}
""",
macros: testMacros
)
}
The error message:
failed - Expanded source should not contain any syntax errors, but contains:
3 │ var x: Int
4 │ var y: Stringpublic init(x: Int, y: String) {self.x = x
5 │ self.y = y}
│ ╰─ error: unexpected code 'self.y = y' in initializer
6 │ }
The error message also includes the expanded syntax tree, which shows there is only one statement in init
body and hence the syntax error (see unexpectedAfterElements
node at the end of the log).
Expanded syntax tree
Expanded syntax tree was:
SourceFileSyntax
├─statements: CodeBlockItemListSyntax
│ ╰─[0]: CodeBlockItemSyntax
│ ╰─item: StructDeclSyntax
│ ├─modifiers: ModifierListSyntax
│ │ ╰─[0]: DeclModifierSyntax
│ │ ╰─name: keyword(SwiftSyntax.Keyword.public)
│ ├─structKeyword: keyword(SwiftSyntax.Keyword.struct)
│ ├─identifier: identifier("Foo")
│ ╰─memberBlock: MemberDeclBlockSyntax
│ ├─leftBrace: leftBrace
│ ├─members: MemberDeclListSyntax
│ │ ├─[0]: MemberDeclListItemSyntax
│ │ │ ╰─decl: VariableDeclSyntax
│ │ │ ├─bindingKeyword: keyword(SwiftSyntax.Keyword.var)
│ │ │ ╰─bindings: PatternBindingListSyntax
│ │ │ ╰─[0]: PatternBindingSyntax
│ │ │ ├─pattern: IdentifierPatternSyntax
│ │ │ │ ╰─identifier: identifier("x")
│ │ │ ╰─typeAnnotation: TypeAnnotationSyntax
│ │ │ ├─colon: colon
│ │ │ ╰─name: identifier("Int")
│ │ ├─[1]: MemberDeclListItemSyntax
│ │ │ ╰─decl: VariableDeclSyntax
│ │ │ ├─bindingKeyword: keyword(SwiftSyntax.Keyword.var)
│ │ │ ╰─bindings: PatternBindingListSyntax
│ │ │ ╰─[0]: PatternBindingSyntax
│ │ │ ├─pattern: IdentifierPatternSyntax
│ │ │ │ ╰─identifier: identifier("y")
│ │ │ ╰─typeAnnotation: TypeAnnotationSyntax
│ │ │ ├─colon: colon
│ │ │ ╰─type: SimpleTypeIdentifierSyntax
│ │ │ ╰─name: identifier("String")
│ │ ╰─[2]: MemberDeclListItemSyntax
│ │ ╰─decl: InitializerDeclSyntax
│ │ ├─modifiers: ModifierListSyntax
│ │ │ ╰─[0]: DeclModifierSyntax
│ │ │ ╰─name: keyword(SwiftSyntax.Keyword.public)
│ │ ├─initKeyword: keyword(SwiftSyntax.Keyword.init)
│ │ ├─signature: FunctionSignatureSyntax
│ │ │ ╰─input: ParameterClauseSyntax
│ │ │ ├─leftParen: leftParen
│ │ │ ├─parameterList: FunctionParameterListSyntax
│ │ │ │ ├─[0]: FunctionParameterSyntax
│ │ │ │ │ ├─firstName: identifier("x")
│ │ │ │ │ ├─colon: colon
│ │ │ │ │ ├─type: SimpleTypeIdentifierSyntax
│ │ │ │ │ │ ╰─name: identifier("Int")
│ │ │ │ │ ╰─trailingComma: comma
│ │ │ │ ╰─[1]: FunctionParameterSyntax
│ │ │ │ ├─firstName: identifier("y")
│ │ │ │ ├─colon: colon
│ │ │ │ ╰─type: SimpleTypeIdentifierSyntax
│ │ │ │ ╰─name: identifier("String")
│ │ │ ╰─rightParen: rightParen
│ │ ╰─body: CodeBlockSyntax
│ │ ├─leftBrace: leftBrace
│ │ ├─statements: CodeBlockItemListSyntax
│ │ │ ╰─[0]: CodeBlockItemSyntax
│ │ │ ╰─item: SequenceExprSyntax
│ │ │ ├─elements: ExprListSyntax
│ │ │ │ ├─[0]: MemberAccessExprSyntax
│ │ │ │ │ ├─base: IdentifierExprSyntax
│ │ │ │ │ │ ╰─identifier: keyword(SwiftSyntax.Keyword.self)
│ │ │ │ │ ├─dot: period
│ │ │ │ │ ╰─name: identifier("x")
│ │ │ │ ├─[1]: AssignmentExprSyntax
│ │ │ │ │ ╰─assignToken: equal
│ │ │ │ ╰─[2]: IdentifierExprSyntax
│ │ │ │ ╰─identifier: identifier("x")
│ │ │ ╰─unexpectedAfterElements: UnexpectedNodesSyntax
│ │ │ ├─[0]: keyword(SwiftSyntax.Keyword.self)
│ │ │ ├─[1]: period
│ │ │ ├─[2]: identifier("y")
│ │ │ ├─[3]: equal
│ │ │ ╰─[4]: identifier("y")
│ │ ╰─rightBrace: rightBrace
│ ╰─rightBrace: rightBrace
╰─eofToken: eof
But I don't understand how that could occur. My macro implementation does adds a \n
character when generating the code. And if it was a bug in my code, the diff check in assertMacroExpansion()
would fail in the first place, because I define the expandedSource
param in my test as below:
"""
public struct Foo {
var x: Int
var y: String
public init(x: Int, y: String) {
self.x = x
self.y = y
}
}
""",
Since the diff check passes, how can the expanded syntax tree in the log is like the above?
So I suspect it's not an issue with my code. However, I did an experiment by downloading other people's implementation on the net and added the test to it. The test passed. So it seemed to suggest the odd issue is specific to my code.
I wonder if anyone have a possible explanation on this odd behavior? I googled but can't find any report of the issue. My environment: macOS 13.4, Xcode 15.0 beta (the first beta release). Thanks.