Skip to content

Commit 14140ef

Browse files
committed
[SwiftParser] Diagnose missing 'in' after closure signature
Tighten closure lookahead to accept signatures without 'in' only when a top-level '->' is present. Add a dedicated ClosureMissingInTests suite for missing-'in' and resync scenarios, and update Recovery test 141 to expect the additional diagnostic and adjusted fixed source.
1 parent a113ef9 commit 14140ef

File tree

3 files changed

+121
-6
lines changed

3 files changed

+121
-6
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: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftParser
2+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftSyntax
3+
import XCTest
4+
5+
final class ClosureMissingInTests: ParserTestCase {
6+
7+
func testMissingInAfterSignature() {
8+
assertParse(
9+
"""
10+
_ = { (x: Int) -> Int 1️⃣0 }
11+
""",
12+
diagnostics: [
13+
DiagnosticSpec(
14+
locationMarker: "1️⃣",
15+
message: "expected 'in' in closure signature",
16+
fixIts: ["insert 'in'"]
17+
)
18+
],
19+
fixedSource: """
20+
_ = { (x: Int) -> Int in 0 }
21+
"""
22+
)
23+
}
24+
25+
func testArrayLiteralNotMisparsedAsSignature() {
26+
assertParse(
27+
"""
28+
_ = { [x, y] }
29+
"""
30+
)
31+
}
32+
33+
func testAsyncIsNotASignatureGate() {
34+
assertParse(
35+
"""
36+
_ = { async }
37+
"""
38+
)
39+
}
40+
41+
func testShorthandParamsWithReturnType() {
42+
assertParse(
43+
"""
44+
_ = { x, _ -> Int 1️⃣x }
45+
""",
46+
diagnostics: [
47+
DiagnosticSpec(
48+
locationMarker: "1️⃣",
49+
message: "expected 'in' in closure signature",
50+
fixIts: ["insert 'in'"]
51+
)
52+
],
53+
fixedSource: """
54+
_ = { x, _ -> Int in x }
55+
"""
56+
)
57+
}
58+
59+
func testResyncTokensBeforeIn() {
60+
assertParse(
61+
"""
62+
_ = { () -> Int
63+
1️⃣0
64+
in
65+
1
66+
}
67+
""",
68+
diagnostics: [
69+
DiagnosticSpec(
70+
locationMarker: "1️⃣",
71+
message: "unexpected code '0' in closure signature"
72+
),
73+
]
74+
)
75+
}
76+
77+
func testMissingInInFunctionArgument() {
78+
assertParse(
79+
"""
80+
test(make: { () -> [Int] 1️⃣
81+
return [3]
82+
}, consume: { _ in
83+
print("Test")
84+
})
85+
""",
86+
diagnostics: [
87+
DiagnosticSpec(
88+
locationMarker: "1️⃣",
89+
message: "expected 'in' in closure signature",
90+
fixIts: ["insert 'in'"]
91+
)
92+
],
93+
fixedSource:
94+
"""
95+
test(make: { () -> [Int] in
96+
return [3]
97+
}, consume: { _ in
98+
print("Test")
99+
})
100+
"""
101+
)
102+
}
103+
}

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,13 +2447,18 @@ final class RecoveryTests: ParserTestCase {
24472447
),
24482448
DiagnosticSpec(
24492449
locationMarker: "4️⃣",
2450-
message: "unexpected code ') -> Int {}' in closure"
2450+
message: "expected 'in' in closure signature",
2451+
fixIts: ["insert 'in'"]
24512452
),
2453+
DiagnosticSpec(
2454+
locationMarker: "4️⃣",
2455+
message: "unexpected code ') -> Int {}' in closure"
2456+
)
24522457
],
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)