Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions Sources/Rules/TrailingCommas.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,41 @@ public extension FormatRule {

if formatter.options.swiftVersion >= "6.2" {
trailingCommaSupported = true

// In Swift 6.2, trailing commas in generic argument lists within expressions
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot, this is a new regression in the past few weeks. Can you find what specific change caused the regression?

This code is pretty complicated. Maybe it would be better to revert the change that caused the regression, or scope that change to only Swift 6.3+.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The regression is in the > endOfScope handler, which unconditionally sets trailingCommaSupported = true for all swiftVersion >= "6.2" contexts since commit 02e45820. However, trailing commas in generic argument lists within expressions (like Array<T>()) require Swift 6.3+. Swift 6.2 only supports them in type contexts and declarations.

The regression surfaces when > ends up on its own line (e.g., after wrapArguments formats the code), at which point addOrRemoveTrailingComma inserts a comma before >.

Rather than reverting 02e45820 (which would lose all the correct Swift 6.2 improvements), I've scoped the expression-context exclusion to swiftVersion < "6.3": when > is followed by (, we check if it's a declaration (func foo<T>(), init<T>(), init?<T>(), subscript<T>()). If not, it's expression context and no trailing comma is added. Updated in commit 7aa0009.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot, I'm very certain it is not commit 02e45820 that introduced the regression. That commit is from September. The regression was introduced within the past few weeks.

// are not supported (only allowed in Swift 6.3+).
// For example, `Array<Int>()` with a trailing comma like `Array<Int,>()` is not valid in Swift 6.2.
// Only generic parameter lists in declarations like `func foo<T>()` or `init<T>()` are supported.
if formatter.options.swiftVersion < "6.3",
let tokenAfterClose = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i),
formatter.tokens[tokenAfterClose] == .startOfScope("(")
{
// Determine if this is a generic parameter list in a declaration
// (e.g. `func foo<T>()` or `init<T>()`) versus a generic argument list
// in an expression (e.g. `Array<T>()`).
let isDeclaration: Bool
if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfScope) {
let prevToken = formatter.tokens[prevIndex]
if prevToken == .keyword("init") || prevToken == .keyword("subscript") {
// `init<T>()` and `subscript<T>()` are declarations
isDeclaration = true
} else if prevToken.isIdentifier,
let kwIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex),
formatter.tokens[kwIndex] == .keyword("func")
{
// `func identifier<T>()` is a declaration
isDeclaration = true
} else {
isDeclaration = false
}
} else {
isDeclaration = false
}

if !isDeclaration {
trailingCommaSupported = false
}
}
} else if formatter.options.swiftVersion == "6.1" {
// In Swift 6.1, only generic lists in concrete type / function / typealias declarations are allowed.
// https://github.com/swiftlang/swift/issues/81474
Expand Down
31 changes: 31 additions & 0 deletions Tests/Rules/TrailingCommasTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,37 @@ final class TrailingCommasTests: XCTestCase {
testFormatting(for: input, output, rule: .trailingCommas, options: options, exclude: [.typeSugar, .propertyTypes])
}

func testTrailingCommasNotAddedToGenericArgumentListInExpressionInSwift6_2() {
// In Swift 6.2, trailing commas in generic argument lists within expressions are not supported.
// e.g. `Array<Int,>()` is not valid until Swift 6.3.
let input = """
private let value = Array<(
foo: String,
bar: Bool
)>()
"""
let options = FormatOptions(trailingCommas: .always, swiftVersion: "6.2")
testFormatting(for: input, rule: .trailingCommas, options: options, exclude: [.typeSugar, .propertyTypes])
}

func testTrailingCommasAddedToGenericArgumentListInExpressionInSwift6_3() {
// In Swift 6.3, trailing commas in generic argument lists within expressions are supported.
let input = """
private let value = Array<(
foo: String,
bar: Bool
)>()
"""
let output = """
private let value = Array<(
foo: String,
bar: Bool,
)>()
"""
let options = FormatOptions(trailingCommas: .always, swiftVersion: "6.3")
testFormatting(for: input, output, rule: .trailingCommas, options: options, exclude: [.typeSugar, .propertyTypes])
}

func testTrailingCommasNotAddedToClosureTupleReturnType() {
// Trailing commas are not supported in closure return types in Swift 6.2 and earlier
let input = """
Expand Down