diff options
Diffstat (limited to 'compiler/rustc_parse/src/parser/expr.rs')
| -rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 345 |
1 files changed, 248 insertions, 97 deletions
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 5157106f4e2..b1b77305e4f 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1,3 +1,4 @@ +// ignore-tidy-filelength use super::diagnostics::SnapshotParser; use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; @@ -9,7 +10,7 @@ use super::{ use crate::errors; use crate::maybe_recover_from_interpolated_ty_qpath; use ast::mut_visit::{noop_visit_expr, MutVisitor}; -use ast::{Path, PathSegment}; +use ast::{GenBlockKind, Pat, Path, PathSegment}; use core::mem; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; @@ -32,10 +33,10 @@ use rustc_macros::Subdiagnostic; use rustc_session::errors::{report_lit_error, ExprParenthesesNeeded}; use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP; use rustc_session::lint::BuiltinLintDiagnostics; -use rustc_span::source_map::{self, Span, Spanned}; +use rustc_span::source_map::{self, Spanned}; use rustc_span::symbol::kw::PathRoot; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, Pos}; +use rustc_span::{BytePos, Pos, Span}; use thin_vec::{thin_vec, ThinVec}; /// Possibly accepts an `token::Interpolated` expression (a pre-parsed expression @@ -46,7 +47,7 @@ use thin_vec::{thin_vec, ThinVec}; macro_rules! maybe_whole_expr { ($p:expr) => { if let token::Interpolated(nt) = &$p.token.kind { - match &**nt { + match &nt.0 { token::NtExpr(e) | token::NtLiteral(e) => { let e = e.clone(); $p.bump(); @@ -1060,7 +1061,7 @@ impl<'a> Parser<'a> { match &*components { // 1e2 [IdentLike(i)] => { - DestructuredFloat::Single(Symbol::intern(&i), span) + DestructuredFloat::Single(Symbol::intern(i), span) } // 1. [IdentLike(i), Punct('.')] => { @@ -1072,7 +1073,7 @@ impl<'a> Parser<'a> { } else { (span, span) }; - let symbol = Symbol::intern(&i); + let symbol = Symbol::intern(i); DestructuredFloat::TrailingDot(symbol, ident_span, dot_span) } // 1.2 | 1.2e3 @@ -1088,8 +1089,8 @@ impl<'a> Parser<'a> { } else { (span, span, span) }; - let symbol1 = Symbol::intern(&i1); - let symbol2 = Symbol::intern(&i2); + let symbol1 = Symbol::intern(i1); + let symbol2 = Symbol::intern(i2); DestructuredFloat::MiddleDot(symbol1, ident1_span, dot_span, symbol2, ident2_span) } // 1e+ | 1e- (recovered) @@ -1441,14 +1442,20 @@ impl<'a> Parser<'a> { } else if this.token.uninterpolated_span().at_least_rust_2018() { // `Span:.at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. if this.check_keyword(kw::Async) { - if this.is_async_block() { + if this.is_gen_block(kw::Async) { // Check for `async {` and `async move {`. - this.parse_async_block() + this.parse_gen_block() } else { this.parse_expr_closure() } } else if this.eat_keyword(kw::Await) { this.recover_incorrect_await_syntax(lo, this.prev_token.span) + } else if this.token.uninterpolated_span().at_least_rust_2024() { + if this.is_gen_block(kw::Gen) { + this.parse_gen_block() + } else { + this.parse_expr_lit() + } } else { this.parse_expr_lit() } @@ -1848,7 +1855,7 @@ impl<'a> Parser<'a> { let lo = self.prev_token.span; let kind = ExprKind::Yield(self.parse_expr_opt()?); let span = lo.to(self.prev_token.span); - self.sess.gated_spans.gate(sym::coroutines, span); + self.sess.gated_spans.gate(sym::yield_expr, span); let expr = self.mk_expr(span, kind); self.maybe_recover_from_bad_qpath(expr) } @@ -1946,7 +1953,7 @@ impl<'a> Parser<'a> { mk_lit_char: impl FnOnce(Symbol, Span) -> L, ) -> PResult<'a, L> { if let token::Interpolated(nt) = &self.token.kind - && let token::NtExpr(e) | token::NtLiteral(e) = &**nt + && let token::NtExpr(e) | token::NtLiteral(e) = &nt.0 && matches!(e.kind, ExprKind::Err) { let mut err = errors::InvalidInterpolatedExpression { span: self.token.span } @@ -2046,7 +2053,7 @@ impl<'a> Parser<'a> { Err(err) => { let span = token.uninterpolated_span(); self.bump(); - report_lit_error(&self.sess, err, lit, span); + report_lit_error(self.sess, err, lit, span); // Pack possible quotes and prefixes from the original literal into // the error literal's symbol so they can be pretty-printed faithfully. let suffixless_lit = token::Lit::new(lit.kind, lit.symbol, None); @@ -2297,13 +2304,14 @@ impl<'a> Parser<'a> { /// Parses an optional `move` prefix to a closure-like construct. fn parse_capture_clause(&mut self) -> PResult<'a, CaptureBy> { if self.eat_keyword(kw::Move) { + let move_kw_span = self.prev_token.span; // Check for `move async` and recover if self.check_keyword(kw::Async) { let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); Err(errors::AsyncMoveOrderIncorrect { span: move_async_span } .into_diagnostic(&self.sess.span_diagnostic)) } else { - Ok(CaptureBy::Value) + Ok(CaptureBy::Value { move_kw: move_kw_span }) } } else { Ok(CaptureBy::Ref) @@ -2434,10 +2442,26 @@ impl<'a> Parser<'a> { self.error_on_extra_if(&cond)?; // Parse block, which will always fail, but we can add a nice note to the error self.parse_block().map_err(|mut err| { - err.span_note( - cond_span, - "the `if` expression is missing a block after this condition", - ); + if self.prev_token == token::Semi + && self.token == token::AndAnd + && let maybe_let = self.look_ahead(1, |t| t.clone()) + && maybe_let.is_keyword(kw::Let) + { + err.span_suggestion( + self.prev_token.span, + "consider removing this semicolon to parse the `let` as part of the same chain", + "", + Applicability::MachineApplicable, + ).span_note( + self.token.span.to(maybe_let.span), + "you likely meant to continue parsing the let-chain starting here", + ); + } else { + err.span_note( + cond_span, + "the `if` expression is missing a block after this condition", + ); + } err })? } @@ -2454,7 +2478,7 @@ impl<'a> Parser<'a> { let mut cond = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)?; - CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond); + CondChecker::new(self).visit_expr(&mut cond); if let ExprKind::Let(_, _, _, None) = cond.kind { // Remove the last feature gating of a `let` expression since it's stable. @@ -2470,6 +2494,8 @@ impl<'a> Parser<'a> { let err = errors::ExpectedExpressionFoundLet { span: self.token.span, reason: ForbiddenLetReason::OtherForbidden, + missing_let: None, + comparison: None, }; if self.prev_token.kind == token::BinOp(token::Or) { // This was part of a closure, the that part of the parser recover. @@ -2583,30 +2609,72 @@ impl<'a> Parser<'a> { } } - /// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten). - fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> { - // Record whether we are about to parse `for (`. - // This is used below for recovery in case of `for ( $stuff ) $block` - // in which case we will suggest `for $stuff $block`. - let begin_paren = match self.token.kind { - token::OpenDelim(Delimiter::Parenthesis) => Some(self.token.span), - _ => None, + fn parse_for_head(&mut self) -> PResult<'a, (P<Pat>, P<Expr>)> { + let begin_paren = if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) { + // Record whether we are about to parse `for (`. + // This is used below for recovery in case of `for ( $stuff ) $block` + // in which case we will suggest `for $stuff $block`. + let start_span = self.token.span; + let left = self.prev_token.span.between(self.look_ahead(1, |t| t.span)); + Some((start_span, left)) + } else { + None + }; + // Try to parse the pattern `for ($PAT) in $EXPR`. + let pat = match ( + self.parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::LikelyTuple, + ), + begin_paren, + ) { + (Ok(pat), _) => pat, // Happy path. + (Err(err), Some((start_span, left))) if self.eat_keyword(kw::In) => { + // We know for sure we have seen `for ($SOMETHING in`. In the happy path this would + // happen right before the return of this method. + let expr = match self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None) { + Ok(expr) => expr, + Err(expr_err) => { + // We don't know what followed the `in`, so cancel and bubble up the + // original error. + expr_err.cancel(); + return Err(err); + } + }; + return if self.token.kind == token::CloseDelim(Delimiter::Parenthesis) { + // We know for sure we have seen `for ($SOMETHING in $EXPR)`, so we recover the + // parser state and emit a targetted suggestion. + let span = vec![start_span, self.token.span]; + let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span)); + self.bump(); // ) + err.cancel(); + self.sess.emit_err(errors::ParenthesesInForHead { + span, + // With e.g. `for (x) in y)` this would replace `(x) in y)` + // with `x) in y)` which is syntactically invalid. + // However, this is prevented before we get here. + sugg: errors::ParenthesesInForHeadSugg { left, right }, + }); + Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr)) + } else { + Err(err) // Some other error, bubble up. + }; + } + (Err(err), _) => return Err(err), // Some other error, bubble up. }; - - let pat = self.parse_pat_allow_top_alt( - None, - RecoverComma::Yes, - RecoverColon::Yes, - CommaRecoveryMode::LikelyTuple, - )?; if !self.eat_keyword(kw::In) { self.error_missing_in_for_loop(); } self.check_for_for_in_in_typo(self.prev_token.span); let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?; + Ok((pat, expr)) + } - let pat = self.recover_parens_around_for_head(pat, begin_paren); - + /// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten). + fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> { + let (pat, expr) = self.parse_for_head()?; // Recover from missing expression in `for` loop if matches!(expr.kind, ExprKind::Block(..)) && !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace)) @@ -2827,47 +2895,10 @@ 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 scanning - // `&&` tokens. - fn check_let_expr(expr: &Expr) -> (bool, bool) { - match &expr.kind { - ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => { - let lhs_rslt = check_let_expr(lhs); - let rhs_rslt = check_let_expr(rhs); - (lhs_rslt.0 || rhs_rslt.0, false) - } - ExprKind::Let(..) => (true, true), - _ => (false, true), - } - } let attrs = self.parse_outer_attributes()?; self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; - let pat = this.parse_pat_allow_top_alt( - None, - RecoverComma::Yes, - RecoverColon::Yes, - CommaRecoveryMode::EitherTupleOrPipe, - )?; - let guard = if this.eat_keyword(kw::If) { - let if_span = this.prev_token.span; - let mut cond = this.parse_match_guard_condition()?; - - CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond); - - let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond); - if has_let_expr { - if does_not_have_bin_op { - // Remove the last feature gating of a `let` expression since it's stable. - this.sess.gated_spans.ungate_last(sym::let_chains, cond.span); - } - let span = if_span.to(cond.span); - this.sess.gated_spans.gate(sym::if_let_guard, span); - } - Some(cond) - } else { - None - }; + let (pat, guard) = this.parse_match_arm_pat_and_guard()?; let arrow_span = this.token.span; if let Err(mut err) = this.expect(&token::FatArrow) { // We might have a `=>` -> `=` or `->` typo (issue #89396). @@ -2881,15 +2912,16 @@ impl<'a> Parser<'a> { "=>", Applicability::MachineApplicable, ); - err.emit(); - this.bump(); - } else if matches!( - (&this.prev_token.kind, &this.token.kind), - (token::DotDotEq, token::Gt) - ) { - // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, - // so we suppress the error here - err.delay_as_bug(); + if matches!( + (&this.prev_token.kind, &this.token.kind), + (token::DotDotEq, token::Gt) + ) { + // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, + // so we suppress the error here + err.delay_as_bug(); + } else { + err.emit(); + } this.bump(); } else { return Err(err); @@ -2996,6 +3028,90 @@ impl<'a> Parser<'a> { }) } + fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> { + // Used to check the `let_chains` and `if_let_guard` features mostly by scanning + // `&&` tokens. + fn check_let_expr(expr: &Expr) -> (bool, bool) { + match &expr.kind { + ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => { + let lhs_rslt = check_let_expr(lhs); + let rhs_rslt = check_let_expr(rhs); + (lhs_rslt.0 || rhs_rslt.0, false) + } + ExprKind::Let(..) => (true, true), + _ => (false, true), + } + } + if !self.eat_keyword(kw::If) { + // No match arm guard present. + return Ok(None); + } + + let if_span = self.prev_token.span; + let mut cond = self.parse_match_guard_condition()?; + + CondChecker::new(self).visit_expr(&mut cond); + + let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond); + if has_let_expr { + if does_not_have_bin_op { + // Remove the last feature gating of a `let` expression since it's stable. + self.sess.gated_spans.ungate_last(sym::let_chains, cond.span); + } + let span = if_span.to(cond.span); + self.sess.gated_spans.gate(sym::if_let_guard, span); + } + Ok(Some(cond)) + } + + fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> { + if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) { + // Detect and recover from `($pat if $cond) => $arm`. + let left = self.token.span; + match self.parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::EitherTupleOrPipe, + ) { + Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)), + Err(err) + if let prev_sp = self.prev_token.span + && let true = self.eat_keyword(kw::If) => + { + // We know for certain we've found `($pat if` so far. + let mut cond = match self.parse_match_guard_condition() { + Ok(cond) => cond, + Err(cond_err) => { + cond_err.cancel(); + return Err(err); + } + }; + err.cancel(); + CondChecker::new(self).visit_expr(&mut cond); + self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]); + self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; + let right = self.prev_token.span; + self.sess.emit_err(errors::ParenthesesInMatchPat { + span: vec![left, right], + sugg: errors::ParenthesesInMatchPatSugg { left, right }, + }); + Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond))) + } + Err(err) => Err(err), + } + } else { + // Regular parser flow: + let pat = self.parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::EitherTupleOrPipe, + )?; + Ok((pat, self.parse_match_arm_guard()?)) + } + } + fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> { self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err( |mut err| { @@ -3059,18 +3175,24 @@ impl<'a> Parser<'a> { && self.token.uninterpolated_span().at_least_rust_2018() } - /// Parses an `async move? {...}` expression. - fn parse_async_block(&mut self) -> PResult<'a, P<Expr>> { + /// Parses an `async move? {...}` or `gen move? {...}` expression. + fn parse_gen_block(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; - self.expect_keyword(kw::Async)?; + let kind = if self.eat_keyword(kw::Async) { + GenBlockKind::Async + } else { + assert!(self.eat_keyword(kw::Gen)); + self.sess.gated_spans.gate(sym::gen_blocks, lo.to(self.token.span)); + GenBlockKind::Gen + }; let capture_clause = self.parse_capture_clause()?; let (attrs, body) = self.parse_inner_attrs_and_block()?; - let kind = ExprKind::Async(capture_clause, body); + let kind = ExprKind::Gen(capture_clause, body, kind); Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs)) } - fn is_async_block(&self) -> bool { - self.token.is_keyword(kw::Async) + fn is_gen_block(&self, kw: Symbol) -> bool { + self.token.is_keyword(kw) && (( // `async move {` self.is_keyword_ahead(1, &[kw::Move]) @@ -3177,7 +3299,7 @@ impl<'a> Parser<'a> { if let Some((ident, _)) = self.token.ident() && !self.token.is_reserved_ident() && self.look_ahead(1, |t| { - AssocOp::from_token(&t).is_some() + AssocOp::from_token(t).is_some() || matches!(t.kind, token::OpenDelim(_)) || t.kind == token::Dot }) @@ -3522,6 +3644,14 @@ pub(crate) enum ForbiddenLetReason { struct CondChecker<'a> { parser: &'a Parser<'a>, forbid_let_reason: Option<ForbiddenLetReason>, + missing_let: Option<errors::MaybeMissingLet>, + comparison: Option<errors::MaybeComparison>, +} + +impl<'a> CondChecker<'a> { + fn new(parser: &'a Parser<'a>) -> Self { + CondChecker { parser, forbid_let_reason: None, missing_let: None, comparison: None } + } } impl MutVisitor for CondChecker<'_> { @@ -3532,11 +3662,13 @@ impl MutVisitor for CondChecker<'_> { match e.kind { ExprKind::Let(_, _, _, ref mut is_recovered @ None) => { if let Some(reason) = self.forbid_let_reason { - *is_recovered = Some( - self.parser - .sess - .emit_err(errors::ExpectedExpressionFoundLet { span, reason }), - ); + *is_recovered = + Some(self.parser.sess.emit_err(errors::ExpectedExpressionFoundLet { + span, + reason, + missing_let: self.missing_let, + comparison: self.comparison, + })); } else { self.parser.sess.gated_spans.gate(sym::let_chains, span); } @@ -3560,9 +3692,28 @@ impl MutVisitor for CondChecker<'_> { noop_visit_expr(e, self); self.forbid_let_reason = forbid_let_reason; } + ExprKind::Assign(ref lhs, _, span) => { + let forbid_let_reason = self.forbid_let_reason; + self.forbid_let_reason = Some(OtherForbidden); + let missing_let = self.missing_let; + if let ExprKind::Binary(_, _, rhs) = &lhs.kind + && let ExprKind::Path(_, _) + | ExprKind::Struct(_) + | ExprKind::Call(_, _) + | ExprKind::Array(_) = rhs.kind + { + self.missing_let = + Some(errors::MaybeMissingLet { span: rhs.span.shrink_to_lo() }); + } + let comparison = self.comparison; + self.comparison = Some(errors::MaybeComparison { span: span.shrink_to_hi() }); + noop_visit_expr(e, self); + self.forbid_let_reason = forbid_let_reason; + self.missing_let = missing_let; + self.comparison = comparison; + } ExprKind::Unary(_, _) | ExprKind::Await(_, _) - | ExprKind::Assign(_, _, _) | ExprKind::AssignOp(_, _, _) | ExprKind::Range(_, _, _) | ExprKind::Try(_) @@ -3596,7 +3747,7 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Match(_, _) | ExprKind::Closure(_) | ExprKind::Block(_, _) - | ExprKind::Async(_, _) + | ExprKind::Gen(_, _, _) | ExprKind::TryBlock(_) | ExprKind::Underscore | ExprKind::Path(_, _) |
