Skip to content

Commit

Permalink
Rollup merge of #97295 - c410-f3r:yet-another-let-chain, r=compiler-e…
Browse files Browse the repository at this point in the history
…rrors

[rustc_parse] Forbid `let`s in certain places

Currently only forbids in locals to resolve #94927 (comment) but feel free to point any other places.
  • Loading branch information
matthiaskrgr authored Jun 26, 2022
2 parents c348bea + 7475867 commit 5b31271
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 196 deletions.
30 changes: 27 additions & 3 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::{
};
use crate::maybe_recover_from_interpolated_ty_qpath;

use core::mem;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::Spacing;
Expand All @@ -26,7 +27,6 @@ use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_span::source_map::{self, Span, Spanned};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{BytePos, Pos};
use std::mem;

/// Possibly accepts an `token::Interpolated` expression (a pre-parsed expression
/// dropped into the token stream, which happens while parsing the result of
Expand Down Expand Up @@ -2343,7 +2343,9 @@ impl<'a> Parser<'a> {

/// Parses the condition of a `if` or `while` expression.
fn parse_cond_expr(&mut self) -> PResult<'a, P<Expr>> {
let cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
let cond = self.with_let_management(true, |local_self| {
local_self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)
})?;

if let ExprKind::Let(..) = cond.kind {
// Remove the last feature gating of a `let` expression since it's stable.
Expand All @@ -2356,6 +2358,13 @@ impl<'a> Parser<'a> {
/// Parses a `let $pat = $expr` pseudo-expression.
/// The `let` token has already been eaten.
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
if !self.let_expr_allowed {
self.struct_span_err(
self.prev_token.span,
"expected expression, found `let` statement",
)
.emit();
}
let lo = self.prev_token.span;
let pat = self.parse_pat_allow_top_alt(
None,
Expand Down Expand Up @@ -2672,6 +2681,8 @@ impl<'a> Parser<'a> {
}

pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
// Used to check the `let_chains` and `if_let_guard` features mostly by scaning
// `&&` tokens.
fn check_let_expr(expr: &Expr) -> (bool, bool) {
match expr.kind {
ExprKind::Binary(_, ref lhs, ref rhs) => {
Expand All @@ -2694,7 +2705,7 @@ impl<'a> Parser<'a> {
)?;
let guard = if this.eat_keyword(kw::If) {
let if_span = this.prev_token.span;
let cond = this.parse_expr()?;
let cond = this.with_let_management(true, |local_this| local_this.parse_expr())?;
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
if has_let_expr {
if does_not_have_bin_op {
Expand Down Expand Up @@ -3256,4 +3267,17 @@ impl<'a> Parser<'a> {
Ok((res, trailing))
})
}

// Calls `f` with the internal `let_expr_allowed` set to `let_expr_allowed` and then
// sets the internal `let_expr_allowed` back to its original value.
fn with_let_management<T>(
&mut self,
let_expr_allowed: bool,
f: impl FnOnce(&mut Self) -> T,
) -> T {
let last_let_expr_allowed = mem::replace(&mut self.let_expr_allowed, let_expr_allowed);
let rslt = f(self);
self.let_expr_allowed = last_let_expr_allowed;
rslt
}
}
6 changes: 5 additions & 1 deletion compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,15 @@ pub struct Parser<'a> {
/// This allows us to recover when the user forget to add braces around
/// multiple statements in the closure body.
pub current_closure: Option<ClosureSpans>,
/// Used to track where `let`s are allowed. For example, `if true && let 1 = 1` is valid
/// but `[1, 2, 3][let _ = ()]` is not.
let_expr_allowed: bool,
}

// This type is used a lot, e.g. it's cloned when matching many declarative macro rules. Make sure
// it doesn't unintentionally get bigger.
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
rustc_data_structures::static_assert_size!(Parser<'_>, 328);
rustc_data_structures::static_assert_size!(Parser<'_>, 336);

/// Stores span information about a closure.
#[derive(Clone)]
Expand Down Expand Up @@ -455,6 +458,7 @@ impl<'a> Parser<'a> {
inner_attr_ranges: Default::default(),
},
current_closure: None,
let_expr_allowed: false,
};

// Make parser point to the first token.
Expand Down
3 changes: 2 additions & 1 deletion src/test/ui/mir/issue-92893.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
struct Bug<A = [(); (let a = (), 1).1]> {
//~^ `let` expressions are not supported here
//~^^ `let` expressions in this position are unstable [E0658]
//~| `let` expressions in this position are unstable [E0658]
//~| expected expression, found `let` statement
a: A
}

Expand Down
8 changes: 7 additions & 1 deletion src/test/ui/mir/issue-92893.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
error: expected expression, found `let` statement
--> $DIR/issue-92893.rs:1:22
|
LL | struct Bug<A = [(); (let a = (), 1).1]> {
| ^^^

error: `let` expressions are not supported here
--> $DIR/issue-92893.rs:1:22
|
Expand All @@ -15,6 +21,6 @@ LL | struct Bug<A = [(); (let a = (), 1).1]> {
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
= help: add `#![feature(let_chains)]` to the crate attributes to enable

error: aborting due to 2 previous errors
error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0658`.
2 changes: 2 additions & 0 deletions src/test/ui/rfc-2294-if-let-guard/feature-gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ fn _macros() {
}
use_expr!((let 0 = 1 && 0 == 0));
//~^ ERROR `let` expressions in this position are unstable
//~| ERROR expected expression, found `let` statement
use_expr!((let 0 = 1));
//~^ ERROR `let` expressions in this position are unstable
//~| ERROR expected expression, found `let` statement
match () {
#[cfg(FALSE)]
() if let 0 = 1 => {}
Expand Down
20 changes: 16 additions & 4 deletions src/test/ui/rfc-2294-if-let-guard/feature-gate.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
error: expected expression, found `let` statement
--> $DIR/feature-gate.rs:59:16
|
LL | use_expr!((let 0 = 1 && 0 == 0));
| ^^^

error: expected expression, found `let` statement
--> $DIR/feature-gate.rs:62:16
|
LL | use_expr!((let 0 = 1));
| ^^^

error: no rules expected the token `let`
--> $DIR/feature-gate.rs:69:15
--> $DIR/feature-gate.rs:71:15
|
LL | macro_rules! use_expr {
| --------------------- when calling this macro
Expand Down Expand Up @@ -58,7 +70,7 @@ LL | () if let Range { start: _, end: _ } = (true..true) && false => {}
= help: you can write `if matches!(<expr>, <pattern>)` instead of `if let <pattern> = <expr>`

error[E0658]: `if let` guards are experimental
--> $DIR/feature-gate.rs:65:12
--> $DIR/feature-gate.rs:67:12
|
LL | () if let 0 = 1 => {}
| ^^^^^^^^^^^^
Expand Down Expand Up @@ -203,14 +215,14 @@ LL | use_expr!((let 0 = 1 && 0 == 0));
= help: add `#![feature(let_chains)]` to the crate attributes to enable

error[E0658]: `let` expressions in this position are unstable
--> $DIR/feature-gate.rs:61:16
--> $DIR/feature-gate.rs:62:16
|
LL | use_expr!((let 0 = 1));
| ^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
= help: add `#![feature(let_chains)]` to the crate attributes to enable

error: aborting due to 23 previous errors
error: aborting due to 25 previous errors

For more information about this error, try `rustc --explain E0658`.
88 changes: 71 additions & 17 deletions src/test/ui/rfc-2497-if-let-chains/disallowed-positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ fn _macros() {
use_expr!((let 0 = 1 && 0 == 0));
//~^ ERROR `let` expressions are not supported here
//~| ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
use_expr!((let 0 = 1));
//~^ ERROR `let` expressions are not supported here
//~| ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
}

fn nested_within_if_expr() {
Expand Down Expand Up @@ -147,7 +149,8 @@ fn nested_within_if_expr() {
//~| ERROR mismatched types
//~| ERROR mismatched types

if let true = let true = true {} //~ ERROR `let` expressions are not supported here
if let true = let true = true {}
//~^ ERROR `let` expressions are not supported here
}

fn nested_within_while_expr() {
Expand Down Expand Up @@ -211,7 +214,8 @@ fn nested_within_while_expr() {
//~| ERROR mismatched types
//~| ERROR mismatched types

while let true = let true = true {} //~ ERROR `let` expressions are not supported here
while let true = let true = true {}
//~^ ERROR `let` expressions are not supported here
}

fn not_error_because_clarified_intent() {
Expand All @@ -225,45 +229,85 @@ fn not_error_because_clarified_intent() {
}

fn outside_if_and_while_expr() {
&let 0 = 0; //~ ERROR `let` expressions are not supported here
&let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement

!let 0 = 0; //~ ERROR `let` expressions are not supported here
*let 0 = 0; //~ ERROR `let` expressions are not supported here
//~^ ERROR type `bool` cannot be dereferenced
-let 0 = 0; //~ ERROR `let` expressions are not supported here
//~^ ERROR cannot apply unary operator `-` to type `bool`
!let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
*let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR type `bool` cannot be dereferenced
//~| ERROR expected expression, found `let` statement
-let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR cannot apply unary operator `-` to type `bool`
//~| ERROR expected expression, found `let` statement

fn _check_try_binds_tighter() -> Result<(), ()> {
let 0 = 0?;
//~^ ERROR the `?` operator can only be applied to values that implement `Try`
Ok(())
}
(let 0 = 0)?; //~ ERROR `let` expressions are not supported here
//~^ ERROR the `?` operator can only be used in a function that returns `Result`
(let 0 = 0)?;
//~^ ERROR `let` expressions are not supported here
//~| ERROR the `?` operator can only be used in a function that returns `Result`
//~| ERROR the `?` operator can only be applied to values that implement `Try`
//~| ERROR expected expression, found `let` statement

true || let 0 = 0; //~ ERROR `let` expressions are not supported here
(true || let 0 = 0); //~ ERROR `let` expressions are not supported here
true && (true || let 0 = 0); //~ ERROR `let` expressions are not supported here
true || let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
(true || let 0 = 0);
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
true && (true || let 0 = 0);
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement

let mut x = true;
x = let 0 = 0; //~ ERROR `let` expressions are not supported here
x = let 0 = 0;
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement

true..(let 0 = 0); //~ ERROR `let` expressions are not supported here
..(let 0 = 0); //~ ERROR `let` expressions are not supported here
(let 0 = 0)..; //~ ERROR `let` expressions are not supported here
true..(let 0 = 0);
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
..(let 0 = 0);
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
(let 0 = 0)..;
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement

(let Range { start: _, end: _ } = true..true || false);
//~^ ERROR `let` expressions are not supported here
//~| ERROR mismatched types
//~| ERROR expected expression, found `let` statement

(let true = let true = true);
//~^ ERROR `let` expressions are not supported here
//~| ERROR expected expression, found `let` statement
//~| ERROR expected expression, found `let` statement

{
#[cfg(FALSE)]
let x = true && let y = 1;
//~^ ERROR expected expression, found `let` statement
}

#[cfg(FALSE)]
{
[1, 2, 3][let _ = ()]
//~^ ERROR expected expression, found `let` statement
}

// Check function tail position.
&let 0 = 0
//~^ ERROR `let` expressions are not supported here
//~| ERROR mismatched types
//~| ERROR expected expression, found `let` statement
}

// Let's make sure that `let` inside const generic arguments are considered.
Expand Down Expand Up @@ -335,4 +379,14 @@ fn with_parenthesis() {
let fun = || true;
if let true = (true && fun()) && (true) {
}

#[cfg(FALSE)]
let x = (true && let y = 1);
//~^ ERROR expected expression, found `let` statement

#[cfg(FALSE)]
{
([1, 2, 3][let _ = ()])
//~^ ERROR expected expression, found `let` statement
}
}
Loading

0 comments on commit 5b31271

Please sign in to comment.