Skip to content

Commit 43a1d29

Browse files
committed
Emulate the Structure of Pattern Binding Conditions
The expression parser has to go out of its way to parse all the way down to primary expressions before realizing that the expression pattern it was after isn't actually an expression pattern. This involves setting a bit of ambient state, that we have to plumb down expression parsing. This is pretty gross, but it emulates what the legacy parser was doing here. Fixes #724 rdar://99669036
1 parent 6cd32e4 commit 43a1d29

File tree

4 files changed

+77
-19
lines changed

4 files changed

+77
-19
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ extension Parser {
7070
// sequenced.
7171
var lastElement: RawExprSyntax
7272

73-
lastElement = self.parseSequenceExpressionElement(flavor, forDirective: forDirective)
73+
lastElement = self.parseSequenceExpressionElement(flavor,
74+
forDirective: forDirective,
75+
inVarOrLet: inVarOrLet)
7476

7577
var loopCondition = LoopProgressCondition()
7678
while loopCondition.evaluate(currentToken) {
@@ -101,7 +103,9 @@ extension Parser {
101103
lastElement = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
102104
break
103105
} else {
104-
lastElement = self.parseSequenceExpressionElement(flavor, forDirective: forDirective)
106+
lastElement = self.parseSequenceExpressionElement(flavor,
107+
forDirective: forDirective,
108+
inVarOrLet: inVarOrLet)
105109
}
106110
}
107111

@@ -153,7 +157,7 @@ extension Parser {
153157
case .infixQuestionMark:
154158
// Save the '?'.
155159
let question = self.eat(.infixQuestionMark)
156-
let firstChoice = self.parseSequenceExpression(flavor)
160+
let firstChoice = self.parseSequenceExpression(flavor, inVarOrLet: inVarOrLet)
157161
// Make sure there's a matching ':' after the middle expr.
158162
let (unexpectedBeforeColon, colon) = self.expect(.colon)
159163

@@ -250,11 +254,13 @@ extension Parser {
250254
@_spi(RawSyntax)
251255
public mutating func parseSequenceExpressionElement(
252256
_ flavor: ExprFlavor,
253-
forDirective: Bool = false
257+
forDirective: Bool = false,
258+
inVarOrLet: Bool = false
254259
) -> RawExprSyntax {
255260
if self.currentToken.isContextualKeyword("await") {
256261
let awaitTok = self.consumeAnyToken()
257-
let sub = self.parseSequenceExpressionElement(flavor)
262+
let sub = self.parseSequenceExpressionElement(flavor,
263+
inVarOrLet: inVarOrLet)
258264
return RawExprSyntax(RawAwaitExprSyntax(
259265
awaitKeyword: awaitTok, expression: sub,
260266
arena: self.arena))
@@ -271,7 +277,9 @@ extension Parser {
271277
}
272278

273279
guard self.at(.tryKeyword) else {
274-
return self.parseUnaryExpression(flavor, forDirective: forDirective)
280+
return self.parseUnaryExpression(flavor,
281+
forDirective: forDirective,
282+
inVarOrLet: inVarOrLet)
275283
}
276284

277285
let tryKeyword = self.eat(.tryKeyword)
@@ -282,7 +290,8 @@ extension Parser {
282290
mark = nil
283291
}
284292

285-
let expression = self.parseSequenceExpressionElement(flavor)
293+
let expression = self.parseSequenceExpressionElement(flavor,
294+
inVarOrLet: inVarOrLet)
286295
return RawExprSyntax(RawTryExprSyntax(
287296
tryKeyword: tryKeyword,
288297
questionOrExclamationMark: mark,
@@ -302,31 +311,32 @@ extension Parser {
302311
@_spi(RawSyntax)
303312
public mutating func parseUnaryExpression(
304313
_ flavor: ExprFlavor,
305-
forDirective: Bool = false
314+
forDirective: Bool = false,
315+
inVarOrLet: Bool = false
306316
) -> RawExprSyntax {
307317
// First check to see if we have the start of a regex literal `/.../`.
308318
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)
309319
switch self.currentToken.tokenKind {
310320
case .prefixAmpersand:
311321
let amp = self.eat(.prefixAmpersand)
312-
let expr = self.parseUnaryExpression(flavor)
322+
let expr = self.parseUnaryExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
313323
return RawExprSyntax(RawInOutExprSyntax(
314324
ampersand: amp, expression: RawExprSyntax(expr),
315325
arena: self.arena))
316326

317327
case .backslash:
318-
return RawExprSyntax(self.parseKeyPathExpression(forDirective: forDirective))
328+
return RawExprSyntax(self.parseKeyPathExpression(forDirective: forDirective, inVarOrLet: inVarOrLet))
319329

320330
case .prefixOperator:
321331
let op = self.eat(.prefixOperator)
322-
let postfix = self.parseUnaryExpression(flavor, forDirective: forDirective)
332+
let postfix = self.parseUnaryExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
323333
return RawExprSyntax(RawPrefixOperatorExprSyntax(
324334
operatorToken: op, postfixExpression: postfix,
325335
arena: self.arena))
326336

327337
default:
328338
// If the next token is not an operator, just parse this as expr-postfix.
329-
return self.parsePostfixExpression(flavor, forDirective: forDirective)
339+
return self.parsePostfixExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
330340
}
331341
}
332342

@@ -347,9 +357,10 @@ extension Parser {
347357
@_spi(RawSyntax)
348358
public mutating func parsePostfixExpression(
349359
_ flavor: ExprFlavor,
350-
forDirective: Bool
360+
forDirective: Bool,
361+
inVarOrLet: Bool
351362
) -> RawExprSyntax {
352-
let head = self.parsePrimaryExpression()
363+
let head = self.parsePrimaryExpression(inVarOrLet: inVarOrLet)
353364
guard !head.is(RawMissingExprSyntax.self) else {
354365
return head
355366
}
@@ -631,7 +642,7 @@ extension Parser {
631642
/// key-path-postfixes → key-path-postfix key-path-postfixes?
632643
/// key-path-postfix → '?' | '!' | 'self' | '[' function-call-argument-list ']'
633644
@_spi(RawSyntax)
634-
public mutating func parseKeyPathExpression(forDirective: Bool) -> RawKeyPathExprSyntax {
645+
public mutating func parseKeyPathExpression(forDirective: Bool, inVarOrLet: Bool) -> RawKeyPathExprSyntax {
635646
// Consume '\'.
636647
let backslash = self.eat(.backslash)
637648

@@ -642,7 +653,7 @@ extension Parser {
642653
// the token is a operator starts with '.', or the following token is '['.
643654
let root: RawExprSyntax?
644655
if !self.currentToken.starts(with: ".") {
645-
root = self.parsePostfixExpression(.basic, forDirective: forDirective)
656+
root = self.parsePostfixExpression(.basic, forDirective: forDirective, inVarOrLet: inVarOrLet)
646657
} else {
647658
root = nil
648659
}
@@ -689,7 +700,7 @@ extension Parser {
689700
/// primary-expression → selector-expression
690701
/// primary-expression → key-path-string-expression
691702
@_spi(RawSyntax)
692-
public mutating func parsePrimaryExpression() -> RawExprSyntax {
703+
public mutating func parsePrimaryExpression(inVarOrLet: Bool) -> RawExprSyntax {
693704
switch self.currentToken.tokenKind {
694705
case .integerLiteral:
695706
let digits = self.eat(.integerLiteral)
@@ -741,6 +752,16 @@ extension Parser {
741752
let tok = self.eat(.__dso_handle__Keyword)
742753
return RawExprSyntax(RawPoundDsohandleExprSyntax(poundDsohandle: tok, arena: self.arena))
743754
case .identifier, .selfKeyword:
755+
// If we have "case let x." or "case let x(", we parse x as a normal
756+
// name, not a binding, because it is the start of an enum pattern or
757+
// call pattern.
758+
if inVarOrLet && !self.lookahead().isNextTokenCallPattern() {
759+
let identifier = self.parseAnyIdentifier()
760+
let pattern = RawPatternSyntax(RawIdentifierPatternSyntax(
761+
identifier: identifier, arena: self.arena))
762+
return RawExprSyntax(RawUnresolvedPatternExprSyntax(pattern: pattern, arena: self.arena))
763+
}
764+
744765
// 'any' followed by another identifier is an existential type.
745766
if self.currentToken.isContextualKeyword("any"),
746767
self.peek().tokenKind == .identifier,
@@ -2102,4 +2123,15 @@ extension Parser.Lookahead {
21022123
}
21032124
return true
21042125
}
2126+
2127+
fileprivate func isNextTokenCallPattern() -> Bool {
2128+
switch self.peek().tokenKind {
2129+
case .period,
2130+
.prefixPeriod,
2131+
.leftParen:
2132+
return true
2133+
default:
2134+
return false
2135+
}
2136+
}
21052137
}

Sources/SwiftParser/Patterns.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,17 @@ extension Parser {
163163
// matching-pattern ::= expr
164164
// Fall back to expression parsing for ambiguous forms. Name lookup will
165165
// disambiguate.
166-
let expr = RawExprSyntax(self.parseSequenceExpression(.basic, inVarOrLet: true))
166+
let patternSyntax = self.parseSequenceExpression(.basic, inVarOrLet: true)
167+
if let pat = patternSyntax.as(RawUnresolvedPatternExprSyntax.self) {
168+
// The most common case here is to parse something that was a lexically
169+
// obvious pattern, which will come back wrapped in an immediate
170+
// RawUnresolvedPatternExprSyntax.
171+
//
172+
// FIXME: This is pretty gross. Let's find a way to disambiguate let
173+
// binding patterns much earlier.
174+
return RawPatternSyntax(pat.pattern)
175+
}
176+
let expr = RawExprSyntax(patternSyntax)
167177
return RawPatternSyntax(RawExpressionPatternSyntax(expression: expr, arena: self.arena))
168178
}
169179
}

Sources/SwiftParser/Statements.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ extension Parser {
217217

218218
// Parse the basic expression case. If we have a leading let/var/case
219219
// keyword or an assignment, then we know this is a binding.
220-
if !self.at(.letKeyword) && !self.at(.varKeyword) && !self.at(.caseKeyword) {
220+
guard self.at(.letKeyword) || self.at(.varKeyword) || self.at(.caseKeyword) else {
221221
// If we lack it, then this is theoretically a boolean condition.
222222
// However, we also need to handle migrating from Swift 2 syntax, in
223223
// which a comma followed by an expression could actually be a pattern

Tests/SwiftParserTest/Statements.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ import XCTest
44

55
final class StatementTests: XCTestCase {
66
func testIf() {
7+
AssertParse("""
8+
if let baz {}
9+
""",
10+
substructure: Syntax(IfStmtSyntax(ifKeyword: .ifKeyword(),
11+
conditions: ConditionElementListSyntax([
12+
ConditionElementSyntax(condition: Syntax(OptionalBindingConditionSyntax(
13+
letOrVarKeyword: .letKeyword(),
14+
pattern: PatternSyntax(IdentifierPatternSyntax(identifier: .identifier("baz"))),
15+
typeAnnotation: nil,
16+
initializer: nil)), trailingComma: nil)
17+
]),
18+
body: .init(leftBrace: .leftBraceToken(),
19+
statements: .init([]),
20+
rightBrace: .rightBraceToken()),
21+
elseKeyword: nil, elseBody: nil)))
22+
723
AssertParse("if let x { }")
824

925
AssertParse(

0 commit comments

Comments
 (0)