Hi! I'm experimenting with a macro that is using FunctionCallExprSyntax
to programmatically generate a function call. I'm seeing some unexpected behavior with trailing commas that looks different between release
and main
(from the swift-syntax
repo). Any ideas if this behavior is intended?
I start with a simple test:
final class FooTests: XCTestCase {
static let parameters = [
TokenSyntax.identifier("bar"),
TokenSyntax.identifier("baz"),
]
}
extension FooTests {
static func call(
with arguments: LabeledExprListSyntax
) -> FunctionCallExprSyntax {
FunctionCallExprSyntax(
calledExpression: DeclReferenceExprSyntax(
baseName: .identifier("foo")
),
leftParen: .leftParenToken(),
arguments: arguments,
rightParen: .rightParenToken()
)
}
}
extension FooTests {
public func testFooMainWithoutComma() {
let call = Self.call(
with: LabeledExprListSyntax {
// FIXME: Cannot convert value of type '[LabeledExprSyntax]' to expected argument type 'LabeledExprListBuilder.Expression' (aka 'LabeledExprSyntax')
Self.parameters.map { parameter in
LabeledExprSyntax(
label: parameter,
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: parameter
),
trailingComma: nil
)
}
}
)
XCTAssertEqual("\(call)", "foo(bar:bar,baz:baz)")
}
}
This test compiled (and passed) when building from main
… but failed to compile from release
. I explicitly pass nil
as a trailingComma
to LabeledExprSyntax
… but I'm getting my expected result and my test is passing.
Here's my next test:
extension FooTests {
public func testFooMainWithComma() {
let call = Self.call(
with: LabeledExprListSyntax {
// FIXME: Cannot convert value of type '[LabeledExprSyntax]' to expected argument type 'LabeledExprListBuilder.Expression' (aka 'LabeledExprSyntax')
Self.parameters.map { parameter in
LabeledExprSyntax(
label: parameter,
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: parameter
),
trailingComma: .commaToken()
)
}
}
)
// FIXME: XCTAssertEqual failed: ("foo(bar:bar,baz:baz,)") is not equal to ("foo(bar:bar,baz:baz)")
XCTAssertEqual("\(call)", "foo(bar:bar,baz:baz)")
}
}
This test compiled (and failed) when building from main
(and failed to compile from release
). I am explicitly passing a commaToken
to trailingComma
and the infra is giving me a comma after every LabeledExprSyntax
.
I can try a different approach to compile from release
:
extension FooTests {
func testFooReleaseWithoutComma() {
let call = Self.call(
with: LabeledExprListSyntax(
Self.parameters.map { parameter in
LabeledExprSyntax(
label: parameter,
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: parameter
),
trailingComma: nil
)
}
)
)
// FIXME: XCTAssertEqual failed: ("foo(bar:barbaz:baz)") is not equal to ("foo(bar:bar,baz:baz)")
XCTAssertEqual("\(call)", "foo(bar:bar,baz:baz)")
}
}
extension FooTests {
func testFooReleaseWithComma() {
let call = Self.call(
with: LabeledExprListSyntax(
Self.parameters.map { parameter in
LabeledExprSyntax(
label: parameter,
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: parameter
),
trailingComma: .commaToken()
)
}
)
)
// FIXME: XCTAssertEqual failed: ("foo(bar:bar,baz:baz,)") is not equal to ("foo(bar:bar,baz:baz)")
XCTAssertEqual("\(call)", "foo(bar:bar,baz:baz)")
}
}
Both those tests fail… passing a nil
for trailingComma
to testFooReleaseWithoutComma
fails… but passing a nil
for trailingComma
to testFooMainWithoutComma
passed.
I can try a different approach to build from release
and also pass a test:
extension FooTests {
func testFooReleaseWithConditionalComma() {
let call = Self.call(
with: LabeledExprListSyntax(
Self.parameters.map { parameter in
LabeledExprSyntax(
label: parameter,
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: parameter
),
trailingComma: (parameter == Self.parameters.last ? nil : .commaToken())
)
}
)
)
XCTAssertEqual("\(call)", "foo(bar:bar,baz:baz)")
}
}
This seems to unblock me… but I'm also open to suggestions if there is another legit way to achieve this same behavior. I have to assume that the Self.parameters
could change depending on where this macro is used… so I don't have an easy way to just hard-code the "function string" directly in my codegen.
Any ideas what is happening here?
- Why is not passing a comma passing my test in
testFooMainWithoutComma
? - Why is not passing a comma failing my test in
testFooReleaseWithoutComma
?
Is this the intended behavior? Could I expect main
to continue behaving like this… or this is a regression that should be changed to be consistent with release
?
Thanks!