
Stabilize let chains in the 2024 edition # Stabilization report This proposes the stabilization of `let_chains` ([tracking issue], [RFC 2497]) in the [2024 edition] of Rust. [tracking issue]: https://github.com/rust-lang/rust/issues/53667 [RFC 2497]: https://github.com/rust-lang/rfcs/pull/2497 [2024 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/index.html ## What is being stabilized The ability to `&&`-chain `let` statements inside `if` and `while` is being stabilized, allowing intermixture with boolean expressions. The patterns inside the `let` sub-expressions can be irrefutable or refutable. ```Rust struct FnCall<'a> { fn_name: &'a str, args: Vec<i32>, } fn is_legal_ident(s: &str) -> bool { s.chars() .all(|c| ('a'..='z').contains(&c) || ('A'..='Z').contains(&c)) } impl<'a> FnCall<'a> { fn parse(s: &'a str) -> Option<Self> { if let Some((fn_name, after_name)) = s.split_once("(") && !fn_name.is_empty() && is_legal_ident(fn_name) && let Some((args_str, "")) = after_name.rsplit_once(")") { let args = args_str .split(',') .map(|arg| arg.parse()) .collect::<Result<Vec<_>, _>>(); args.ok().map(|args| FnCall { fn_name, args }) } else { None } } fn exec(&self) -> Option<i32> { let iter = self.args.iter().copied(); match self.fn_name { "sum" => Some(iter.sum()), "max" => iter.max(), "min" => iter.min(), _ => None, } } } fn main() { println!("{:?}", FnCall::parse("sum(1,2,3)").unwrap().exec()); println!("{:?}", FnCall::parse("max(4,5)").unwrap().exec()); } ``` The feature will only be stabilized for the 2024 edition and future editions. Users of past editions will get an error with a hint to update the edition. closes #53667 ## Why 2024 edition? Rust generally tries to ship new features to all editions. So even the oldest editions receive the newest features. However, sometimes a feature requires a breaking change so much that offering the feature without the breaking change makes no sense. This occurs rarely, but has happened in the 2018 edition already with `async` and `await` syntax. It required an edition boundary in order for `async`/`await` to become keywords, and the entire feature foots on those keywords. In the instance of let chains, the issue is the drop order of `if let` chains. If we want `if let` chains to be compatible with `if let`, drop order makes it hard for us to [generate correct MIR]. It would be strange to have different behaviour for `if let ... {}` and `if true && let ... {}`. So it's better to [stay consistent with `if let`]. In edition 2024, [drop order changes] have been introduced to make `if let` temporaries be lived more shortly. These changes also affected `if let` chains. These changes make sense even if you don't take the `if let` chains MIR generation problem into account. But if we want to use them as the solution to the MIR generation problem, we need to restrict let chains to edition 2024 and beyond: for let chains, it's not just a change towards more sensible behaviour, but one required for correct function. [generate correct MIR]: https://github.com/rust-lang/rust/issues/104843 [stay consistent with `if let`]: https://github.com/rust-lang/rust/pull/103293#issuecomment-1293408574 [drop order changes]: https://github.com/rust-lang/rust/issues/124085 ## Introduction considerations As edition 2024 is very new, this stabilization PR only makes it possible to use let chains on 2024 without that feature gate, it doesn't mark that feature gate as stable/removed. I would propose to continue offering the `let_chains` feature (behind a feature gate) for a limited time (maybe 3 months after stabilization?) on older editions to allow nightly users to adopt edition 2024 at their own pace. After that, the feature gate shall be marked as *stabilized*, not removed, and replaced by an error on editions 2021 and below. ## Implementation history * History from before March 14, 2022 can be found in the [original stabilization PR] that was reverted. * https://github.com/rust-lang/rust/pull/94927 * https://github.com/rust-lang/rust/pull/94951 * https://github.com/rust-lang/rust/pull/94974 * https://github.com/rust-lang/rust/pull/95008 * https://github.com/rust-lang/rust/pull/97295 * https://github.com/rust-lang/rust/pull/98633 * https://github.com/rust-lang/rust/pull/99731 * https://github.com/rust-lang/rust/pull/102394 * https://github.com/rust-lang/rust/pull/100526 * https://github.com/rust-lang/rust/pull/100538 * https://github.com/rust-lang/rust/pull/102998 * https://github.com/rust-lang/rust/pull/103405 * https://github.com/rust-lang/rust/pull/103293 * https://github.com/rust-lang/rust/pull/107251 * https://github.com/rust-lang/rust/pull/110568 * https://github.com/rust-lang/rust/pull/115677 * https://github.com/rust-lang/rust/pull/117743 * https://github.com/rust-lang/rust/pull/117770 * https://github.com/rust-lang/rust/pull/118191 * https://github.com/rust-lang/rust/pull/119554 * https://github.com/rust-lang/rust/pull/129394 * https://github.com/rust-lang/rust/pull/132828 * https://github.com/rust-lang/reference/pull/1179 * https://github.com/rust-lang/reference/pull/1251 * https://github.com/rust-lang/rustfmt/pull/5910 [original stabilization PR]: https://github.com/rust-lang/rust/pull/94927 ## Adoption history ### In the compiler * History before March 14, 2022 can be found in the [original stabilization PR]. * https://github.com/rust-lang/rust/pull/115983 * https://github.com/rust-lang/rust/pull/116549 * https://github.com/rust-lang/rust/pull/116688 ### Outside of the compiler * https://github.com/rust-lang/rust-clippy/pull/11750 * [rspack](https://github.com/web-infra-dev/rspack) * [risingwave](https://github.com/risingwavelabs/risingwave) * [dylint](https://github.com/trailofbits/dylint) * [convex-backend](https://github.com/get-convex/convex-backend) * [tikv](https://github.com/tikv/tikv) * [Daft](https://github.com/Eventual-Inc/Daft) * [greptimedb](https://github.com/GreptimeTeam/greptimedb) ## Tests <details> ### Intentional restrictions [`partially-macro-expanded.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/partially-macro-expanded.rs
), [`macro-expanded.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/macro-expanded.rs
): it is possible to use macros to expand to both the pattern and the expression inside a let chain, but not to the entire `let pat = expr` operand. [`parens.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/parens.rs
): `if (let pat = expr)` is not allowed in chains [`ensure-that-let-else-does-not-interact-with-let-chains.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/ensure-that-let-else-does-not-interact-with-let-chains.rs
): `let...else` doesn't support chaining. ### Overlap with match guards [`move-guard-if-let-chain.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs
): test for the `use moved value` error working well in match guards. could maybe be extended with let chains that have more than one `let` [`shadowing.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/shadowing.rs
): shadowing in if let guards works as expected [`ast-validate-guards.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/ast-validate-guards.rs
): let chains in match guards require the match guards feature gate ### Simple cases from the early days PR #88642 has added some tests with very simple usages of `let else`, mostly as regression tests to early bugs. [`then-else-blocks.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/then-else-blocks.rs
) [`ast-lowering-does-not-wrap-let-chains.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/ast-lowering-does-not-wrap-let-chains.rs
) [`issue-90722.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/issue-90722.rs
) [`issue-92145.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/issue-92145.rs
) ### Drop order/MIR scoping tests [`issue-100276.rs`](4adafcf40a/tests/ui/drop/issue-100276.rs
): let expressions on RHS aren't terminating scopes [`drop_order.rs`](4adafcf40a/tests/ui/drop/drop_order.rs
): exhaustive temporary drop order test for various Rust constructs, including let chains [`scope.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/scope.rs
): match guard scoping test [`drop-scope.rs`](4adafcf40a/tests/ui/rfcs/rfc-2294-if-let-guard/drop-scope.rs
): another match guard scoping test, ensuring that temporaries in if-let guards live for the arm [`drop_order_if_let_rescope.rs`](4adafcf40a/tests/ui/drop/drop_order_if_let_rescope.rs
): if let rescoping on edition 2024, including chains [`mir_let_chains_drop_order.rs`](4adafcf40a/tests/ui/mir/mir_let_chains_drop_order.rs
): comprehensive drop order test for let chains, distinguishes editions 2021 and 2024. [`issue-99938.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/issue-99938.rs
), [`issue-99852.rs`](4adafcf40a/tests/ui/mir/issue-99852.rs
) both bad MIR ICEs fixed by #102394 ### Linting [`irrefutable-lets.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/irrefutable-lets.rs
): trailing and leading irrefutable let patterns get linted for, others don't. The lint is turned off for `else if`. [`issue-121070-let-range.rs`](4adafcf40a/tests/ui/lint/issue-121070-let-range.rs
): regression test for false positive of the unused parens lint, precedence requires the `()`s here ### Parser: intentional restrictions [`disallowed-positions.rs`](2128d8df0e/tests/ui/rfcs/rfc-2497-if-let-chains/disallowed-positions.rs
): `let` in expression context is rejected everywhere except at the top level [`invalid-let-in-a-valid-let-context.rs`](4adafcf40a/tests/ui/rfcs/rfc-2497-if-let-chains/invalid-let-in-a-valid-let-context.rs
): nested `let` is not allowed (let's are no legal expressions just because they are allowed in `if` and `while`). ### Parser: recovery [`issue-103381.rs`](4adafcf40a/tests/ui/parser/issues/issue-103381.rs
): Graceful recovery of incorrect chaining of `if` and `if let` [`semi-in-let-chain.rs`](4adafcf40a/tests/ui/parser/semi-in-let-chain.rs
): Ensure that stray `;`s in let chains give nice errors (`if_chain!` users might be accustomed to `;`s) [`deli-ident-issue-1.rs`](4adafcf40a/tests/ui/parser/deli-ident-issue-1.rs
), [`brace-in-let-chain.rs`](4adafcf40a/tests/ui/parser/brace-in-let-chain.rs
): Ensure that stray unclosed `{`s in let chains give nice errors and hints ### Misc [`conflicting_bindings.rs`](4adafcf40a/tests/ui/pattern/usefulness/conflicting_bindings.rs
): the conflicting bindings check also works in let chains. Personally, I'd extend it to chains with multiple let's as well. [`let-chains-attr.rs`](4adafcf40a/tests/ui/expr/if/attrs/let-chains-attr.rs
): attributes work on let chains ### Tangential tests with `#![feature(let_chains)]` [`if-let.rs`](4adafcf40a/tests/coverage/branch/if-let.rs
): MC/DC coverage tests for let chains [`logical_or_in_conditional.rs`](4adafcf40a/tests/mir-opt/building/logical_or_in_conditional.rs
): not really about let chains, more about dropping/scoping behaviour of `||` [`stringify.rs`](4adafcf40a/tests/ui/macros/stringify.rs
): exhaustive test of the `stringify` macro [`expanded-interpolation.rs`](4adafcf40a/tests/ui/unpretty/expanded-interpolation.rs
), [`expanded-exhaustive.rs`](4adafcf40a/tests/ui/unpretty/expanded-exhaustive.rs
): Exhaustive test of `-Zunpretty` [`diverges-not.rs`](4adafcf40a/tests/ui/rfcs/rfc-0000-never_patterns/diverges-not.rs
): Never type, mostly tangential to let chains </details> ## Possible future work * There is proposals to allow `if let Pat(bindings) = expr {}` to be written as `if expr is Pat(bindings) {}` ([RFC 3573]). `if let` chains are a natural extension of the already existing `if let` syntax, and I'd argue orthogonal towards `is` syntax. * https://github.com/rust-lang/lang-team/issues/297 * One could have similar chaining inside `let ... else` statements. There is no proposed RFC for this however, nor is it implemented on nightly. * Match guards have the `if` keyword as well, but on stable Rust, they don't support `let`. The functionality is available via an unstable feature ([`if_let_guard` tracking issue]). Stabilization of let chains affects this feature in so far as match guards containing let chains now only need the `if_let_guard` feature gate be present instead of also the `let_chains` feature (NOTE: this PR doesn't implement this simplification, it's left for future work). [RFC 3573]: https://github.com/rust-lang/rfcs/pull/3573 [`if_let_guard` tracking issue]: https://github.com/rust-lang/rust/issues/51114 ## Open questions / blockers - [ ] bad recovery if you don't put a `let` (I don't think this is a blocker): [#117977](https://github.com/rust-lang/rust/issues/117977) - [x] An instance where a temporary lives shorter than with nested ifs, breaking compilation: [#103476](https://github.com/rust-lang/rust/issues/103476). Personally I don't think this is a blocker either, as it's an edge case. Edit: turns out to not reproduce in edition 2025 any more, due to let rescoping. regression test added in #133093 - [x] One should probably extend the tests for `move-guard-if-let-chain.rs` and `conflicting_bindings.rs` to have chains with multiple let's: done in 133093 - [x] Parsing rejection tests: addressed by https://github.com/rust-lang/rust/pull/132828 - [x] [Style](https://rust-lang.zulipchat.com/#narrow/channel/346005-t-style/topic/let.20chains.20stabilization.20and.20formatting): https://github.com/rust-lang/rust/pull/139456 - [x] https://github.com/rust-lang/rust/issues/86730 explicitly mentions `let_else`. I think we can live with `let pat = expr` not evaluating as `expr` for macro_rules macros, especially given that `let pat = expr` is not a legal expression anywhere except inside `if` and `while`. - [x] Documentation in the reference: https://github.com/rust-lang/reference/pull/1740 - [x] Add chapter to the Rust 2024 [edition guide]: https://github.com/rust-lang/edition-guide/pull/337 - [x] Resolve open questions on desired drop order. [original reference PR]: https://github.com/rust-lang/reference/pull/1179 [edition guide]: https://github.com/rust-lang/edition-guide
4197 lines
175 KiB
Rust
4197 lines
175 KiB
Rust
// ignore-tidy-filelength
|
|
|
|
use core::mem;
|
|
use core::ops::{Bound, ControlFlow};
|
|
|
|
use ast::mut_visit::{self, MutVisitor};
|
|
use ast::token::IdentIsRaw;
|
|
use ast::{CoroutineKind, ForLoopKind, GenBlockKind, MatchKind, Pat, Path, PathSegment, Recovered};
|
|
use rustc_ast::ptr::P;
|
|
use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, Token, TokenKind};
|
|
use rustc_ast::tokenstream::TokenTree;
|
|
use rustc_ast::util::case::Case;
|
|
use rustc_ast::util::classify;
|
|
use rustc_ast::util::parser::{AssocOp, ExprPrecedence, Fixity, prec_let_scrutinee_needs_par};
|
|
use rustc_ast::visit::{Visitor, walk_expr};
|
|
use rustc_ast::{
|
|
self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind,
|
|
BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl,
|
|
FnRetTy, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind,
|
|
UnOp, UnsafeBinderCastKind, YieldKind,
|
|
};
|
|
use rustc_data_structures::stack::ensure_sufficient_stack;
|
|
use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic};
|
|
use rustc_literal_escaper::unescape_char;
|
|
use rustc_macros::Subdiagnostic;
|
|
use rustc_session::errors::{ExprParenthesesNeeded, report_lit_error};
|
|
use rustc_session::lint::BuiltinLintDiag;
|
|
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
|
|
use rustc_span::edition::Edition;
|
|
use rustc_span::source_map::{self, Spanned};
|
|
use rustc_span::{BytePos, ErrorGuaranteed, Ident, Pos, Span, Symbol, kw, sym};
|
|
use thin_vec::{ThinVec, thin_vec};
|
|
use tracing::instrument;
|
|
|
|
use super::diagnostics::SnapshotParser;
|
|
use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
|
|
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
|
|
use super::{
|
|
AttrWrapper, BlockMode, ClosureSpans, ExpTokenPair, ForceCollect, Parser, PathStyle,
|
|
Restrictions, SemiColonMode, SeqSep, TokenType, Trailing, UsePreAttrPos,
|
|
};
|
|
use crate::{errors, exp, maybe_recover_from_interpolated_ty_qpath};
|
|
|
|
#[derive(Debug)]
|
|
pub(super) enum DestructuredFloat {
|
|
/// 1e2
|
|
Single(Symbol, Span),
|
|
/// 1.
|
|
TrailingDot(Symbol, Span, Span),
|
|
/// 1.2 | 1.2e3
|
|
MiddleDot(Symbol, Span, Span, Symbol, Span),
|
|
/// Invalid
|
|
Error,
|
|
}
|
|
|
|
impl<'a> Parser<'a> {
|
|
/// Parses an expression.
|
|
#[inline]
|
|
pub fn parse_expr(&mut self) -> PResult<'a, P<Expr>> {
|
|
self.current_closure.take();
|
|
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.parse_expr_res(Restrictions::empty(), attrs).map(|res| res.0)
|
|
}
|
|
|
|
/// Parses an expression, forcing tokens to be collected.
|
|
pub fn parse_expr_force_collect(&mut self) -> PResult<'a, P<Expr>> {
|
|
self.current_closure.take();
|
|
|
|
// If the expression is associative (e.g. `1 + 2`), then any preceding
|
|
// outer attribute actually belongs to the first inner sub-expression.
|
|
// In which case we must use the pre-attr pos to include the attribute
|
|
// in the collected tokens for the outer expression.
|
|
let pre_attr_pos = self.collect_pos();
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.collect_tokens(
|
|
Some(pre_attr_pos),
|
|
AttrWrapper::empty(),
|
|
ForceCollect::Yes,
|
|
|this, _empty_attrs| {
|
|
let (expr, is_assoc) = this.parse_expr_res(Restrictions::empty(), attrs)?;
|
|
let use_pre_attr_pos =
|
|
if is_assoc { UsePreAttrPos::Yes } else { UsePreAttrPos::No };
|
|
Ok((expr, Trailing::No, use_pre_attr_pos))
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn parse_expr_anon_const(&mut self) -> PResult<'a, AnonConst> {
|
|
self.parse_expr().map(|value| AnonConst { id: DUMMY_NODE_ID, value })
|
|
}
|
|
|
|
fn parse_expr_catch_underscore(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
match self.parse_expr_res(restrictions, attrs) {
|
|
Ok((expr, _)) => Ok(expr),
|
|
Err(err) => match self.token.ident() {
|
|
Some((Ident { name: kw::Underscore, .. }, IdentIsRaw::No))
|
|
if self.may_recover() && self.look_ahead(1, |t| t == &token::Comma) =>
|
|
{
|
|
// Special-case handling of `foo(_, _, _)`
|
|
let guar = err.emit();
|
|
self.bump();
|
|
Ok(self.mk_expr(self.prev_token.span, ExprKind::Err(guar)))
|
|
}
|
|
_ => Err(err),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Parses a sequence of expressions delimited by parentheses.
|
|
fn parse_expr_paren_seq(&mut self) -> PResult<'a, ThinVec<P<Expr>>> {
|
|
self.parse_paren_comma_seq(|p| p.parse_expr_catch_underscore(Restrictions::empty()))
|
|
.map(|(r, _)| r)
|
|
}
|
|
|
|
/// Parses an expression, subject to the given restrictions.
|
|
#[inline]
|
|
pub(super) fn parse_expr_res(
|
|
&mut self,
|
|
r: Restrictions,
|
|
attrs: AttrWrapper,
|
|
) -> PResult<'a, (P<Expr>, bool)> {
|
|
self.with_res(r, |this| this.parse_expr_assoc_with(Bound::Unbounded, attrs))
|
|
}
|
|
|
|
/// Parses an associative expression with operators of at least `min_prec` precedence.
|
|
/// The `bool` in the return value indicates if it was an assoc expr, i.e. with an operator
|
|
/// followed by a subexpression (e.g. `1 + 2`).
|
|
pub(super) fn parse_expr_assoc_with(
|
|
&mut self,
|
|
min_prec: Bound<ExprPrecedence>,
|
|
attrs: AttrWrapper,
|
|
) -> PResult<'a, (P<Expr>, bool)> {
|
|
let lhs = if self.token.is_range_separator() {
|
|
return self.parse_expr_prefix_range(attrs).map(|res| (res, false));
|
|
} else {
|
|
self.parse_expr_prefix(attrs)?
|
|
};
|
|
self.parse_expr_assoc_rest_with(min_prec, false, lhs)
|
|
}
|
|
|
|
/// Parses the rest of an associative expression (i.e. the part after the lhs) with operators
|
|
/// of at least `min_prec` precedence. The `bool` in the return value indicates if something
|
|
/// was actually parsed.
|
|
pub(super) fn parse_expr_assoc_rest_with(
|
|
&mut self,
|
|
min_prec: Bound<ExprPrecedence>,
|
|
starts_stmt: bool,
|
|
mut lhs: P<Expr>,
|
|
) -> PResult<'a, (P<Expr>, bool)> {
|
|
let mut parsed_something = false;
|
|
if !self.should_continue_as_assoc_expr(&lhs) {
|
|
return Ok((lhs, parsed_something));
|
|
}
|
|
|
|
self.expected_token_types.insert(TokenType::Operator);
|
|
while let Some(op) = self.check_assoc_op() {
|
|
let lhs_span = self.interpolated_or_expr_span(&lhs);
|
|
let cur_op_span = self.token.span;
|
|
let restrictions = if op.node.is_assign_like() {
|
|
self.restrictions & Restrictions::NO_STRUCT_LITERAL
|
|
} else {
|
|
self.restrictions
|
|
};
|
|
let prec = op.node.precedence();
|
|
if match min_prec {
|
|
Bound::Included(min_prec) => prec < min_prec,
|
|
Bound::Excluded(min_prec) => prec <= min_prec,
|
|
Bound::Unbounded => false,
|
|
} {
|
|
break;
|
|
}
|
|
// Check for deprecated `...` syntax
|
|
if self.token == token::DotDotDot && op.node == AssocOp::Range(RangeLimits::Closed) {
|
|
self.err_dotdotdot_syntax(self.token.span);
|
|
}
|
|
|
|
if self.token == token::LArrow {
|
|
self.err_larrow_operator(self.token.span);
|
|
}
|
|
|
|
parsed_something = true;
|
|
self.bump();
|
|
if op.node.is_comparison() {
|
|
if let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? {
|
|
return Ok((expr, parsed_something));
|
|
}
|
|
}
|
|
|
|
// Look for JS' `===` and `!==` and recover
|
|
if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node
|
|
&& self.token == token::Eq
|
|
&& self.prev_token.span.hi() == self.token.span.lo()
|
|
{
|
|
let sp = op.span.to(self.token.span);
|
|
let sugg = bop.as_str().into();
|
|
let invalid = format!("{sugg}=");
|
|
self.dcx().emit_err(errors::InvalidComparisonOperator {
|
|
span: sp,
|
|
invalid: invalid.clone(),
|
|
sub: errors::InvalidComparisonOperatorSub::Correctable {
|
|
span: sp,
|
|
invalid,
|
|
correct: sugg,
|
|
},
|
|
});
|
|
self.bump();
|
|
}
|
|
|
|
// Look for PHP's `<>` and recover
|
|
if op.node == AssocOp::Binary(BinOpKind::Lt)
|
|
&& self.token == token::Gt
|
|
&& self.prev_token.span.hi() == self.token.span.lo()
|
|
{
|
|
let sp = op.span.to(self.token.span);
|
|
self.dcx().emit_err(errors::InvalidComparisonOperator {
|
|
span: sp,
|
|
invalid: "<>".into(),
|
|
sub: errors::InvalidComparisonOperatorSub::Correctable {
|
|
span: sp,
|
|
invalid: "<>".into(),
|
|
correct: "!=".into(),
|
|
},
|
|
});
|
|
self.bump();
|
|
}
|
|
|
|
// Look for C++'s `<=>` and recover
|
|
if op.node == AssocOp::Binary(BinOpKind::Le)
|
|
&& self.token == token::Gt
|
|
&& self.prev_token.span.hi() == self.token.span.lo()
|
|
{
|
|
let sp = op.span.to(self.token.span);
|
|
self.dcx().emit_err(errors::InvalidComparisonOperator {
|
|
span: sp,
|
|
invalid: "<=>".into(),
|
|
sub: errors::InvalidComparisonOperatorSub::Spaceship(sp),
|
|
});
|
|
self.bump();
|
|
}
|
|
|
|
if self.prev_token == token::Plus
|
|
&& self.token == token::Plus
|
|
&& self.prev_token.span.between(self.token.span).is_empty()
|
|
{
|
|
let op_span = self.prev_token.span.to(self.token.span);
|
|
// Eat the second `+`
|
|
self.bump();
|
|
lhs = self.recover_from_postfix_increment(lhs, op_span, starts_stmt)?;
|
|
continue;
|
|
}
|
|
|
|
if self.prev_token == token::Minus
|
|
&& self.token == token::Minus
|
|
&& self.prev_token.span.between(self.token.span).is_empty()
|
|
&& !self.look_ahead(1, |tok| tok.can_begin_expr())
|
|
{
|
|
let op_span = self.prev_token.span.to(self.token.span);
|
|
// Eat the second `-`
|
|
self.bump();
|
|
lhs = self.recover_from_postfix_decrement(lhs, op_span, starts_stmt)?;
|
|
continue;
|
|
}
|
|
|
|
let op = op.node;
|
|
// Special cases:
|
|
if op == AssocOp::Cast {
|
|
lhs = self.parse_assoc_op_cast(lhs, lhs_span, ExprKind::Cast)?;
|
|
continue;
|
|
} else if let AssocOp::Range(limits) = op {
|
|
// If we didn't have to handle `x..`/`x..=`, it would be pretty easy to
|
|
// generalise it to the Fixity::None code.
|
|
lhs = self.parse_expr_range(prec, lhs, limits, cur_op_span)?;
|
|
break;
|
|
}
|
|
|
|
let min_prec = match op.fixity() {
|
|
Fixity::Right => Bound::Included(prec),
|
|
Fixity::Left | Fixity::None => Bound::Excluded(prec),
|
|
};
|
|
let (rhs, _) = self.with_res(restrictions - Restrictions::STMT_EXPR, |this| {
|
|
let attrs = this.parse_outer_attributes()?;
|
|
this.parse_expr_assoc_with(min_prec, attrs)
|
|
})?;
|
|
|
|
let span = self.mk_expr_sp(&lhs, lhs_span, rhs.span);
|
|
lhs = match op {
|
|
AssocOp::Binary(ast_op) => {
|
|
let binary = self.mk_binary(source_map::respan(cur_op_span, ast_op), lhs, rhs);
|
|
self.mk_expr(span, binary)
|
|
}
|
|
AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span)),
|
|
AssocOp::AssignOp(aop) => {
|
|
let aopexpr = self.mk_assign_op(source_map::respan(cur_op_span, aop), lhs, rhs);
|
|
self.mk_expr(span, aopexpr)
|
|
}
|
|
AssocOp::Cast | AssocOp::Range(_) => {
|
|
self.dcx().span_bug(span, "AssocOp should have been handled by special case")
|
|
}
|
|
};
|
|
}
|
|
|
|
Ok((lhs, parsed_something))
|
|
}
|
|
|
|
fn should_continue_as_assoc_expr(&mut self, lhs: &Expr) -> bool {
|
|
match (self.expr_is_complete(lhs), AssocOp::from_token(&self.token)) {
|
|
// Semi-statement forms are odd:
|
|
// See https://github.com/rust-lang/rust/issues/29071
|
|
(true, None) => false,
|
|
(false, _) => true, // Continue parsing the expression.
|
|
// An exhaustive check is done in the following block, but these are checked first
|
|
// because they *are* ambiguous but also reasonable looking incorrect syntax, so we
|
|
// want to keep their span info to improve diagnostics in these cases in a later stage.
|
|
(true, Some(AssocOp::Binary(
|
|
BinOpKind::Mul | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
|
|
BinOpKind::Sub | // `{ 42 } -5`
|
|
BinOpKind::Add | // `{ 42 } + 42` (unary plus)
|
|
BinOpKind::And | // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`
|
|
BinOpKind::Or | // `{ 42 } || 42` ("logical or" or closure)
|
|
BinOpKind::BitOr // `{ 42 } | 42` or `{ 42 } |x| 42`
|
|
))) => {
|
|
// These cases are ambiguous and can't be identified in the parser alone.
|
|
//
|
|
// Bitwise AND is left out because guessing intent is hard. We can make
|
|
// suggestions based on the assumption that double-refs are rarely intentional,
|
|
// and closures are distinct enough that they don't get mixed up with their
|
|
// return value.
|
|
let sp = self.psess.source_map().start_point(self.token.span);
|
|
self.psess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
|
|
false
|
|
}
|
|
(true, Some(op)) if !op.can_continue_expr_unambiguously() => false,
|
|
(true, Some(_)) => {
|
|
self.error_found_expr_would_be_stmt(lhs);
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We've found an expression that would be parsed as a statement,
|
|
/// but the next token implies this should be parsed as an expression.
|
|
/// For example: `if let Some(x) = x { x } else { 0 } / 2`.
|
|
fn error_found_expr_would_be_stmt(&self, lhs: &Expr) {
|
|
self.dcx().emit_err(errors::FoundExprWouldBeStmt {
|
|
span: self.token.span,
|
|
token: self.token,
|
|
suggestion: ExprParenthesesNeeded::surrounding(lhs.span),
|
|
});
|
|
}
|
|
|
|
/// Possibly translate the current token to an associative operator.
|
|
/// The method does not advance the current token.
|
|
///
|
|
/// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively.
|
|
pub(super) fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
|
|
let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) {
|
|
// When parsing const expressions, stop parsing when encountering `>`.
|
|
(
|
|
Some(
|
|
AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge)
|
|
| AssocOp::AssignOp(AssignOpKind::ShrAssign),
|
|
),
|
|
_,
|
|
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
|
|
return None;
|
|
}
|
|
// When recovering patterns as expressions, stop parsing when encountering an
|
|
// assignment `=`, an alternative `|`, or a range `..`.
|
|
(
|
|
Some(
|
|
AssocOp::Assign
|
|
| AssocOp::AssignOp(_)
|
|
| AssocOp::Binary(BinOpKind::BitOr)
|
|
| AssocOp::Range(_),
|
|
),
|
|
_,
|
|
) if self.restrictions.contains(Restrictions::IS_PAT) => {
|
|
return None;
|
|
}
|
|
(Some(op), _) => (op, self.token.span),
|
|
(None, Some((Ident { name: sym::and, span }, IdentIsRaw::No)))
|
|
if self.may_recover() =>
|
|
{
|
|
self.dcx().emit_err(errors::InvalidLogicalOperator {
|
|
span: self.token.span,
|
|
incorrect: "and".into(),
|
|
sub: errors::InvalidLogicalOperatorSub::Conjunction(self.token.span),
|
|
});
|
|
(AssocOp::Binary(BinOpKind::And), span)
|
|
}
|
|
(None, Some((Ident { name: sym::or, span }, IdentIsRaw::No))) if self.may_recover() => {
|
|
self.dcx().emit_err(errors::InvalidLogicalOperator {
|
|
span: self.token.span,
|
|
incorrect: "or".into(),
|
|
sub: errors::InvalidLogicalOperatorSub::Disjunction(self.token.span),
|
|
});
|
|
(AssocOp::Binary(BinOpKind::Or), span)
|
|
}
|
|
_ => return None,
|
|
};
|
|
Some(source_map::respan(span, op))
|
|
}
|
|
|
|
/// Checks if this expression is a successfully parsed statement.
|
|
fn expr_is_complete(&self, e: &Expr) -> bool {
|
|
self.restrictions.contains(Restrictions::STMT_EXPR) && classify::expr_is_complete(e)
|
|
}
|
|
|
|
/// Parses `x..y`, `x..=y`, and `x..`/`x..=`.
|
|
/// The other two variants are handled in `parse_prefix_range_expr` below.
|
|
fn parse_expr_range(
|
|
&mut self,
|
|
prec: ExprPrecedence,
|
|
lhs: P<Expr>,
|
|
limits: RangeLimits,
|
|
cur_op_span: Span,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let rhs = if self.is_at_start_of_range_notation_rhs() {
|
|
let maybe_lt = self.token;
|
|
let attrs = self.parse_outer_attributes()?;
|
|
Some(
|
|
self.parse_expr_assoc_with(Bound::Excluded(prec), attrs)
|
|
.map_err(|err| self.maybe_err_dotdotlt_syntax(maybe_lt, err))?
|
|
.0,
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
let rhs_span = rhs.as_ref().map_or(cur_op_span, |x| x.span);
|
|
let span = self.mk_expr_sp(&lhs, lhs.span, rhs_span);
|
|
let range = self.mk_range(Some(lhs), rhs, limits);
|
|
Ok(self.mk_expr(span, range))
|
|
}
|
|
|
|
fn is_at_start_of_range_notation_rhs(&self) -> bool {
|
|
if self.token.can_begin_expr() {
|
|
// Parse `for i in 1.. { }` as infinite loop, not as `for i in (1..{})`.
|
|
if self.token == token::OpenBrace {
|
|
return !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
|
|
}
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Parses prefix-forms of range notation: `..expr`, `..`, `..=expr`.
|
|
fn parse_expr_prefix_range(&mut self, attrs: AttrWrapper) -> PResult<'a, P<Expr>> {
|
|
if !attrs.is_empty() {
|
|
let err = errors::DotDotRangeAttribute { span: self.token.span };
|
|
self.dcx().emit_err(err);
|
|
}
|
|
|
|
// Check for deprecated `...` syntax.
|
|
if self.token == token::DotDotDot {
|
|
self.err_dotdotdot_syntax(self.token.span);
|
|
}
|
|
|
|
debug_assert!(
|
|
self.token.is_range_separator(),
|
|
"parse_prefix_range_expr: token {:?} is not DotDot/DotDotEq",
|
|
self.token
|
|
);
|
|
|
|
let limits = match self.token.kind {
|
|
token::DotDot => RangeLimits::HalfOpen,
|
|
_ => RangeLimits::Closed,
|
|
};
|
|
let op = AssocOp::from_token(&self.token);
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.collect_tokens_for_expr(attrs, |this, attrs| {
|
|
let lo = this.token.span;
|
|
let maybe_lt = this.look_ahead(1, |t| t.clone());
|
|
this.bump();
|
|
let (span, opt_end) = if this.is_at_start_of_range_notation_rhs() {
|
|
// RHS must be parsed with more associativity than the dots.
|
|
let attrs = this.parse_outer_attributes()?;
|
|
this.parse_expr_assoc_with(Bound::Excluded(op.unwrap().precedence()), attrs)
|
|
.map(|(x, _)| (lo.to(x.span), Some(x)))
|
|
.map_err(|err| this.maybe_err_dotdotlt_syntax(maybe_lt, err))?
|
|
} else {
|
|
(lo, None)
|
|
};
|
|
let range = this.mk_range(None, opt_end, limits);
|
|
Ok(this.mk_expr_with_attrs(span, range, attrs))
|
|
})
|
|
}
|
|
|
|
/// Parses a prefix-unary-operator expr.
|
|
fn parse_expr_prefix(&mut self, attrs: AttrWrapper) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
|
|
macro_rules! make_it {
|
|
($this:ident, $attrs:expr, |this, _| $body:expr) => {
|
|
$this.collect_tokens_for_expr($attrs, |$this, attrs| {
|
|
let (hi, ex) = $body?;
|
|
Ok($this.mk_expr_with_attrs(lo.to(hi), ex, attrs))
|
|
})
|
|
};
|
|
}
|
|
|
|
let this = self;
|
|
|
|
// Note: when adding new unary operators, don't forget to adjust TokenKind::can_begin_expr()
|
|
match this.token.uninterpolate().kind {
|
|
// `!expr`
|
|
token::Bang => make_it!(this, attrs, |this, _| this.parse_expr_unary(lo, UnOp::Not)),
|
|
// `~expr`
|
|
token::Tilde => make_it!(this, attrs, |this, _| this.recover_tilde_expr(lo)),
|
|
// `-expr`
|
|
token::Minus => {
|
|
make_it!(this, attrs, |this, _| this.parse_expr_unary(lo, UnOp::Neg))
|
|
}
|
|
// `*expr`
|
|
token::Star => {
|
|
make_it!(this, attrs, |this, _| this.parse_expr_unary(lo, UnOp::Deref))
|
|
}
|
|
// `&expr` and `&&expr`
|
|
token::And | token::AndAnd => {
|
|
make_it!(this, attrs, |this, _| this.parse_expr_borrow(lo))
|
|
}
|
|
// `+lit`
|
|
token::Plus if this.look_ahead(1, |tok| tok.is_numeric_lit()) => {
|
|
let mut err = errors::LeadingPlusNotSupported {
|
|
span: lo,
|
|
remove_plus: None,
|
|
add_parentheses: None,
|
|
};
|
|
|
|
// a block on the LHS might have been intended to be an expression instead
|
|
if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) {
|
|
err.add_parentheses = Some(ExprParenthesesNeeded::surrounding(*sp));
|
|
} else {
|
|
err.remove_plus = Some(lo);
|
|
}
|
|
this.dcx().emit_err(err);
|
|
|
|
this.bump();
|
|
let attrs = this.parse_outer_attributes()?;
|
|
this.parse_expr_prefix(attrs)
|
|
}
|
|
// Recover from `++x`:
|
|
token::Plus if this.look_ahead(1, |t| *t == token::Plus) => {
|
|
let starts_stmt =
|
|
this.prev_token == token::Semi || this.prev_token == token::CloseBrace;
|
|
let pre_span = this.token.span.to(this.look_ahead(1, |t| t.span));
|
|
// Eat both `+`s.
|
|
this.bump();
|
|
this.bump();
|
|
|
|
let operand_expr = this.parse_expr_dot_or_call(attrs)?;
|
|
this.recover_from_prefix_increment(operand_expr, pre_span, starts_stmt)
|
|
}
|
|
token::Ident(..) if this.token.is_keyword(kw::Box) => {
|
|
make_it!(this, attrs, |this, _| this.parse_expr_box(lo))
|
|
}
|
|
token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => {
|
|
make_it!(this, attrs, |this, _| this.recover_not_expr(lo))
|
|
}
|
|
_ => return this.parse_expr_dot_or_call(attrs),
|
|
}
|
|
}
|
|
|
|
fn parse_expr_prefix_common(&mut self, lo: Span) -> PResult<'a, (Span, P<Expr>)> {
|
|
self.bump();
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let expr = if self.token.is_range_separator() {
|
|
self.parse_expr_prefix_range(attrs)
|
|
} else {
|
|
self.parse_expr_prefix(attrs)
|
|
}?;
|
|
let span = self.interpolated_or_expr_span(&expr);
|
|
Ok((lo.to(span), expr))
|
|
}
|
|
|
|
fn parse_expr_unary(&mut self, lo: Span, op: UnOp) -> PResult<'a, (Span, ExprKind)> {
|
|
let (span, expr) = self.parse_expr_prefix_common(lo)?;
|
|
Ok((span, self.mk_unary(op, expr)))
|
|
}
|
|
|
|
/// Recover on `~expr` in favor of `!expr`.
|
|
fn recover_tilde_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> {
|
|
self.dcx().emit_err(errors::TildeAsUnaryOperator(lo));
|
|
|
|
self.parse_expr_unary(lo, UnOp::Not)
|
|
}
|
|
|
|
/// Parse `box expr` - this syntax has been removed, but we still parse this
|
|
/// for now to provide a more useful error
|
|
fn parse_expr_box(&mut self, box_kw: Span) -> PResult<'a, (Span, ExprKind)> {
|
|
let (span, expr) = self.parse_expr_prefix_common(box_kw)?;
|
|
// Make a multipart suggestion instead of `span_to_snippet` in case source isn't available
|
|
let box_kw_and_lo = box_kw.until(self.interpolated_or_expr_span(&expr));
|
|
let hi = span.shrink_to_hi();
|
|
let sugg = errors::AddBoxNew { box_kw_and_lo, hi };
|
|
let guar = self.dcx().emit_err(errors::BoxSyntaxRemoved { span, sugg });
|
|
Ok((span, ExprKind::Err(guar)))
|
|
}
|
|
|
|
fn is_mistaken_not_ident_negation(&self) -> bool {
|
|
let token_cannot_continue_expr = |t: &Token| match t.uninterpolate().kind {
|
|
// These tokens can start an expression after `!`, but
|
|
// can't continue an expression after an ident
|
|
token::Ident(name, is_raw) => token::ident_can_begin_expr(name, t.span, is_raw),
|
|
token::Literal(..) | token::Pound => true,
|
|
_ => t.is_metavar_expr(),
|
|
};
|
|
self.token.is_ident_named(sym::not) && self.look_ahead(1, token_cannot_continue_expr)
|
|
}
|
|
|
|
/// Recover on `not expr` in favor of `!expr`.
|
|
fn recover_not_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> {
|
|
let negated_token = self.look_ahead(1, |t| *t);
|
|
|
|
let sub_diag = if negated_token.is_numeric_lit() {
|
|
errors::NotAsNegationOperatorSub::SuggestNotBitwise
|
|
} else if negated_token.is_bool_lit() {
|
|
errors::NotAsNegationOperatorSub::SuggestNotLogical
|
|
} else {
|
|
errors::NotAsNegationOperatorSub::SuggestNotDefault
|
|
};
|
|
|
|
self.dcx().emit_err(errors::NotAsNegationOperator {
|
|
negated: negated_token.span,
|
|
negated_desc: super::token_descr(&negated_token),
|
|
// Span the `not` plus trailing whitespace to avoid
|
|
// trailing whitespace after the `!` in our suggestion
|
|
sub: sub_diag(
|
|
self.psess.source_map().span_until_non_whitespace(lo.to(negated_token.span)),
|
|
),
|
|
});
|
|
|
|
self.parse_expr_unary(lo, UnOp::Not)
|
|
}
|
|
|
|
/// Returns the span of expr if it was not interpolated, or the span of the interpolated token.
|
|
fn interpolated_or_expr_span(&self, expr: &Expr) -> Span {
|
|
match self.prev_token.kind {
|
|
token::NtIdent(..) | token::NtLifetime(..) => self.prev_token.span,
|
|
token::CloseInvisible(InvisibleOrigin::MetaVar(_)) => {
|
|
// `expr.span` is the interpolated span, because invisible open
|
|
// and close delims both get marked with the same span, one
|
|
// that covers the entire thing between them. (See
|
|
// `rustc_expand::mbe::transcribe::transcribe`.)
|
|
self.prev_token.span
|
|
}
|
|
_ => expr.span,
|
|
}
|
|
}
|
|
|
|
fn parse_assoc_op_cast(
|
|
&mut self,
|
|
lhs: P<Expr>,
|
|
lhs_span: Span,
|
|
expr_kind: fn(P<Expr>, P<Ty>) -> ExprKind,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let mk_expr = |this: &mut Self, lhs: P<Expr>, rhs: P<Ty>| {
|
|
this.mk_expr(this.mk_expr_sp(&lhs, lhs_span, rhs.span), expr_kind(lhs, rhs))
|
|
};
|
|
|
|
// Save the state of the parser before parsing type normally, in case there is a
|
|
// LessThan comparison after this cast.
|
|
let parser_snapshot_before_type = self.clone();
|
|
let cast_expr = match self.parse_as_cast_ty() {
|
|
Ok(rhs) => mk_expr(self, lhs, rhs),
|
|
Err(type_err) => {
|
|
if !self.may_recover() {
|
|
return Err(type_err);
|
|
}
|
|
|
|
// Rewind to before attempting to parse the type with generics, to recover
|
|
// from situations like `x as usize < y` in which we first tried to parse
|
|
// `usize < y` as a type with generic arguments.
|
|
let parser_snapshot_after_type = mem::replace(self, parser_snapshot_before_type);
|
|
|
|
// Check for typo of `'a: loop { break 'a }` with a missing `'`.
|
|
match (&lhs.kind, &self.token.kind) {
|
|
(
|
|
// `foo: `
|
|
ExprKind::Path(None, ast::Path { segments, .. }),
|
|
token::Ident(kw::For | kw::Loop | kw::While, IdentIsRaw::No),
|
|
) if let [segment] = segments.as_slice() => {
|
|
let snapshot = self.create_snapshot_for_diagnostic();
|
|
let label = Label {
|
|
ident: Ident::from_str_and_span(
|
|
&format!("'{}", segment.ident),
|
|
segment.ident.span,
|
|
),
|
|
};
|
|
match self.parse_expr_labeled(label, false) {
|
|
Ok(expr) => {
|
|
type_err.cancel();
|
|
self.dcx().emit_err(errors::MalformedLoopLabel {
|
|
span: label.ident.span,
|
|
suggestion: label.ident.span.shrink_to_lo(),
|
|
});
|
|
return Ok(expr);
|
|
}
|
|
Err(err) => {
|
|
err.cancel();
|
|
self.restore_snapshot(snapshot);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
match self.parse_path(PathStyle::Expr) {
|
|
Ok(path) => {
|
|
let span_after_type = parser_snapshot_after_type.token.span;
|
|
let expr = mk_expr(
|
|
self,
|
|
lhs,
|
|
self.mk_ty(path.span, TyKind::Path(None, path.clone())),
|
|
);
|
|
|
|
let args_span = self.look_ahead(1, |t| t.span).to(span_after_type);
|
|
let suggestion = errors::ComparisonOrShiftInterpretedAsGenericSugg {
|
|
left: expr.span.shrink_to_lo(),
|
|
right: expr.span.shrink_to_hi(),
|
|
};
|
|
|
|
match self.token.kind {
|
|
token::Lt => {
|
|
self.dcx().emit_err(errors::ComparisonInterpretedAsGeneric {
|
|
comparison: self.token.span,
|
|
r#type: path,
|
|
args: args_span,
|
|
suggestion,
|
|
})
|
|
}
|
|
token::Shl => self.dcx().emit_err(errors::ShiftInterpretedAsGeneric {
|
|
shift: self.token.span,
|
|
r#type: path,
|
|
args: args_span,
|
|
suggestion,
|
|
}),
|
|
_ => {
|
|
// We can end up here even without `<` being the next token, for
|
|
// example because `parse_ty_no_plus` returns `Err` on keywords,
|
|
// but `parse_path` returns `Ok` on them due to error recovery.
|
|
// Return original error and parser state.
|
|
*self = parser_snapshot_after_type;
|
|
return Err(type_err);
|
|
}
|
|
};
|
|
|
|
// Successfully parsed the type path leaving a `<` yet to parse.
|
|
type_err.cancel();
|
|
|
|
// Keep `x as usize` as an expression in AST and continue parsing.
|
|
expr
|
|
}
|
|
Err(path_err) => {
|
|
// Couldn't parse as a path, return original error and parser state.
|
|
path_err.cancel();
|
|
*self = parser_snapshot_after_type;
|
|
return Err(type_err);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Try to parse a postfix operator such as `.`, `?`, or index (`[]`)
|
|
// after a cast. If one is present, emit an error then return a valid
|
|
// parse tree; For something like `&x as T[0]` will be as if it was
|
|
// written `((&x) as T)[0]`.
|
|
|
|
let span = cast_expr.span;
|
|
|
|
let with_postfix = self.parse_expr_dot_or_call_with(AttrVec::new(), cast_expr, span)?;
|
|
|
|
// Check if an illegal postfix operator has been added after the cast.
|
|
// If the resulting expression is not a cast, it is an illegal postfix operator.
|
|
if !matches!(with_postfix.kind, ExprKind::Cast(_, _)) {
|
|
let msg = format!(
|
|
"cast cannot be followed by {}",
|
|
match with_postfix.kind {
|
|
ExprKind::Index(..) => "indexing",
|
|
ExprKind::Try(_) => "`?`",
|
|
ExprKind::Field(_, _) => "a field access",
|
|
ExprKind::MethodCall(_) => "a method call",
|
|
ExprKind::Call(_, _) => "a function call",
|
|
ExprKind::Await(_, _) => "`.await`",
|
|
ExprKind::Use(_, _) => "`.use`",
|
|
ExprKind::Match(_, _, MatchKind::Postfix) => "a postfix match",
|
|
ExprKind::Err(_) => return Ok(with_postfix),
|
|
_ => unreachable!("parse_dot_or_call_expr_with_ shouldn't produce this"),
|
|
}
|
|
);
|
|
let mut err = self.dcx().struct_span_err(span, msg);
|
|
|
|
let suggest_parens = |err: &mut Diag<'_>| {
|
|
let suggestions = vec![
|
|
(span.shrink_to_lo(), "(".to_string()),
|
|
(span.shrink_to_hi(), ")".to_string()),
|
|
];
|
|
err.multipart_suggestion(
|
|
"try surrounding the expression in parentheses",
|
|
suggestions,
|
|
Applicability::MachineApplicable,
|
|
);
|
|
};
|
|
|
|
suggest_parens(&mut err);
|
|
|
|
err.emit();
|
|
};
|
|
Ok(with_postfix)
|
|
}
|
|
|
|
/// Parse `& mut? <expr>` or `& raw [ const | mut ] <expr>`.
|
|
fn parse_expr_borrow(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> {
|
|
self.expect_and()?;
|
|
let has_lifetime = self.token.is_lifetime() && self.look_ahead(1, |t| t != &token::Colon);
|
|
let lifetime = has_lifetime.then(|| self.expect_lifetime()); // For recovery, see below.
|
|
let (borrow_kind, mutbl) = self.parse_borrow_modifiers();
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let expr = if self.token.is_range_separator() {
|
|
self.parse_expr_prefix_range(attrs)
|
|
} else {
|
|
self.parse_expr_prefix(attrs)
|
|
}?;
|
|
let hi = self.interpolated_or_expr_span(&expr);
|
|
let span = lo.to(hi);
|
|
if let Some(lt) = lifetime {
|
|
self.error_remove_borrow_lifetime(span, lt.ident.span.until(expr.span));
|
|
}
|
|
|
|
// Add expected tokens if we parsed `&raw` as an expression.
|
|
// This will make sure we see "expected `const`, `mut`", and
|
|
// guides recovery in case we write `&raw expr`.
|
|
if borrow_kind == ast::BorrowKind::Ref
|
|
&& mutbl == ast::Mutability::Not
|
|
&& matches!(&expr.kind, ExprKind::Path(None, p) if p.is_ident(kw::Raw))
|
|
{
|
|
self.expected_token_types.insert(TokenType::KwMut);
|
|
self.expected_token_types.insert(TokenType::KwConst);
|
|
}
|
|
|
|
Ok((span, ExprKind::AddrOf(borrow_kind, mutbl, expr)))
|
|
}
|
|
|
|
fn error_remove_borrow_lifetime(&self, span: Span, lt_span: Span) {
|
|
self.dcx().emit_err(errors::LifetimeInBorrowExpression { span, lifetime_span: lt_span });
|
|
}
|
|
|
|
/// Parse `mut?` or `raw [ const | mut ]`.
|
|
fn parse_borrow_modifiers(&mut self) -> (ast::BorrowKind, ast::Mutability) {
|
|
if self.check_keyword(exp!(Raw)) && self.look_ahead(1, Token::is_mutability) {
|
|
// `raw [ const | mut ]`.
|
|
let found_raw = self.eat_keyword(exp!(Raw));
|
|
assert!(found_raw);
|
|
let mutability = self.parse_const_or_mut().unwrap();
|
|
(ast::BorrowKind::Raw, mutability)
|
|
} else {
|
|
// `mut?`
|
|
(ast::BorrowKind::Ref, self.parse_mutability())
|
|
}
|
|
}
|
|
|
|
/// Parses `a.b` or `a(13)` or `a[4]` or just `a`.
|
|
fn parse_expr_dot_or_call(&mut self, attrs: AttrWrapper) -> PResult<'a, P<Expr>> {
|
|
self.collect_tokens_for_expr(attrs, |this, attrs| {
|
|
let base = this.parse_expr_bottom()?;
|
|
let span = this.interpolated_or_expr_span(&base);
|
|
this.parse_expr_dot_or_call_with(attrs, base, span)
|
|
})
|
|
}
|
|
|
|
pub(super) fn parse_expr_dot_or_call_with(
|
|
&mut self,
|
|
mut attrs: ast::AttrVec,
|
|
mut e: P<Expr>,
|
|
lo: Span,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let mut res = ensure_sufficient_stack(|| {
|
|
loop {
|
|
let has_question =
|
|
if self.prev_token == TokenKind::Ident(kw::Return, IdentIsRaw::No) {
|
|
// We are using noexpect here because we don't expect a `?` directly after
|
|
// a `return` which could be suggested otherwise.
|
|
self.eat_noexpect(&token::Question)
|
|
} else {
|
|
self.eat(exp!(Question))
|
|
};
|
|
if has_question {
|
|
// `expr?`
|
|
e = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Try(e));
|
|
continue;
|
|
}
|
|
let has_dot = if self.prev_token == TokenKind::Ident(kw::Return, IdentIsRaw::No) {
|
|
// We are using noexpect here because we don't expect a `.` directly after
|
|
// a `return` which could be suggested otherwise.
|
|
self.eat_noexpect(&token::Dot)
|
|
} else if self.token == TokenKind::RArrow && self.may_recover() {
|
|
// Recovery for `expr->suffix`.
|
|
self.bump();
|
|
let span = self.prev_token.span;
|
|
self.dcx().emit_err(errors::ExprRArrowCall { span });
|
|
true
|
|
} else {
|
|
self.eat(exp!(Dot))
|
|
};
|
|
if has_dot {
|
|
// expr.f
|
|
e = self.parse_dot_suffix_expr(lo, e)?;
|
|
continue;
|
|
}
|
|
if self.expr_is_complete(&e) {
|
|
return Ok(e);
|
|
}
|
|
e = match self.token.kind {
|
|
token::OpenParen => self.parse_expr_fn_call(lo, e),
|
|
token::OpenBracket => self.parse_expr_index(lo, e)?,
|
|
_ => return Ok(e),
|
|
}
|
|
}
|
|
});
|
|
|
|
// Stitch the list of outer attributes onto the return value. A little
|
|
// bit ugly, but the best way given the current code structure.
|
|
if !attrs.is_empty()
|
|
&& let Ok(expr) = &mut res
|
|
{
|
|
mem::swap(&mut expr.attrs, &mut attrs);
|
|
expr.attrs.extend(attrs)
|
|
}
|
|
res
|
|
}
|
|
|
|
pub(super) fn parse_dot_suffix_expr(
|
|
&mut self,
|
|
lo: Span,
|
|
base: P<Expr>,
|
|
) -> PResult<'a, P<Expr>> {
|
|
// At this point we've consumed something like `expr.` and `self.token` holds the token
|
|
// after the dot.
|
|
match self.token.uninterpolate().kind {
|
|
token::Ident(..) => self.parse_dot_suffix(base, lo),
|
|
token::Literal(token::Lit { kind: token::Integer, symbol, suffix }) => {
|
|
let ident_span = self.token.span;
|
|
self.bump();
|
|
Ok(self.mk_expr_tuple_field_access(lo, ident_span, base, symbol, suffix))
|
|
}
|
|
token::Literal(token::Lit { kind: token::Float, symbol, suffix }) => {
|
|
Ok(match self.break_up_float(symbol, self.token.span) {
|
|
// 1e2
|
|
DestructuredFloat::Single(sym, _sp) => {
|
|
// `foo.1e2`: a single complete dot access, fully consumed. We end up with
|
|
// the `1e2` token in `self.prev_token` and the following token in
|
|
// `self.token`.
|
|
let ident_span = self.token.span;
|
|
self.bump();
|
|
self.mk_expr_tuple_field_access(lo, ident_span, base, sym, suffix)
|
|
}
|
|
// 1.
|
|
DestructuredFloat::TrailingDot(sym, ident_span, dot_span) => {
|
|
// `foo.1.`: a single complete dot access and the start of another.
|
|
// We end up with the `sym` (`1`) token in `self.prev_token` and a dot in
|
|
// `self.token`.
|
|
assert!(suffix.is_none());
|
|
self.token = Token::new(token::Ident(sym, IdentIsRaw::No), ident_span);
|
|
self.bump_with((Token::new(token::Dot, dot_span), self.token_spacing));
|
|
self.mk_expr_tuple_field_access(lo, ident_span, base, sym, None)
|
|
}
|
|
// 1.2 | 1.2e3
|
|
DestructuredFloat::MiddleDot(
|
|
sym1,
|
|
ident1_span,
|
|
_dot_span,
|
|
sym2,
|
|
ident2_span,
|
|
) => {
|
|
// `foo.1.2` (or `foo.1.2e3`): two complete dot accesses. We end up with
|
|
// the `sym2` (`2` or `2e3`) token in `self.prev_token` and the following
|
|
// token in `self.token`.
|
|
let next_token2 =
|
|
Token::new(token::Ident(sym2, IdentIsRaw::No), ident2_span);
|
|
self.bump_with((next_token2, self.token_spacing));
|
|
self.bump();
|
|
let base1 =
|
|
self.mk_expr_tuple_field_access(lo, ident1_span, base, sym1, None);
|
|
self.mk_expr_tuple_field_access(lo, ident2_span, base1, sym2, suffix)
|
|
}
|
|
DestructuredFloat::Error => base,
|
|
})
|
|
}
|
|
_ => {
|
|
self.error_unexpected_after_dot();
|
|
Ok(base)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn error_unexpected_after_dot(&self) {
|
|
let actual = super::token_descr(&self.token);
|
|
let span = self.token.span;
|
|
let sm = self.psess.source_map();
|
|
let (span, actual) = match (&self.token.kind, self.subparser_name) {
|
|
(token::Eof, Some(_)) if let Ok(snippet) = sm.span_to_snippet(sm.next_point(span)) => {
|
|
(span.shrink_to_hi(), format!("`{}`", snippet))
|
|
}
|
|
(token::CloseInvisible(InvisibleOrigin::MetaVar(_)), _) => {
|
|
// No need to report an error. This case will only occur when parsing a pasted
|
|
// metavariable, and we should have emitted an error when parsing the macro call in
|
|
// the first place. E.g. in this code:
|
|
// ```
|
|
// macro_rules! m { ($e:expr) => { $e }; }
|
|
//
|
|
// fn main() {
|
|
// let f = 1;
|
|
// m!(f.);
|
|
// }
|
|
// ```
|
|
// we'll get an error "unexpected token: `)` when parsing the `m!(f.)`, so we don't
|
|
// want to issue a second error when parsing the expansion `«f.»` (where `«`/`»`
|
|
// represent the invisible delimiters).
|
|
self.dcx().span_delayed_bug(span, "bad dot expr in metavariable");
|
|
return;
|
|
}
|
|
_ => (span, actual),
|
|
};
|
|
self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual });
|
|
}
|
|
|
|
/// We need an identifier or integer, but the next token is a float.
|
|
/// Break the float into components to extract the identifier or integer.
|
|
///
|
|
/// See also [`TokenKind::break_two_token_op`] which does similar splitting of `>>` into `>`.
|
|
//
|
|
// FIXME: With current `TokenCursor` it's hard to break tokens into more than 2
|
|
// parts unless those parts are processed immediately. `TokenCursor` should either
|
|
// support pushing "future tokens" (would be also helpful to `break_and_eat`), or
|
|
// we should break everything including floats into more basic proc-macro style
|
|
// tokens in the lexer (probably preferable).
|
|
pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
|
|
#[derive(Debug)]
|
|
enum FloatComponent {
|
|
IdentLike(String),
|
|
Punct(char),
|
|
}
|
|
use FloatComponent::*;
|
|
|
|
let float_str = float.as_str();
|
|
let mut components = Vec::new();
|
|
let mut ident_like = String::new();
|
|
for c in float_str.chars() {
|
|
if c == '_' || c.is_ascii_alphanumeric() {
|
|
ident_like.push(c);
|
|
} else if matches!(c, '.' | '+' | '-') {
|
|
if !ident_like.is_empty() {
|
|
components.push(IdentLike(mem::take(&mut ident_like)));
|
|
}
|
|
components.push(Punct(c));
|
|
} else {
|
|
panic!("unexpected character in a float token: {c:?}")
|
|
}
|
|
}
|
|
if !ident_like.is_empty() {
|
|
components.push(IdentLike(ident_like));
|
|
}
|
|
|
|
// With proc macros the span can refer to anything, the source may be too short,
|
|
// or too long, or non-ASCII. It only makes sense to break our span into components
|
|
// if its underlying text is identical to our float literal.
|
|
let can_take_span_apart =
|
|
|| self.span_to_snippet(span).as_deref() == Ok(float_str).as_deref();
|
|
|
|
match &*components {
|
|
// 1e2
|
|
[IdentLike(i)] => {
|
|
DestructuredFloat::Single(Symbol::intern(i), span)
|
|
}
|
|
// 1.
|
|
[IdentLike(left), Punct('.')] => {
|
|
let (left_span, dot_span) = if can_take_span_apart() {
|
|
let left_span = span.with_hi(span.lo() + BytePos::from_usize(left.len()));
|
|
let dot_span = span.with_lo(left_span.hi());
|
|
(left_span, dot_span)
|
|
} else {
|
|
(span, span)
|
|
};
|
|
let left = Symbol::intern(left);
|
|
DestructuredFloat::TrailingDot(left, left_span, dot_span)
|
|
}
|
|
// 1.2 | 1.2e3
|
|
[IdentLike(left), Punct('.'), IdentLike(right)] => {
|
|
let (left_span, dot_span, right_span) = if can_take_span_apart() {
|
|
let left_span = span.with_hi(span.lo() + BytePos::from_usize(left.len()));
|
|
let dot_span = span.with_lo(left_span.hi()).with_hi(left_span.hi() + BytePos(1));
|
|
let right_span = span.with_lo(dot_span.hi());
|
|
(left_span, dot_span, right_span)
|
|
} else {
|
|
(span, span, span)
|
|
};
|
|
let left = Symbol::intern(left);
|
|
let right = Symbol::intern(right);
|
|
DestructuredFloat::MiddleDot(left, left_span, dot_span, right, right_span)
|
|
}
|
|
// 1e+ | 1e- (recovered)
|
|
[IdentLike(_), Punct('+' | '-')] |
|
|
// 1e+2 | 1e-2
|
|
[IdentLike(_), Punct('+' | '-'), IdentLike(_)] |
|
|
// 1.2e+ | 1.2e-
|
|
[IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-')] |
|
|
// 1.2e+3 | 1.2e-3
|
|
[IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => {
|
|
// See the FIXME about `TokenCursor` above.
|
|
self.error_unexpected_after_dot();
|
|
DestructuredFloat::Error
|
|
}
|
|
_ => panic!("unexpected components in a float token: {components:?}"),
|
|
}
|
|
}
|
|
|
|
/// Parse the field access used in offset_of, matched by `$(e:expr)+`.
|
|
/// Currently returns a list of idents. However, it should be possible in
|
|
/// future to also do array indices, which might be arbitrary expressions.
|
|
fn parse_floating_field_access(&mut self) -> PResult<'a, P<[Ident]>> {
|
|
let mut fields = Vec::new();
|
|
let mut trailing_dot = None;
|
|
|
|
loop {
|
|
// This is expected to use a metavariable $(args:expr)+, but the builtin syntax
|
|
// could be called directly. Calling `parse_expr` allows this function to only
|
|
// consider `Expr`s.
|
|
let expr = self.parse_expr()?;
|
|
let mut current = &expr;
|
|
let start_idx = fields.len();
|
|
loop {
|
|
match current.kind {
|
|
ExprKind::Field(ref left, right) => {
|
|
// Field access is read right-to-left.
|
|
fields.insert(start_idx, right);
|
|
trailing_dot = None;
|
|
current = left;
|
|
}
|
|
// Parse this both to give helpful error messages and to
|
|
// verify it can be done with this parser setup.
|
|
ExprKind::Index(ref left, ref _right, span) => {
|
|
self.dcx().emit_err(errors::ArrayIndexInOffsetOf(span));
|
|
current = left;
|
|
}
|
|
ExprKind::Lit(token::Lit {
|
|
kind: token::Float | token::Integer,
|
|
symbol,
|
|
suffix,
|
|
}) => {
|
|
if let Some(suffix) = suffix {
|
|
self.expect_no_tuple_index_suffix(current.span, suffix);
|
|
}
|
|
match self.break_up_float(symbol, current.span) {
|
|
// 1e2
|
|
DestructuredFloat::Single(sym, sp) => {
|
|
trailing_dot = None;
|
|
fields.insert(start_idx, Ident::new(sym, sp));
|
|
}
|
|
// 1.
|
|
DestructuredFloat::TrailingDot(sym, sym_span, dot_span) => {
|
|
assert!(suffix.is_none());
|
|
trailing_dot = Some(dot_span);
|
|
fields.insert(start_idx, Ident::new(sym, sym_span));
|
|
}
|
|
// 1.2 | 1.2e3
|
|
DestructuredFloat::MiddleDot(
|
|
symbol1,
|
|
span1,
|
|
_dot_span,
|
|
symbol2,
|
|
span2,
|
|
) => {
|
|
trailing_dot = None;
|
|
fields.insert(start_idx, Ident::new(symbol2, span2));
|
|
fields.insert(start_idx, Ident::new(symbol1, span1));
|
|
}
|
|
DestructuredFloat::Error => {
|
|
trailing_dot = None;
|
|
fields.insert(start_idx, Ident::new(symbol, self.prev_token.span));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
ExprKind::Path(None, Path { ref segments, .. }) => {
|
|
match &segments[..] {
|
|
[PathSegment { ident, args: None, .. }] => {
|
|
trailing_dot = None;
|
|
fields.insert(start_idx, *ident)
|
|
}
|
|
_ => {
|
|
self.dcx().emit_err(errors::InvalidOffsetOf(current.span));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
_ => {
|
|
self.dcx().emit_err(errors::InvalidOffsetOf(current.span));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.token.kind.close_delim().is_some() || self.token.kind == token::Comma {
|
|
break;
|
|
} else if trailing_dot.is_none() {
|
|
// This loop should only repeat if there is a trailing dot.
|
|
self.dcx().emit_err(errors::InvalidOffsetOf(self.token.span));
|
|
break;
|
|
}
|
|
}
|
|
if let Some(dot) = trailing_dot {
|
|
self.dcx().emit_err(errors::InvalidOffsetOf(dot));
|
|
}
|
|
Ok(fields.into_iter().collect())
|
|
}
|
|
|
|
fn mk_expr_tuple_field_access(
|
|
&self,
|
|
lo: Span,
|
|
ident_span: Span,
|
|
base: P<Expr>,
|
|
field: Symbol,
|
|
suffix: Option<Symbol>,
|
|
) -> P<Expr> {
|
|
if let Some(suffix) = suffix {
|
|
self.expect_no_tuple_index_suffix(ident_span, suffix);
|
|
}
|
|
self.mk_expr(lo.to(ident_span), ExprKind::Field(base, Ident::new(field, ident_span)))
|
|
}
|
|
|
|
/// Parse a function call expression, `expr(...)`.
|
|
fn parse_expr_fn_call(&mut self, lo: Span, fun: P<Expr>) -> P<Expr> {
|
|
let snapshot = if self.token == token::OpenParen {
|
|
Some((self.create_snapshot_for_diagnostic(), fun.kind.clone()))
|
|
} else {
|
|
None
|
|
};
|
|
let open_paren = self.token.span;
|
|
|
|
let seq = self
|
|
.parse_expr_paren_seq()
|
|
.map(|args| self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args)));
|
|
match self.maybe_recover_struct_lit_bad_delims(lo, open_paren, seq, snapshot) {
|
|
Ok(expr) => expr,
|
|
Err(err) => self.recover_seq_parse_error(exp!(OpenParen), exp!(CloseParen), lo, err),
|
|
}
|
|
}
|
|
|
|
/// If we encounter a parser state that looks like the user has written a `struct` literal with
|
|
/// parentheses instead of braces, recover the parser state and provide suggestions.
|
|
#[instrument(skip(self, seq, snapshot), level = "trace")]
|
|
fn maybe_recover_struct_lit_bad_delims(
|
|
&mut self,
|
|
lo: Span,
|
|
open_paren: Span,
|
|
seq: PResult<'a, P<Expr>>,
|
|
snapshot: Option<(SnapshotParser<'a>, ExprKind)>,
|
|
) -> PResult<'a, P<Expr>> {
|
|
match (self.may_recover(), seq, snapshot) {
|
|
(true, Err(err), Some((mut snapshot, ExprKind::Path(None, path)))) => {
|
|
snapshot.bump(); // `(`
|
|
match snapshot.parse_struct_fields(path.clone(), false, exp!(CloseParen)) {
|
|
Ok((fields, ..)) if snapshot.eat(exp!(CloseParen)) => {
|
|
// We are certain we have `Enum::Foo(a: 3, b: 4)`, suggest
|
|
// `Enum::Foo { a: 3, b: 4 }` or `Enum::Foo(3, 4)`.
|
|
self.restore_snapshot(snapshot);
|
|
let close_paren = self.prev_token.span;
|
|
let span = lo.to(close_paren);
|
|
// filter shorthand fields
|
|
let fields: Vec<_> =
|
|
fields.into_iter().filter(|field| !field.is_shorthand).collect();
|
|
|
|
let guar = if !fields.is_empty() &&
|
|
// `token.kind` should not be compared here.
|
|
// This is because the `snapshot.token.kind` is treated as the same as
|
|
// that of the open delim in `TokenTreesReader::parse_token_tree`, even
|
|
// if they are different.
|
|
self.span_to_snippet(close_paren).is_ok_and(|snippet| snippet == ")")
|
|
{
|
|
err.cancel();
|
|
self.dcx()
|
|
.create_err(errors::ParenthesesWithStructFields {
|
|
span,
|
|
r#type: path,
|
|
braces_for_struct: errors::BracesForStructLiteral {
|
|
first: open_paren,
|
|
second: close_paren,
|
|
},
|
|
no_fields_for_fn: errors::NoFieldsForFnCall {
|
|
fields: fields
|
|
.into_iter()
|
|
.map(|field| field.span.until(field.expr.span))
|
|
.collect(),
|
|
},
|
|
})
|
|
.emit()
|
|
} else {
|
|
err.emit()
|
|
};
|
|
Ok(self.mk_expr_err(span, guar))
|
|
}
|
|
Ok(_) => Err(err),
|
|
Err(err2) => {
|
|
err2.cancel();
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
(_, seq, _) => seq,
|
|
}
|
|
}
|
|
|
|
/// Parse an indexing expression `expr[...]`.
|
|
fn parse_expr_index(&mut self, lo: Span, base: P<Expr>) -> PResult<'a, P<Expr>> {
|
|
let prev_span = self.prev_token.span;
|
|
let open_delim_span = self.token.span;
|
|
self.bump(); // `[`
|
|
let index = self.parse_expr()?;
|
|
self.suggest_missing_semicolon_before_array(prev_span, open_delim_span)?;
|
|
self.expect(exp!(CloseBracket))?;
|
|
Ok(self.mk_expr(
|
|
lo.to(self.prev_token.span),
|
|
self.mk_index(base, index, open_delim_span.to(self.prev_token.span)),
|
|
))
|
|
}
|
|
|
|
/// Assuming we have just parsed `.`, continue parsing into an expression.
|
|
fn parse_dot_suffix(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
|
|
if self.token_uninterpolated_span().at_least_rust_2018() && self.eat_keyword(exp!(Await)) {
|
|
return Ok(self.mk_await_expr(self_arg, lo));
|
|
}
|
|
|
|
if self.eat_keyword(exp!(Use)) {
|
|
let use_span = self.prev_token.span;
|
|
self.psess.gated_spans.gate(sym::ergonomic_clones, use_span);
|
|
return Ok(self.mk_use_expr(self_arg, lo));
|
|
}
|
|
|
|
// Post-fix match
|
|
if self.eat_keyword(exp!(Match)) {
|
|
let match_span = self.prev_token.span;
|
|
self.psess.gated_spans.gate(sym::postfix_match, match_span);
|
|
return self.parse_match_block(lo, match_span, self_arg, MatchKind::Postfix);
|
|
}
|
|
|
|
// Parse a postfix `yield`.
|
|
if self.eat_keyword(exp!(Yield)) {
|
|
let yield_span = self.prev_token.span;
|
|
self.psess.gated_spans.gate(sym::yield_expr, yield_span);
|
|
return Ok(
|
|
self.mk_expr(lo.to(yield_span), ExprKind::Yield(YieldKind::Postfix(self_arg)))
|
|
);
|
|
}
|
|
|
|
let fn_span_lo = self.token.span;
|
|
let mut seg = self.parse_path_segment(PathStyle::Expr, None)?;
|
|
self.check_trailing_angle_brackets(&seg, &[exp!(OpenParen)]);
|
|
self.check_turbofish_missing_angle_brackets(&mut seg);
|
|
|
|
if self.check(exp!(OpenParen)) {
|
|
// Method call `expr.f()`
|
|
let args = self.parse_expr_paren_seq()?;
|
|
let fn_span = fn_span_lo.to(self.prev_token.span);
|
|
let span = lo.to(self.prev_token.span);
|
|
Ok(self.mk_expr(
|
|
span,
|
|
ExprKind::MethodCall(Box::new(ast::MethodCall {
|
|
seg,
|
|
receiver: self_arg,
|
|
args,
|
|
span: fn_span,
|
|
})),
|
|
))
|
|
} else {
|
|
// Field access `expr.f`
|
|
let span = lo.to(self.prev_token.span);
|
|
if let Some(args) = seg.args {
|
|
// See `StashKey::GenericInFieldExpr` for more info on why we stash this.
|
|
self.dcx()
|
|
.create_err(errors::FieldExpressionWithGeneric(args.span()))
|
|
.stash(seg.ident.span, StashKey::GenericInFieldExpr);
|
|
}
|
|
|
|
Ok(self.mk_expr(span, ExprKind::Field(self_arg, seg.ident)))
|
|
}
|
|
}
|
|
|
|
/// At the bottom (top?) of the precedence hierarchy,
|
|
/// Parses things like parenthesized exprs, macros, `return`, etc.
|
|
///
|
|
/// N.B., this does not parse outer attributes, and is private because it only works
|
|
/// correctly if called from `parse_expr_dot_or_call`.
|
|
fn parse_expr_bottom(&mut self) -> PResult<'a, P<Expr>> {
|
|
maybe_recover_from_interpolated_ty_qpath!(self, true);
|
|
|
|
let span = self.token.span;
|
|
if let Some(expr) = self.eat_metavar_seq_with_matcher(
|
|
|mv_kind| matches!(mv_kind, MetaVarKind::Expr { .. }),
|
|
|this| {
|
|
// Force collection (as opposed to just `parse_expr`) is required to avoid the
|
|
// attribute duplication seen in #138478.
|
|
let expr = this.parse_expr_force_collect();
|
|
// FIXME(nnethercote) Sometimes with expressions we get a trailing comma, possibly
|
|
// related to the FIXME in `collect_tokens_for_expr`. Examples are the multi-line
|
|
// `assert_eq!` calls involving arguments annotated with `#[rustfmt::skip]` in
|
|
// `compiler/rustc_index/src/bit_set/tests.rs`.
|
|
if this.token.kind == token::Comma {
|
|
this.bump();
|
|
}
|
|
expr
|
|
},
|
|
) {
|
|
return Ok(expr);
|
|
} else if let Some(lit) =
|
|
self.eat_metavar_seq(MetaVarKind::Literal, |this| this.parse_literal_maybe_minus())
|
|
{
|
|
return Ok(lit);
|
|
} else if let Some(block) =
|
|
self.eat_metavar_seq(MetaVarKind::Block, |this| this.parse_block())
|
|
{
|
|
return Ok(self.mk_expr(span, ExprKind::Block(block, None)));
|
|
} else if let Some(path) =
|
|
self.eat_metavar_seq(MetaVarKind::Path, |this| this.parse_path(PathStyle::Type))
|
|
{
|
|
return Ok(self.mk_expr(span, ExprKind::Path(None, path)));
|
|
}
|
|
|
|
// Outer attributes are already parsed and will be
|
|
// added to the return value after the fact.
|
|
|
|
let restrictions = self.restrictions;
|
|
self.with_res(restrictions - Restrictions::ALLOW_LET, |this| {
|
|
// Note: adding new syntax here? Don't forget to adjust `TokenKind::can_begin_expr()`.
|
|
let lo = this.token.span;
|
|
if let token::Literal(_) = this.token.kind {
|
|
// This match arm is a special-case of the `_` match arm below and
|
|
// could be removed without changing functionality, but it's faster
|
|
// to have it here, especially for programs with large constants.
|
|
this.parse_expr_lit()
|
|
} else if this.check(exp!(OpenParen)) {
|
|
this.parse_expr_tuple_parens(restrictions)
|
|
} else if this.check(exp!(OpenBrace)) {
|
|
this.parse_expr_block(None, lo, BlockCheckMode::Default)
|
|
} else if this.check(exp!(Or)) || this.check(exp!(OrOr)) {
|
|
this.parse_expr_closure().map_err(|mut err| {
|
|
// If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }`
|
|
// then suggest parens around the lhs.
|
|
if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) {
|
|
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
|
|
}
|
|
err
|
|
})
|
|
} else if this.check(exp!(OpenBracket)) {
|
|
this.parse_expr_array_or_repeat(exp!(CloseBracket))
|
|
} else if this.is_builtin() {
|
|
this.parse_expr_builtin()
|
|
} else if this.check_path() {
|
|
this.parse_expr_path_start()
|
|
} else if this.check_keyword(exp!(Move))
|
|
|| this.check_keyword(exp!(Use))
|
|
|| this.check_keyword(exp!(Static))
|
|
|| this.check_const_closure()
|
|
{
|
|
this.parse_expr_closure()
|
|
} else if this.eat_keyword(exp!(If)) {
|
|
this.parse_expr_if()
|
|
} else if this.check_keyword(exp!(For)) {
|
|
if this.choose_generics_over_qpath(1) {
|
|
this.parse_expr_closure()
|
|
} else {
|
|
assert!(this.eat_keyword(exp!(For)));
|
|
this.parse_expr_for(None, lo)
|
|
}
|
|
} else if this.eat_keyword(exp!(While)) {
|
|
this.parse_expr_while(None, lo)
|
|
} else if let Some(label) = this.eat_label() {
|
|
this.parse_expr_labeled(label, true)
|
|
} else if this.eat_keyword(exp!(Loop)) {
|
|
this.parse_expr_loop(None, lo).map_err(|mut err| {
|
|
err.span_label(lo, "while parsing this `loop` expression");
|
|
err
|
|
})
|
|
} else if this.eat_keyword(exp!(Match)) {
|
|
this.parse_expr_match().map_err(|mut err| {
|
|
err.span_label(lo, "while parsing this `match` expression");
|
|
err
|
|
})
|
|
} else if this.eat_keyword(exp!(Unsafe)) {
|
|
this.parse_expr_block(None, lo, BlockCheckMode::Unsafe(ast::UserProvided)).map_err(
|
|
|mut err| {
|
|
err.span_label(lo, "while parsing this `unsafe` expression");
|
|
err
|
|
},
|
|
)
|
|
} else if this.check_inline_const(0) {
|
|
this.parse_const_block(lo, false)
|
|
} else if this.may_recover() && this.is_do_catch_block() {
|
|
this.recover_do_catch()
|
|
} else if this.is_try_block() {
|
|
this.expect_keyword(exp!(Try))?;
|
|
this.parse_try_block(lo)
|
|
} else if this.eat_keyword(exp!(Return)) {
|
|
this.parse_expr_return()
|
|
} else if this.eat_keyword(exp!(Continue)) {
|
|
this.parse_expr_continue(lo)
|
|
} else if this.eat_keyword(exp!(Break)) {
|
|
this.parse_expr_break()
|
|
} else if this.eat_keyword(exp!(Yield)) {
|
|
this.parse_expr_yield()
|
|
} else if this.is_do_yeet() {
|
|
this.parse_expr_yeet()
|
|
} else if this.eat_keyword(exp!(Become)) {
|
|
this.parse_expr_become()
|
|
} else if this.check_keyword(exp!(Let)) {
|
|
this.parse_expr_let(restrictions)
|
|
} else if this.eat_keyword(exp!(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.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(exp!(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(exp!(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_noexpect(kw::Await) {
|
|
this.recover_incorrect_await_syntax(lo)
|
|
} else {
|
|
this.parse_expr_lit()
|
|
}
|
|
} else {
|
|
this.parse_expr_lit()
|
|
}
|
|
})
|
|
}
|
|
|
|
fn parse_expr_lit(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
match self.parse_opt_token_lit() {
|
|
Some((token_lit, _)) => {
|
|
let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Lit(token_lit));
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
None => self.try_macro_suggestion(),
|
|
}
|
|
}
|
|
|
|
fn parse_expr_tuple_parens(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
self.expect(exp!(OpenParen))?;
|
|
let (es, trailing_comma) = match self.parse_seq_to_end(
|
|
exp!(CloseParen),
|
|
SeqSep::trailing_allowed(exp!(Comma)),
|
|
|p| p.parse_expr_catch_underscore(restrictions.intersection(Restrictions::ALLOW_LET)),
|
|
) {
|
|
Ok(x) => x,
|
|
Err(err) => {
|
|
return Ok(self.recover_seq_parse_error(
|
|
exp!(OpenParen),
|
|
exp!(CloseParen),
|
|
lo,
|
|
err,
|
|
));
|
|
}
|
|
};
|
|
let kind = if es.len() == 1 && matches!(trailing_comma, Trailing::No) {
|
|
// `(e)` is parenthesized `e`.
|
|
ExprKind::Paren(es.into_iter().next().unwrap())
|
|
} else {
|
|
// `(e,)` is a tuple with only one field, `e`.
|
|
ExprKind::Tup(es)
|
|
};
|
|
let expr = self.mk_expr(lo.to(self.prev_token.span), kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
fn parse_expr_array_or_repeat(&mut self, close: ExpTokenPair<'_>) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
self.bump(); // `[` or other open delim
|
|
|
|
let kind = if self.eat(close) {
|
|
// Empty vector
|
|
ExprKind::Array(ThinVec::new())
|
|
} else {
|
|
// Non-empty vector
|
|
let first_expr = self.parse_expr()?;
|
|
if self.eat(exp!(Semi)) {
|
|
// Repeating array syntax: `[ 0; 512 ]`
|
|
let count = self.parse_expr_anon_const()?;
|
|
self.expect(close)?;
|
|
ExprKind::Repeat(first_expr, count)
|
|
} else if self.eat(exp!(Comma)) {
|
|
// Vector with two or more elements.
|
|
let sep = SeqSep::trailing_allowed(exp!(Comma));
|
|
let (mut exprs, _) = self.parse_seq_to_end(close, sep, |p| p.parse_expr())?;
|
|
exprs.insert(0, first_expr);
|
|
ExprKind::Array(exprs)
|
|
} else {
|
|
// Vector with one element
|
|
self.expect(close)?;
|
|
ExprKind::Array(thin_vec![first_expr])
|
|
}
|
|
};
|
|
let expr = self.mk_expr(lo.to(self.prev_token.span), kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
fn parse_expr_path_start(&mut self) -> PResult<'a, P<Expr>> {
|
|
let maybe_eq_tok = self.prev_token;
|
|
let (qself, path) = if self.eat_lt() {
|
|
let lt_span = self.prev_token.span;
|
|
let (qself, path) = self.parse_qpath(PathStyle::Expr).map_err(|mut err| {
|
|
// Suggests using '<=' if there is an error parsing qpath when the previous token
|
|
// is an '=' token. Only emits suggestion if the '<' token and '=' token are
|
|
// directly adjacent (i.e. '=<')
|
|
if maybe_eq_tok == TokenKind::Eq && maybe_eq_tok.span.hi() == lt_span.lo() {
|
|
let eq_lt = maybe_eq_tok.span.to(lt_span);
|
|
err.span_suggestion(eq_lt, "did you mean", "<=", Applicability::Unspecified);
|
|
}
|
|
err
|
|
})?;
|
|
(Some(qself), path)
|
|
} else {
|
|
(None, self.parse_path(PathStyle::Expr)?)
|
|
};
|
|
|
|
// `!`, as an operator, is prefix, so we know this isn't that.
|
|
let (span, kind) = if self.eat(exp!(Bang)) {
|
|
// MACRO INVOCATION expression
|
|
if qself.is_some() {
|
|
self.dcx().emit_err(errors::MacroInvocationWithQualifiedPath(path.span));
|
|
}
|
|
let lo = path.span;
|
|
let mac = P(MacCall { path, args: self.parse_delim_args()? });
|
|
(lo.to(self.prev_token.span), ExprKind::MacCall(mac))
|
|
} else if self.check(exp!(OpenBrace))
|
|
&& let Some(expr) = self.maybe_parse_struct_expr(&qself, &path)
|
|
{
|
|
if qself.is_some() {
|
|
self.psess.gated_spans.gate(sym::more_qualified_paths, path.span);
|
|
}
|
|
return expr;
|
|
} else {
|
|
(path.span, ExprKind::Path(qself, path))
|
|
};
|
|
|
|
let expr = self.mk_expr(span, kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `'label: $expr`. The label is already parsed.
|
|
pub(super) fn parse_expr_labeled(
|
|
&mut self,
|
|
label_: Label,
|
|
mut consume_colon: bool,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let lo = label_.ident.span;
|
|
let label = Some(label_);
|
|
let ate_colon = self.eat(exp!(Colon));
|
|
let tok_sp = self.token.span;
|
|
let expr = if self.eat_keyword(exp!(While)) {
|
|
self.parse_expr_while(label, lo)
|
|
} else if self.eat_keyword(exp!(For)) {
|
|
self.parse_expr_for(label, lo)
|
|
} else if self.eat_keyword(exp!(Loop)) {
|
|
self.parse_expr_loop(label, lo)
|
|
} else if self.check_noexpect(&token::OpenBrace) || self.token.is_metavar_block() {
|
|
self.parse_expr_block(label, lo, BlockCheckMode::Default)
|
|
} else if !ate_colon
|
|
&& self.may_recover()
|
|
&& (self.token.kind.close_delim().is_some() || self.token.is_punct())
|
|
&& could_be_unclosed_char_literal(label_.ident)
|
|
{
|
|
let (lit, _) =
|
|
self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| {
|
|
self_.dcx().create_err(errors::UnexpectedTokenAfterLabel {
|
|
span: self_.token.span,
|
|
remove_label: None,
|
|
enclose_in_block: None,
|
|
})
|
|
});
|
|
consume_colon = false;
|
|
Ok(self.mk_expr(lo, ExprKind::Lit(lit)))
|
|
} else if !ate_colon
|
|
&& (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt))
|
|
{
|
|
// We're probably inside of a `Path<'a>` that needs a turbofish
|
|
let guar = self.dcx().emit_err(errors::UnexpectedTokenAfterLabel {
|
|
span: self.token.span,
|
|
remove_label: None,
|
|
enclose_in_block: None,
|
|
});
|
|
consume_colon = false;
|
|
Ok(self.mk_expr_err(lo, guar))
|
|
} else {
|
|
let mut err = errors::UnexpectedTokenAfterLabel {
|
|
span: self.token.span,
|
|
remove_label: None,
|
|
enclose_in_block: None,
|
|
};
|
|
|
|
// Continue as an expression in an effort to recover on `'label: non_block_expr`.
|
|
let expr = self.parse_expr().map(|expr| {
|
|
let span = expr.span;
|
|
|
|
let found_labeled_breaks = {
|
|
struct FindLabeledBreaksVisitor;
|
|
|
|
impl<'ast> Visitor<'ast> for FindLabeledBreaksVisitor {
|
|
type Result = ControlFlow<()>;
|
|
fn visit_expr(&mut self, ex: &'ast Expr) -> ControlFlow<()> {
|
|
if let ExprKind::Break(Some(_label), _) = ex.kind {
|
|
ControlFlow::Break(())
|
|
} else {
|
|
walk_expr(self, ex)
|
|
}
|
|
}
|
|
}
|
|
|
|
FindLabeledBreaksVisitor.visit_expr(&expr).is_break()
|
|
};
|
|
|
|
// Suggestion involves adding a labeled block.
|
|
//
|
|
// If there are no breaks that may use this label, suggest removing the label and
|
|
// recover to the unmodified expression.
|
|
if !found_labeled_breaks {
|
|
err.remove_label = Some(lo.until(span));
|
|
|
|
return expr;
|
|
}
|
|
|
|
err.enclose_in_block = Some(errors::UnexpectedTokenAfterLabelSugg {
|
|
left: span.shrink_to_lo(),
|
|
right: span.shrink_to_hi(),
|
|
});
|
|
|
|
// Replace `'label: non_block_expr` with `'label: {non_block_expr}` in order to suppress future errors about `break 'label`.
|
|
let stmt = self.mk_stmt(span, StmtKind::Expr(expr));
|
|
let blk = self.mk_block(thin_vec![stmt], BlockCheckMode::Default, span);
|
|
self.mk_expr(span, ExprKind::Block(blk, label))
|
|
});
|
|
|
|
self.dcx().emit_err(err);
|
|
expr
|
|
}?;
|
|
|
|
if !ate_colon && consume_colon {
|
|
self.dcx().emit_err(errors::RequireColonAfterLabeledExpression {
|
|
span: expr.span,
|
|
label: lo,
|
|
label_end: lo.between(tok_sp),
|
|
});
|
|
}
|
|
|
|
Ok(expr)
|
|
}
|
|
|
|
/// Emit an error when a char is parsed as a lifetime or label because of a missing quote.
|
|
pub(super) fn recover_unclosed_char<L>(
|
|
&self,
|
|
ident: Ident,
|
|
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
|
|
err: impl FnOnce(&Self) -> Diag<'a>,
|
|
) -> L {
|
|
assert!(could_be_unclosed_char_literal(ident));
|
|
self.dcx()
|
|
.try_steal_modify_and_emit_err(ident.span, StashKey::LifetimeIsChar, |err| {
|
|
err.span_suggestion_verbose(
|
|
ident.span.shrink_to_hi(),
|
|
"add `'` to close the char literal",
|
|
"'",
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
})
|
|
.unwrap_or_else(|| {
|
|
err(self)
|
|
.with_span_suggestion_verbose(
|
|
ident.span.shrink_to_hi(),
|
|
"add `'` to close the char literal",
|
|
"'",
|
|
Applicability::MaybeIncorrect,
|
|
)
|
|
.emit()
|
|
});
|
|
let name = ident.without_first_quote().name;
|
|
mk_lit_char(name, ident.span)
|
|
}
|
|
|
|
/// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead.
|
|
fn recover_do_catch(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
|
|
self.bump(); // `do`
|
|
self.bump(); // `catch`
|
|
|
|
let span = lo.to(self.prev_token.span);
|
|
self.dcx().emit_err(errors::DoCatchSyntaxRemoved { span });
|
|
|
|
self.parse_try_block(lo)
|
|
}
|
|
|
|
/// Parse an expression if the token can begin one.
|
|
fn parse_expr_opt(&mut self) -> PResult<'a, Option<P<Expr>>> {
|
|
Ok(if self.token.can_begin_expr() { Some(self.parse_expr()?) } else { None })
|
|
}
|
|
|
|
/// Parse `"return" expr?`.
|
|
fn parse_expr_return(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.prev_token.span;
|
|
let kind = ExprKind::Ret(self.parse_expr_opt()?);
|
|
let expr = self.mk_expr(lo.to(self.prev_token.span), kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `"do" "yeet" expr?`.
|
|
fn parse_expr_yeet(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
|
|
self.bump(); // `do`
|
|
self.bump(); // `yeet`
|
|
|
|
let kind = ExprKind::Yeet(self.parse_expr_opt()?);
|
|
|
|
let span = lo.to(self.prev_token.span);
|
|
self.psess.gated_spans.gate(sym::yeet_expr, span);
|
|
let expr = self.mk_expr(span, kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `"become" expr`, with `"become"` token already eaten.
|
|
fn parse_expr_become(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.prev_token.span;
|
|
let kind = ExprKind::Become(self.parse_expr()?);
|
|
let span = lo.to(self.prev_token.span);
|
|
self.psess.gated_spans.gate(sym::explicit_tail_calls, span);
|
|
let expr = self.mk_expr(span, kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten.
|
|
/// If the label is followed immediately by a `:` token, the label and `:` are
|
|
/// parsed as part of the expression (i.e. a labeled loop). The language team has
|
|
/// decided in #87026 to require parentheses as a visual aid to avoid confusion if
|
|
/// the break expression of an unlabeled break is a labeled loop (as in
|
|
/// `break 'lbl: loop {}`); a labeled break with an unlabeled loop as its value
|
|
/// expression only gets a warning for compatibility reasons; and a labeled break
|
|
/// with a labeled loop does not even get a warning because there is no ambiguity.
|
|
fn parse_expr_break(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.prev_token.span;
|
|
let mut label = self.eat_label();
|
|
let kind = if self.token == token::Colon
|
|
&& let Some(label) = label.take()
|
|
{
|
|
// The value expression can be a labeled loop, see issue #86948, e.g.:
|
|
// `loop { break 'label: loop { break 'label 42; }; }`
|
|
let lexpr = self.parse_expr_labeled(label, true)?;
|
|
self.dcx().emit_err(errors::LabeledLoopInBreak {
|
|
span: lexpr.span,
|
|
sub: errors::WrapInParentheses::Expression {
|
|
left: lexpr.span.shrink_to_lo(),
|
|
right: lexpr.span.shrink_to_hi(),
|
|
},
|
|
});
|
|
Some(lexpr)
|
|
} else if self.token != token::OpenBrace
|
|
|| !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
|
|
{
|
|
let mut expr = self.parse_expr_opt()?;
|
|
if let Some(expr) = &mut expr {
|
|
if label.is_some()
|
|
&& match &expr.kind {
|
|
ExprKind::While(_, _, None)
|
|
| ExprKind::ForLoop { label: None, .. }
|
|
| ExprKind::Loop(_, None, _) => true,
|
|
ExprKind::Block(block, None) => {
|
|
matches!(block.rules, BlockCheckMode::Default)
|
|
}
|
|
_ => false,
|
|
}
|
|
{
|
|
self.psess.buffer_lint(
|
|
BREAK_WITH_LABEL_AND_LOOP,
|
|
lo.to(expr.span),
|
|
ast::CRATE_NODE_ID,
|
|
BuiltinLintDiag::BreakWithLabelAndLoop(expr.span),
|
|
);
|
|
}
|
|
|
|
// Recover `break label aaaaa`
|
|
if self.may_recover()
|
|
&& let ExprKind::Path(None, p) = &expr.kind
|
|
&& let [segment] = &*p.segments
|
|
&& let &ast::PathSegment { ident, args: None, .. } = segment
|
|
&& let Some(next) = self.parse_expr_opt()?
|
|
{
|
|
label = Some(self.recover_ident_into_label(ident));
|
|
*expr = next;
|
|
}
|
|
}
|
|
|
|
expr
|
|
} else {
|
|
None
|
|
};
|
|
let expr = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Break(label, kind));
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `"continue" label?`.
|
|
fn parse_expr_continue(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
|
|
let mut label = self.eat_label();
|
|
|
|
// Recover `continue label` -> `continue 'label`
|
|
if self.may_recover()
|
|
&& label.is_none()
|
|
&& let Some((ident, _)) = self.token.ident()
|
|
{
|
|
self.bump();
|
|
label = Some(self.recover_ident_into_label(ident));
|
|
}
|
|
|
|
let kind = ExprKind::Continue(label);
|
|
Ok(self.mk_expr(lo.to(self.prev_token.span), kind))
|
|
}
|
|
|
|
/// Parse `"yield" expr?`.
|
|
fn parse_expr_yield(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.prev_token.span;
|
|
let kind = ExprKind::Yield(YieldKind::Prefix(self.parse_expr_opt()?));
|
|
let span = lo.to(self.prev_token.span);
|
|
self.psess.gated_spans.gate(sym::yield_expr, span);
|
|
let expr = self.mk_expr(span, kind);
|
|
self.maybe_recover_from_bad_qpath(expr)
|
|
}
|
|
|
|
/// Parse `builtin # ident(args,*)`.
|
|
fn parse_expr_builtin(&mut self) -> PResult<'a, P<Expr>> {
|
|
self.parse_builtin(|this, lo, ident| {
|
|
Ok(match ident.name {
|
|
sym::offset_of => Some(this.parse_expr_offset_of(lo)?),
|
|
sym::type_ascribe => Some(this.parse_expr_type_ascribe(lo)?),
|
|
sym::wrap_binder => {
|
|
Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Wrap)?)
|
|
}
|
|
sym::unwrap_binder => {
|
|
Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Unwrap)?)
|
|
}
|
|
_ => None,
|
|
})
|
|
})
|
|
}
|
|
|
|
pub(crate) fn parse_builtin<T>(
|
|
&mut self,
|
|
parse: impl FnOnce(&mut Parser<'a>, Span, Ident) -> PResult<'a, Option<T>>,
|
|
) -> PResult<'a, T> {
|
|
let lo = self.token.span;
|
|
|
|
self.bump(); // `builtin`
|
|
self.bump(); // `#`
|
|
|
|
let Some((ident, IdentIsRaw::No)) = self.token.ident() else {
|
|
let err = self.dcx().create_err(errors::ExpectedBuiltinIdent { span: self.token.span });
|
|
return Err(err);
|
|
};
|
|
self.psess.gated_spans.gate(sym::builtin_syntax, ident.span);
|
|
self.bump();
|
|
|
|
self.expect(exp!(OpenParen))?;
|
|
let ret = if let Some(res) = parse(self, lo, ident)? {
|
|
Ok(res)
|
|
} else {
|
|
let err = self.dcx().create_err(errors::UnknownBuiltinConstruct {
|
|
span: lo.to(ident.span),
|
|
name: ident,
|
|
});
|
|
return Err(err);
|
|
};
|
|
self.expect(exp!(CloseParen))?;
|
|
|
|
ret
|
|
}
|
|
|
|
/// Built-in macro for `offset_of!` expressions.
|
|
pub(crate) fn parse_expr_offset_of(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
|
|
let container = self.parse_ty()?;
|
|
self.expect(exp!(Comma))?;
|
|
|
|
let fields = self.parse_floating_field_access()?;
|
|
let trailing_comma = self.eat_noexpect(&TokenKind::Comma);
|
|
|
|
if let Err(mut e) = self.expect_one_of(&[], &[exp!(CloseParen)]) {
|
|
if trailing_comma {
|
|
e.note("unexpected third argument to offset_of");
|
|
} else {
|
|
e.note("offset_of expects dot-separated field and variant names");
|
|
}
|
|
e.emit();
|
|
}
|
|
|
|
// Eat tokens until the macro call ends.
|
|
if self.may_recover() {
|
|
while !self.token.kind.is_close_delim_or_eof() {
|
|
self.bump();
|
|
}
|
|
}
|
|
|
|
let span = lo.to(self.token.span);
|
|
Ok(self.mk_expr(span, ExprKind::OffsetOf(container, fields)))
|
|
}
|
|
|
|
/// Built-in macro for type ascription expressions.
|
|
pub(crate) fn parse_expr_type_ascribe(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
|
|
let expr = self.parse_expr()?;
|
|
self.expect(exp!(Comma))?;
|
|
let ty = self.parse_ty()?;
|
|
let span = lo.to(self.token.span);
|
|
Ok(self.mk_expr(span, ExprKind::Type(expr, ty)))
|
|
}
|
|
|
|
pub(crate) fn parse_expr_unsafe_binder_cast(
|
|
&mut self,
|
|
lo: Span,
|
|
kind: UnsafeBinderCastKind,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let expr = self.parse_expr()?;
|
|
let ty = if self.eat(exp!(Comma)) { Some(self.parse_ty()?) } else { None };
|
|
let span = lo.to(self.token.span);
|
|
Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty)))
|
|
}
|
|
|
|
/// Returns a string literal if the next token is a string literal.
|
|
/// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind,
|
|
/// and returns `None` if the next token is not literal at all.
|
|
pub fn parse_str_lit(&mut self) -> Result<ast::StrLit, Option<MetaItemLit>> {
|
|
match self.parse_opt_meta_item_lit() {
|
|
Some(lit) => match lit.kind {
|
|
ast::LitKind::Str(symbol_unescaped, style) => Ok(ast::StrLit {
|
|
style,
|
|
symbol: lit.symbol,
|
|
suffix: lit.suffix,
|
|
span: lit.span,
|
|
symbol_unescaped,
|
|
}),
|
|
_ => Err(Some(lit)),
|
|
},
|
|
None => Err(None),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn mk_token_lit_char(name: Symbol, span: Span) -> (token::Lit, Span) {
|
|
(token::Lit { symbol: name, suffix: None, kind: token::Char }, span)
|
|
}
|
|
|
|
fn mk_meta_item_lit_char(name: Symbol, span: Span) -> MetaItemLit {
|
|
ast::MetaItemLit {
|
|
symbol: name,
|
|
suffix: None,
|
|
kind: ast::LitKind::Char(name.as_str().chars().next().unwrap_or('_')),
|
|
span,
|
|
}
|
|
}
|
|
|
|
fn handle_missing_lit<L>(
|
|
&mut self,
|
|
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
|
|
) -> PResult<'a, L> {
|
|
let token = self.token;
|
|
let err = |self_: &Self| {
|
|
let msg = format!("unexpected token: {}", super::token_descr(&token));
|
|
self_.dcx().struct_span_err(token.span, msg)
|
|
};
|
|
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
|
|
// makes sense.
|
|
if let Some((ident, IdentIsRaw::No)) = self.token.lifetime()
|
|
&& could_be_unclosed_char_literal(ident)
|
|
{
|
|
let lt = self.expect_lifetime();
|
|
Ok(self.recover_unclosed_char(lt.ident, mk_lit_char, err))
|
|
} else {
|
|
Err(err(self))
|
|
}
|
|
}
|
|
|
|
pub(super) fn parse_token_lit(&mut self) -> PResult<'a, (token::Lit, Span)> {
|
|
self.parse_opt_token_lit()
|
|
.ok_or(())
|
|
.or_else(|()| self.handle_missing_lit(Parser::mk_token_lit_char))
|
|
}
|
|
|
|
pub(super) fn parse_meta_item_lit(&mut self) -> PResult<'a, MetaItemLit> {
|
|
self.parse_opt_meta_item_lit()
|
|
.ok_or(())
|
|
.or_else(|()| self.handle_missing_lit(Parser::mk_meta_item_lit_char))
|
|
}
|
|
|
|
fn recover_after_dot(&mut self) {
|
|
if self.token == token::Dot {
|
|
// Attempt to recover `.4` as `0.4`. We don't currently have any syntax where
|
|
// dot would follow an optional literal, so we do this unconditionally.
|
|
let recovered = self.look_ahead(1, |next_token| {
|
|
// If it's an integer that looks like a float, then recover as such.
|
|
//
|
|
// We will never encounter the exponent part of a floating
|
|
// point literal here, since there's no use of the exponent
|
|
// syntax that also constitutes a valid integer, so we need
|
|
// not check for that.
|
|
if let token::Literal(token::Lit { kind: token::Integer, symbol, suffix }) =
|
|
next_token.kind
|
|
&& suffix.is_none_or(|s| s == sym::f32 || s == sym::f64)
|
|
&& symbol.as_str().chars().all(|c| c.is_numeric() || c == '_')
|
|
&& self.token.span.hi() == next_token.span.lo()
|
|
{
|
|
let s = String::from("0.") + symbol.as_str();
|
|
let kind = TokenKind::lit(token::Float, Symbol::intern(&s), suffix);
|
|
Some(Token::new(kind, self.token.span.to(next_token.span)))
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
if let Some(recovered) = recovered {
|
|
self.dcx().emit_err(errors::FloatLiteralRequiresIntegerPart {
|
|
span: recovered.span,
|
|
suggestion: recovered.span.shrink_to_lo(),
|
|
});
|
|
self.bump();
|
|
self.token = recovered;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Keep this in sync with `Token::can_begin_literal_maybe_minus` and
|
|
/// `Lit::from_token` (excluding unary negation).
|
|
fn eat_token_lit(&mut self) -> Option<token::Lit> {
|
|
match self.token.uninterpolate().kind {
|
|
token::Ident(name, IdentIsRaw::No) if name.is_bool_lit() => {
|
|
self.bump();
|
|
Some(token::Lit::new(token::Bool, name, None))
|
|
}
|
|
token::Literal(token_lit) => {
|
|
self.bump();
|
|
Some(token_lit)
|
|
}
|
|
token::OpenInvisible(InvisibleOrigin::MetaVar(MetaVarKind::Literal)) => {
|
|
let lit = self
|
|
.eat_metavar_seq(MetaVarKind::Literal, |this| this.parse_literal_maybe_minus())
|
|
.expect("metavar seq literal");
|
|
let ast::ExprKind::Lit(token_lit) = lit.kind else {
|
|
panic!("didn't reparse a literal");
|
|
};
|
|
Some(token_lit)
|
|
}
|
|
token::OpenInvisible(InvisibleOrigin::MetaVar(
|
|
mv_kind @ MetaVarKind::Expr { can_begin_literal_maybe_minus: true, .. },
|
|
)) => {
|
|
let expr = self
|
|
.eat_metavar_seq(mv_kind, |this| this.parse_expr())
|
|
.expect("metavar seq expr");
|
|
if let ast::ExprKind::Lit(token_lit) = expr.kind {
|
|
Some(token_lit)
|
|
} else if let ast::ExprKind::Unary(UnOp::Neg, inner) = &expr.kind
|
|
&& let ast::Expr { kind: ast::ExprKind::Lit(_), .. } = **inner
|
|
{
|
|
None
|
|
} else {
|
|
panic!("unexpected reparsed expr: {:?}", expr.kind);
|
|
}
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Matches `lit = true | false | token_lit`.
|
|
/// Returns `None` if the next token is not a literal.
|
|
fn parse_opt_token_lit(&mut self) -> Option<(token::Lit, Span)> {
|
|
self.recover_after_dot();
|
|
let span = self.token.span;
|
|
self.eat_token_lit().map(|token_lit| (token_lit, span))
|
|
}
|
|
|
|
/// Matches `lit = true | false | token_lit`.
|
|
/// Returns `None` if the next token is not a literal.
|
|
fn parse_opt_meta_item_lit(&mut self) -> Option<MetaItemLit> {
|
|
self.recover_after_dot();
|
|
let span = self.token.span;
|
|
let uninterpolated_span = self.token_uninterpolated_span();
|
|
self.eat_token_lit().map(|token_lit| {
|
|
match MetaItemLit::from_token_lit(token_lit, span) {
|
|
Ok(lit) => lit,
|
|
Err(err) => {
|
|
let guar = report_lit_error(&self.psess, err, token_lit, uninterpolated_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(token_lit.kind, token_lit.symbol, None);
|
|
let symbol = Symbol::intern(&suffixless_lit.to_string());
|
|
let token_lit = token::Lit::new(token::Err(guar), symbol, token_lit.suffix);
|
|
MetaItemLit::from_token_lit(token_lit, uninterpolated_span).unwrap()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(super) fn expect_no_tuple_index_suffix(&self, span: Span, suffix: Symbol) {
|
|
if [sym::i32, sym::u32, sym::isize, sym::usize].contains(&suffix) {
|
|
// #59553: warn instead of reject out of hand to allow the fix to percolate
|
|
// through the ecosystem when people fix their macros
|
|
self.dcx().emit_warn(errors::InvalidLiteralSuffixOnTupleIndex {
|
|
span,
|
|
suffix,
|
|
exception: true,
|
|
});
|
|
} else {
|
|
self.dcx().emit_err(errors::InvalidLiteralSuffixOnTupleIndex {
|
|
span,
|
|
suffix,
|
|
exception: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Matches `'-' lit | lit` (cf. `ast_validation::AstValidator::check_expr_within_pat`).
|
|
/// Keep this in sync with `Token::can_begin_literal_maybe_minus`.
|
|
pub fn parse_literal_maybe_minus(&mut self) -> PResult<'a, P<Expr>> {
|
|
if let Some(expr) = self.eat_metavar_seq_with_matcher(
|
|
|mv_kind| matches!(mv_kind, MetaVarKind::Expr { .. }),
|
|
|this| {
|
|
// FIXME(nnethercote) The `expr` case should only match if
|
|
// `e` is an `ExprKind::Lit` or an `ExprKind::Unary` containing
|
|
// an `UnOp::Neg` and an `ExprKind::Lit`, like how
|
|
// `can_begin_literal_maybe_minus` works. But this method has
|
|
// been over-accepting for a long time, and to make that change
|
|
// here requires also changing some `parse_literal_maybe_minus`
|
|
// call sites to accept additional expression kinds. E.g.
|
|
// `ExprKind::Path` must be accepted when parsing range
|
|
// patterns. That requires some care. So for now, we continue
|
|
// being less strict here than we should be.
|
|
this.parse_expr()
|
|
},
|
|
) {
|
|
return Ok(expr);
|
|
} else if let Some(lit) =
|
|
self.eat_metavar_seq(MetaVarKind::Literal, |this| this.parse_literal_maybe_minus())
|
|
{
|
|
return Ok(lit);
|
|
}
|
|
|
|
let lo = self.token.span;
|
|
let minus_present = self.eat(exp!(Minus));
|
|
let (token_lit, span) = self.parse_token_lit()?;
|
|
let expr = self.mk_expr(span, ExprKind::Lit(token_lit));
|
|
|
|
if minus_present {
|
|
Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_unary(UnOp::Neg, expr)))
|
|
} else {
|
|
Ok(expr)
|
|
}
|
|
}
|
|
|
|
fn is_array_like_block(&mut self) -> bool {
|
|
self.token.kind == TokenKind::OpenBrace
|
|
&& self
|
|
.look_ahead(1, |t| matches!(t.kind, TokenKind::Ident(..) | TokenKind::Literal(_)))
|
|
&& self.look_ahead(2, |t| t == &token::Comma)
|
|
&& self.look_ahead(3, |t| t.can_begin_expr())
|
|
}
|
|
|
|
/// Emits a suggestion if it looks like the user meant an array but
|
|
/// accidentally used braces, causing the code to be interpreted as a block
|
|
/// expression.
|
|
fn maybe_suggest_brackets_instead_of_braces(&mut self, lo: Span) -> Option<P<Expr>> {
|
|
let mut snapshot = self.create_snapshot_for_diagnostic();
|
|
match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) {
|
|
Ok(arr) => {
|
|
let guar = self.dcx().emit_err(errors::ArrayBracketsInsteadOfBraces {
|
|
span: arr.span,
|
|
sub: errors::ArrayBracketsInsteadOfBracesSugg {
|
|
left: lo,
|
|
right: snapshot.prev_token.span,
|
|
},
|
|
});
|
|
|
|
self.restore_snapshot(snapshot);
|
|
Some(self.mk_expr_err(arr.span, guar))
|
|
}
|
|
Err(e) => {
|
|
e.cancel();
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn suggest_missing_semicolon_before_array(
|
|
&self,
|
|
prev_span: Span,
|
|
open_delim_span: Span,
|
|
) -> PResult<'a, ()> {
|
|
if !self.may_recover() {
|
|
return Ok(());
|
|
}
|
|
|
|
if self.token == token::Comma {
|
|
if !self.psess.source_map().is_multiline(prev_span.until(self.token.span)) {
|
|
return Ok(());
|
|
}
|
|
let mut snapshot = self.create_snapshot_for_diagnostic();
|
|
snapshot.bump();
|
|
match snapshot.parse_seq_to_before_end(
|
|
exp!(CloseBracket),
|
|
SeqSep::trailing_allowed(exp!(Comma)),
|
|
|p| p.parse_expr(),
|
|
) {
|
|
Ok(_)
|
|
// When the close delim is `)`, `token.kind` is expected to be `token::CloseParen`,
|
|
// but the actual `token.kind` is `token::CloseBracket`.
|
|
// This is because the `token.kind` of the close delim is treated as the same as
|
|
// that of the open delim in `TokenTreesReader::parse_token_tree`, even if the delimiters of them are different.
|
|
// Therefore, `token.kind` should not be compared here.
|
|
if snapshot
|
|
.span_to_snippet(snapshot.token.span)
|
|
.is_ok_and(|snippet| snippet == "]") =>
|
|
{
|
|
return Err(self.dcx().create_err(errors::MissingSemicolonBeforeArray {
|
|
open_delim: open_delim_span,
|
|
semicolon: prev_span.shrink_to_hi(),
|
|
}));
|
|
}
|
|
Ok(_) => (),
|
|
Err(err) => err.cancel(),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses a block or unsafe block.
|
|
pub(super) fn parse_expr_block(
|
|
&mut self,
|
|
opt_label: Option<Label>,
|
|
lo: Span,
|
|
blk_mode: BlockCheckMode,
|
|
) -> PResult<'a, P<Expr>> {
|
|
if self.may_recover() && self.is_array_like_block() {
|
|
if let Some(arr) = self.maybe_suggest_brackets_instead_of_braces(lo) {
|
|
return Ok(arr);
|
|
}
|
|
}
|
|
|
|
if self.token.is_metavar_block() {
|
|
self.dcx().emit_err(errors::InvalidBlockMacroSegment {
|
|
span: self.token.span,
|
|
context: lo.to(self.token.span),
|
|
wrap: errors::WrapInExplicitBlock {
|
|
lo: self.token.span.shrink_to_lo(),
|
|
hi: self.token.span.shrink_to_hi(),
|
|
},
|
|
});
|
|
}
|
|
|
|
let (attrs, blk) = self.parse_block_common(lo, blk_mode, None)?;
|
|
Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs))
|
|
}
|
|
|
|
/// Parse a block which takes no attributes and has no label
|
|
fn parse_simple_block(&mut self) -> PResult<'a, P<Expr>> {
|
|
let blk = self.parse_block()?;
|
|
Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None)))
|
|
}
|
|
|
|
/// Parses a closure expression (e.g., `move |args| expr`).
|
|
fn parse_expr_closure(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
|
|
let before = self.prev_token;
|
|
let binder = if self.check_keyword(exp!(For)) {
|
|
let lo = self.token.span;
|
|
let (lifetime_defs, _) = self.parse_late_bound_lifetime_defs()?;
|
|
let span = lo.to(self.prev_token.span);
|
|
|
|
self.psess.gated_spans.gate(sym::closure_lifetime_binder, span);
|
|
|
|
ClosureBinder::For { span, generic_params: lifetime_defs }
|
|
} else {
|
|
ClosureBinder::NotPresent
|
|
};
|
|
|
|
let constness = self.parse_closure_constness();
|
|
|
|
let movability =
|
|
if self.eat_keyword(exp!(Static)) { Movability::Static } else { Movability::Movable };
|
|
|
|
let coroutine_kind = if self.token_uninterpolated_span().at_least_rust_2018() {
|
|
self.parse_coroutine_kind(Case::Sensitive)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let capture_clause = self.parse_capture_clause()?;
|
|
let (fn_decl, fn_arg_span) = self.parse_fn_block_decl()?;
|
|
let decl_hi = self.prev_token.span;
|
|
let mut body = match &fn_decl.output {
|
|
// No return type.
|
|
FnRetTy::Default(_) => {
|
|
let restrictions =
|
|
self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET;
|
|
let prev = self.prev_token;
|
|
let token = self.token;
|
|
let attrs = self.parse_outer_attributes()?;
|
|
match self.parse_expr_res(restrictions, attrs) {
|
|
Ok((expr, _)) => expr,
|
|
Err(err) => self.recover_closure_body(err, before, prev, token, lo, decl_hi)?,
|
|
}
|
|
}
|
|
// Explicit return type (`->`) needs block `-> T { }`.
|
|
FnRetTy::Ty(ty) => self.parse_closure_block_body(ty.span)?,
|
|
};
|
|
|
|
match coroutine_kind {
|
|
Some(CoroutineKind::Async { .. }) => {}
|
|
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.psess.gated_spans.gate(sym::gen_blocks, span);
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
if self.token == TokenKind::Semi
|
|
&& let Some(last) = self.token_cursor.stack.last()
|
|
&& let Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _)) = last.curr()
|
|
&& self.may_recover()
|
|
{
|
|
// It is likely that the closure body is a block but where the
|
|
// braces have been removed. We will recover and eat the next
|
|
// statements later in the parsing process.
|
|
body = self.mk_expr_err(
|
|
body.span,
|
|
self.dcx().span_delayed_bug(body.span, "recovered a closure body as a block"),
|
|
);
|
|
}
|
|
|
|
let body_span = body.span;
|
|
|
|
let closure = self.mk_expr(
|
|
lo.to(body.span),
|
|
ExprKind::Closure(Box::new(ast::Closure {
|
|
binder,
|
|
capture_clause,
|
|
constness,
|
|
coroutine_kind,
|
|
movability,
|
|
fn_decl,
|
|
body,
|
|
fn_decl_span: lo.to(decl_hi),
|
|
fn_arg_span,
|
|
})),
|
|
);
|
|
|
|
// Disable recovery for closure body
|
|
let spans =
|
|
ClosureSpans { whole_closure: closure.span, closing_pipe: decl_hi, body: body_span };
|
|
self.current_closure = Some(spans);
|
|
|
|
Ok(closure)
|
|
}
|
|
|
|
/// If an explicit return type is given, require a block to appear (RFC 968).
|
|
fn parse_closure_block_body(&mut self, ret_span: Span) -> PResult<'a, P<Expr>> {
|
|
if self.may_recover()
|
|
&& self.token.can_begin_expr()
|
|
&& self.token.kind != TokenKind::OpenBrace
|
|
&& !self.token.is_metavar_block()
|
|
{
|
|
let snapshot = self.create_snapshot_for_diagnostic();
|
|
let restrictions =
|
|
self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET;
|
|
let tok = self.token.clone();
|
|
match self.parse_expr_res(restrictions, AttrWrapper::empty()) {
|
|
Ok((expr, _)) => {
|
|
let descr = super::token_descr(&tok);
|
|
let mut diag = self
|
|
.dcx()
|
|
.struct_span_err(tok.span, format!("expected `{{`, found {descr}"));
|
|
diag.span_label(
|
|
ret_span,
|
|
"explicit return type requires closure body to be enclosed in braces",
|
|
);
|
|
diag.multipart_suggestion_verbose(
|
|
"wrap the expression in curly braces",
|
|
vec![
|
|
(expr.span.shrink_to_lo(), "{ ".to_string()),
|
|
(expr.span.shrink_to_hi(), " }".to_string()),
|
|
],
|
|
Applicability::MachineApplicable,
|
|
);
|
|
diag.emit();
|
|
return Ok(expr);
|
|
}
|
|
Err(diag) => {
|
|
diag.cancel();
|
|
self.restore_snapshot(snapshot);
|
|
}
|
|
}
|
|
}
|
|
|
|
let body_lo = self.token.span;
|
|
self.parse_expr_block(None, body_lo, BlockCheckMode::Default)
|
|
}
|
|
|
|
/// Parses an optional `move` or `use` prefix to a closure-like construct.
|
|
fn parse_capture_clause(&mut self) -> PResult<'a, CaptureBy> {
|
|
if self.eat_keyword(exp!(Move)) {
|
|
let move_kw_span = self.prev_token.span;
|
|
// Check for `move async` and recover
|
|
if self.check_keyword(exp!(Async)) {
|
|
let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo);
|
|
Err(self
|
|
.dcx()
|
|
.create_err(errors::AsyncMoveOrderIncorrect { span: move_async_span }))
|
|
} else {
|
|
Ok(CaptureBy::Value { move_kw: move_kw_span })
|
|
}
|
|
} else if self.eat_keyword(exp!(Use)) {
|
|
let use_kw_span = self.prev_token.span;
|
|
self.psess.gated_spans.gate(sym::ergonomic_clones, use_kw_span);
|
|
// Check for `use async` and recover
|
|
if self.check_keyword(exp!(Async)) {
|
|
let use_async_span = self.token.span.with_lo(self.prev_token.span.data().lo);
|
|
Err(self.dcx().create_err(errors::AsyncUseOrderIncorrect { span: use_async_span }))
|
|
} else {
|
|
Ok(CaptureBy::Use { use_kw: use_kw_span })
|
|
}
|
|
} else {
|
|
Ok(CaptureBy::Ref)
|
|
}
|
|
}
|
|
|
|
/// Parses the `|arg, arg|` header of a closure.
|
|
fn parse_fn_block_decl(&mut self) -> PResult<'a, (P<FnDecl>, Span)> {
|
|
let arg_start = self.token.span.lo();
|
|
|
|
let inputs = if self.eat(exp!(OrOr)) {
|
|
ThinVec::new()
|
|
} else {
|
|
self.expect(exp!(Or))?;
|
|
let args = self
|
|
.parse_seq_to_before_tokens(
|
|
&[exp!(Or)],
|
|
&[&token::OrOr],
|
|
SeqSep::trailing_allowed(exp!(Comma)),
|
|
|p| p.parse_fn_block_param(),
|
|
)?
|
|
.0;
|
|
self.expect_or()?;
|
|
args
|
|
};
|
|
let arg_span = self.prev_token.span.with_lo(arg_start);
|
|
let output =
|
|
self.parse_ret_ty(AllowPlus::Yes, RecoverQPath::Yes, RecoverReturnSign::Yes)?;
|
|
|
|
Ok((P(FnDecl { inputs, output }), arg_span))
|
|
}
|
|
|
|
/// Parses a parameter in a closure header (e.g., `|arg, arg|`).
|
|
fn parse_fn_block_param(&mut self) -> PResult<'a, Param> {
|
|
let lo = self.token.span;
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
|
|
let pat = this.parse_pat_no_top_alt(Some(Expected::ParameterName), None)?;
|
|
let ty = if this.eat(exp!(Colon)) {
|
|
this.parse_ty()?
|
|
} else {
|
|
this.mk_ty(pat.span, TyKind::Infer)
|
|
};
|
|
|
|
Ok((
|
|
Param {
|
|
attrs,
|
|
ty,
|
|
pat,
|
|
span: lo.to(this.prev_token.span),
|
|
id: DUMMY_NODE_ID,
|
|
is_placeholder: false,
|
|
},
|
|
Trailing::from(this.token == token::Comma),
|
|
UsePreAttrPos::No,
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Parses an `if` expression (`if` token already eaten).
|
|
fn parse_expr_if(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.prev_token.span;
|
|
// Scoping code checks the top level edition of the `if`; let's match it here.
|
|
// The `CondChecker` also checks the edition of the `let` itself, just to make sure.
|
|
let let_chains_policy = LetChainsPolicy::EditionDependent { current_edition: lo.edition() };
|
|
let cond = self.parse_expr_cond(let_chains_policy)?;
|
|
self.parse_if_after_cond(lo, cond)
|
|
}
|
|
|
|
fn parse_if_after_cond(&mut self, lo: Span, mut cond: P<Expr>) -> PResult<'a, P<Expr>> {
|
|
let cond_span = cond.span;
|
|
// Tries to interpret `cond` as either a missing expression if it's a block,
|
|
// or as an unfinished expression if it's a binop and the RHS is a block.
|
|
// We could probably add more recoveries here too...
|
|
let mut recover_block_from_condition = |this: &mut Self| {
|
|
let block = match &mut cond.kind {
|
|
ExprKind::Binary(Spanned { span: binop_span, .. }, _, right)
|
|
if let ExprKind::Block(_, None) = right.kind =>
|
|
{
|
|
let guar = this.dcx().emit_err(errors::IfExpressionMissingThenBlock {
|
|
if_span: lo,
|
|
missing_then_block_sub:
|
|
errors::IfExpressionMissingThenBlockSub::UnfinishedCondition(
|
|
cond_span.shrink_to_lo().to(*binop_span),
|
|
),
|
|
let_else_sub: None,
|
|
});
|
|
std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi(), guar))
|
|
}
|
|
ExprKind::Block(_, None) => {
|
|
let guar = this.dcx().emit_err(errors::IfExpressionMissingCondition {
|
|
if_span: lo.with_neighbor(cond.span).shrink_to_hi(),
|
|
block_span: self.psess.source_map().start_point(cond_span),
|
|
});
|
|
std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi(), guar))
|
|
}
|
|
_ => {
|
|
return None;
|
|
}
|
|
};
|
|
if let ExprKind::Block(block, _) = &block.kind {
|
|
Some(block.clone())
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
};
|
|
// Parse then block
|
|
let thn = if self.token.is_keyword(kw::Else) {
|
|
if let Some(block) = recover_block_from_condition(self) {
|
|
block
|
|
} else {
|
|
let let_else_sub = matches!(cond.kind, ExprKind::Let(..))
|
|
.then(|| errors::IfExpressionLetSomeSub { if_span: lo.until(cond_span) });
|
|
|
|
let guar = self.dcx().emit_err(errors::IfExpressionMissingThenBlock {
|
|
if_span: lo,
|
|
missing_then_block_sub: errors::IfExpressionMissingThenBlockSub::AddThenBlock(
|
|
cond_span.shrink_to_hi(),
|
|
),
|
|
let_else_sub,
|
|
});
|
|
self.mk_block_err(cond_span.shrink_to_hi(), guar)
|
|
}
|
|
} else {
|
|
let attrs = self.parse_outer_attributes()?; // For recovery.
|
|
let maybe_fatarrow = self.token;
|
|
let block = if self.check(exp!(OpenBrace)) {
|
|
self.parse_block()?
|
|
} else if let Some(block) = recover_block_from_condition(self) {
|
|
block
|
|
} else {
|
|
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| {
|
|
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 {
|
|
// Look for usages of '=>' where '>=' might be intended
|
|
if maybe_fatarrow == token::FatArrow {
|
|
err.span_suggestion(
|
|
maybe_fatarrow.span,
|
|
"you might have meant to write a \"greater than or equal to\" comparison",
|
|
">=",
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
}
|
|
err.span_note(
|
|
cond_span,
|
|
"the `if` expression is missing a block after this condition",
|
|
);
|
|
}
|
|
err
|
|
})?
|
|
};
|
|
self.error_on_if_block_attrs(lo, false, block.span, attrs);
|
|
block
|
|
};
|
|
let els = if self.eat_keyword(exp!(Else)) { Some(self.parse_expr_else()?) } else { None };
|
|
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els)))
|
|
}
|
|
|
|
/// Parses the condition of a `if` or `while` expression.
|
|
///
|
|
/// The specified `edition` in `let_chains_policy` should be that of the whole `if` construct,
|
|
/// i.e. the same span we use to later decide whether the drop behaviour should be that of
|
|
/// edition `..=2021` or that of `2024..`.
|
|
// Public because it is used in rustfmt forks such as https://github.com/tucant/rustfmt/blob/30c83df9e1db10007bdd16dafce8a86b404329b2/src/parse/macros/html.rs#L57 for custom if expressions.
|
|
pub fn parse_expr_cond(&mut self, let_chains_policy: LetChainsPolicy) -> PResult<'a, P<Expr>> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let (mut cond, _) =
|
|
self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL | Restrictions::ALLOW_LET, attrs)?;
|
|
|
|
CondChecker::new(self, let_chains_policy).visit_expr(&mut cond);
|
|
|
|
Ok(cond)
|
|
}
|
|
|
|
/// Parses a `let $pat = $expr` pseudo-expression.
|
|
fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
|
|
let recovered = if !restrictions.contains(Restrictions::ALLOW_LET) {
|
|
let err = errors::ExpectedExpressionFoundLet {
|
|
span: self.token.span,
|
|
reason: ForbiddenLetReason::OtherForbidden,
|
|
missing_let: None,
|
|
comparison: None,
|
|
};
|
|
if self.prev_token == token::Or {
|
|
// This was part of a closure, the that part of the parser recover.
|
|
return Err(self.dcx().create_err(err));
|
|
} else {
|
|
Recovered::Yes(self.dcx().emit_err(err))
|
|
}
|
|
} else {
|
|
Recovered::No
|
|
};
|
|
self.bump(); // Eat `let` token
|
|
let lo = self.prev_token.span;
|
|
let pat = self.parse_pat_no_top_guard(
|
|
None,
|
|
RecoverComma::Yes,
|
|
RecoverColon::Yes,
|
|
CommaRecoveryMode::LikelyTuple,
|
|
)?;
|
|
if self.token == token::EqEq {
|
|
self.dcx().emit_err(errors::ExpectedEqForLetExpr {
|
|
span: self.token.span,
|
|
sugg_span: self.token.span,
|
|
});
|
|
self.bump();
|
|
} else {
|
|
self.expect(exp!(Eq))?;
|
|
}
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let (expr, _) =
|
|
self.parse_expr_assoc_with(Bound::Excluded(prec_let_scrutinee_needs_par()), attrs)?;
|
|
let span = lo.to(expr.span);
|
|
Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span, recovered)))
|
|
}
|
|
|
|
/// Parses an `else { ... }` expression (`else` token already eaten).
|
|
fn parse_expr_else(&mut self) -> PResult<'a, P<Expr>> {
|
|
let else_span = self.prev_token.span; // `else`
|
|
let attrs = self.parse_outer_attributes()?; // For recovery.
|
|
let expr = if self.eat_keyword(exp!(If)) {
|
|
ensure_sufficient_stack(|| self.parse_expr_if())?
|
|
} else if self.check(exp!(OpenBrace)) {
|
|
self.parse_simple_block()?
|
|
} else {
|
|
let snapshot = self.create_snapshot_for_diagnostic();
|
|
let first_tok = super::token_descr(&self.token);
|
|
let first_tok_span = self.token.span;
|
|
match self.parse_expr() {
|
|
Ok(cond)
|
|
// Try to guess the difference between a "condition-like" vs
|
|
// "statement-like" expression.
|
|
//
|
|
// We are seeing the following code, in which $cond is neither
|
|
// ExprKind::Block nor ExprKind::If (the 2 cases wherein this
|
|
// would be valid syntax).
|
|
//
|
|
// if ... {
|
|
// } else $cond
|
|
//
|
|
// If $cond is "condition-like" such as ExprKind::Binary, we
|
|
// want to suggest inserting `if`.
|
|
//
|
|
// if ... {
|
|
// } else if a == b {
|
|
// ^^
|
|
// }
|
|
//
|
|
// We account for macro calls that were meant as conditions as well.
|
|
//
|
|
// if ... {
|
|
// } else if macro! { foo bar } {
|
|
// ^^
|
|
// }
|
|
//
|
|
// If $cond is "statement-like" such as ExprKind::While then we
|
|
// want to suggest wrapping in braces.
|
|
//
|
|
// if ... {
|
|
// } else {
|
|
// ^
|
|
// while true {}
|
|
// }
|
|
// ^
|
|
if self.check(exp!(OpenBrace))
|
|
&& (classify::expr_requires_semi_to_be_stmt(&cond)
|
|
|| matches!(cond.kind, ExprKind::MacCall(..)))
|
|
=>
|
|
{
|
|
self.dcx().emit_err(errors::ExpectedElseBlock {
|
|
first_tok_span,
|
|
first_tok,
|
|
else_span,
|
|
condition_start: cond.span.shrink_to_lo(),
|
|
});
|
|
self.parse_if_after_cond(cond.span.shrink_to_lo(), cond)?
|
|
}
|
|
Err(e) => {
|
|
e.cancel();
|
|
self.restore_snapshot(snapshot);
|
|
self.parse_simple_block()?
|
|
},
|
|
Ok(_) => {
|
|
self.restore_snapshot(snapshot);
|
|
self.parse_simple_block()?
|
|
},
|
|
}
|
|
};
|
|
self.error_on_if_block_attrs(else_span, true, expr.span, attrs);
|
|
Ok(expr)
|
|
}
|
|
|
|
fn error_on_if_block_attrs(
|
|
&self,
|
|
ctx_span: Span,
|
|
is_ctx_else: bool,
|
|
branch_span: Span,
|
|
attrs: AttrWrapper,
|
|
) {
|
|
if !attrs.is_empty()
|
|
&& let [x0 @ xn] | [x0, .., xn] = &*attrs.take_for_recovery(self.psess)
|
|
{
|
|
let attributes = x0.span.until(branch_span);
|
|
let last = xn.span;
|
|
let ctx = if is_ctx_else { "else" } else { "if" };
|
|
self.dcx().emit_err(errors::OuterAttributeNotAllowedOnIfElse {
|
|
last,
|
|
branch_span,
|
|
ctx_span,
|
|
ctx: ctx.to_string(),
|
|
attributes,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn error_on_extra_if(&mut self, cond: &P<Expr>) -> PResult<'a, ()> {
|
|
if let ExprKind::Binary(Spanned { span: binop_span, node: binop }, _, right) = &cond.kind
|
|
&& let BinOpKind::And = binop
|
|
&& let ExprKind::If(cond, ..) = &right.kind
|
|
{
|
|
Err(self.dcx().create_err(errors::UnexpectedIfWithIf(
|
|
binop_span.shrink_to_hi().to(cond.span.shrink_to_lo()),
|
|
)))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn parse_for_head(&mut self) -> PResult<'a, (P<Pat>, P<Expr>)> {
|
|
let begin_paren = if self.token == token::OpenParen {
|
|
// 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_guard(
|
|
None,
|
|
RecoverComma::Yes,
|
|
RecoverColon::Yes,
|
|
CommaRecoveryMode::LikelyTuple,
|
|
),
|
|
begin_paren,
|
|
) {
|
|
(Ok(pat), _) => pat, // Happy path.
|
|
(Err(err), Some((start_span, left))) if self.eat_keyword(exp!(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 attrs = self.parse_outer_attributes()?;
|
|
let (expr, _) = match self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, attrs) {
|
|
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 == token::CloseParen {
|
|
// We know for sure we have seen `for ($SOMETHING in $EXPR)`, so we recover the
|
|
// parser state and emit a targeted 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.dcx().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.
|
|
};
|
|
if !self.eat_keyword(exp!(In)) {
|
|
self.error_missing_in_for_loop();
|
|
}
|
|
self.check_for_for_in_in_typo(self.prev_token.span);
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let (expr, _) = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, attrs)?;
|
|
Ok((pat, expr))
|
|
}
|
|
|
|
/// Parses `for await? <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 is_await =
|
|
self.token_uninterpolated_span().at_least_rust_2018() && self.eat_keyword(exp!(Await));
|
|
|
|
if is_await {
|
|
self.psess.gated_spans.gate(sym::async_for_loop, self.prev_token.span);
|
|
}
|
|
|
|
let kind = if is_await { ForLoopKind::ForAwait } else { ForLoopKind::For };
|
|
|
|
let (pat, expr) = self.parse_for_head()?;
|
|
// Recover from missing expression in `for` loop
|
|
if matches!(expr.kind, ExprKind::Block(..))
|
|
&& self.token.kind != token::OpenBrace
|
|
&& self.may_recover()
|
|
{
|
|
let guar = self
|
|
.dcx()
|
|
.emit_err(errors::MissingExpressionInForLoop { span: expr.span.shrink_to_lo() });
|
|
let err_expr = self.mk_expr(expr.span, ExprKind::Err(guar));
|
|
let block = self.mk_block(thin_vec![], BlockCheckMode::Default, self.prev_token.span);
|
|
return Ok(self.mk_expr(
|
|
lo.to(self.prev_token.span),
|
|
ExprKind::ForLoop { pat, iter: err_expr, body: block, label: opt_label, kind },
|
|
));
|
|
}
|
|
|
|
let (attrs, loop_block) = self.parse_inner_attrs_and_block(
|
|
// Only suggest moving erroneous block label to the loop header
|
|
// if there is not already a label there
|
|
opt_label.is_none().then_some(lo),
|
|
)?;
|
|
|
|
let kind = ExprKind::ForLoop { pat, iter: expr, body: loop_block, label: opt_label, kind };
|
|
|
|
self.recover_loop_else("for", lo)?;
|
|
|
|
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
|
|
}
|
|
|
|
/// Recovers from an `else` clause after a loop (`for...else`, `while...else`)
|
|
fn recover_loop_else(&mut self, loop_kind: &'static str, loop_kw: Span) -> PResult<'a, ()> {
|
|
if self.token.is_keyword(kw::Else) && self.may_recover() {
|
|
let else_span = self.token.span;
|
|
self.bump();
|
|
let else_clause = self.parse_expr_else()?;
|
|
self.dcx().emit_err(errors::LoopElseNotSupported {
|
|
span: else_span.to(else_clause.span),
|
|
loop_kind,
|
|
loop_kw,
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn error_missing_in_for_loop(&mut self) {
|
|
let (span, sub): (_, fn(_) -> _) = if self.token.is_ident_named(sym::of) {
|
|
// Possibly using JS syntax (#75311).
|
|
let span = self.token.span;
|
|
self.bump();
|
|
(span, errors::MissingInInForLoopSub::InNotOf)
|
|
} else {
|
|
(self.prev_token.span.between(self.token.span), errors::MissingInInForLoopSub::AddIn)
|
|
};
|
|
|
|
self.dcx().emit_err(errors::MissingInInForLoop { span, sub: sub(span) });
|
|
}
|
|
|
|
/// Parses a `while` or `while let` expression (`while` token already eaten).
|
|
fn parse_expr_while(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
|
|
let policy = LetChainsPolicy::EditionDependent { current_edition: lo.edition() };
|
|
let cond = self.parse_expr_cond(policy).map_err(|mut err| {
|
|
err.span_label(lo, "while parsing the condition of this `while` expression");
|
|
err
|
|
})?;
|
|
let (attrs, body) = self
|
|
.parse_inner_attrs_and_block(
|
|
// Only suggest moving erroneous block label to the loop header
|
|
// if there is not already a label there
|
|
opt_label.is_none().then_some(lo),
|
|
)
|
|
.map_err(|mut err| {
|
|
err.span_label(lo, "while parsing the body of this `while` expression");
|
|
err.span_label(cond.span, "this `while` condition successfully parsed");
|
|
err
|
|
})?;
|
|
|
|
self.recover_loop_else("while", lo)?;
|
|
|
|
Ok(self.mk_expr_with_attrs(
|
|
lo.to(self.prev_token.span),
|
|
ExprKind::While(cond, body, opt_label),
|
|
attrs,
|
|
))
|
|
}
|
|
|
|
/// Parses `loop { ... }` (`loop` token already eaten).
|
|
fn parse_expr_loop(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
|
|
let loop_span = self.prev_token.span;
|
|
let (attrs, body) = self.parse_inner_attrs_and_block(
|
|
// Only suggest moving erroneous block label to the loop header
|
|
// if there is not already a label there
|
|
opt_label.is_none().then_some(lo),
|
|
)?;
|
|
self.recover_loop_else("loop", lo)?;
|
|
Ok(self.mk_expr_with_attrs(
|
|
lo.to(self.prev_token.span),
|
|
ExprKind::Loop(body, opt_label, loop_span),
|
|
attrs,
|
|
))
|
|
}
|
|
|
|
pub(crate) fn eat_label(&mut self) -> Option<Label> {
|
|
if let Some((ident, is_raw)) = self.token.lifetime() {
|
|
// Disallow `'fn`, but with a better error message than `expect_lifetime`.
|
|
if matches!(is_raw, IdentIsRaw::No) && ident.without_first_quote().is_reserved() {
|
|
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
|
|
}
|
|
|
|
self.bump();
|
|
Some(Label { ident })
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Parses a `match ... { ... }` expression (`match` token already eaten).
|
|
fn parse_expr_match(&mut self) -> PResult<'a, P<Expr>> {
|
|
let match_span = self.prev_token.span;
|
|
let attrs = self.parse_outer_attributes()?;
|
|
let (scrutinee, _) = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, attrs)?;
|
|
|
|
self.parse_match_block(match_span, match_span, scrutinee, MatchKind::Prefix)
|
|
}
|
|
|
|
/// Parses the block of a `match expr { ... }` or a `expr.match { ... }`
|
|
/// expression. This is after the match token and scrutinee are eaten
|
|
fn parse_match_block(
|
|
&mut self,
|
|
lo: Span,
|
|
match_span: Span,
|
|
scrutinee: P<Expr>,
|
|
match_kind: MatchKind,
|
|
) -> PResult<'a, P<Expr>> {
|
|
if let Err(mut e) = self.expect(exp!(OpenBrace)) {
|
|
if self.token == token::Semi {
|
|
e.span_suggestion_short(
|
|
match_span,
|
|
"try removing this `match`",
|
|
"",
|
|
Applicability::MaybeIncorrect, // speculative
|
|
);
|
|
}
|
|
if self.maybe_recover_unexpected_block_label(None) {
|
|
e.cancel();
|
|
self.bump();
|
|
} else {
|
|
return Err(e);
|
|
}
|
|
}
|
|
let attrs = self.parse_inner_attributes()?;
|
|
|
|
let mut arms = ThinVec::new();
|
|
while self.token != token::CloseBrace {
|
|
match self.parse_arm() {
|
|
Ok(arm) => arms.push(arm),
|
|
Err(e) => {
|
|
// Recover by skipping to the end of the block.
|
|
let guar = e.emit();
|
|
self.recover_stmt();
|
|
let span = lo.to(self.token.span);
|
|
if self.token == token::CloseBrace {
|
|
self.bump();
|
|
}
|
|
// Always push at least one arm to make the match non-empty
|
|
arms.push(Arm {
|
|
attrs: Default::default(),
|
|
pat: self.mk_pat(span, ast::PatKind::Err(guar)),
|
|
guard: None,
|
|
body: Some(self.mk_expr_err(span, guar)),
|
|
span,
|
|
id: DUMMY_NODE_ID,
|
|
is_placeholder: false,
|
|
});
|
|
return Ok(self.mk_expr_with_attrs(
|
|
span,
|
|
ExprKind::Match(scrutinee, arms, match_kind),
|
|
attrs,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
let hi = self.token.span;
|
|
self.bump();
|
|
Ok(self.mk_expr_with_attrs(lo.to(hi), ExprKind::Match(scrutinee, arms, match_kind), attrs))
|
|
}
|
|
|
|
/// Attempt to recover from match arm body with statements and no surrounding braces.
|
|
fn parse_arm_body_missing_braces(
|
|
&mut self,
|
|
first_expr: &P<Expr>,
|
|
arrow_span: Span,
|
|
) -> Option<(Span, ErrorGuaranteed)> {
|
|
if self.token != token::Semi {
|
|
return None;
|
|
}
|
|
let start_snapshot = self.create_snapshot_for_diagnostic();
|
|
let semi_sp = self.token.span;
|
|
self.bump(); // `;`
|
|
let mut stmts =
|
|
vec![self.mk_stmt(first_expr.span, ast::StmtKind::Expr(first_expr.clone()))];
|
|
let err = |this: &Parser<'_>, stmts: Vec<ast::Stmt>| {
|
|
let span = stmts[0].span.to(stmts[stmts.len() - 1].span);
|
|
|
|
let guar = this.dcx().emit_err(errors::MatchArmBodyWithoutBraces {
|
|
statements: span,
|
|
arrow: arrow_span,
|
|
num_statements: stmts.len(),
|
|
sub: if stmts.len() > 1 {
|
|
errors::MatchArmBodyWithoutBracesSugg::AddBraces {
|
|
left: span.shrink_to_lo(),
|
|
right: span.shrink_to_hi(),
|
|
}
|
|
} else {
|
|
errors::MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp }
|
|
},
|
|
});
|
|
(span, guar)
|
|
};
|
|
// We might have either a `,` -> `;` typo, or a block without braces. We need
|
|
// a more subtle parsing strategy.
|
|
loop {
|
|
if self.token == token::CloseBrace {
|
|
// We have reached the closing brace of the `match` expression.
|
|
return Some(err(self, stmts));
|
|
}
|
|
if self.token == token::Comma {
|
|
self.restore_snapshot(start_snapshot);
|
|
return None;
|
|
}
|
|
let pre_pat_snapshot = self.create_snapshot_for_diagnostic();
|
|
match self.parse_pat_no_top_alt(None, None) {
|
|
Ok(_pat) => {
|
|
if self.token == token::FatArrow {
|
|
// Reached arm end.
|
|
self.restore_snapshot(pre_pat_snapshot);
|
|
return Some(err(self, stmts));
|
|
}
|
|
}
|
|
Err(err) => {
|
|
err.cancel();
|
|
}
|
|
}
|
|
|
|
self.restore_snapshot(pre_pat_snapshot);
|
|
match self.parse_stmt_without_recovery(true, ForceCollect::No, false) {
|
|
// Consume statements for as long as possible.
|
|
Ok(Some(stmt)) => {
|
|
stmts.push(stmt);
|
|
}
|
|
Ok(None) => {
|
|
self.restore_snapshot(start_snapshot);
|
|
break;
|
|
}
|
|
// We couldn't parse either yet another statement missing it's
|
|
// enclosing block nor the next arm's pattern or closing brace.
|
|
Err(stmt_err) => {
|
|
stmt_err.cancel();
|
|
self.restore_snapshot(start_snapshot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
|
|
let lo = this.token.span;
|
|
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(exp!(FatArrow));
|
|
let is_almost_fat_arrow =
|
|
TokenKind::FatArrow.similar_tokens().contains(&this.token.kind);
|
|
|
|
// this avoids the compiler saying that a `,` or `}` was expected even though
|
|
// the pattern isn't a never pattern (and thus an arm body is required)
|
|
let armless = (!is_fat_arrow && !is_almost_fat_arrow && pat.could_be_never_pattern())
|
|
|| matches!(this.token.kind, token::Comma | token::CloseBrace);
|
|
|
|
let mut result = if armless {
|
|
// A pattern without a body, allowed for never patterns.
|
|
arm_body = None;
|
|
let span = lo.to(this.prev_token.span);
|
|
this.expect_one_of(&[exp!(Comma)], &[exp!(CloseBrace)]).map(|x| {
|
|
// Don't gate twice
|
|
if !pat.contains_never_pattern() {
|
|
this.psess.gated_spans.gate(sym::never_patterns, span);
|
|
}
|
|
x
|
|
})
|
|
} else {
|
|
if let Err(mut err) = this.expect(exp!(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 arrow_span = this.prev_token.span;
|
|
let arm_start_span = this.token.span;
|
|
|
|
let attrs = this.parse_outer_attributes()?;
|
|
let (expr, _) =
|
|
this.parse_expr_res(Restrictions::STMT_EXPR, attrs).map_err(|mut err| {
|
|
err.span_label(arrow_span, "while parsing the `match` arm starting here");
|
|
err
|
|
})?;
|
|
|
|
let require_comma =
|
|
!classify::expr_is_complete(&expr) && this.token != token::CloseBrace;
|
|
|
|
if !require_comma {
|
|
arm_body = Some(expr);
|
|
// Eat a comma if it exists, though.
|
|
let _ = this.eat(exp!(Comma));
|
|
Ok(Recovered::No)
|
|
} else if let Some((span, guar)) =
|
|
this.parse_arm_body_missing_braces(&expr, arrow_span)
|
|
{
|
|
let body = this.mk_expr_err(span, guar);
|
|
arm_body = Some(body);
|
|
Ok(Recovered::Yes(guar))
|
|
} else {
|
|
let expr_span = expr.span;
|
|
arm_body = Some(expr);
|
|
this.expect_one_of(&[exp!(Comma)], &[exp!(CloseBrace)]).map_err(|mut err| {
|
|
if this.token == token::FatArrow {
|
|
let sm = this.psess.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",
|
|
);
|
|
}
|
|
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_no_top_guard(
|
|
None,
|
|
RecoverComma::Yes,
|
|
RecoverColon::Yes,
|
|
CommaRecoveryMode::EitherTupleOrPipe,
|
|
)
|
|
.map_err(|err| err.cancel())
|
|
.is_ok();
|
|
if pattern_follows && snapshot.check(exp!(FatArrow)) {
|
|
err.cancel();
|
|
let guar = this.dcx().emit_err(errors::MissingCommaAfterMatchArm {
|
|
span: arm_span.shrink_to_hi(),
|
|
});
|
|
return Ok(Recovered::Yes(guar));
|
|
}
|
|
Err(err)
|
|
});
|
|
}
|
|
result?;
|
|
|
|
Ok((
|
|
ast::Arm {
|
|
attrs,
|
|
pat,
|
|
guard,
|
|
body: arm_body,
|
|
span: arm_span,
|
|
id: DUMMY_NODE_ID,
|
|
is_placeholder: false,
|
|
},
|
|
Trailing::No,
|
|
UsePreAttrPos::No,
|
|
))
|
|
})
|
|
}
|
|
|
|
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
|
|
// Used to check the `if_let_guard` feature mostly by scanning
|
|
// `&&` tokens.
|
|
fn has_let_expr(expr: &Expr) -> bool {
|
|
match &expr.kind {
|
|
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
|
|
let lhs_rslt = has_let_expr(lhs);
|
|
let rhs_rslt = has_let_expr(rhs);
|
|
lhs_rslt || rhs_rslt
|
|
}
|
|
ExprKind::Let(..) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
if !self.eat_keyword(exp!(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, LetChainsPolicy::AlwaysAllowed).visit_expr(&mut cond);
|
|
|
|
if has_let_expr(&cond) {
|
|
let span = if_span.to(cond.span);
|
|
self.psess.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 == token::OpenParen {
|
|
let left = self.token.span;
|
|
let pat = self.parse_pat_no_top_guard(
|
|
None,
|
|
RecoverComma::Yes,
|
|
RecoverColon::Yes,
|
|
CommaRecoveryMode::EitherTupleOrPipe,
|
|
)?;
|
|
if let ast::PatKind::Paren(subpat) = &pat.kind
|
|
&& let ast::PatKind::Guard(..) = &subpat.kind
|
|
{
|
|
// Detect and recover from `($pat if $cond) => $arm`.
|
|
// FIXME(guard_patterns): convert this to a normal guard instead
|
|
let span = pat.span;
|
|
let ast::PatKind::Paren(subpat) = pat.into_inner().kind else { unreachable!() };
|
|
let ast::PatKind::Guard(_, mut cond) = subpat.into_inner().kind else {
|
|
unreachable!()
|
|
};
|
|
self.psess.gated_spans.ungate_last(sym::guard_patterns, cond.span);
|
|
CondChecker::new(self, LetChainsPolicy::AlwaysAllowed).visit_expr(&mut cond);
|
|
let right = self.prev_token.span;
|
|
self.dcx().emit_err(errors::ParenthesesInMatchPat {
|
|
span: vec![left, right],
|
|
sugg: errors::ParenthesesInMatchPatSugg { left, right },
|
|
});
|
|
Ok((self.mk_pat(span, ast::PatKind::Wild), Some(cond)))
|
|
} else {
|
|
Ok((pat, self.parse_match_arm_guard()?))
|
|
}
|
|
} else {
|
|
// Regular parser flow:
|
|
let pat = self.parse_pat_no_top_guard(
|
|
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>> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
match self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, attrs) {
|
|
Ok((expr, _)) => Ok(expr),
|
|
Err(mut err) => {
|
|
if self.prev_token == token::OpenBrace {
|
|
let sugg_sp = self.prev_token.span.shrink_to_lo();
|
|
// Consume everything within the braces, let's avoid further parse
|
|
// errors.
|
|
self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
|
|
let msg = "you might have meant to start a match arm after the match guard";
|
|
if self.eat(exp!(CloseBrace)) {
|
|
let applicability = if self.token != token::FatArrow {
|
|
// We have high confidence that we indeed didn't have a struct
|
|
// literal in the match guard, but rather we had some operation
|
|
// that ended in a path, immediately followed by a block that was
|
|
// meant to be the match arm.
|
|
Applicability::MachineApplicable
|
|
} else {
|
|
Applicability::MaybeIncorrect
|
|
};
|
|
err.span_suggestion_verbose(sugg_sp, msg, "=> ", applicability);
|
|
}
|
|
}
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_builtin(&self) -> bool {
|
|
self.token.is_keyword(kw::Builtin) && self.look_ahead(1, |t| *t == token::Pound)
|
|
}
|
|
|
|
/// Parses a `try {...}` expression (`try` token already eaten).
|
|
fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> {
|
|
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
|
|
if self.eat_keyword(exp!(Catch)) {
|
|
Err(self.dcx().create_err(errors::CatchAfterTry { span: self.prev_token.span }))
|
|
} else {
|
|
let span = span_lo.to(body.span);
|
|
self.psess.gated_spans.gate(sym::try_blocks, span);
|
|
Ok(self.mk_expr_with_attrs(span, ExprKind::TryBlock(body), attrs))
|
|
}
|
|
}
|
|
|
|
fn is_do_catch_block(&self) -> bool {
|
|
self.token.is_keyword(kw::Do)
|
|
&& self.is_keyword_ahead(1, &[kw::Catch])
|
|
&& self.look_ahead(2, |t| *t == token::OpenBrace || t.is_metavar_block())
|
|
&& !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
|
|
}
|
|
|
|
fn is_do_yeet(&self) -> bool {
|
|
self.token.is_keyword(kw::Do) && self.is_keyword_ahead(1, &[kw::Yeet])
|
|
}
|
|
|
|
fn is_try_block(&self) -> bool {
|
|
self.token.is_keyword(kw::Try)
|
|
&& self.look_ahead(1, |t| *t == token::OpenBrace || t.is_metavar_block())
|
|
&& self.token_uninterpolated_span().at_least_rust_2018()
|
|
}
|
|
|
|
/// Parses an `async move? {...}` or `gen move? {...}` expression.
|
|
fn parse_gen_block(&mut self) -> PResult<'a, P<Expr>> {
|
|
let lo = self.token.span;
|
|
let kind = if self.eat_keyword(exp!(Async)) {
|
|
if self.eat_keyword(exp!(Gen)) { GenBlockKind::AsyncGen } else { GenBlockKind::Async }
|
|
} else {
|
|
assert!(self.eat_keyword(exp!(Gen)));
|
|
GenBlockKind::Gen
|
|
};
|
|
match kind {
|
|
GenBlockKind::Async => {
|
|
// `async` blocks are stable
|
|
}
|
|
GenBlockKind::Gen | GenBlockKind::AsyncGen => {
|
|
self.psess.gated_spans.gate(sym::gen_blocks, lo.to(self.prev_token.span));
|
|
}
|
|
}
|
|
let capture_clause = self.parse_capture_clause()?;
|
|
let decl_span = lo.to(self.prev_token.span);
|
|
let (attrs, body) = self.parse_inner_attrs_and_block(None)?;
|
|
let kind = ExprKind::Gen(capture_clause, body, kind, decl_span);
|
|
Ok(self.mk_expr_with_attrs(lo.to(self.prev_token.span), kind, attrs))
|
|
}
|
|
|
|
fn is_gen_block(&self, kw: Symbol, lookahead: usize) -> bool {
|
|
self.is_keyword_ahead(lookahead, &[kw])
|
|
&& ((
|
|
// `async move {`
|
|
self.is_keyword_ahead(lookahead + 1, &[kw::Move, kw::Use])
|
|
&& self.look_ahead(lookahead + 2, |t| {
|
|
*t == token::OpenBrace || t.is_metavar_block()
|
|
})
|
|
) || (
|
|
// `async {`
|
|
self.look_ahead(lookahead + 1, |t| *t == token::OpenBrace || t.is_metavar_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 {
|
|
// `{ ident, ` and `{ ident: ` cannot start a block.
|
|
self.look_ahead(1, |t| t.is_ident())
|
|
&& self.look_ahead(2, |t| t == &token::Comma || t == &token::Colon)
|
|
}
|
|
|
|
fn maybe_parse_struct_expr(
|
|
&mut self,
|
|
qself: &Option<P<ast::QSelf>>,
|
|
path: &ast::Path,
|
|
) -> Option<PResult<'a, P<Expr>>> {
|
|
let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
|
|
if struct_allowed || self.is_certainly_not_a_block() {
|
|
if let Err(err) = self.expect(exp!(OpenBrace)) {
|
|
return Some(Err(err));
|
|
}
|
|
let expr = self.parse_expr_struct(qself.clone(), path.clone(), true);
|
|
if let (Ok(expr), false) = (&expr, struct_allowed) {
|
|
// This is a struct literal, but we don't can't accept them here.
|
|
self.dcx().emit_err(errors::StructLiteralNotAllowedHere {
|
|
span: expr.span,
|
|
sub: errors::StructLiteralNotAllowedHereSugg {
|
|
left: path.span.shrink_to_lo(),
|
|
right: expr.span.shrink_to_hi(),
|
|
},
|
|
});
|
|
}
|
|
return Some(expr);
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn parse_struct_fields(
|
|
&mut self,
|
|
pth: ast::Path,
|
|
recover: bool,
|
|
close: ExpTokenPair<'_>,
|
|
) -> PResult<
|
|
'a,
|
|
(
|
|
ThinVec<ExprField>,
|
|
ast::StructRest,
|
|
Option<ErrorGuaranteed>, /* async blocks are forbidden in Rust 2015 */
|
|
),
|
|
> {
|
|
let mut fields = ThinVec::new();
|
|
let mut base = ast::StructRest::None;
|
|
let mut recovered_async = None;
|
|
let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD);
|
|
|
|
let async_block_err = |e: &mut Diag<'_>, span: Span| {
|
|
errors::AsyncBlockIn2015 { span }.add_to_diag(e);
|
|
errors::HelpUseLatestEdition::new().add_to_diag(e);
|
|
};
|
|
|
|
while self.token != *close.tok {
|
|
if self.eat(exp!(DotDot)) || self.recover_struct_field_dots(close.tok) {
|
|
let exp_span = self.prev_token.span;
|
|
// We permit `.. }` on the left-hand side of a destructuring assignment.
|
|
if self.check(close) {
|
|
base = ast::StructRest::Rest(self.prev_token.span);
|
|
break;
|
|
}
|
|
match self.parse_expr() {
|
|
Ok(e) => base = ast::StructRest::Base(e),
|
|
Err(e) if recover => {
|
|
e.emit();
|
|
self.recover_stmt();
|
|
}
|
|
Err(e) => return Err(e),
|
|
}
|
|
self.recover_struct_comma_after_dotdot(exp_span);
|
|
break;
|
|
}
|
|
|
|
// Peek the field's ident before parsing its expr in order to emit better diagnostics.
|
|
let peek = self
|
|
.token
|
|
.ident()
|
|
.filter(|(ident, is_raw)| {
|
|
(!ident.is_reserved() || matches!(is_raw, IdentIsRaw::Yes))
|
|
&& self.look_ahead(1, |tok| *tok == token::Colon)
|
|
})
|
|
.map(|(ident, _)| ident);
|
|
|
|
// We still want a field even if its expr didn't parse.
|
|
let field_ident = |this: &Self, guar: ErrorGuaranteed| {
|
|
peek.map(|ident| {
|
|
let span = ident.span;
|
|
ExprField {
|
|
ident,
|
|
span,
|
|
expr: this.mk_expr_err(span, guar),
|
|
is_shorthand: false,
|
|
attrs: AttrVec::new(),
|
|
id: DUMMY_NODE_ID,
|
|
is_placeholder: false,
|
|
}
|
|
})
|
|
};
|
|
|
|
let parsed_field = match self.parse_expr_field() {
|
|
Ok(f) => Ok(f),
|
|
Err(mut e) => {
|
|
if pth == kw::Async {
|
|
async_block_err(&mut e, pth.span);
|
|
} else {
|
|
e.span_label(pth.span, "while parsing this struct");
|
|
}
|
|
|
|
if let Some((ident, _)) = self.token.ident()
|
|
&& !self.token.is_reserved_ident()
|
|
&& self.look_ahead(1, |t| {
|
|
AssocOp::from_token(t).is_some()
|
|
|| matches!(
|
|
t.kind,
|
|
token::OpenParen | token::OpenBracket | token::OpenBrace
|
|
)
|
|
|| *t == token::Dot
|
|
})
|
|
{
|
|
// Looks like they tried to write a shorthand, complex expression,
|
|
// E.g.: `n + m`, `f(a)`, `a[i]`, `S { x: 3 }`, or `x.y`.
|
|
e.span_suggestion_verbose(
|
|
self.token.span.shrink_to_lo(),
|
|
"try naming a field",
|
|
&format!("{ident}: ",),
|
|
Applicability::MaybeIncorrect,
|
|
);
|
|
}
|
|
if in_if_guard && close.token_type == TokenType::CloseBrace {
|
|
return Err(e);
|
|
}
|
|
|
|
if !recover {
|
|
return Err(e);
|
|
}
|
|
|
|
let guar = e.emit();
|
|
if pth == kw::Async {
|
|
recovered_async = Some(guar);
|
|
}
|
|
|
|
// If the next token is a comma, then try to parse
|
|
// what comes next as additional fields, rather than
|
|
// bailing out until next `}`.
|
|
if self.token != token::Comma {
|
|
self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
|
|
if self.token != token::Comma {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Err(guar)
|
|
}
|
|
};
|
|
|
|
let is_shorthand = parsed_field.as_ref().is_ok_and(|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::Colon);
|
|
|
|
match self.expect_one_of(&[exp!(Comma)], &[close]) {
|
|
Ok(_) => {
|
|
if let Ok(f) = parsed_field.or_else(|guar| field_ident(self, guar).ok_or(guar))
|
|
{
|
|
// Only include the field if there's no parse error for the field name.
|
|
fields.push(f);
|
|
}
|
|
}
|
|
Err(mut e) => {
|
|
if pth == kw::Async {
|
|
async_block_err(&mut e, pth.span);
|
|
} else {
|
|
e.span_label(pth.span, "while parsing this struct");
|
|
if peek.is_some() {
|
|
e.span_suggestion(
|
|
self.prev_token.span.shrink_to_hi(),
|
|
"try adding a comma",
|
|
",",
|
|
Applicability::MachineApplicable,
|
|
);
|
|
}
|
|
}
|
|
if !recover {
|
|
return Err(e);
|
|
}
|
|
let guar = e.emit();
|
|
if pth == kw::Async {
|
|
recovered_async = Some(guar);
|
|
} else if let Some(f) = field_ident(self, guar) {
|
|
fields.push(f);
|
|
}
|
|
self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
|
|
let _ = self.eat(exp!(Comma));
|
|
}
|
|
}
|
|
}
|
|
Ok((fields, base, recovered_async))
|
|
}
|
|
|
|
/// Precondition: already parsed the '{'.
|
|
pub(super) fn parse_expr_struct(
|
|
&mut self,
|
|
qself: Option<P<ast::QSelf>>,
|
|
pth: ast::Path,
|
|
recover: bool,
|
|
) -> PResult<'a, P<Expr>> {
|
|
let lo = pth.span;
|
|
let (fields, base, recovered_async) =
|
|
self.parse_struct_fields(pth.clone(), recover, exp!(CloseBrace))?;
|
|
let span = lo.to(self.token.span);
|
|
self.expect(exp!(CloseBrace))?;
|
|
let expr = if let Some(guar) = recovered_async {
|
|
ExprKind::Err(guar)
|
|
} else {
|
|
ExprKind::Struct(P(ast::StructExpr { qself, path: pth, fields, rest: base }))
|
|
};
|
|
Ok(self.mk_expr(span, expr))
|
|
}
|
|
|
|
fn recover_struct_comma_after_dotdot(&mut self, span: Span) {
|
|
if self.token != token::Comma {
|
|
return;
|
|
}
|
|
self.dcx().emit_err(errors::CommaAfterBaseStruct {
|
|
span: span.to(self.prev_token.span),
|
|
comma: self.token.span,
|
|
});
|
|
self.recover_stmt();
|
|
}
|
|
|
|
fn recover_struct_field_dots(&mut self, close: &TokenKind) -> bool {
|
|
if !self.look_ahead(1, |t| t == close) && self.eat(exp!(DotDotDot)) {
|
|
// recover from typo of `...`, suggest `..`
|
|
let span = self.prev_token.span;
|
|
self.dcx().emit_err(errors::MissingDotDot { token_span: span, sugg_span: span });
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Converts an ident into 'label and emits an "expected a label, found an identifier" error.
|
|
fn recover_ident_into_label(&mut self, ident: Ident) -> Label {
|
|
// Convert `label` -> `'label`,
|
|
// so that nameres doesn't complain about non-existing label
|
|
let label = format!("'{}", ident.name);
|
|
let ident = Ident { name: Symbol::intern(&label), span: ident.span };
|
|
|
|
self.dcx().emit_err(errors::ExpectedLabelFoundIdent {
|
|
span: ident.span,
|
|
start: ident.span.shrink_to_lo(),
|
|
});
|
|
|
|
Label { ident }
|
|
}
|
|
|
|
/// Parses `ident (COLON expr)?`.
|
|
fn parse_expr_field(&mut self) -> PResult<'a, ExprField> {
|
|
let attrs = self.parse_outer_attributes()?;
|
|
self.recover_vcs_conflict_marker();
|
|
self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
|
|
let lo = this.token.span;
|
|
|
|
// Check if a colon exists one ahead. This means we're parsing a fieldname.
|
|
let is_shorthand = !this.look_ahead(1, |t| t == &token::Colon || t == &token::Eq);
|
|
// Proactively check whether parsing the field will be incorrect.
|
|
let is_wrong = this.token.is_ident()
|
|
&& !this.token.is_reserved_ident()
|
|
&& !this.look_ahead(1, |t| {
|
|
t == &token::Colon
|
|
|| t == &token::Eq
|
|
|| t == &token::Comma
|
|
|| t == &token::CloseBrace
|
|
|| t == &token::CloseParen
|
|
});
|
|
if is_wrong {
|
|
return Err(this.dcx().create_err(errors::ExpectedStructField {
|
|
span: this.look_ahead(1, |t| t.span),
|
|
ident_span: this.token.span,
|
|
token: this.look_ahead(1, |t| *t),
|
|
}));
|
|
}
|
|
let (ident, expr) = if is_shorthand {
|
|
// Mimic `x: x` for the `x` field shorthand.
|
|
let ident = this.parse_ident_common(false)?;
|
|
let path = ast::Path::from_ident(ident);
|
|
(ident, this.mk_expr(ident.span, ExprKind::Path(None, path)))
|
|
} else {
|
|
let ident = this.parse_field_name()?;
|
|
this.error_on_eq_field_init(ident);
|
|
this.bump(); // `:`
|
|
(ident, this.parse_expr()?)
|
|
};
|
|
|
|
Ok((
|
|
ast::ExprField {
|
|
ident,
|
|
span: lo.to(expr.span),
|
|
expr,
|
|
is_shorthand,
|
|
attrs,
|
|
id: DUMMY_NODE_ID,
|
|
is_placeholder: false,
|
|
},
|
|
Trailing::from(this.token == token::Comma),
|
|
UsePreAttrPos::No,
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Check for `=`. This means the source incorrectly attempts to
|
|
/// initialize a field with an eq rather than a colon.
|
|
fn error_on_eq_field_init(&self, field_name: Ident) {
|
|
if self.token != token::Eq {
|
|
return;
|
|
}
|
|
|
|
self.dcx().emit_err(errors::EqFieldInit {
|
|
span: self.token.span,
|
|
eq: field_name.span.shrink_to_hi().to(self.token.span),
|
|
});
|
|
}
|
|
|
|
fn err_dotdotdot_syntax(&self, span: Span) {
|
|
self.dcx().emit_err(errors::DotDotDot { span });
|
|
}
|
|
|
|
fn err_larrow_operator(&self, span: Span) {
|
|
self.dcx().emit_err(errors::LeftArrowOperator { span });
|
|
}
|
|
|
|
fn mk_assign_op(&self, assign_op: AssignOp, lhs: P<Expr>, rhs: P<Expr>) -> ExprKind {
|
|
ExprKind::AssignOp(assign_op, lhs, rhs)
|
|
}
|
|
|
|
fn mk_range(
|
|
&mut self,
|
|
start: Option<P<Expr>>,
|
|
end: Option<P<Expr>>,
|
|
limits: RangeLimits,
|
|
) -> ExprKind {
|
|
if end.is_none() && limits == RangeLimits::Closed {
|
|
let guar = self.inclusive_range_with_incorrect_end();
|
|
ExprKind::Err(guar)
|
|
} else {
|
|
ExprKind::Range(start, end, limits)
|
|
}
|
|
}
|
|
|
|
fn mk_unary(&self, unop: UnOp, expr: P<Expr>) -> ExprKind {
|
|
ExprKind::Unary(unop, expr)
|
|
}
|
|
|
|
fn mk_binary(&self, binop: BinOp, lhs: P<Expr>, rhs: P<Expr>) -> ExprKind {
|
|
ExprKind::Binary(binop, lhs, rhs)
|
|
}
|
|
|
|
fn mk_index(&self, expr: P<Expr>, idx: P<Expr>, brackets_span: Span) -> ExprKind {
|
|
ExprKind::Index(expr, idx, brackets_span)
|
|
}
|
|
|
|
fn mk_call(&self, f: P<Expr>, args: ThinVec<P<Expr>>) -> ExprKind {
|
|
ExprKind::Call(f, args)
|
|
}
|
|
|
|
fn mk_await_expr(&mut self, self_arg: P<Expr>, lo: Span) -> P<Expr> {
|
|
let span = lo.to(self.prev_token.span);
|
|
let await_expr = self.mk_expr(span, ExprKind::Await(self_arg, self.prev_token.span));
|
|
self.recover_from_await_method_call();
|
|
await_expr
|
|
}
|
|
|
|
fn mk_use_expr(&mut self, self_arg: P<Expr>, lo: Span) -> P<Expr> {
|
|
let span = lo.to(self.prev_token.span);
|
|
let use_expr = self.mk_expr(span, ExprKind::Use(self_arg, self.prev_token.span));
|
|
self.recover_from_use();
|
|
use_expr
|
|
}
|
|
|
|
pub(crate) fn mk_expr_with_attrs(&self, span: Span, kind: ExprKind, attrs: AttrVec) -> P<Expr> {
|
|
P(Expr { kind, span, attrs, id: DUMMY_NODE_ID, tokens: None })
|
|
}
|
|
|
|
pub(crate) fn mk_expr(&self, span: Span, kind: ExprKind) -> P<Expr> {
|
|
self.mk_expr_with_attrs(span, kind, AttrVec::new())
|
|
}
|
|
|
|
pub(super) fn mk_expr_err(&self, span: Span, guar: ErrorGuaranteed) -> P<Expr> {
|
|
self.mk_expr(span, ExprKind::Err(guar))
|
|
}
|
|
|
|
/// Create expression span ensuring the span of the parent node
|
|
/// is larger than the span of lhs and rhs, including the attributes.
|
|
fn mk_expr_sp(&self, lhs: &P<Expr>, lhs_span: Span, rhs_span: Span) -> Span {
|
|
lhs.attrs
|
|
.iter()
|
|
.find(|a| a.style == AttrStyle::Outer)
|
|
.map_or(lhs_span, |a| a.span)
|
|
.to(rhs_span)
|
|
}
|
|
|
|
fn collect_tokens_for_expr(
|
|
&mut self,
|
|
attrs: AttrWrapper,
|
|
f: impl FnOnce(&mut Self, ast::AttrVec) -> PResult<'a, P<Expr>>,
|
|
) -> PResult<'a, P<Expr>> {
|
|
self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
|
|
let res = f(this, attrs)?;
|
|
let trailing = Trailing::from(
|
|
this.restrictions.contains(Restrictions::STMT_EXPR)
|
|
&& this.token == token::Semi
|
|
// FIXME: pass an additional condition through from the place
|
|
// where we know we need a comma, rather than assuming that
|
|
// `#[attr] expr,` always captures a trailing comma.
|
|
|| this.token == token::Comma,
|
|
);
|
|
Ok((res, trailing, UsePreAttrPos::No))
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Could this lifetime/label be an unclosed char literal? For example, `'a`
|
|
/// could be, but `'abc` could not.
|
|
pub(crate) fn could_be_unclosed_char_literal(ident: Ident) -> bool {
|
|
ident.name.as_str().starts_with('\'')
|
|
&& unescape_char(ident.without_first_quote().name.as_str()).is_ok()
|
|
}
|
|
|
|
/// Used to forbid `let` expressions in certain syntactic locations.
|
|
#[derive(Clone, Copy, Subdiagnostic)]
|
|
pub(crate) enum ForbiddenLetReason {
|
|
/// `let` is not valid and the source environment is not important
|
|
OtherForbidden,
|
|
/// A let chain with the `||` operator
|
|
#[note(parse_not_supported_or)]
|
|
NotSupportedOr(#[primary_span] Span),
|
|
/// A let chain with invalid parentheses
|
|
///
|
|
/// For example, `let 1 = 1 && (expr && expr)` is allowed
|
|
/// but `(let 1 = 1 && (let 1 = 1 && (let 1 = 1))) && let a = 1` is not
|
|
#[note(parse_not_supported_parentheses)]
|
|
NotSupportedParentheses(#[primary_span] Span),
|
|
}
|
|
|
|
/// Whether let chains are allowed on all editions, or it's edition dependent (allowed only on
|
|
/// 2024 and later). In case of edition dependence, specify the currently present edition.
|
|
pub enum LetChainsPolicy {
|
|
AlwaysAllowed,
|
|
EditionDependent { current_edition: Edition },
|
|
}
|
|
|
|
/// Visitor to check for invalid use of `ExprKind::Let` that can't
|
|
/// easily be caught in parsing. For example:
|
|
///
|
|
/// ```rust,ignore (example)
|
|
/// // Only know that the let isn't allowed once the `||` token is reached
|
|
/// if let Some(x) = y || true {}
|
|
/// // Only know that the let isn't allowed once the second `=` token is reached.
|
|
/// if let Some(x) = y && z = 1 {}
|
|
/// ```
|
|
struct CondChecker<'a> {
|
|
parser: &'a Parser<'a>,
|
|
let_chains_policy: LetChainsPolicy,
|
|
depth: u32,
|
|
forbid_let_reason: Option<ForbiddenLetReason>,
|
|
missing_let: Option<errors::MaybeMissingLet>,
|
|
comparison: Option<errors::MaybeComparison>,
|
|
}
|
|
|
|
impl<'a> CondChecker<'a> {
|
|
fn new(parser: &'a Parser<'a>, let_chains_policy: LetChainsPolicy) -> Self {
|
|
CondChecker {
|
|
parser,
|
|
forbid_let_reason: None,
|
|
missing_let: None,
|
|
comparison: None,
|
|
let_chains_policy,
|
|
depth: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MutVisitor for CondChecker<'_> {
|
|
fn visit_expr(&mut self, e: &mut P<Expr>) {
|
|
self.depth += 1;
|
|
use ForbiddenLetReason::*;
|
|
|
|
let span = e.span;
|
|
match e.kind {
|
|
ExprKind::Let(_, _, _, ref mut recovered @ Recovered::No) => {
|
|
if let Some(reason) = self.forbid_let_reason {
|
|
*recovered = Recovered::Yes(self.parser.dcx().emit_err(
|
|
errors::ExpectedExpressionFoundLet {
|
|
span,
|
|
reason,
|
|
missing_let: self.missing_let,
|
|
comparison: self.comparison,
|
|
},
|
|
));
|
|
} else if self.depth > 1 {
|
|
// Top level `let` is always allowed; only gate chains
|
|
match self.let_chains_policy {
|
|
LetChainsPolicy::AlwaysAllowed => (),
|
|
LetChainsPolicy::EditionDependent { current_edition } => {
|
|
if !current_edition.at_least_rust_2024() || !span.at_least_rust_2024() {
|
|
self.parser.psess.gated_spans.gate(sym::let_chains, span);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ExprKind::Binary(Spanned { node: BinOpKind::And, .. }, _, _) => {
|
|
mut_visit::walk_expr(self, e);
|
|
}
|
|
ExprKind::Binary(Spanned { node: BinOpKind::Or, span: or_span }, _, _)
|
|
if let None | Some(NotSupportedOr(_)) = self.forbid_let_reason =>
|
|
{
|
|
let forbid_let_reason = self.forbid_let_reason;
|
|
self.forbid_let_reason = Some(NotSupportedOr(or_span));
|
|
mut_visit::walk_expr(self, e);
|
|
self.forbid_let_reason = forbid_let_reason;
|
|
}
|
|
ExprKind::Paren(ref inner)
|
|
if let None | Some(NotSupportedParentheses(_)) = self.forbid_let_reason =>
|
|
{
|
|
let forbid_let_reason = self.forbid_let_reason;
|
|
self.forbid_let_reason = Some(NotSupportedParentheses(inner.span));
|
|
mut_visit::walk_expr(self, e);
|
|
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() });
|
|
mut_visit::walk_expr(self, e);
|
|
self.forbid_let_reason = forbid_let_reason;
|
|
self.missing_let = missing_let;
|
|
self.comparison = comparison;
|
|
}
|
|
ExprKind::Unary(_, _)
|
|
| ExprKind::Await(_, _)
|
|
| ExprKind::Use(_, _)
|
|
| ExprKind::AssignOp(_, _, _)
|
|
| ExprKind::Range(_, _, _)
|
|
| ExprKind::Try(_)
|
|
| ExprKind::AddrOf(_, _, _)
|
|
| ExprKind::Binary(_, _, _)
|
|
| ExprKind::Field(_, _)
|
|
| ExprKind::Index(_, _, _)
|
|
| ExprKind::Call(_, _)
|
|
| ExprKind::MethodCall(_)
|
|
| ExprKind::Tup(_)
|
|
| ExprKind::Paren(_) => {
|
|
let forbid_let_reason = self.forbid_let_reason;
|
|
self.forbid_let_reason = Some(OtherForbidden);
|
|
mut_visit::walk_expr(self, e);
|
|
self.forbid_let_reason = forbid_let_reason;
|
|
}
|
|
ExprKind::Cast(ref mut op, _)
|
|
| ExprKind::Type(ref mut op, _)
|
|
| ExprKind::UnsafeBinderCast(_, ref mut op, _) => {
|
|
let forbid_let_reason = self.forbid_let_reason;
|
|
self.forbid_let_reason = Some(OtherForbidden);
|
|
self.visit_expr(op);
|
|
self.forbid_let_reason = forbid_let_reason;
|
|
}
|
|
ExprKind::Let(_, _, _, Recovered::Yes(_))
|
|
| ExprKind::Array(_)
|
|
| ExprKind::ConstBlock(_)
|
|
| ExprKind::Lit(_)
|
|
| ExprKind::If(_, _, _)
|
|
| ExprKind::While(_, _, _)
|
|
| ExprKind::ForLoop { .. }
|
|
| ExprKind::Loop(_, _, _)
|
|
| ExprKind::Match(_, _, _)
|
|
| ExprKind::Closure(_)
|
|
| ExprKind::Block(_, _)
|
|
| ExprKind::Gen(_, _, _, _)
|
|
| ExprKind::TryBlock(_)
|
|
| ExprKind::Underscore
|
|
| ExprKind::Path(_, _)
|
|
| ExprKind::Break(_, _)
|
|
| ExprKind::Continue(_)
|
|
| ExprKind::Ret(_)
|
|
| ExprKind::InlineAsm(_)
|
|
| ExprKind::OffsetOf(_, _)
|
|
| ExprKind::MacCall(_)
|
|
| ExprKind::Struct(_)
|
|
| ExprKind::Repeat(_, _)
|
|
| ExprKind::Yield(_)
|
|
| ExprKind::Yeet(_)
|
|
| ExprKind::Become(_)
|
|
| ExprKind::IncludedBytes(_)
|
|
| ExprKind::FormatArgs(_)
|
|
| ExprKind::Err(_)
|
|
| ExprKind::Dummy => {
|
|
// These would forbid any let expressions they contain already.
|
|
}
|
|
}
|
|
self.depth -= 1;
|
|
}
|
|
}
|