Repository: apple/swift-syntax (swift)

Issue:
[Parser] Misparsed function parameter with keyword argument '_const'
### Description

One would expect `FunctionParameterSyntax.firstName` to be `"_const"`,

```swift
func const(_const map: String) {}
```

However the parser incorrectly recognizes `_const` as `DeclModifier`, and `FunctionParameterSyntax.firstName` becomes `"map"`.

### Steps to Reproduce

_No response_

Pull request:
The new parser’s behavior differed from the old parser here.

rdar://133105836
Fixes #2771

Patch:
diff --git a/Sources/SwiftParser/Parameters.swift b/Sources/SwiftParser/Parameters.swift
index 06354b5837b..6cf2d8a1152 100644
--- a/Sources/SwiftParser/Parameters.swift
+++ b/Sources/SwiftParser/Parameters.swift
@@ -263,17 +263,13 @@ extension Parser {
   mutating func parseParameterModifiers(isClosure: Bool) -> RawDeclModifierListSyntax {
     var elements = [RawDeclModifierSyntax]()
     var loopProgress = LoopProgressCondition()
-    MODIFIER_LOOP: while self.hasProgressed(&loopProgress) {
-      switch self.at(anyIn: ParameterModifier.self) {
-      case (._const, let handle)?:
-        elements.append(RawDeclModifierSyntax(name: self.eat(handle), detail: nil, arena: self.arena))
-      case (.isolated, let handle)?
-      where self.withLookahead({ !$0.startsParameterName(isClosure: isClosure, allowMisplacedSpecifierRecovery: false) }
-      ):
-        elements.append(RawDeclModifierSyntax(name: self.eat(handle), detail: nil, arena: self.arena))
-      default:
-        break MODIFIER_LOOP
+    while self.hasProgressed(&loopProgress) {
+      guard let match = self.at(anyIn: ParameterModifier.self),
+        !withLookahead({ $0.startsParameterName(isClosure: isClosure, allowMisplacedSpecifierRecovery: false) })
+      else {
+        break
       }
+      elements.append(RawDeclModifierSyntax(name: self.eat(match.handle), detail: nil, arena: self.arena))
     }
     if elements.isEmpty {
       return self.emptyCollection(RawDeclModifierListSyntax.self)
diff --git a/Sources/SwiftParser/Patterns.swift b/Sources/SwiftParser/Patterns.swift
index de785dab436..7fcf3434513 100644
--- a/Sources/SwiftParser/Patterns.swift
+++ b/Sources/SwiftParser/Patterns.swift
@@ -375,6 +375,7 @@ extension Parser.Lookahead {
         && !self.at(.keyword(.repeat))
         && !self.at(.keyword(.__shared))
         && !self.at(.keyword(.__owned))
+        && !self.at(.keyword(._const))
         && !self.at(.keyword(.borrowing))
         && !self.at(.keyword(.consuming))
         && !(experimentalFeatures.contains(.sendingArgsAndResults) && self.at(.keyword(.sending)))


Test patch:
diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift
index 35a680d090b..ad6d957e4a2 100644
--- a/Tests/SwiftParserTest/DeclarationTests.swift
+++ b/Tests/SwiftParserTest/DeclarationTests.swift
@@ -3288,4 +3288,36 @@ final class DeclarationTests: ParserTestCase {
         """
     )
   }
+
+  func testConstAsArgumentLabel() {
+    assertParse(
+      "func const(_const: String) {}",
+      substructure: FunctionParameterSyntax(
+        firstName: .identifier("_const"),
+        colon: .colonToken(),
+        type: TypeSyntax(IdentifierTypeSyntax(name: .identifier("String")))
+      )
+    )
+
+    assertParse(
+      "func const(_const map: String) {}",
+      substructure: FunctionParameterSyntax(
+        firstName: .identifier("_const"),
+        secondName: .identifier("map"),
+        colon: .colonToken(),
+        type: TypeSyntax(IdentifierTypeSyntax(name: .identifier("String")))
+      )
+    )
+
+    assertParse(
+      "func const(_const x y: String) {}",
+      substructure: FunctionParameterSyntax(
+        modifiers: [DeclModifierSyntax(name: .keyword(._const))],
+        firstName: .identifier("x"),
+        secondName: .identifier("y"),
+        colon: .colonToken(),
+        type: TypeSyntax(IdentifierTypeSyntax(name: .identifier("String")))
+      )
+    )
+  }
 }