diff options
Diffstat (limited to 'compiler/rustc_parse/src/parser/expr.rs')
-rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 614 |
1 files changed, 396 insertions, 218 deletions
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 235b28b6e..cd3e8b92f 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::{GenBlockKind, Path, PathSegment}; +use ast::{CoroutineKind, GenBlockKind, Pat, Path, PathSegment}; use core::mem; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; @@ -20,7 +21,7 @@ use rustc_ast::util::parser::{prec_let_scrutinee_needs_par, AssocOp, Fixity}; use rustc_ast::visit::Visitor; use rustc_ast::{self as ast, AttrStyle, AttrVec, CaptureBy, ExprField, UnOp, DUMMY_NODE_ID}; use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty, TyKind}; -use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits}; +use rustc_ast::{Arm, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits}; use rustc_ast::{ClosureBinder, MetaItemLit, StmtKind}; use rustc_ast_pretty::pprust; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -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) @@ -1268,7 +1269,7 @@ impl<'a> Parser<'a> { .collect(), }, } - .into_diagnostic(&self.sess.span_diagnostic); + .into_diagnostic(self.dcx()); replacement_err.emit(); let old_err = mem::replace(err, replacement_err); @@ -1439,22 +1440,25 @@ impl<'a> Parser<'a> { } else if this.eat_keyword(kw::Underscore) { Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore)) } 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_gen_block(kw::Async) { - // Check for `async {` and `async move {`. + // `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. + if this.token.uninterpolated_span().at_least_rust_2024() + // check for `gen {}` and `gen move {}` + // or `async gen {}` and `async gen move {}` + && (this.is_gen_block(kw::Gen, 0) + || (this.check_keyword(kw::Async) && this.is_gen_block(kw::Gen, 1))) + { + // FIXME: (async) gen closures aren't yet parsed. + this.parse_gen_block() + } else if this.check_keyword(kw::Async) { + // FIXME(gen_blocks): Parse `gen async` and suggest swap + if this.is_gen_block(kw::Async, 0) { + // Check for `async {` and `async move {`, this.parse_gen_block() } else { this.parse_expr_closure() } - } else if this.eat_keyword(kw::Await) { + } else if this.eat_keyword_noexpect(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() } @@ -1689,8 +1693,7 @@ impl<'a> Parser<'a> { mk_lit_char: impl FnOnce(Symbol, Span) -> L, err: impl FnOnce(&Self) -> DiagnosticBuilder<'a, ErrorGuaranteed>, ) -> L { - if let Some(mut diag) = - self.sess.span_diagnostic.steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar) + if let Some(mut diag) = self.dcx().steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar) { diag.span_suggestion_verbose( lifetime.span.shrink_to_hi(), @@ -1880,8 +1883,8 @@ impl<'a> Parser<'a> { self.bump(); // `#` let Some((ident, false)) = self.token.ident() else { - let err = errors::ExpectedBuiltinIdent { span: self.token.span } - .into_diagnostic(&self.sess.span_diagnostic); + let err = + errors::ExpectedBuiltinIdent { span: self.token.span }.into_diagnostic(self.dcx()); return Err(err); }; self.sess.gated_spans.gate(sym::builtin_syntax, ident.span); @@ -1892,7 +1895,7 @@ impl<'a> Parser<'a> { Ok(res) } else { let err = errors::UnknownBuiltinConstruct { span: lo.to(ident.span), name: ident.name } - .into_diagnostic(&self.sess.span_diagnostic); + .into_diagnostic(self.dcx()); return Err(err); }; self.expect(&TokenKind::CloseDelim(Delimiter::Parenthesis))?; @@ -1952,11 +1955,11 @@ 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 } - .into_diagnostic(&self.sess.span_diagnostic); + .into_diagnostic(self.dcx()); err.downgrade_to_delayed_bug(); return Err(err); } @@ -2052,7 +2055,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); @@ -2168,7 +2171,7 @@ impl<'a> Parser<'a> { return Err(errors::MissingSemicolonBeforeArray { open_delim: open_delim_span, semicolon: prev_span.shrink_to_hi(), - }.into_diagnostic(&self.sess.span_diagnostic)); + }.into_diagnostic(self.dcx())); } Ok(_) => (), Err(err) => err.cancel(), @@ -2233,10 +2236,10 @@ impl<'a> Parser<'a> { let movability = if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable }; - let asyncness = if self.token.uninterpolated_span().at_least_rust_2018() { - self.parse_asyncness(Case::Sensitive) + let coroutine_kind = if self.token.uninterpolated_span().at_least_rust_2018() { + self.parse_coroutine_kind(Case::Sensitive) } else { - Async::No + None }; let capture_clause = self.parse_capture_clause()?; @@ -2260,13 +2263,21 @@ impl<'a> Parser<'a> { } }; - if let Async::Yes { span, .. } = asyncness { - // Feature-gate `async ||` closures. - self.sess.gated_spans.gate(sym::async_closure, span); + match coroutine_kind { + Some(CoroutineKind::Async { span, .. }) => { + // Feature-gate `async ||` closures. + self.sess.gated_spans.gate(sym::async_closure, span); + } + Some(CoroutineKind::Gen { span, .. }) | Some(CoroutineKind::AsyncGen { span, .. }) => { + // Feature-gate `gen ||` and `async gen ||` closures. + // FIXME(gen_blocks): This perhaps should be a different gate. + self.sess.gated_spans.gate(sym::gen_blocks, span); + } + None => {} } if self.token.kind == TokenKind::Semi - && matches!(self.token_cursor.stack.last(), Some((_, Delimiter::Parenthesis, _))) + && matches!(self.token_cursor.stack.last(), Some((.., Delimiter::Parenthesis))) && self.may_recover() { // It is likely that the closure body is a block but where the @@ -2283,7 +2294,7 @@ impl<'a> Parser<'a> { binder, capture_clause, constness, - asyncness, + coroutine_kind, movability, fn_decl, body, @@ -2308,7 +2319,7 @@ impl<'a> Parser<'a> { 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)) + .into_diagnostic(self.dcx())) } else { Ok(CaptureBy::Value { move_kw: move_kw_span }) } @@ -2477,7 +2488,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. @@ -2493,10 +2504,12 @@ 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. - return Err(err.into_diagnostic(&self.sess.span_diagnostic)); + return Err(err.into_diagnostic(self.dcx())); } else { Some(self.sess.emit_err(err)) } @@ -2606,30 +2619,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)) @@ -2850,167 +2905,167 @@ 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) + let (pat, guard) = this.parse_match_arm_pat_and_guard()?; + + let span_before_body = this.prev_token.span; + let arm_body; + let is_fat_arrow = this.check(&token::FatArrow); + let is_almost_fat_arrow = TokenKind::FatArrow + .similar_tokens() + .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind)); + let mut result = if !is_fat_arrow && !is_almost_fat_arrow { + // A pattern without a body, allowed for never patterns. + arm_body = None; + this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]).map( + |x| { + // Don't gate twice + if !pat.contains_never_pattern() { + this.sess.gated_spans.gate(sym::never_patterns, pat.span); + } + x + }, + ) } else { - None - }; - let arrow_span = this.token.span; - if let Err(mut err) = this.expect(&token::FatArrow) { - // We might have a `=>` -> `=` or `->` typo (issue #89396). - if TokenKind::FatArrow - .similar_tokens() - .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind)) - { - err.span_suggestion( - this.token.span, - "use a fat arrow to start a match arm", - "=>", - 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(); - this.bump(); - } else { - return Err(err); + if let Err(mut err) = this.expect(&token::FatArrow) { + // We might have a `=>` -> `=` or `->` typo (issue #89396). + if is_almost_fat_arrow { + err.span_suggestion( + this.token.span, + "use a fat arrow to start a match arm", + "=>", + Applicability::MachineApplicable, + ); + 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); + } } - } - let arm_start_span = this.token.span; + let arrow_span = this.prev_token.span; + let arm_start_span = this.token.span; - let expr = this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| { - err.span_label(arrow_span, "while parsing the `match` arm starting here"); - err - })?; + let expr = + this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| { + err.span_label(arrow_span, "while parsing the `match` arm starting here"); + err + })?; - let require_comma = classify::expr_requires_semi_to_be_stmt(&expr) - && this.token != token::CloseDelim(Delimiter::Brace); - - let hi = this.prev_token.span; - - if require_comma { - let sm = this.sess.source_map(); - if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) { - let span = body.span; - return Ok(( - ast::Arm { - attrs, - pat, - guard, - body, - span, - id: DUMMY_NODE_ID, - is_placeholder: false, - }, - TrailingToken::None, - )); - } - this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]) - .or_else(|mut err| { - if this.token == token::FatArrow { - if let Ok(expr_lines) = sm.span_to_lines(expr.span) - && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span) - && arm_start_lines.lines[0].end_col == expr_lines.lines[0].end_col - && expr_lines.lines.len() == 2 - { - // We check whether there's any trailing code in the parse span, - // if there isn't, we very likely have the following: - // - // X | &Y => "y" - // | -- - missing comma - // | | - // | arrow_span - // X | &X => "x" - // | - ^^ self.token.span - // | | - // | parsed until here as `"y" & X` - err.span_suggestion_short( - arm_start_span.shrink_to_hi(), - "missing a comma here to end this `match` arm", - ",", - Applicability::MachineApplicable, + let require_comma = classify::expr_requires_semi_to_be_stmt(&expr) + && this.token != token::CloseDelim(Delimiter::Brace); + + if !require_comma { + arm_body = Some(expr); + this.eat(&token::Comma); + Ok(false) + } else if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) { + arm_body = Some(body); + Ok(true) + } else { + let expr_span = expr.span; + arm_body = Some(expr); + this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]) + .map_err(|mut err| { + if this.token == token::FatArrow { + let sm = this.sess.source_map(); + if let Ok(expr_lines) = sm.span_to_lines(expr_span) + && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span) + && arm_start_lines.lines[0].end_col + == expr_lines.lines[0].end_col + && expr_lines.lines.len() == 2 + { + // We check whether there's any trailing code in the parse span, + // if there isn't, we very likely have the following: + // + // X | &Y => "y" + // | -- - missing comma + // | | + // | arrow_span + // X | &X => "x" + // | - ^^ self.token.span + // | | + // | parsed until here as `"y" & X` + err.span_suggestion_short( + arm_start_span.shrink_to_hi(), + "missing a comma here to end this `match` arm", + ",", + Applicability::MachineApplicable, + ); + } + } else { + err.span_label( + arrow_span, + "while parsing the `match` arm starting here", ); - return Err(err); - } - } else { - // FIXME(compiler-errors): We could also recover `; PAT =>` here - - // Try to parse a following `PAT =>`, if successful - // then we should recover. - let mut snapshot = this.create_snapshot_for_diagnostic(); - let pattern_follows = snapshot - .parse_pat_allow_top_alt( - None, - RecoverComma::Yes, - RecoverColon::Yes, - CommaRecoveryMode::EitherTupleOrPipe, - ) - .map_err(|err| err.cancel()) - .is_ok(); - if pattern_follows && snapshot.check(&TokenKind::FatArrow) { - err.cancel(); - this.sess.emit_err(errors::MissingCommaAfterMatchArm { - span: hi.shrink_to_hi(), - }); - return Ok(true); } - } - err.span_label(arrow_span, "while parsing the `match` arm starting here"); - Err(err) - })?; - } else { - this.eat(&token::Comma); + err + }) + } + }; + + let hi_span = arm_body.as_ref().map_or(span_before_body, |body| body.span); + let arm_span = lo.to(hi_span); + + // We want to recover: + // X | Some(_) => foo() + // | - missing comma + // X | None => "x" + // | ^^^^ self.token.span + // as well as: + // X | Some(!) + // | - missing comma + // X | None => "x" + // | ^^^^ self.token.span + // But we musn't recover + // X | pat[0] => {} + // | ^ self.token.span + let recover_missing_comma = arm_body.is_some() || pat.could_be_never_pattern(); + if recover_missing_comma { + result = result.or_else(|err| { + // FIXME(compiler-errors): We could also recover `; PAT =>` here + + // Try to parse a following `PAT =>`, if successful + // then we should recover. + let mut snapshot = this.create_snapshot_for_diagnostic(); + let pattern_follows = snapshot + .parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::EitherTupleOrPipe, + ) + .map_err(|err| err.cancel()) + .is_ok(); + if pattern_follows && snapshot.check(&TokenKind::FatArrow) { + err.cancel(); + this.sess.emit_err(errors::MissingCommaAfterMatchArm { + span: arm_span.shrink_to_hi(), + }); + return Ok(true); + } + Err(err) + }); } + result?; Ok(( ast::Arm { attrs, pat, guard, - body: expr, - span: lo.to(hi), + body: arm_body, + span: arm_span, id: DUMMY_NODE_ID, is_placeholder: false, }, @@ -3019,6 +3074,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| { @@ -3054,8 +3193,7 @@ impl<'a> Parser<'a> { fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> { let (attrs, body) = self.parse_inner_attrs_and_block()?; if self.eat_keyword(kw::Catch) { - Err(errors::CatchAfterTry { span: self.prev_token.span } - .into_diagnostic(&self.sess.span_diagnostic)) + Err(errors::CatchAfterTry { span: self.prev_token.span }.into_diagnostic(self.dcx())) } else { let span = span_lo.to(body.span); self.sess.gated_spans.gate(sym::try_blocks, span); @@ -3086,34 +3224,45 @@ impl<'a> Parser<'a> { fn parse_gen_block(&mut self) -> PResult<'a, P<Expr>> { let lo = self.token.span; let kind = if self.eat_keyword(kw::Async) { - GenBlockKind::Async + if self.eat_keyword(kw::Gen) { GenBlockKind::AsyncGen } else { GenBlockKind::Async } } else { assert!(self.eat_keyword(kw::Gen)); - self.sess.gated_spans.gate(sym::gen_blocks, lo.to(self.token.span)); GenBlockKind::Gen }; + match kind { + GenBlockKind::Async => { + // `async` blocks are stable + } + GenBlockKind::Gen | GenBlockKind::AsyncGen => { + self.sess.gated_spans.gate(sym::gen_blocks, lo.to(self.prev_token.span)); + } + } let capture_clause = self.parse_capture_clause()?; let (attrs, body) = self.parse_inner_attrs_and_block()?; let kind = ExprKind::Gen(capture_clause, body, kind); Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs)) } - fn is_gen_block(&self, kw: Symbol) -> bool { - self.token.is_keyword(kw) + fn is_gen_block(&self, kw: Symbol, lookahead: usize) -> bool { + self.is_keyword_ahead(lookahead, &[kw]) && (( // `async move {` - self.is_keyword_ahead(1, &[kw::Move]) - && self.look_ahead(2, |t| { + self.is_keyword_ahead(lookahead + 1, &[kw::Move]) + && self.look_ahead(lookahead + 2, |t| { *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block() }) ) || ( // `async {` - self.look_ahead(1, |t| { + self.look_ahead(lookahead + 1, |t| { *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block() }) )) } + pub(super) fn is_async_gen_block(&self) -> bool { + self.token.is_keyword(kw::Async) && self.is_gen_block(kw::Gen, 1) + } + fn is_certainly_not_a_block(&self) -> bool { self.look_ahead(1, |t| t.is_ident()) && ( @@ -3206,7 +3355,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 }) @@ -3386,7 +3535,7 @@ impl<'a> Parser<'a> { ident_span: this.token.span, token: this.look_ahead(1, |t| t.clone()), } - .into_diagnostic(&self.sess.span_diagnostic)); + .into_diagnostic(&self.sess.dcx)); } let (ident, expr) = if is_shorthand { // Mimic `x: x` for the `x` field shorthand. @@ -3551,6 +3700,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<'_> { @@ -3561,11 +3718,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); } @@ -3589,9 +3748,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(_) |