From eab672b5c244f26738a7f8af978302a65a5c54a7 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sat, 17 Jun 2023 10:12:43 +0100 Subject: [PATCH] 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.foo(x)` and `let (y[0], x)` to compile, where `x` is treated as a binding, but no other identifier is. rdar://108738034 --- Sources/SwiftParser/Expressions.swift | 26 ++-- Tests/SwiftParserTest/PatternTests.swift | 171 +++++++++++++++++++++++ 2 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 Tests/SwiftParserTest/PatternTests.swift diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 846e2bffee3..471e62ba988 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -1220,10 +1220,10 @@ extension Parser { ) ) case (.identifier, let handle)?, (.self, let handle)?, (.`init`, let handle)?: - // 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 pattern.admitsBinding && !self.lookahead().isNextTokenCallPattern() { + // If we have "case let x" followed by ".", "(", "[", or a generic + // argument list, we parse x as a normal name, not a binding, because it + // is the start of an enum or expr pattern. + if pattern.admitsBinding && self.lookahead().isInBindingPatternPosition() { let identifier = self.eat(handle) let pattern = RawPatternSyntax( RawIdentifierPatternSyntax( @@ -2759,13 +2759,21 @@ extension Parser.Lookahead { return self.peek().isLexerClassifiedKeyword || TokenSpec(.identifier) ~= self.peek() } - fileprivate func isNextTokenCallPattern() -> Bool { + fileprivate func isInBindingPatternPosition() -> Bool { + // Cannot form a binding pattern if a generic argument list follows, this + // is something like 'case let E.foo(x)'. + if self.peek().isContextualPunctuator("<") { + var lookahead = self.lookahead() + lookahead.consumeAnyToken() + return !lookahead.canParseAsGenericArgumentList() + } switch self.peek().rawTokenKind { - case .period, - .leftParen: - return true - default: + // A '.' indicates a member access, '(' and '[' indicate a function call or + // subscript. We can't form a binding pattern as the base of these. + case .period, .leftParen, .leftSquare: return false + default: + return true } } } diff --git a/Tests/SwiftParserTest/PatternTests.swift b/Tests/SwiftParserTest/PatternTests.swift new file mode 100644 index 00000000000..1d47d87c446 --- /dev/null +++ b/Tests/SwiftParserTest/PatternTests.swift @@ -0,0 +1,171 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 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(RawSyntax) import SwiftSyntax +@_spi(RawSyntax) import SwiftParser +import XCTest + +final class PatternTests: XCTestCase { + private var genericArgEnumPattern: Syntax { + // let E.e(y) + Syntax( + ValueBindingPatternSyntax( + bindingKeyword: .keyword(.let), + valuePattern: ExpressionPatternSyntax( + expression: FunctionCallExprSyntax( + calledExpression: MemberAccessExprSyntax( + base: SpecializeExprSyntax( + expression: IdentifierExprSyntax(identifier: .identifier("E")), + genericArgumentClause: GenericArgumentClauseSyntax( + arguments: .init([ + .init(argumentType: SimpleTypeIdentifierSyntax(name: .identifier("Int"))) + ]) + ) + ), + name: .identifier("e") + ), + leftParen: .leftParenToken(), + argumentList: TupleExprElementListSyntax([ + .init( + expression: UnresolvedPatternExprSyntax( + pattern: IdentifierPatternSyntax(identifier: .identifier("y")) + ) + ) + ]), + rightParen: .rightParenToken() + ) + ) + ) + ) + } + + func testNonBinding1() { + assertParse( + """ + if case let E.e(y) = x {} + """, + substructure: genericArgEnumPattern + ) + } + + func testNonBinding2() { + assertParse( + """ + switch e { + case let E.e(y): + y + } + """, + substructure: genericArgEnumPattern + ) + } + + private var tupleWithSubscriptAndBindingPattern: Syntax { + // let (y[0], z) + Syntax( + ValueBindingPatternSyntax( + bindingKeyword: .keyword(.let), + valuePattern: ExpressionPatternSyntax( + expression: TupleExprSyntax( + elements: .init([ + .init( + expression: SubscriptExprSyntax( + calledExpression: IdentifierExprSyntax(identifier: .identifier("y")), + leftBracket: .leftSquareToken(), + argumentList: TupleExprElementListSyntax([ + .init(expression: IntegerLiteralExprSyntax(digits: .integerLiteral("0"))) + ]), + rightBracket: .rightSquareToken() + ), + trailingComma: .commaToken() + ), + .init( + expression: UnresolvedPatternExprSyntax( + pattern: IdentifierPatternSyntax(identifier: .identifier("z")) + ) + ), + ]) + ) + ) + ) + ) + } + + func testNonBinding3() { + assertParse( + """ + if case let (y[0], z) = x {} + """, + substructure: tupleWithSubscriptAndBindingPattern + ) + } + + func testNonBinding4() { + assertParse( + """ + switch x { + case let (y[0], z): + z + } + """, + substructure: tupleWithSubscriptAndBindingPattern + ) + } + + private var subscriptWithBindingPattern: Syntax { + // let y[z] + Syntax( + ValueBindingPatternSyntax( + bindingKeyword: .keyword(.let), + valuePattern: ExpressionPatternSyntax( + expression: SubscriptExprSyntax( + calledExpression: IdentifierExprSyntax(identifier: .identifier("y")), + leftBracket: .leftSquareToken(), + argumentList: TupleExprElementListSyntax([ + .init( + expression: UnresolvedPatternExprSyntax( + pattern: IdentifierPatternSyntax(identifier: .identifier("z")) + ) + ) + ]), + rightBracket: .rightSquareToken() + ) + ) + ) + ) + } + + func testNonBinding5() { + assertParse( + """ + if case let y[z] = x {} + """, + substructure: subscriptWithBindingPattern + ) + } + + func testNonBinding6() { + assertParse( + """ + switch 0 { + case let y[z]: + z + case y[z]: + 0 + default: + 0 + } + """, + substructure: subscriptWithBindingPattern + ) + } +}