Skip to content

Commit ac4301c

Browse files
committed
Avoid creating binding patterns in a couple more positions
If we have an identifier followed by either `[` or a generic argument list, avoid turning it into a binding pattern, as that would be invalid. This is similar to the existing rule we have where a following `(` prevents a binding pattern from being formed. This allows patterns such as `let E<Int>.foo(x)` and `let (y[0], x)` to compile, where `x` is treated as a binding, but no other identifier is. rdar://108738034
1 parent 80cf536 commit ac4301c

File tree

2 files changed

+181
-9
lines changed

2 files changed

+181
-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: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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(arguments: .init([
29+
.init(argumentType: SimpleTypeIdentifierSyntax(name: .identifier("Int")))
30+
]))
31+
), name: .identifier("e")
32+
),
33+
leftParen: .leftParenToken(),
34+
argumentList: TupleExprElementListSyntax([
35+
.init(
36+
expression: UnresolvedPatternExprSyntax(
37+
pattern: IdentifierPatternSyntax(identifier: .identifier("y"))
38+
)
39+
)
40+
]),
41+
rightParen: .rightParenToken()
42+
)
43+
)
44+
)
45+
)
46+
}
47+
48+
func testNonBinding1() {
49+
assertParse(
50+
"""
51+
if case let E<Int>.e(y) = x {}
52+
""",
53+
substructure: genericArgEnumPattern
54+
)
55+
}
56+
57+
func testNonBinding2() {
58+
assertParse(
59+
"""
60+
switch e {
61+
case let E<Int>.e(y):
62+
y
63+
}
64+
""",
65+
substructure: genericArgEnumPattern
66+
)
67+
}
68+
69+
private var tupleWithSubscriptAndBindingPattern: Syntax {
70+
// let (y[0], z)
71+
Syntax(
72+
ValueBindingPatternSyntax(
73+
bindingKeyword: .keyword(.let),
74+
valuePattern: ExpressionPatternSyntax(
75+
expression: TupleExprSyntax(elements: .init([
76+
.init(
77+
expression: SubscriptExprSyntax(
78+
calledExpression: IdentifierExprSyntax(identifier: .identifier("y")),
79+
leftBracket: .leftSquareToken(),
80+
argumentList: TupleExprElementListSyntax([
81+
.init(expression: IntegerLiteralExprSyntax(digits: .integerLiteral("0")))
82+
]),
83+
rightBracket: .rightSquareToken()
84+
),
85+
trailingComma: .commaToken()
86+
),
87+
.init(
88+
expression: UnresolvedPatternExprSyntax(
89+
pattern: IdentifierPatternSyntax(identifier: .identifier("z"))
90+
)
91+
)
92+
]))
93+
)
94+
)
95+
)
96+
}
97+
98+
func testNonBinding3() {
99+
assertParse(
100+
"""
101+
if case let (y[0], z) = x {}
102+
""",
103+
substructure: tupleWithSubscriptAndBindingPattern
104+
)
105+
}
106+
107+
func testNonBinding4() {
108+
assertParse(
109+
"""
110+
switch x {
111+
case let (y[0], z):
112+
z
113+
}
114+
""",
115+
substructure: tupleWithSubscriptAndBindingPattern
116+
)
117+
}
118+
119+
private var subscriptWithBindingPattern: Syntax {
120+
// let y[z]
121+
Syntax(
122+
ValueBindingPatternSyntax(
123+
bindingKeyword: .keyword(.let),
124+
valuePattern: ExpressionPatternSyntax(
125+
expression: SubscriptExprSyntax(
126+
calledExpression: IdentifierExprSyntax(identifier: .identifier("y")),
127+
leftBracket: .leftSquareToken(),
128+
argumentList: TupleExprElementListSyntax([
129+
.init(expression: UnresolvedPatternExprSyntax(
130+
pattern: IdentifierPatternSyntax(identifier: .identifier("z"))
131+
))
132+
]),
133+
rightBracket: .rightSquareToken()
134+
)
135+
)
136+
)
137+
)
138+
}
139+
140+
func testNonBinding5() {
141+
assertParse(
142+
"""
143+
if case let y[z] = x {}
144+
""",
145+
substructure: subscriptWithBindingPattern
146+
)
147+
}
148+
149+
func testNonBinding6() {
150+
assertParse(
151+
"""
152+
switch 0 {
153+
case let y[z]:
154+
z
155+
case y[z]:
156+
0
157+
default:
158+
0
159+
}
160+
""",
161+
substructure: subscriptWithBindingPattern
162+
)
163+
}
164+
}

0 commit comments

Comments
 (0)