Skip to content

Commit 8d34d0f

Browse files
authored
Merge pull request #1804 from hamishknight/getting-out-of-a-bind
2 parents e6db8ad + eab672b commit 8d34d0f

File tree

2 files changed

+188
-9
lines changed

2 files changed

+188
-9
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,10 +1220,10 @@ extension Parser {
12201220
)
12211221
)
12221222
case (.identifier, let handle)?, (.self, let handle)?, (.`init`, let handle)?:
1223-
// If we have "case let x." or "case let x(", we parse x as a normal
1224-
// name, not a binding, because it is the start of an enum pattern or
1225-
// call pattern.
1226-
if pattern.admitsBinding && !self.lookahead().isNextTokenCallPattern() {
1223+
// If we have "case let x" followed by ".", "(", "[", or a generic
1224+
// argument list, we parse x as a normal name, not a binding, because it
1225+
// is the start of an enum or expr pattern.
1226+
if pattern.admitsBinding && self.lookahead().isInBindingPatternPosition() {
12271227
let identifier = self.eat(handle)
12281228
let pattern = RawPatternSyntax(
12291229
RawIdentifierPatternSyntax(
@@ -2759,13 +2759,21 @@ extension Parser.Lookahead {
27592759
return self.peek().isLexerClassifiedKeyword || TokenSpec(.identifier) ~= self.peek()
27602760
}
27612761

2762-
fileprivate func isNextTokenCallPattern() -> Bool {
2762+
fileprivate func isInBindingPatternPosition() -> Bool {
2763+
// Cannot form a binding pattern if a generic argument list follows, this
2764+
// is something like 'case let E<Int>.foo(x)'.
2765+
if self.peek().isContextualPunctuator("<") {
2766+
var lookahead = self.lookahead()
2767+
lookahead.consumeAnyToken()
2768+
return !lookahead.canParseAsGenericArgumentList()
2769+
}
27632770
switch self.peek().rawTokenKind {
2764-
case .period,
2765-
.leftParen:
2766-
return true
2767-
default:
2771+
// A '.' indicates a member access, '(' and '[' indicate a function call or
2772+
// subscript. We can't form a binding pattern as the base of these.
2773+
case .period, .leftParen, .leftSquare:
27682774
return false
2775+
default:
2776+
return true
27692777
}
27702778
}
27712779
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 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(RawSyntax) import SwiftSyntax
14+
@_spi(RawSyntax) import SwiftParser
15+
import XCTest
16+
17+
final class PatternTests: XCTestCase {
18+
private var genericArgEnumPattern: Syntax {
19+
// let E<Int>.e(y)
20+
Syntax(
21+
ValueBindingPatternSyntax(
22+
bindingKeyword: .keyword(.let),
23+
valuePattern: ExpressionPatternSyntax(
24+
expression: FunctionCallExprSyntax(
25+
calledExpression: MemberAccessExprSyntax(
26+
base: SpecializeExprSyntax(
27+
expression: IdentifierExprSyntax(identifier: .identifier("E")),
28+
genericArgumentClause: GenericArgumentClauseSyntax(
29+
arguments: .init([
30+
.init(argumentType: SimpleTypeIdentifierSyntax(name: .identifier("Int")))
31+
])
32+
)
33+
),
34+
name: .identifier("e")
35+
),
36+
leftParen: .leftParenToken(),
37+
argumentList: TupleExprElementListSyntax([
38+
.init(
39+
expression: UnresolvedPatternExprSyntax(
40+
pattern: IdentifierPatternSyntax(identifier: .identifier("y"))
41+
)
42+
)
43+
]),
44+
rightParen: .rightParenToken()
45+
)
46+
)
47+
)
48+
)
49+
}
50+
51+
func testNonBinding1() {
52+
assertParse(
53+
"""
54+
if case let E<Int>.e(y) = x {}
55+
""",
56+
substructure: genericArgEnumPattern
57+
)
58+
}
59+
60+
func testNonBinding2() {
61+
assertParse(
62+
"""
63+
switch e {
64+
case let E<Int>.e(y):
65+
y
66+
}
67+
""",
68+
substructure: genericArgEnumPattern
69+
)
70+
}
71+
72+
private var tupleWithSubscriptAndBindingPattern: Syntax {
73+
// let (y[0], z)
74+
Syntax(
75+
ValueBindingPatternSyntax(
76+
bindingKeyword: .keyword(.let),
77+
valuePattern: ExpressionPatternSyntax(
78+
expression: TupleExprSyntax(
79+
elements: .init([
80+
.init(
81+
expression: SubscriptExprSyntax(
82+
calledExpression: IdentifierExprSyntax(identifier: .identifier("y")),
83+
leftBracket: .leftSquareToken(),
84+
argumentList: TupleExprElementListSyntax([
85+
.init(expression: IntegerLiteralExprSyntax(digits: .integerLiteral("0")))
86+
]),
87+
rightBracket: .rightSquareToken()
88+
),
89+
trailingComma: .commaToken()
90+
),
91+
.init(
92+
expression: UnresolvedPatternExprSyntax(
93+
pattern: IdentifierPatternSyntax(identifier: .identifier("z"))
94+
)
95+
),
96+
])
97+
)
98+
)
99+
)
100+
)
101+
}
102+
103+
func testNonBinding3() {
104+
assertParse(
105+
"""
106+
if case let (y[0], z) = x {}
107+
""",
108+
substructure: tupleWithSubscriptAndBindingPattern
109+
)
110+
}
111+
112+
func testNonBinding4() {
113+
assertParse(
114+
"""
115+
switch x {
116+
case let (y[0], z):
117+
z
118+
}
119+
""",
120+
substructure: tupleWithSubscriptAndBindingPattern
121+
)
122+
}
123+
124+
private var subscriptWithBindingPattern: Syntax {
125+
// let y[z]
126+
Syntax(
127+
ValueBindingPatternSyntax(
128+
bindingKeyword: .keyword(.let),
129+
valuePattern: ExpressionPatternSyntax(
130+
expression: SubscriptExprSyntax(
131+
calledExpression: IdentifierExprSyntax(identifier: .identifier("y")),
132+
leftBracket: .leftSquareToken(),
133+
argumentList: TupleExprElementListSyntax([
134+
.init(
135+
expression: UnresolvedPatternExprSyntax(
136+
pattern: IdentifierPatternSyntax(identifier: .identifier("z"))
137+
)
138+
)
139+
]),
140+
rightBracket: .rightSquareToken()
141+
)
142+
)
143+
)
144+
)
145+
}
146+
147+
func testNonBinding5() {
148+
assertParse(
149+
"""
150+
if case let y[z] = x {}
151+
""",
152+
substructure: subscriptWithBindingPattern
153+
)
154+
}
155+
156+
func testNonBinding6() {
157+
assertParse(
158+
"""
159+
switch 0 {
160+
case let y[z]:
161+
z
162+
case y[z]:
163+
0
164+
default:
165+
0
166+
}
167+
""",
168+
substructure: subscriptWithBindingPattern
169+
)
170+
}
171+
}

0 commit comments

Comments
 (0)