Repository: apple/swift-syntax (swift)

Issue:
Improve diagnostic for unnamed closure parameter
### Issue Kind

Bad Diagnostic Produced

### Source Code

```swift
test { ([X]) in }
```

### Description

New parser:
```
error: unexpected code '[X]' in parameter clause
test { ([X]) in }
        ^~~
```

Old parser:
```
warning: unnamed parameters must be written with the empty name '_' [parameter_unnamed_warn]
test { ([X]) in }
        ^
        _:
```

This is a special case. More generally, the old parser is capable of diagnosing this kind of closure parameter production gracefully when all of the following are true:

* An argument name cannot be parsed ([link](https://github.com/apple/swift/blob/17c30f162d1fd69ae8a511e2496559090829ce51/lib/Parse/ParsePattern.cpp#L311))
* A type can be parsed per `Parser::canParseType()`, and said type is followed by `,`, `)`, or `=` ([link](https://github.com/apple/swift/blob/17c30f162d1fd69ae8a511e2496559090829ce51/lib/Parse/ParsePattern.cpp#L367))
___
Related to #2331.

Pull request:
fix #2340

Patch:
diff --git a/Sources/SwiftParser/Parameters.swift b/Sources/SwiftParser/Parameters.swift
index 1b7add9845a..06354b5837b 100644
--- a/Sources/SwiftParser/Parameters.swift
+++ b/Sources/SwiftParser/Parameters.swift
@@ -160,10 +160,23 @@ extension Parser {
     let misplacedSpecifiers = parseMisplacedSpecifiers()
 
     let names = self.parseParameterNames()
-    let colon = self.consume(if: .colon)
+    var colon = self.consume(if: .colon)
+    // try to parse the type regardless of the presence of the preceding colon
+    // to tackle any unnamed parameter or missing colon
+    // e.g. [X], (:[X]) or (x [X])
+    let canParseType = withLookahead { $0.canParseType() }
     let type: RawTypeSyntax?
-    if colon != nil {
+    if canParseType {
       type = self.parseType(misplacedSpecifiers: misplacedSpecifiers)
+      if colon == nil {
+        // mark the preceding colon as missing if the type is present
+        // e.g. [X] or (x [X])
+        colon = missingToken(.colon)
+      }
+    } else if colon != nil {
+      // mark the type as missing if the preceding colon is present
+      // e.g. (:) or (_:)
+      type = RawTypeSyntax(RawMissingTypeSyntax(arena: self.arena))
     } else {
       type = nil
     }


Test patch:
diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift
index 786c61bda43..f5b2f9e0454 100644
--- a/Tests/SwiftParserTest/ExpressionTests.swift
+++ b/Tests/SwiftParserTest/ExpressionTests.swift
@@ -2875,6 +2875,77 @@ final class StatementExpressionTests: ParserTestCase {
     )
   }
 
+  func testClosureWithMalformedParameters() {
+    assertParse(
+      """
+      test { (1️⃣[X]) in }
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "expected identifier and ':' in parameter", fixIts: ["insert identifier and ':'"])
+      ],
+      fixedSource: """
+        test { (<#identifier#>: [X]) in }
+        """
+    )
+
+    assertParse(
+      """
+      test { (1️⃣: [X]) in }
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "expected identifier in parameter", fixIts: ["insert identifier"])
+      ],
+      fixedSource: """
+        test { (<#identifier#>: [X]) in }
+        """
+    )
+
+    assertParse(
+      """
+      test { (1️⃣:2️⃣) in }
+      """,
+      diagnostics: [
+        DiagnosticSpec(
+          locationMarker: "1️⃣",
+          message: "expected identifier in parameter",
+          fixIts: ["insert identifier"]
+        ),
+        DiagnosticSpec(
+          locationMarker: "2️⃣",
+          message: "expected type in parameter",
+          fixIts: ["insert type"]
+        ),
+      ],
+      fixedSource: """
+        test { (<#identifier#>: <#type#>) in }
+        """
+    )
+
+    assertParse(
+      """
+      test { (foo1️⃣ @bar baz) in }
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "expected ':' in parameter", fixIts: ["insert ':'"])
+      ],
+      fixedSource: """
+        test { (foo: @bar baz) in }
+        """
+    )
+
+    assertParse(
+      """
+      test { (x: 1️⃣) in }
+      """,
+      diagnostics: [
+        DiagnosticSpec(message: "expected type in parameter", fixIts: ["insert type"])
+      ],
+      fixedSource: """
+        test { (x: <#type#>) in }
+        """
+    )
+  }
+
   func testTypedThrowsDisambiguation() {
     assertParse(
       "[() throws(MyError) 1️⃣async -> Void]()",