Skip to content

Emulate the Structure of Pattern Binding Conditions #729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 8, 2022
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
66 changes: 49 additions & 17 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ extension Parser {
// sequenced.
var lastElement: RawExprSyntax

lastElement = self.parseSequenceExpressionElement(flavor, forDirective: forDirective)
lastElement = self.parseSequenceExpressionElement(flavor,
forDirective: forDirective,
inVarOrLet: inVarOrLet)

var loopCondition = LoopProgressCondition()
while loopCondition.evaluate(currentToken) {
Expand Down Expand Up @@ -101,7 +103,9 @@ extension Parser {
lastElement = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
break
} else {
lastElement = self.parseSequenceExpressionElement(flavor, forDirective: forDirective)
lastElement = self.parseSequenceExpressionElement(flavor,
forDirective: forDirective,
inVarOrLet: inVarOrLet)
}
}

Expand Down Expand Up @@ -153,7 +157,7 @@ extension Parser {
case .infixQuestionMark:
// Save the '?'.
let question = self.eat(.infixQuestionMark)
let firstChoice = self.parseSequenceExpression(flavor)
let firstChoice = self.parseSequenceExpression(flavor, inVarOrLet: inVarOrLet)
// Make sure there's a matching ':' after the middle expr.
let (unexpectedBeforeColon, colon) = self.expect(.colon)

Expand Down Expand Up @@ -250,11 +254,13 @@ extension Parser {
@_spi(RawSyntax)
public mutating func parseSequenceExpressionElement(
_ flavor: ExprFlavor,
forDirective: Bool = false
forDirective: Bool = false,
inVarOrLet: Bool = false
) -> RawExprSyntax {
if self.currentToken.isContextualKeyword("await") {
let awaitTok = self.consumeAnyToken()
let sub = self.parseSequenceExpressionElement(flavor)
let sub = self.parseSequenceExpressionElement(flavor,
inVarOrLet: inVarOrLet)
return RawExprSyntax(RawAwaitExprSyntax(
awaitKeyword: awaitTok, expression: sub,
arena: self.arena))
Expand All @@ -271,7 +277,9 @@ extension Parser {
}

guard self.at(.tryKeyword) else {
return self.parseUnaryExpression(flavor, forDirective: forDirective)
return self.parseUnaryExpression(flavor,
forDirective: forDirective,
inVarOrLet: inVarOrLet)
}

let tryKeyword = self.eat(.tryKeyword)
Expand All @@ -282,7 +290,8 @@ extension Parser {
mark = nil
}

let expression = self.parseSequenceExpressionElement(flavor)
let expression = self.parseSequenceExpressionElement(flavor,
inVarOrLet: inVarOrLet)
return RawExprSyntax(RawTryExprSyntax(
tryKeyword: tryKeyword,
questionOrExclamationMark: mark,
Expand All @@ -302,31 +311,32 @@ extension Parser {
@_spi(RawSyntax)
public mutating func parseUnaryExpression(
_ flavor: ExprFlavor,
forDirective: Bool = false
forDirective: Bool = false,
inVarOrLet: Bool = false
) -> RawExprSyntax {
// First check to see if we have the start of a regex literal `/.../`.
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)
switch self.currentToken.tokenKind {
case .prefixAmpersand:
let amp = self.eat(.prefixAmpersand)
let expr = self.parseUnaryExpression(flavor)
let expr = self.parseUnaryExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
return RawExprSyntax(RawInOutExprSyntax(
ampersand: amp, expression: RawExprSyntax(expr),
arena: self.arena))

case .backslash:
return RawExprSyntax(self.parseKeyPathExpression(forDirective: forDirective))
return RawExprSyntax(self.parseKeyPathExpression(forDirective: forDirective, inVarOrLet: inVarOrLet))

case .prefixOperator:
let op = self.eat(.prefixOperator)
let postfix = self.parseUnaryExpression(flavor, forDirective: forDirective)
let postfix = self.parseUnaryExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
return RawExprSyntax(RawPrefixOperatorExprSyntax(
operatorToken: op, postfixExpression: postfix,
arena: self.arena))

default:
// If the next token is not an operator, just parse this as expr-postfix.
return self.parsePostfixExpression(flavor, forDirective: forDirective)
return self.parsePostfixExpression(flavor, forDirective: forDirective, inVarOrLet: inVarOrLet)
}
}

Expand All @@ -347,9 +357,10 @@ extension Parser {
@_spi(RawSyntax)
public mutating func parsePostfixExpression(
_ flavor: ExprFlavor,
forDirective: Bool
forDirective: Bool,
inVarOrLet: Bool
) -> RawExprSyntax {
let head = self.parsePrimaryExpression()
let head = self.parsePrimaryExpression(inVarOrLet: inVarOrLet)
guard !head.is(RawMissingExprSyntax.self) else {
return head
}
Expand Down Expand Up @@ -631,7 +642,7 @@ extension Parser {
/// key-path-postfixes → key-path-postfix key-path-postfixes?
/// key-path-postfix → '?' | '!' | 'self' | '[' function-call-argument-list ']'
@_spi(RawSyntax)
public mutating func parseKeyPathExpression(forDirective: Bool) -> RawKeyPathExprSyntax {
public mutating func parseKeyPathExpression(forDirective: Bool, inVarOrLet: Bool) -> RawKeyPathExprSyntax {
// Consume '\'.
let backslash = self.eat(.backslash)

Expand All @@ -642,7 +653,7 @@ extension Parser {
// the token is a operator starts with '.', or the following token is '['.
let root: RawExprSyntax?
if !self.currentToken.starts(with: ".") {
root = self.parsePostfixExpression(.basic, forDirective: forDirective)
root = self.parsePostfixExpression(.basic, forDirective: forDirective, inVarOrLet: inVarOrLet)
} else {
root = nil
}
Expand Down Expand Up @@ -689,7 +700,7 @@ extension Parser {
/// primary-expression → selector-expression
/// primary-expression → key-path-string-expression
@_spi(RawSyntax)
public mutating func parsePrimaryExpression() -> RawExprSyntax {
public mutating func parsePrimaryExpression(inVarOrLet: Bool) -> RawExprSyntax {
switch self.currentToken.tokenKind {
case .integerLiteral:
let digits = self.eat(.integerLiteral)
Expand Down Expand Up @@ -741,6 +752,16 @@ extension Parser {
let tok = self.eat(.__dso_handle__Keyword)
return RawExprSyntax(RawPoundDsohandleExprSyntax(poundDsohandle: tok, arena: self.arena))
case .identifier, .selfKeyword:
// If we have "case let x." or "case let x(", we parse x as a normal
// name, not a binding, because it is the start of an enum pattern or
// call pattern.
if inVarOrLet && !self.lookahead().isNextTokenCallPattern() {
let identifier = self.parseAnyIdentifier()
let pattern = RawPatternSyntax(RawIdentifierPatternSyntax(
identifier: identifier, arena: self.arena))
return RawExprSyntax(RawUnresolvedPatternExprSyntax(pattern: pattern, arena: self.arena))
}

// 'any' followed by another identifier is an existential type.
if self.currentToken.isContextualKeyword("any"),
self.peek().tokenKind == .identifier,
Expand Down Expand Up @@ -2102,4 +2123,15 @@ extension Parser.Lookahead {
}
return true
}

fileprivate func isNextTokenCallPattern() -> Bool {
switch self.peek().tokenKind {
case .period,
.prefixPeriod,
.leftParen:
return true
default:
return false
}
}
}
12 changes: 11 additions & 1 deletion Sources/SwiftParser/Patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,17 @@ extension Parser {
// matching-pattern ::= expr
// Fall back to expression parsing for ambiguous forms. Name lookup will
// disambiguate.
let expr = RawExprSyntax(self.parseSequenceExpression(.basic, inVarOrLet: true))
let patternSyntax = self.parseSequenceExpression(.basic, inVarOrLet: true)
if let pat = patternSyntax.as(RawUnresolvedPatternExprSyntax.self) {
// The most common case here is to parse something that was a lexically
// obvious pattern, which will come back wrapped in an immediate
// RawUnresolvedPatternExprSyntax.
//
// FIXME: This is pretty gross. Let's find a way to disambiguate let
// binding patterns much earlier.
return RawPatternSyntax(pat.pattern)
}
let expr = RawExprSyntax(patternSyntax)
return RawPatternSyntax(RawExpressionPatternSyntax(expression: expr, arena: self.arena))
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ extension Parser {

// Parse the basic expression case. If we have a leading let/var/case
// keyword or an assignment, then we know this is a binding.
if !self.at(.letKeyword) && !self.at(.varKeyword) && !self.at(.caseKeyword) {
guard self.at(.letKeyword) || self.at(.varKeyword) || self.at(.caseKeyword) else {
// If we lack it, then this is theoretically a boolean condition.
// However, we also need to handle migrating from Swift 2 syntax, in
// which a comma followed by an expression could actually be a pattern
Expand Down
16 changes: 16 additions & 0 deletions Tests/SwiftParserTest/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ import XCTest

final class StatementTests: XCTestCase {
func testIf() {
AssertParse("""
if let baz {}
""",
substructure: Syntax(IfStmtSyntax(ifKeyword: .ifKeyword(),
conditions: ConditionElementListSyntax([
ConditionElementSyntax(condition: Syntax(OptionalBindingConditionSyntax(
letOrVarKeyword: .letKeyword(),
pattern: PatternSyntax(IdentifierPatternSyntax(identifier: .identifier("baz"))),
typeAnnotation: nil,
initializer: nil)), trailingComma: nil)
]),
body: .init(leftBrace: .leftBraceToken(),
statements: .init([]),
rightBrace: .rightBraceToken()),
elseKeyword: nil, elseBody: nil)))

AssertParse("if let x { }")

AssertParse(
Expand Down