Skip to content

Commit 3801548

Browse files
authored
Merge pull request #3162 from kwikysw/parse-closure-missing-in-syntax
[SwiftParser] Diagnose missing 'in' after closure signature
2 parents a113ef9 + 494d492 commit 3801548

File tree

3 files changed

+132
-5
lines changed

3 files changed

+132
-5
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2567,6 +2567,7 @@ extension Parser.Lookahead {
25672567
mutating func canParseClosureSignature() -> Bool {
25682568
// Consume attributes.
25692569
var lookahead = self.lookahead()
2570+
var sawTopLevelArrowInLookahead = false
25702571
var attributesProgress = LoopProgressCondition()
25712572
while lookahead.consume(if: .atSign) != nil, lookahead.hasProgressed(&attributesProgress) {
25722573
guard lookahead.at(.identifier) else {
@@ -2630,15 +2631,21 @@ extension Parser.Lookahead {
26302631
return false
26312632
}
26322633

2634+
sawTopLevelArrowInLookahead = true
2635+
26332636
lookahead.consumeEffectsSpecifiers()
26342637
}
26352638

26362639
// Parse the 'in' at the end.
2637-
guard lookahead.at(.keyword(.in)) else {
2638-
return false
2640+
if lookahead.at(.keyword(.in)) {
2641+
// Okay, we have a closure signature.
2642+
return true
26392643
}
2640-
// Okay, we have a closure signature.
2641-
return true
2644+
2645+
// Even if 'in' is missing, the presence of a top-level '->' makes this look like a
2646+
// closure signature. There's no other valid syntax that could legally
2647+
// contain '->' at this position.
2648+
return sawTopLevelArrowInLookahead
26422649
}
26432650
}
26442651

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftParser
14+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftSyntax
15+
import XCTest
16+
17+
final class ClosureMissingInTests: ParserTestCase {
18+
19+
func testMissingInAfterSignature() {
20+
assertParse(
21+
"""
22+
_ = { (x: Int) -> Int 1️⃣0 }
23+
""",
24+
diagnostics: [
25+
DiagnosticSpec(
26+
locationMarker: "1️⃣",
27+
message: "expected 'in' in closure signature",
28+
fixIts: ["insert 'in'"]
29+
)
30+
],
31+
fixedSource: """
32+
_ = { (x: Int) -> Int in 0 }
33+
"""
34+
)
35+
}
36+
37+
func testArrayLiteralNotMisparsedAsSignature() {
38+
assertParse(
39+
"""
40+
_ = { [x, y] }
41+
"""
42+
)
43+
}
44+
45+
func testAsyncIsNotASignatureGate() {
46+
assertParse(
47+
"""
48+
_ = { async }
49+
"""
50+
)
51+
}
52+
53+
func testShorthandParamsWithReturnType() {
54+
assertParse(
55+
"""
56+
_ = { x, _ -> Int 1️⃣x }
57+
""",
58+
diagnostics: [
59+
DiagnosticSpec(
60+
locationMarker: "1️⃣",
61+
message: "expected 'in' in closure signature",
62+
fixIts: ["insert 'in'"]
63+
)
64+
],
65+
fixedSource: """
66+
_ = { x, _ -> Int in x }
67+
"""
68+
)
69+
}
70+
71+
func testResyncTokensBeforeIn() {
72+
assertParse(
73+
"""
74+
_ = { () -> Int
75+
1️⃣0
76+
in
77+
1
78+
}
79+
""",
80+
diagnostics: [
81+
DiagnosticSpec(
82+
locationMarker: "1️⃣",
83+
message: "unexpected code '0' in closure signature"
84+
)
85+
]
86+
)
87+
}
88+
89+
func testMissingInInFunctionArgument() {
90+
assertParse(
91+
"""
92+
test(make: { () -> [Int] 1️⃣
93+
return [3]
94+
}, consume: { _ in
95+
print("Test")
96+
})
97+
""",
98+
diagnostics: [
99+
DiagnosticSpec(
100+
locationMarker: "1️⃣",
101+
message: "expected 'in' in closure signature",
102+
fixIts: ["insert 'in'"]
103+
)
104+
],
105+
fixedSource:
106+
"""
107+
test(make: { () -> [Int] in
108+
return [3]
109+
}, consume: { _ in
110+
print("Test")
111+
})
112+
"""
113+
)
114+
}
115+
}

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,11 @@ final class RecoveryTests: ParserTestCase {
24452445
locationMarker: "3️⃣",
24462446
message: "unexpected code '[' in function"
24472447
),
2448+
DiagnosticSpec(
2449+
locationMarker: "4️⃣",
2450+
message: "expected 'in' in closure signature",
2451+
fixIts: ["insert 'in'"]
2452+
),
24482453
DiagnosticSpec(
24492454
locationMarker: "4️⃣",
24502455
message: "unexpected code ') -> Int {}' in closure"
@@ -2453,7 +2458,7 @@ final class RecoveryTests: ParserTestCase {
24532458
fixedSource: """
24542459
#if true
24552460
struct Foo19605164 {
2456-
func a(s: S) [{{g) -> Int {}
2461+
func a(s: S) [{{g in) -> Int {}
24572462
}}}
24582463
#endif
24592464
"""

0 commit comments

Comments
 (0)