Skip to content

[Parse] Avoid creating binding patterns in a couple more positions #66718

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
Jun 19, 2023
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
33 changes: 25 additions & 8 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1664,19 +1664,35 @@ ParserResult<Expr> Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) {
}

case tok::identifier: // foo
case tok::kw_self: // self

case tok::kw_self: { // self
auto canParseBindingInPattern = [&]() {
if (InBindingPattern != PatternBindingState::ImplicitlyImmutable &&
!InBindingPattern.getIntroducer().hasValue()) {
return false;
}
// If we have "case let x.", "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, call, or subscript.
if (peekToken().isAny(tok::period, tok::period_prefix, tok::l_paren,
tok::l_square)) {
return false;
}
// If we have a generic argument list, this is something like
// "case let E<Int>.e(y)", and 'E' should be parsed as a normal name, not
// a binding.
if (peekToken().isAnyOperator() && peekToken().getText().equals("<")) {
BacktrackingScope S(*this);
consumeToken();
return !canParseAsGenericArgumentList();
}
return true;
}();
// If we are parsing a refutable pattern and are inside a let/var pattern,
// the identifiers change to be value bindings instead of decl references.
// Parse and return this as an UnresolvedPatternExpr around a binding. This
// will be resolved (or rejected) by sema when the overall refutable pattern
// it transformed from an expression into a pattern.
if ((InBindingPattern == PatternBindingState::ImplicitlyImmutable ||
InBindingPattern.getIntroducer().hasValue()) &&
// 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.
peekToken().isNot(tok::period, tok::period_prefix, tok::l_paren)) {
if (canParseBindingInPattern) {
Identifier name;
SourceLoc loc = consumeIdentifier(name, /*diagnoseDollarPrefix=*/false);
// If we have an inout/let/var, set that as our introducer. otherwise
Expand Down Expand Up @@ -1710,6 +1726,7 @@ ParserResult<Expr> Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) {
}

LLVM_FALLTHROUGH;
}
case tok::kw_Self: // Self
return parseExprIdentifier();

Expand Down
21 changes: 21 additions & 0 deletions test/Constraints/rdar108738034.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %target-typecheck-verify-swift

// rdar://108738034: Make sure we can type-check this.
enum E<T>: Error {
case e(T)
}

struct S {
func bar(_: (Error?) -> Void) {}
}

func foo(_ s: S) {
s.bar { error in
guard let error = error else {
return
}
if case let E<Int>.e(y) = error {
print(y)
}
}
}
48 changes: 48 additions & 0 deletions test/Parse/matching_patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,51 @@ let (responseObject: Int?) = op1
// expected-error @-1 {{expected ',' separator}} {{25-25=,}}
// expected-error @-2 {{expected pattern}}
// expected-error @-3 {{cannot convert value of type 'Int?' to specified type '(responseObject: _)'}}

enum E<T> {
case e(T)
}

// rdar://108738034 - Make sure we don't treat 'E' as a binding, but can treat
// 'y' as a binding
func testNonBinding1(_ x: E<Int>) -> Int {
if case let E<Int>.e(y) = x { y } else { 0 }
}

func testNonBinding2(_ e: E<Int>) -> Int {
switch e {
case let E<Int>.e(y):
y
}
}

// In this case, 'y' should be an identifier, but 'z' is a binding.
func testNonBinding3(_ x: (Int, Int), y: [Int]) -> Int {
if case let (y[0], z) = x { z } else { 0 }
}

func testNonBinding4(_ x: (Int, Int), y: [Int]) -> Int {
switch x {
case let (y[0], z):
z
default:
0
}
}

func testNonBinding5(_ x: Int, y: [Int]) {
// We treat 'z' here as a binding, which is invalid.
if case let y[z] = x {} // expected-error {{pattern variable binding cannot appear in an expression}}
}

func testNonBinding6(y: [Int], z: Int) -> Int {
switch 0 {
// We treat 'z' here as a binding, which is invalid.
case let y[z]: // expected-error {{pattern variable binding cannot appear in an expression}}
z
case y[z]: // This is fine
0
default:
0
}
}