From 7bc8b94d5d762d0f63f888cd15433dadc9978075 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 09:07:22 -0700 Subject: [PATCH 1/3] Add trailing comma support in cases missing from Swift 6.1 --- Sources/SwiftParser/Attributes.swift | 17 ++++++- Sources/SwiftParser/Availability.swift | 5 ++ Sources/SwiftParser/Nominals.swift | 5 ++ Sources/SwiftParser/Types.swift | 5 ++ Tests/SwiftParserTest/AttributeTests.swift | 48 +++++++++++++++++++ Tests/SwiftParserTest/DeclarationTests.swift | 27 +++++++++++ Tests/SwiftParserTest/TypeTests.swift | 38 +++++++++++++++ .../translated/AvailabilityQueryTests.swift | 12 +---- ...AvailabilityQueryUnavailabilityTests.swift | 28 ++++++----- 9 files changed, 161 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index c29fa786846..7df62ae0617 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -362,7 +362,7 @@ extension Parser { let additionalArgs = self.parseArgumentListElements( pattern: .none, flavor: .attributeArguments, - allowTrailingComma: false + allowTrailingComma: true ) return [roleElement] + additionalArgs } @@ -852,6 +852,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawBackDeployedAttributeArgumentsSyntax( unexpectedBeforeLabel, @@ -883,6 +888,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawOriginallyDefinedInAttributeArgumentsSyntax( @@ -1001,6 +1011,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawDocumentationAttributeArgumentListSyntax(elements: arguments, arena: self.arena) diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index fc0979937a2..caa41583d10 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -47,6 +47,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure, there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil && self.hasProgressed(&availabilityArgumentProgress) } diff --git a/Sources/SwiftParser/Nominals.swift b/Sources/SwiftParser/Nominals.swift index bbd15cf5618..eebdcd6fa0c 100644 --- a/Sources/SwiftParser/Nominals.swift +++ b/Sources/SwiftParser/Nominals.swift @@ -361,6 +361,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing comma, there are no more elements + if at(prefix: ">") { + break + } } while keepGoing != nil && self.hasProgressed(&loopProgress) } let rangle = self.expectWithoutRecovery(prefix: ">", as: .rightAngle) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 67c7604ce0f..098215aab30 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -430,6 +430,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing comma, we're done parsing the list + if self.at(prefix: ">") { + break + } } while keepGoing != nil && self.hasProgressed(&loopProgress) } diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 5d82301c61e..2159c1f0b63 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -470,6 +470,15 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @backDeployed( + before: macOS 12.0, + ) + struct Foo {} + """ + ) + assertParse( """ @backDeployed(before: macos 12.0, iOS 15.0) @@ -477,6 +486,15 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @backDeployed( + before: macos 12.0, + iOS 15.0,) + struct Foo {} + """ + ) + assertParse( """ @available(macOS 11.0, *) @@ -537,6 +555,16 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @_originallyDefinedIn( + module: "ToasterKit", + macOS 10.15, + ) + struct Vehicle {} + """ + ) + assertParse( """ @_originallyDefinedIn(module: "ToasterKit", macOS 10.15, iOS 13) @@ -846,6 +874,26 @@ final class AttributeTests: ParserTestCase { } """ ) + + assertParse( + """ + @attached( + member, + names: named(deinit), + ) + macro m() + """ + ) + + assertParse( + """ + @attached( + extension, + conformances: P1, P2, + ) + macro AddAllConformances() + """ + ) } func testAttachedExtensionAttribute() { diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 7ec097ec5bd..f55aa4c1a3d 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -3529,4 +3529,31 @@ final class DeclarationTests: ParserTestCase { ] ) } + + func testTrailingCommas() { + assertParse( + """ + protocol Baaz< + Foo, + Bar, + > { + associatedtype Foo + associatedtype Bar + } + """ + ) + + assertParse( + """ + struct Foo< + T1, + T2, + T3, + >: Baaz< + T1, + T2, + > {} + """ + ) + } } diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 6e49d5fa75d..2d317551c14 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -734,6 +734,44 @@ final class TypeTests: ParserTestCase { fixedSource: "func foo(test: nonisolated(nonsendinghello) () async -> Void)" ) } + + func testTrailingCommas() { + assertParse( + """ + let foo: ( + bar: String, + quux: String, + ) + """ + ) + + assertParse( + """ + let closure: ( + String, + String, + ) -> ( + bar: String, + quux: String, + ) + """ + ) + + assertParse( + """ + struct Foo {} + + typealias Bar< + T1, + T2, + > = Foo< + T1, + T2, + Bool, + > + """ + ) + } } final class InlineArrayTypeTests: ParserTestCase { diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift index 4425e329fd5..6646317adca 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift @@ -400,17 +400,7 @@ final class AvailabilityQueryTests: ParserTestCase { """ if #available(OSX 10.51,1️⃣) { } - """, - diagnostics: [ - DiagnosticSpec( - message: "expected version restriction in availability argument", - fixIts: ["insert version restriction"] - ) - ], - fixedSource: """ - if #available(OSX 10.51, <#identifier#>) { - } - """ + """ ) } diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift index 24d1827bcc0..d99f63155f4 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift @@ -381,19 +381,9 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { func testAvailabilityQueryUnavailability23() { assertParse( """ - if #unavailable(OSX 10.51,1️⃣) { + if #unavailable(OSX 10.51,) { } - """, - diagnostics: [ - DiagnosticSpec( - message: "expected version restriction in availability argument", - fixIts: ["insert version restriction"] - ) - ], - fixedSource: """ - if #unavailable(OSX 10.51, <#identifier#>) { - } - """ + """ ) } @@ -611,4 +601,18 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { ] ) } + + func testTrailingComma() { + assertParse( + """ + func fooDeprecated() { + if #available( + iOS 18.0, + macOS 14.0, + *, + ) {} + } + """ + ) + } } From 24976faa919d871e864473fcb93ed95366753e56 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 15:52:54 -0700 Subject: [PATCH 2/3] Revert attribute changes --- Sources/SwiftParser/Attributes.swift | 17 +------ Sources/SwiftParser/Availability.swift | 5 -- Tests/SwiftParserTest/AttributeTests.swift | 48 ------------------- .../translated/AvailabilityQueryTests.swift | 12 ++++- ...AvailabilityQueryUnavailabilityTests.swift | 28 +++++------ 5 files changed, 24 insertions(+), 86 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 7df62ae0617..c29fa786846 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -362,7 +362,7 @@ extension Parser { let additionalArgs = self.parseArgumentListElements( pattern: .none, flavor: .attributeArguments, - allowTrailingComma: true + allowTrailingComma: false ) return [roleElement] + additionalArgs } @@ -852,11 +852,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawBackDeployedAttributeArgumentsSyntax( unexpectedBeforeLabel, @@ -888,11 +883,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawOriginallyDefinedInAttributeArgumentsSyntax( @@ -1011,11 +1001,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawDocumentationAttributeArgumentListSyntax(elements: arguments, arena: self.arena) diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index caa41583d10..fc0979937a2 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -47,11 +47,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure, there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil && self.hasProgressed(&availabilityArgumentProgress) } diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 2159c1f0b63..5d82301c61e 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -470,15 +470,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @backDeployed( - before: macOS 12.0, - ) - struct Foo {} - """ - ) - assertParse( """ @backDeployed(before: macos 12.0, iOS 15.0) @@ -486,15 +477,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @backDeployed( - before: macos 12.0, - iOS 15.0,) - struct Foo {} - """ - ) - assertParse( """ @available(macOS 11.0, *) @@ -555,16 +537,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @_originallyDefinedIn( - module: "ToasterKit", - macOS 10.15, - ) - struct Vehicle {} - """ - ) - assertParse( """ @_originallyDefinedIn(module: "ToasterKit", macOS 10.15, iOS 13) @@ -874,26 +846,6 @@ final class AttributeTests: ParserTestCase { } """ ) - - assertParse( - """ - @attached( - member, - names: named(deinit), - ) - macro m() - """ - ) - - assertParse( - """ - @attached( - extension, - conformances: P1, P2, - ) - macro AddAllConformances() - """ - ) } func testAttachedExtensionAttribute() { diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift index 6646317adca..4425e329fd5 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift @@ -400,7 +400,17 @@ final class AvailabilityQueryTests: ParserTestCase { """ if #available(OSX 10.51,1️⃣) { } - """ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected version restriction in availability argument", + fixIts: ["insert version restriction"] + ) + ], + fixedSource: """ + if #available(OSX 10.51, <#identifier#>) { + } + """ ) } diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift index d99f63155f4..24d1827bcc0 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift @@ -381,9 +381,19 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { func testAvailabilityQueryUnavailability23() { assertParse( """ - if #unavailable(OSX 10.51,) { + if #unavailable(OSX 10.51,1️⃣) { } - """ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected version restriction in availability argument", + fixIts: ["insert version restriction"] + ) + ], + fixedSource: """ + if #unavailable(OSX 10.51, <#identifier#>) { + } + """ ) } @@ -601,18 +611,4 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { ] ) } - - func testTrailingComma() { - assertParse( - """ - func fooDeprecated() { - if #available( - iOS 18.0, - macOS 14.0, - *, - ) {} - } - """ - ) - } } From 85c1d4bc38064d408bc47738afa9bc6074b43807 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 19:00:38 -0700 Subject: [PATCH 3/3] Support trailing commas in types within expressions --- Sources/SwiftParser/Types.swift | 3 ++- Tests/SwiftParserTest/ExpressionTests.swift | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 098215aab30..58eacfa59b9 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1057,7 +1057,8 @@ extension Parser.Lookahead { return false } // Parse the comma, if the list continues. - } while self.consume(if: .comma) != nil && self.hasProgressed(&loopProgress) + // This could be the trailing comma. + } while self.consume(if: .comma) != nil && !self.at(prefix: ">") && self.hasProgressed(&loopProgress) } guard self.consume(ifPrefix: ">", as: .rightAngle) != nil else { diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index e63d753d2da..3eb7d83add2 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -2125,6 +2125,26 @@ final class ExpressionTests: ParserTestCase { """ ) } + + func testTrailingCommasInTypeExpressions() { + assertParse( + """ + let _ = Foo2.self + """ + ) + + assertParse( + """ + let _ = Foo2() + """ + ) + + assertParse( + """ + let _ = ((Int, Bool, String,) -> Void).self + """ + ) + } } final class MemberExprTests: ParserTestCase {