Recover more expressions in patterns
This commit is contained in:
parent
7fc70f870a
commit
c2047219b5
20 changed files with 631 additions and 280 deletions
|
@ -41,7 +41,7 @@ use super::{
|
|||
use crate::{errors, maybe_recover_from_interpolated_ty_qpath};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DestructuredFloat {
|
||||
pub(super) enum DestructuredFloat {
|
||||
/// 1e2
|
||||
Single(Symbol, Span),
|
||||
/// 1.
|
||||
|
@ -1041,7 +1041,7 @@ impl<'a> Parser<'a> {
|
|||
// 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).
|
||||
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
|
||||
pub(super) fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
|
||||
#[derive(Debug)]
|
||||
enum FloatComponent {
|
||||
IdentLike(String),
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::errors::{
|
|||
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
|
||||
UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, WrapInParens,
|
||||
};
|
||||
use crate::parser::expr::could_be_unclosed_char_literal;
|
||||
use crate::parser::expr::{could_be_unclosed_char_literal, DestructuredFloat};
|
||||
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
|
@ -342,7 +342,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by a method call or an operator.
|
||||
/// Ensures that the last parsed pattern (or pattern range bound) is not followed by an expression.
|
||||
///
|
||||
/// `is_end_bound` indicates whether the last parsed thing was the end bound of a range pattern (see [`parse_pat_range_end`](Self::parse_pat_range_end))
|
||||
/// in order to say "expected a pattern range bound" instead of "expected a pattern";
|
||||
|
@ -350,38 +350,64 @@ impl<'a> Parser<'a> {
|
|||
/// 0..=1 + 2
|
||||
/// ^^^^^
|
||||
/// ```
|
||||
/// Only the end bound is spanned, and this function have no idea if there were a `..=` before `pat_span`, hence the parameter.
|
||||
/// Only the end bound is spanned in this case, and this function has no idea if there was a `..=` before `pat_span`, hence the parameter.
|
||||
///
|
||||
/// This function returns `Some` if a trailing expression was recovered, and said expression's span.
|
||||
#[must_use = "the pattern must be discarded as `PatKind::Err` if this function returns Some"]
|
||||
fn maybe_recover_trailing_expr(
|
||||
&mut self,
|
||||
pat_span: Span,
|
||||
is_end_bound: bool,
|
||||
) -> Option<ErrorGuaranteed> {
|
||||
) -> Option<(ErrorGuaranteed, Span)> {
|
||||
if self.prev_token.is_keyword(kw::Underscore) || !self.may_recover() {
|
||||
// Don't recover anything after an `_` or if recovery is disabled.
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check for `.hello()`, but allow `.Hello()` to be recovered as `, Hello()` in `parse_seq_to_before_tokens()`.
|
||||
let has_trailing_method = self.check_noexpect(&token::Dot)
|
||||
// Returns `true` iff `token` is an unsuffixed integer.
|
||||
let is_one_tuple_index = |_: &Self, token: &Token| -> bool {
|
||||
use token::{Lit, LitKind};
|
||||
|
||||
matches!(
|
||||
token.kind,
|
||||
token::Literal(Lit { kind: LitKind::Integer, symbol: _, suffix: None })
|
||||
)
|
||||
};
|
||||
|
||||
// Returns `true` iff `token` is an unsuffixed `x.y` float.
|
||||
let is_two_tuple_indexes = |this: &Self, token: &Token| -> bool {
|
||||
use token::{Lit, LitKind};
|
||||
|
||||
if let token::Literal(Lit { kind: LitKind::Float, symbol, suffix: None }) = token.kind
|
||||
&& let DestructuredFloat::MiddleDot(..) = this.break_up_float(symbol, token.span)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Check for `.hello` or `.0`.
|
||||
let has_dot_expr = self.check_noexpect(&token::Dot) // `.`
|
||||
&& self.look_ahead(1, |tok| {
|
||||
tok.ident()
|
||||
.and_then(|(ident, _)| ident.name.as_str().chars().next())
|
||||
.is_some_and(char::is_lowercase)
|
||||
})
|
||||
&& self.look_ahead(2, |t| *t == token::OpenDelim(Delimiter::Parenthesis));
|
||||
tok.is_ident() // `hello`
|
||||
|| is_one_tuple_index(&self, &tok) // `0`
|
||||
|| is_two_tuple_indexes(&self, &tok) // `0.0`
|
||||
});
|
||||
|
||||
// Check for operators.
|
||||
// `|` is excluded as it is used in pattern alternatives and lambdas,
|
||||
// `?` is included for error propagation,
|
||||
// `[` is included for indexing operations,
|
||||
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`)
|
||||
// `[]` is excluded as `a[]` isn't an expression and should be recovered as `a, []` (cf. `tests/ui/parser/pat-lt-bracket-7.rs`),
|
||||
// `as` is included for type casts
|
||||
let has_trailing_operator = matches!(self.token.kind, token::BinOp(op) if op != BinOpToken::Or)
|
||||
|| self.token == token::Question
|
||||
|| (self.token == token::OpenDelim(Delimiter::Bracket)
|
||||
&& self.look_ahead(1, |t| *t != token::CloseDelim(Delimiter::Bracket)));
|
||||
&& self.look_ahead(1, |t| *t != token::CloseDelim(Delimiter::Bracket))) // excludes `[]`
|
||||
|| self.token.is_keyword(kw::As);
|
||||
|
||||
if !has_trailing_method && !has_trailing_operator {
|
||||
if !has_dot_expr && !has_trailing_operator {
|
||||
// Nothing to recover here.
|
||||
return None;
|
||||
}
|
||||
|
@ -391,44 +417,41 @@ impl<'a> Parser<'a> {
|
|||
snapshot.restrictions.insert(Restrictions::IS_PAT);
|
||||
|
||||
// Parse `?`, `.f`, `(arg0, arg1, ...)` or `[expr]` until they've all been eaten.
|
||||
if let Ok(expr) = snapshot
|
||||
let Ok(expr) = snapshot
|
||||
.parse_expr_dot_or_call_with(
|
||||
AttrVec::new(),
|
||||
self.mk_expr(pat_span, ExprKind::Dummy), // equivalent to transforming the parsed pattern into an `Expr`
|
||||
pat_span,
|
||||
)
|
||||
.map_err(|err| err.cancel())
|
||||
{
|
||||
let non_assoc_span = expr.span;
|
||||
else {
|
||||
// We got a trailing method/operator, but that wasn't an expression.
|
||||
return None;
|
||||
};
|
||||
|
||||
// Parse an associative expression such as `+ expr`, `% expr`, ...
|
||||
// Assignments, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
|
||||
if let Ok((expr, _)) =
|
||||
snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel())
|
||||
{
|
||||
// We got a valid expression.
|
||||
self.restore_snapshot(snapshot);
|
||||
self.restrictions.remove(Restrictions::IS_PAT);
|
||||
// Parse an associative expression such as `+ expr`, `% expr`, ...
|
||||
// Assignments, ranges and `|` are disabled by [`Restrictions::IS_PAT`].
|
||||
let Ok((expr, _)) =
|
||||
snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel())
|
||||
else {
|
||||
// We got a trailing method/operator, but that wasn't an expression.
|
||||
return None;
|
||||
};
|
||||
|
||||
let is_bound = is_end_bound
|
||||
// is_start_bound: either `..` or `)..`
|
||||
|| self.token.is_range_separator()
|
||||
|| self.token == token::CloseDelim(Delimiter::Parenthesis)
|
||||
&& self.look_ahead(1, Token::is_range_separator);
|
||||
// We got a valid expression.
|
||||
self.restore_snapshot(snapshot);
|
||||
self.restrictions.remove(Restrictions::IS_PAT);
|
||||
|
||||
// Check that `parse_expr_assoc_with` didn't eat a rhs.
|
||||
let is_method_call = has_trailing_method && non_assoc_span == expr.span;
|
||||
let is_bound = is_end_bound
|
||||
// is_start_bound: either `..` or `)..`
|
||||
|| self.token.is_range_separator()
|
||||
|| self.token == token::CloseDelim(Delimiter::Parenthesis)
|
||||
&& self.look_ahead(1, Token::is_range_separator);
|
||||
|
||||
return Some(self.dcx().emit_err(UnexpectedExpressionInPattern {
|
||||
span: expr.span,
|
||||
is_bound,
|
||||
is_method_call,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// We got a trailing method/operator, but we couldn't parse an expression.
|
||||
None
|
||||
Some((
|
||||
self.dcx().emit_err(UnexpectedExpressionInPattern { span: expr.span, is_bound }),
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
|
||||
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
|
||||
|
@ -544,7 +567,7 @@ impl<'a> Parser<'a> {
|
|||
self.parse_pat_tuple_struct(qself, path)?
|
||||
} else {
|
||||
match self.maybe_recover_trailing_expr(span, false) {
|
||||
Some(guar) => PatKind::Err(guar),
|
||||
Some((guar, _)) => PatKind::Err(guar),
|
||||
None => PatKind::Path(qself, path),
|
||||
}
|
||||
}
|
||||
|
@ -577,10 +600,10 @@ impl<'a> Parser<'a> {
|
|||
// Try to parse everything else as literal with optional minus
|
||||
match self.parse_literal_maybe_minus() {
|
||||
Ok(begin) => {
|
||||
let begin = match self.maybe_recover_trailing_expr(begin.span, false) {
|
||||
Some(guar) => self.mk_expr_err(begin.span, guar),
|
||||
None => begin,
|
||||
};
|
||||
let begin = self
|
||||
.maybe_recover_trailing_expr(begin.span, false)
|
||||
.map(|(guar, sp)| self.mk_expr_err(sp, guar))
|
||||
.unwrap_or(begin);
|
||||
|
||||
match self.parse_range_end() {
|
||||
Some(form) => self.parse_pat_range_begin_with(begin, form)?,
|
||||
|
@ -721,7 +744,8 @@ impl<'a> Parser<'a> {
|
|||
// For backward compatibility, `(..)` is a tuple pattern as well.
|
||||
let paren_pattern =
|
||||
fields.len() == 1 && !(matches!(trailing_comma, Trailing::Yes) || fields[0].is_rest());
|
||||
if paren_pattern {
|
||||
|
||||
let pat = if paren_pattern {
|
||||
let pat = fields.into_iter().next().unwrap();
|
||||
let close_paren = self.prev_token.span;
|
||||
|
||||
|
@ -739,7 +763,7 @@ impl<'a> Parser<'a> {
|
|||
},
|
||||
});
|
||||
|
||||
self.parse_pat_range_begin_with(begin.clone(), form)
|
||||
self.parse_pat_range_begin_with(begin.clone(), form)?
|
||||
}
|
||||
// recover ranges with parentheses around the `(start)..`
|
||||
PatKind::Err(guar)
|
||||
|
@ -754,15 +778,20 @@ impl<'a> Parser<'a> {
|
|||
},
|
||||
});
|
||||
|
||||
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)
|
||||
self.parse_pat_range_begin_with(self.mk_expr_err(pat.span, *guar), form)?
|
||||
}
|
||||
|
||||
// (pat) with optional parentheses
|
||||
_ => Ok(PatKind::Paren(pat)),
|
||||
_ => PatKind::Paren(pat),
|
||||
}
|
||||
} else {
|
||||
Ok(PatKind::Tuple(fields))
|
||||
}
|
||||
PatKind::Tuple(fields)
|
||||
};
|
||||
|
||||
Ok(match self.maybe_recover_trailing_expr(open_paren.to(self.prev_token.span), false) {
|
||||
None => pat,
|
||||
Some((guar, _)) => PatKind::Err(guar),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a mutable binding with the `mut` token already eaten.
|
||||
|
@ -1015,7 +1044,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
Ok(match recovered {
|
||||
Some(guar) => self.mk_expr_err(bound.span, guar),
|
||||
Some((guar, sp)) => self.mk_expr_err(sp, guar),
|
||||
None => bound,
|
||||
})
|
||||
}
|
||||
|
@ -1084,7 +1113,7 @@ impl<'a> Parser<'a> {
|
|||
// but not `ident @ subpat` as `subpat` was already checked and `ident` continues with `@`.
|
||||
|
||||
let pat = if sub.is_none()
|
||||
&& let Some(guar) = self.maybe_recover_trailing_expr(ident.span, false)
|
||||
&& let Some((guar, _)) = self.maybe_recover_trailing_expr(ident.span, false)
|
||||
{
|
||||
PatKind::Err(guar)
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue