From 8297d91f10adcc53ad3c5f1a9cb3681eca1d8613 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Tue, 11 Nov 2025 14:26:45 +1000 Subject: [PATCH 1/4] Fix up AttributeTypeSyntax handling There's a few issues here: 1. Type specifiers can now have arguments (eg. `nonisolated(nonsending)`) 2. Some type specifiers can be specified "late", which are currently unhandled 3. We were using a mix of `.same` and `.continue` breaks (`.continue` for specifiers and `.same` for attributes) 4. Attributes were grouped separately to specifiers Fixes https://github.com/swiftlang/swift-format/issues/1081 (cherry picked from commit b2091bd02082d0f623324cf4061dda9dc037fe3c) --- .../PrettyPrint/TokenStreamCreator.swift | 105 +++++++++++++----- .../PrettyPrint/FunctionTypeTests.swift | 61 ++++++++++ 2 files changed, 136 insertions(+), 30 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 291410be4..2436fd2c9 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2394,13 +2394,25 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { override func visit(_ node: AttributedTypeSyntax) -> SyntaxVisitorContinueKind { before(node.firstToken(viewMode: .sourceAccurate), tokens: .open) - arrangeAttributeList(node.attributes) + + let breakToken: Token = .break(.continue, newlines: .elective(ignoresDiscretionary: true)) for specifier in node.specifiers { after( - specifier.firstToken(viewMode: .sourceAccurate), - tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)) + specifier.lastToken(viewMode: .sourceAccurate), + tokens: breakToken + ) + } + arrangeAttributeList(node.attributes, suppressFinalBreak: false, lineBreak: breakToken, shouldGroup: false) + for specifier in node.lateSpecifiers { + after( + specifier.lastToken(viewMode: .sourceAccurate), + tokens: breakToken ) } + + before(node.baseType.firstToken(viewMode: .sourceAccurate), tokens: .open) + after(node.baseType.lastToken(viewMode: .sourceAccurate), tokens: .close) + after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } @@ -3066,40 +3078,73 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { private func arrangeAttributeList( _ attributes: AttributeListSyntax?, suppressFinalBreak: Bool = false, - separateByLineBreaks: Bool = false + separateByLineBreaks: Bool = false, + shouldGroup: Bool = true ) { - if let attributes = attributes { - let behavior: NewlineBehavior = separateByLineBreaks ? .hard : .elective + let behavior: NewlineBehavior = separateByLineBreaks ? .hard : .elective + arrangeAttributeList( + attributes, + suppressFinalBreak: suppressFinalBreak, + lineBreak: .break(.same, newlines: behavior), + shouldGroup: shouldGroup + ) + } + + /// Applies formatting tokens around and between the attributes in an attribute list. + private func arrangeAttributeList( + _ attributes: AttributeListSyntax?, + suppressFinalBreak: Bool, + lineBreak: Token, + shouldGroup: Bool + ) { + guard let attributes, !attributes.isEmpty else { + return + } + + if shouldGroup { before(attributes.firstToken(viewMode: .sourceAccurate), tokens: .open) - if attributes.dropLast().isEmpty, - let ifConfig = attributes.first?.as(IfConfigDeclSyntax.self) - { - for clause in ifConfig.clauses { - if let nestedAttributes = AttributeListSyntax(clause.elements) { - arrangeAttributeList(nestedAttributes, suppressFinalBreak: true, separateByLineBreaks: separateByLineBreaks) - } + } + + if attributes.dropLast().isEmpty, + let ifConfig = attributes.first?.as(IfConfigDeclSyntax.self) + { + for clause in ifConfig.clauses { + if let nestedAttributes = AttributeListSyntax(clause.elements) { + arrangeAttributeList( + nestedAttributes, + suppressFinalBreak: true, + lineBreak: lineBreak, + shouldGroup: shouldGroup + ) } - } else { - for element in attributes.dropLast() { - if let ifConfig = element.as(IfConfigDeclSyntax.self) { - for clause in ifConfig.clauses { - if let nestedAttributes = AttributeListSyntax(clause.elements) { - arrangeAttributeList( - nestedAttributes, - suppressFinalBreak: true, - separateByLineBreaks: separateByLineBreaks - ) - } + } + } else { + for element in attributes.dropLast() { + if let ifConfig = element.as(IfConfigDeclSyntax.self) { + for clause in ifConfig.clauses { + if let nestedAttributes = AttributeListSyntax(clause.elements) { + arrangeAttributeList( + nestedAttributes, + suppressFinalBreak: true, + lineBreak: lineBreak, + shouldGroup: shouldGroup + ) } - } else { - after(element.lastToken(viewMode: .sourceAccurate), tokens: .break(.same, newlines: behavior)) } + } else { + after(element.lastToken(viewMode: .sourceAccurate), tokens: lineBreak) } } - var afterAttributeTokens = [Token.close] - if !suppressFinalBreak { - afterAttributeTokens.append(.break(.same, newlines: behavior)) - } + } + + var afterAttributeTokens = [Token]() + if shouldGroup { + afterAttributeTokens.append(.close) + } + if !suppressFinalBreak { + afterAttributeTokens.append(lineBreak) + } + if !afterAttributeTokens.isEmpty { after(attributes.lastToken(viewMode: .sourceAccurate), tokens: afterAttributeTokens) } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift index 7f1a7edc5..2bb5b326f 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift @@ -283,4 +283,65 @@ final class FunctionTypeTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected, linelength: 17) } + + func testFunctionTypeWithTypeSpecifier() { + let input = + """ + func f(_ body: nonisolated(nonsending) () async -> Void) {} + + func f(_ body: @Foo @Bar nonisolated(nonsending) () async -> Void) {} + + func f(_ body: nonisolated(nonsending) @Foo @Bar () async -> Void) {} + + func f(_ body: inout @Foo @Bar nonisolated(nonsending) () async -> Void) {} + """ + + assertPrettyPrintEqual( + input: input, + expected: """ + func f(_ body: nonisolated(nonsending) () async -> Void) {} + + func f(_ body: @Foo @Bar nonisolated(nonsending) () async -> Void) {} + + func f(_ body: nonisolated(nonsending) @Foo @Bar () async -> Void) {} + + func f(_ body: inout @Foo @Bar nonisolated(nonsending) () async -> Void) {} + + """, + linelength: 80 + ) + + assertPrettyPrintEqual( + input: input, + expected: """ + func f( + _ body: + nonisolated(nonsending) + () async -> Void + ) {} + + func f( + _ body: + @Foo @Bar + nonisolated(nonsending) + () async -> Void + ) {} + + func f( + _ body: + nonisolated(nonsending) + @Foo @Bar () async -> Void + ) {} + + func f( + _ body: + inout @Foo @Bar + nonisolated(nonsending) + () async -> Void + ) {} + + """, + linelength: 30 + ) + } } From ed283be6d93bcb765d3fa6a49c50b9c224a7d7ae Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Thu, 13 Nov 2025 07:20:14 +1000 Subject: [PATCH 2/4] Remove added group around base type in `AttributedTypeSyntax` Causes quite a few changes in eg. sourcekit-lsp, so presumably will in other codebases as well. Ideally we'd add this behind some new versioning in the config. As an example of a change this causes: ``` private let logMessageToIndexLog: @Sendable ( _ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind ) -> Void ``` To: ``` @Sendable ( _ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind ) -> Void ``` (cherry picked from commit d022b8b9596f1456ea485c32b49c425905010813) --- .../SwiftFormat/PrettyPrint/TokenStreamCreator.swift | 3 --- .../PrettyPrint/FunctionTypeTests.swift | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index 2436fd2c9..da88d3324 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -2410,9 +2410,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { ) } - before(node.baseType.firstToken(viewMode: .sourceAccurate), tokens: .open) - after(node.baseType.lastToken(viewMode: .sourceAccurate), tokens: .close) - after(node.lastToken(viewMode: .sourceAccurate), tokens: .close) return .visitChildren } diff --git a/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift b/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift index 2bb5b326f..d7b7f30cf 100644 --- a/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift +++ b/Tests/SwiftFormatTests/PrettyPrint/FunctionTypeTests.swift @@ -316,15 +316,15 @@ final class FunctionTypeTests: PrettyPrintTestCase { expected: """ func f( _ body: - nonisolated(nonsending) - () async -> Void + nonisolated(nonsending) () + async -> Void ) {} func f( _ body: @Foo @Bar - nonisolated(nonsending) - () async -> Void + nonisolated(nonsending) () + async -> Void ) {} func f( @@ -336,8 +336,8 @@ final class FunctionTypeTests: PrettyPrintTestCase { func f( _ body: inout @Foo @Bar - nonisolated(nonsending) - () async -> Void + nonisolated(nonsending) () + async -> Void ) {} """, From 94a0fceacf19db3ebb06a1c4111a20a7123a959e Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Wed, 10 Dec 2025 08:58:45 +1000 Subject: [PATCH 3/4] Update pull request workflow to use the tagged github-workflows Now that workflows is a more stable, use the most recent tag instead of main to avoid changes to default jobs preventing merging. (cherry picked from commit 58125eb0a517ffc647c9665021ee321f23ccc386) --- .github/workflows/pull_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 777a2d19d..4cbd55a03 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -14,12 +14,12 @@ concurrency: jobs: tests: name: Test - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.2 with: linux_exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" soundness: name: Soundness - uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.2 with: license_header_check_project_name: "Swift.org" api_breakage_check_allowlist_path: "api-breakages.txt" From 18092b3f57a90714faa2df7196d380f797b73d38 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:22:40 +0900 Subject: [PATCH 4/4] Disable Swift 5.9 CI job on Windows Exclude `"5.9"` from `windows_swift_versions` until #1094 is resolved on swift-markdown's side (cherry picked from commit 2c5948731a47956e7eec9738218ed88a8db3db9d) --- .github/workflows/pull_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4cbd55a03..5f086de96 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -17,6 +17,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.2 with: linux_exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" + windows_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}]" soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.2