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 } 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]()",