Repository: apple/swift-syntax (swift)

Issue:
`10e10` is not emitted as an invalid version tuple
### Description

https://github.com/apple/swift-syntax/pull/1549#discussion_r1179886829

Test case `testVersionParsing`

### Steps to Reproduce

_No response_

Pull request:
Resolve #1613 

During the process of rebasing the changes from the main branch, an issue arose, leading to the closure and reopening of the previous PR(#1717).
I sincerely apologize for the inconvenience caused.

Based on the feedback provided in the comment here(https://github.com/apple/swift-syntax/pull/1717#discussion_r1221876190), I have made the necessary revisions and added several relevant test cases.

Patch:
diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift
index 85e9b84b137..444020e3b10 100644
--- a/Sources/SwiftParser/Availability.swift
+++ b/Sources/SwiftParser/Availability.swift
@@ -253,6 +253,23 @@ extension Parser {
     return self.consumeAnyToken()
   }
 
+  /// Consume the unexpected version token(e.g. integerLiteral, floatingLiteral, identifier) until the period no longer appears.
+  private mutating func parseUnexpectedVersionTokens() -> RawUnexpectedNodesSyntax? {
+    var unexpectedTokens: [RawTokenSyntax] = []
+    var keepGoing: RawTokenSyntax? = nil
+    var loopProgress = LoopProgressCondition()
+    repeat {
+      if let keepGoing {
+        unexpectedTokens.append(keepGoing)
+      }
+      if let unexpectedVersion = self.consume(if: .integerLiteral, .floatingLiteral, .identifier) {
+        unexpectedTokens.append(unexpectedVersion)
+      }
+      keepGoing = self.consume(if: .period)
+    } while keepGoing != nil && loopProgress.evaluate(currentToken)
+    return RawUnexpectedNodesSyntax(unexpectedTokens, arena: self.arena)
+  }
+
   /// Parse a dot-separated list of version numbers.
   ///
   /// Grammar
@@ -286,24 +303,29 @@ extension Parser {
         } else {
           trailingComponents.append(versionComponent)
         }
-      }
-
-      var unexpectedTrailingComponents: RawUnexpectedNodesSyntax?
 
-      if !trailingComponents.isEmpty {
-        unexpectedTrailingComponents = RawUnexpectedNodesSyntax(elements: trailingComponents.compactMap { $0.as(RawSyntax.self) }, arena: self.arena)
+        if version.isMissing {
+          break
+        }
       }
 
+      let unexpectedTrailingComponents = RawUnexpectedNodesSyntax(trailingComponents, arena: self.arena)
+      let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
       return RawVersionTupleSyntax(
         major: major,
         components: RawVersionComponentListSyntax(elements: components, arena: self.arena),
-        unexpectedTrailingComponents,
+        RawUnexpectedNodesSyntax(combining: unexpectedTrailingComponents, unexpectedAfterComponents, arena: self.arena),
         arena: self.arena
       )
-
     } else {
       let major = self.expectDecimalIntegerWithoutRecovery()
-      return RawVersionTupleSyntax(major: major, components: nil, arena: self.arena)
+      let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
+      return RawVersionTupleSyntax(
+        major: major,
+        components: nil,
+        unexpectedAfterComponents,
+        arena: self.arena
+      )
     }
   }
 }
diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
index e2061e0f644..840f36fcd05 100644
--- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
+++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
@@ -1893,14 +1893,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
       return .skipChildren
     }
 
-    if let trailingComponents = node.unexpectedAfterComponents,
-      let components = node.components
-    {
-      addDiagnostic(
-        trailingComponents,
-        TrailingVersionAreIgnored(major: node.major, components: components),
-        handledNodes: [trailingComponents.id]
-      )
+    if let unexpectedAfterComponents = node.unexpectedAfterComponents {
+      if let components = node.components,
+        unexpectedAfterComponents.allSatisfy({ $0.is(VersionComponentSyntax.self) })
+      {
+        addDiagnostic(
+          unexpectedAfterComponents,
+          TrailingVersionAreIgnored(major: node.major, components: components),
+          handledNodes: [unexpectedAfterComponents.id]
+        )
+      } else {
+        addDiagnostic(
+          unexpectedAfterComponents,
+          CannotParseVersionTuple(versionTuple: unexpectedAfterComponents),
+          handledNodes: [node.major.id, node.components?.id, unexpectedAfterComponents.id].compactMap { $0 }
+        )
+      }
     }
 
     return .visitChildren
diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift
index 8b000d2757e..9f43fa51626 100644
--- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift
+++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift
@@ -286,7 +286,7 @@ public struct CannotParseVersionTuple: ParserError {
   public let versionTuple: UnexpectedNodesSyntax
 
   public var message: String {
-    return "cannot parse version \(versionTuple)"
+    return "cannot parse version component \(versionTuple.shortSingleLineContentDescription)"
   }
 }
 


Test patch:
diff --git a/Tests/SwiftParserTest/AvailabilityTests.swift b/Tests/SwiftParserTest/AvailabilityTests.swift
index 8b71a793e75..a5b1148561e 100644
--- a/Tests/SwiftParserTest/AvailabilityTests.swift
+++ b/Tests/SwiftParserTest/AvailabilityTests.swift
@@ -142,19 +142,21 @@ final class AvailabilityTests: XCTestCase {
       )
     )
 
+    assertParse(
+      """
+      @available(OSX 10.0.1, *)
+      func test() {}
+      """
+    )
+
     assertParse(
       """
       @available(OSX 1️⃣10e10)
       func test() {}
       """,
       diagnostics: [
-        DiagnosticSpec(message: "expected version tuple in version restriction", fixIts: ["insert version tuple"]),
-        DiagnosticSpec(message: "unexpected code '10e10' in attribute"),
-      ],
-      fixedSource: """
-        @available(OSX <#integer literal#>10e10)
-        func test() {}
-        """
+        DiagnosticSpec(message: "cannot parse version component code '10e10'")
+      ]
     )
 
     assertParse(
@@ -163,13 +165,8 @@ final class AvailabilityTests: XCTestCase {
       func test() {}
       """,
       diagnostics: [
-        DiagnosticSpec(message: "expected integer literal in version tuple", fixIts: ["insert integer literal"]),
-        DiagnosticSpec(message: "unexpected code '0e10' in attribute"),
-      ],
-      fixedSource: """
-        @available(OSX 10.<#integer literal#>0e10)
-        func test() {}
-        """
+        DiagnosticSpec(message: "cannot parse version component code '0e10'")
+      ]
     )
 
     assertParse(
@@ -178,13 +175,8 @@ final class AvailabilityTests: XCTestCase {
       func test() {}
       """,
       diagnostics: [
-        DiagnosticSpec(message: "expected version tuple in version restriction", fixIts: ["insert version tuple"]),
-        DiagnosticSpec(message: "unexpected code '0xff' in attribute"),
-      ],
-      fixedSource: """
-        @available(OSX <#integer literal#>0xff)
-        func test() {}
-        """
+        DiagnosticSpec(message: "cannot parse version component code '0xff'")
+      ]
     )
 
     assertParse(
@@ -193,13 +185,28 @@ final class AvailabilityTests: XCTestCase {
       func test() {}
       """,
       diagnostics: [
-        DiagnosticSpec(message: "expected integer literal in version tuple", fixIts: ["insert integer literal"]),
-        DiagnosticSpec(message: "unexpected code '0xff' in attribute"),
-      ],
-      fixedSource: """
-        @available(OSX 1.0.<#integer literal#>0xff)
-        func test() {}
-        """
+        DiagnosticSpec(message: "cannot parse version component code '0xff'")
+      ]
+    )
+
+    assertParse(
+      """
+      @available(OSX 1.0.1️⃣0xff, *)
+      func test() {}
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "cannot parse version component code '0xff'")
+      ]
+    )
+
+    assertParse(
+      """
+      @available(OSX 1.0.1️⃣asdf)
+      func test() {}
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "cannot parse version component code 'asdf'")
+      ]
     )
   }
 }
diff --git a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift
index 76e8f733c3b..cb0f644dc1f 100644
--- a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift
+++ b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift
@@ -388,7 +388,7 @@ final class IfconfigExprTests: XCTestCase {
       #endif
       """#,
       diagnostics: [
-        DiagnosticSpec(message: "cannot parse version \"\"")
+        DiagnosticSpec(message: #"cannot parse version component code '""'"#)
       ]
     )
   }
@@ -401,7 +401,7 @@ final class IfconfigExprTests: XCTestCase {
       #endif
       """,
       diagnostics: [
-        DiagnosticSpec(message: "cannot parse version >=2.2")
+        DiagnosticSpec(message: "cannot parse version component code '>=2.2'")
       ]
     )
   }
@@ -414,7 +414,7 @@ final class IfconfigExprTests: XCTestCase {
       #endif
       """,
       diagnostics: [
-        DiagnosticSpec(message: "cannot parse version 20A301")
+        DiagnosticSpec(message: "cannot parse version component code '20A301'")
       ]
     )
   }
@@ -427,7 +427,7 @@ final class IfconfigExprTests: XCTestCase {
       #endif
       """#,
       diagnostics: [
-        DiagnosticSpec(message: "cannot parse version \"20A301\"")
+        DiagnosticSpec(message: #"cannot parse version component code '"20A301"'"#)
       ]
     )
   }