summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_parse/src/parser/expr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_parse/src/parser/expr.rs')
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs614
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(_)