use rustc_ast::util::literal::LitError;
use rustc_ast::util::parser::{prec_let_scrutinee_needs_par, AssocOp, Fixity};
use rustc_ast::visit::Visitor;
-use rustc_ast::StmtKind;
use rustc_ast::{self as ast, AttrStyle, AttrVec, CaptureBy, ExprField, Lit, 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::{ClosureBinder, StmtKind};
use rustc_ast_pretty::pprust;
use rustc_data_structures::thin_vec::ThinVec;
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, PResult};
cast_expr: P<Expr>,
) -> PResult<'a, P<Expr>> {
let span = cast_expr.span;
- let maybe_ascription_span = if let ExprKind::Type(ascripted_expr, _) = &cast_expr.kind {
- Some(ascripted_expr.span.shrink_to_hi().with_hi(span.hi()))
- } else {
- None
- };
+ let (cast_kind, maybe_ascription_span) =
+ if let ExprKind::Type(ascripted_expr, _) = &cast_expr.kind {
+ ("type ascription", Some(ascripted_expr.span.shrink_to_hi().with_hi(span.hi())))
+ } else {
+ ("cast", None)
+ };
// Save the memory location of expr before parsing any following postfix operators.
// This will be compared with the memory location of the output expression.
// If the resulting expression is not a cast, or has a different memory location, it is an illegal postfix operator.
if !matches!(with_postfix.kind, ExprKind::Cast(_, _) | ExprKind::Type(_, _)) || changed {
let msg = format!(
- "casts cannot be followed by {}",
+ "{cast_kind} cannot be followed by {}",
match with_postfix.kind {
ExprKind::Index(_, _) => "indexing",
ExprKind::Try(_) => "`?`",
self.parse_if_expr(attrs)
} else if self.check_keyword(kw::For) {
if self.choose_generics_over_qpath(1) {
- // NOTE(Centril, eddyb): DO NOT REMOVE! Beyond providing parser recovery,
- // this is an insurance policy in case we allow qpaths in (tuple-)struct patterns.
- // When `for <Foo as Bar>::Proj in $expr $block` is wanted,
- // you can disambiguate in favor of a pattern with `(...)`.
- self.recover_quantified_closure_expr(attrs)
+ self.parse_closure_expr(attrs)
} else {
assert!(self.eat_keyword(kw::For));
self.parse_for_expr(None, self.prev_token.span, attrs)
} else if self.is_do_yeet() {
self.parse_yeet_expr(attrs)
} else if self.check_keyword(kw::Let) {
- self.manage_let_chains_context();
- self.bump();
self.parse_let_expr(attrs)
} else if self.eat_keyword(kw::Underscore) {
Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs))
Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new()))
}
- /// Recover on an explicitly quantified closure expression, e.g., `for<'a> |x: &'a u8| *x + 1`.
- fn recover_quantified_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+ /// Parses a closure expression (e.g., `move |args| expr`).
+ fn parse_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
let lo = self.token.span;
- let _ = self.parse_late_bound_lifetime_defs()?;
- let span_for = lo.to(self.prev_token.span);
- let closure = self.parse_closure_expr(attrs)?;
- self.struct_span_err(span_for, "cannot introduce explicit parameters for a closure")
- .span_label(closure.span, "the parameters are attached to this closure")
- .span_suggestion(
- span_for,
- "remove the parameters",
- "",
- Applicability::MachineApplicable,
- )
- .emit();
+ let binder = if self.check_keyword(kw::For) {
+ let lo = self.token.span;
+ let lifetime_defs = self.parse_late_bound_lifetime_defs()?;
+ let span = lo.to(self.prev_token.span);
- Ok(self.mk_expr_err(lo.to(closure.span)))
- }
+ self.sess.gated_spans.gate(sym::closure_lifetime_binder, span);
- /// Parses a closure expression (e.g., `move |args| expr`).
- fn parse_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
- let lo = self.token.span;
+ ClosureBinder::For { span, generic_params: P::from_vec(lifetime_defs) }
+ } else {
+ ClosureBinder::NotPresent
+ };
let movability =
if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
let closure = self.mk_expr(
lo.to(body.span),
- ExprKind::Closure(capture_clause, asyncness, movability, decl, body, lo.to(decl_hi)),
+ ExprKind::Closure(
+ binder,
+ capture_clause,
+ asyncness,
+ movability,
+ decl,
+ body,
+ lo.to(decl_hi),
+ ),
attrs,
);
/// Parses the condition of a `if` or `while` expression.
fn parse_cond_expr(&mut self) -> PResult<'a, P<Expr>> {
- 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.
- self.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
- }
+ self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, None)
+ }
- Ok(cond)
- }
-
- // Checks if `let` is in an invalid position like `let x = let y = 1;` or
- // if the current `let` is in a let_chains context but nested in another
- // expression like `if let Some(_) = _opt && [1, 2, 3][let _ = ()] = 1`.
- //
- // This method expects that the current token is `let`.
- fn manage_let_chains_context(&mut self) {
- debug_assert!(matches!(self.token.kind, TokenKind::Ident(kw::Let, _)));
- let is_in_a_let_chains_context_but_nested_in_other_expr = self.let_expr_allowed
- && !matches!(
- self.prev_token.kind,
- TokenKind::AndAnd
- | TokenKind::CloseDelim(Delimiter::Brace)
- | TokenKind::Ident(kw::If, _)
- | TokenKind::Ident(kw::While, _)
- );
- if !self.let_expr_allowed || is_in_a_let_chains_context_but_nested_in_other_expr {
+ /// Parses a `let $pat = $expr` pseudo-expression.
+ fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+ // This is a *approximate* heuristic that detects if `let` chains are
+ // being parsed in the right position. It's approximate because it
+ // doesn't deny all invalid `let` expressions, just completely wrong usages.
+ let not_in_chain = !matches!(
+ self.prev_token.kind,
+ TokenKind::AndAnd | TokenKind::Ident(kw::If, _) | TokenKind::Ident(kw::While, _)
+ );
+ if !self.restrictions.contains(Restrictions::ALLOW_LET) || not_in_chain {
self.struct_span_err(self.token.span, "expected expression, found `let` statement")
.emit();
}
- }
- /// 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>> {
+ self.bump(); // Eat `let` token
let lo = self.prev_token.span;
let pat = self.parse_pat_allow_top_alt(
None,
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
})?;
let span = lo.to(expr.span);
- self.sess.gated_spans.gate(sym::let_chains, span);
Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span), attrs))
}
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) {
+ fn check_let_expr(expr: &Expr) -> bool {
match expr.kind {
- ExprKind::Binary(_, ref lhs, ref rhs) => {
- let lhs_rslt = check_let_expr(lhs);
- let rhs_rslt = check_let_expr(rhs);
- (lhs_rslt.0 || rhs_rslt.0, false)
+ ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, ref lhs, ref rhs) => {
+ check_let_expr(lhs) || check_let_expr(rhs)
}
- ExprKind::Let(..) => (true, true),
- _ => (false, true),
+ ExprKind::Let(..) => true,
+ _ => false,
}
}
let attrs = self.parse_outer_attributes()?;
)?;
let guard = if this.eat_keyword(kw::If) {
let if_span = this.prev_token.span;
- 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 {
- // 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 cond = this.parse_expr_res(Restrictions::ALLOW_LET, None)?;
+ if check_let_expr(&cond) {
let span = if_span.to(cond.span);
this.sess.gated_spans.gate(sym::if_let_guard, span);
}
}
};
+ let is_shorthand = parsed_field.as_ref().map_or(false, |f| f.is_shorthand);
+ // A shorthand field can be turned into a full field with `:`.
+ // We should point this out.
+ self.check_or_expected(!is_shorthand, TokenType::Token(token::Colon));
+
match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
Ok(_) => {
if let Some(f) = parsed_field.or(recovery_field) {
",",
Applicability::MachineApplicable,
);
+ } else if is_shorthand
+ && (AssocOp::from_token(&self.token).is_some()
+ || matches!(&self.token.kind, token::OpenDelim(_))
+ || self.token.kind == token::Dot)
+ {
+ // Looks like they tried to write a shorthand, complex expression.
+ let ident = parsed_field.expect("is_shorthand implies Some").ident;
+ e.span_suggestion(
+ ident.span.shrink_to_lo(),
+ "try naming a field",
+ &format!("{ident}: "),
+ Applicability::HasPlaceholders,
+ );
}
}
if !recover {
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
- }
}