Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2567,6 +2567,7 @@ extension Parser.Lookahead {
mutating func canParseClosureSignature() -> Bool {
// Consume attributes.
var lookahead = self.lookahead()
var sawTopLevelArrowInLookahead = false
var attributesProgress = LoopProgressCondition()
while lookahead.consume(if: .atSign) != nil, lookahead.hasProgressed(&attributesProgress) {
guard lookahead.at(.identifier) else {
Expand Down Expand Up @@ -2630,15 +2631,21 @@ extension Parser.Lookahead {
return false
}

sawTopLevelArrowInLookahead = true

lookahead.consumeEffectsSpecifiers()
}

// Parse the 'in' at the end.
guard lookahead.at(.keyword(.in)) else {
return false
if lookahead.at(.keyword(.in)) {
// Okay, we have a closure signature.
return true
}
// Okay, we have a closure signature.
return true

// Even if 'in' is missing, the presence of a top-level '->' makes this look like a
// closure signature. There's no other valid syntax that could legally
// contain '->' at this position.
return sawTopLevelArrowInLookahead
}
}

Expand Down
115 changes: 115 additions & 0 deletions Tests/SwiftParserTest/ClosureMissingInTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftParser
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftSyntax
import XCTest

final class ClosureMissingInTests: ParserTestCase {

func testMissingInAfterSignature() {
assertParse(
"""
_ = { (x: Int) -> Int 1️⃣0 }
""",
diagnostics: [
DiagnosticSpec(
locationMarker: "1️⃣",
message: "expected 'in' in closure signature",
fixIts: ["insert 'in'"]
)
],
fixedSource: """
_ = { (x: Int) -> Int in 0 }
"""
)
}

func testArrayLiteralNotMisparsedAsSignature() {
assertParse(
"""
_ = { [x, y] }
"""
)
}

func testAsyncIsNotASignatureGate() {
assertParse(
"""
_ = { async }
"""
)
}

func testShorthandParamsWithReturnType() {
assertParse(
"""
_ = { x, _ -> Int 1️⃣x }
""",
diagnostics: [
DiagnosticSpec(
locationMarker: "1️⃣",
message: "expected 'in' in closure signature",
fixIts: ["insert 'in'"]
)
],
fixedSource: """
_ = { x, _ -> Int in x }
"""
)
}

func testResyncTokensBeforeIn() {
assertParse(
"""
_ = { () -> Int
1️⃣0
in
1
}
""",
diagnostics: [
DiagnosticSpec(
locationMarker: "1️⃣",
message: "unexpected code '0' in closure signature"
)
]
)
}

func testMissingInInFunctionArgument() {
assertParse(
"""
test(make: { () -> [Int] 1️⃣
return [3]
}, consume: { _ in
print("Test")
})
""",
diagnostics: [
DiagnosticSpec(
locationMarker: "1️⃣",
message: "expected 'in' in closure signature",
fixIts: ["insert 'in'"]
)
],
fixedSource:
"""
test(make: { () -> [Int] in
return [3]
}, consume: { _ in
print("Test")
})
"""
)
}
}
7 changes: 6 additions & 1 deletion Tests/SwiftParserTest/translated/RecoveryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,11 @@ final class RecoveryTests: ParserTestCase {
locationMarker: "3️⃣",
message: "unexpected code '[' in function"
),
DiagnosticSpec(
locationMarker: "4️⃣",
message: "expected 'in' in closure signature",
fixIts: ["insert 'in'"]
),
DiagnosticSpec(
locationMarker: "4️⃣",
message: "unexpected code ') -> Int {}' in closure"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, IIUC this means canParseClosureSignature recognized -> Int as the part of the signature, but the actual signature parsing gave up parsing it at 4️⃣, right? That mismatch is not great, could you file an issue? Even before merging this PR is fine, please just state "After #3162 ... "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: #3163

Expand All @@ -2453,7 +2458,7 @@ final class RecoveryTests: ParserTestCase {
fixedSource: """
#if true
struct Foo19605164 {
func a(s: S) [{{g) -> Int {}
func a(s: S) [{{g in) -> Int {}
}}}
#endif
"""
Expand Down