diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index d1eb69fbbc7b3..d67ae14934429 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use rustc_ast::token::Token; use rustc_ast::{Path, Visibility}; use rustc_errors::{ - AddToDiagnostic, Applicability, DiagCtxt, DiagnosticBuilder, IntoDiagnostic, Level, MultiSpan, + AddToDiagnostic, Applicability, DiagCtxt, DiagnosticBuilder, IntoDiagnostic, Level, SubdiagnosticMessage, }; use rustc_macros::{Diagnostic, Subdiagnostic}; @@ -2382,7 +2382,7 @@ pub(crate) struct ExpectedCommaAfterPatternField { #[diag(parse_unexpected_paren_in_range_pat)] pub(crate) struct UnexpectedParenInRangePat { #[primary_span] - pub span: MultiSpan, + pub span: Vec, #[subdiagnostic] pub sugg: UnexpectedParenInRangePatSugg, } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 50cf332ee7f48..1029b4b18e0bd 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1,14 +1,14 @@ use super::{ForceCollect, Parser, PathStyle, TrailingToken}; use crate::errors::{ - AmbiguousRangePattern, BoxNotPat, DotDotDotForRemainingFields, - DotDotDotRangeToPatternNotAllowed, DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, - ExpectedBindingLeftOfAt, ExpectedCommaAfterPatternField, - GenericArgsInPatRequireTurbofishSyntax, InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, - InclusiveRangeNoEnd, InvalidMutInPattern, PatternOnWrongSideOfAt, RefMutOrderIncorrect, - RemoveLet, RepeatedMutInPattern, SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, - TopLevelOrPatternNotAllowedSugg, TrailingVertNotAllowed, UnexpectedLifetimeInPattern, - UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg, - UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, + self, AmbiguousRangePattern, DotDotDotForRemainingFields, DotDotDotRangeToPatternNotAllowed, + DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt, + ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax, + InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern, + PatternOnWrongSideOfAt, RefMutOrderIncorrect, RemoveLet, RepeatedMutInPattern, + SwitchRefBoxOrder, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, + TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedParenInRangePat, + UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam, + UnexpectedVertVertInPattern, }; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor}; @@ -19,7 +19,7 @@ use rustc_ast::{ PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, }; use rustc_ast_pretty::pprust; -use rustc_errors::{Applicability, DiagnosticBuilder, MultiSpan, PResult}; +use rustc_errors::{Applicability, DiagnosticBuilder, PResult}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::source_map::{respan, Spanned}; use rustc_span::symbol::{kw, sym, Ident}; @@ -604,7 +604,7 @@ impl<'a> Parser<'a> { && let Some(form) = self.parse_range_end() => { self.dcx().emit_err(UnexpectedParenInRangePat { - span: MultiSpan::from_spans(vec![open_paren, close_paren]), + span: vec![open_paren, close_paren], sugg: UnexpectedParenInRangePatSugg { start_span: open_paren, end_span: close_paren, @@ -745,6 +745,43 @@ impl<'a> Parser<'a> { Some(respan(self.prev_token.span, re)) } + /// Parse using `f(self)`, but may eat parentheses before and after calling `f(self)` (and emit an error in this case.) + /// Can trivially be generalized for other [`Delimiter`] and errors other than [`UnexpectedParenInRangePat`] if desired. + fn maybe_recover_paren_around_range_bound(&mut self, f: F) -> PResult<'a, T> + where + F: Fn(&mut Parser<'a>) -> PResult<'a, T>, + { + // recover from leading `(` + if self.may_recover() && self.token.kind == token::OpenDelim(Delimiter::Parenthesis) { + let mut snapshot = self.create_snapshot_for_diagnostic(); + snapshot.bump(); + + let open_paren = snapshot.prev_token.span; + let parsed = f(&mut *snapshot)?; + + // check for trailing `)` + if snapshot.eat_noexpect(&token::CloseDelim(Delimiter::Parenthesis)) { + self.restore_snapshot(snapshot); + + self.dcx().emit_err(UnexpectedParenInRangePat { + span: vec![open_paren, self.prev_token.span], + sugg: UnexpectedParenInRangePatSugg { + start_span: open_paren, + end_span: self.prev_token.span, + }, + }); + + Ok(parsed) + } else { + // `f(snapshot)` parsed something but we're now missing a `)`, + // so just abort the recovery attempt + f(self) + } + } else { + f(self) + } + } + /// Parse a range pattern `$begin $form $end?` where `$form = ".." | "..." | "..=" ;`. /// `$begin $form` has already been parsed. fn parse_pat_range_begin_with( @@ -752,17 +789,9 @@ impl<'a> Parser<'a> { begin: P, re: Spanned, ) -> PResult<'a, PatKind> { - // recover from `(` - let open_paren = (self.may_recover() - && self.token.kind == token::OpenDelim(Delimiter::Parenthesis)) - .then(|| { - self.bump(); - self.prev_token.span - }); - let end = if self.is_pat_range_end_start(0) { // Parsing e.g. `X..=Y`. - Some(self.parse_pat_range_end()?) + Some(self.maybe_recover_paren_around_range_bound(|this| this.parse_pat_range_end())?) } else { // Parsing e.g. `X..`. if let RangeEnd::Included(_) = re.node { @@ -772,18 +801,6 @@ impl<'a> Parser<'a> { None }; - if let Some(span) = open_paren { - self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; - - self.dcx().emit_err(UnexpectedParenInRangePat { - span: MultiSpan::from_spans(vec![span, self.prev_token.span]), - sugg: UnexpectedParenInRangePatSugg { - start_span: span, - end_span: self.prev_token.span, - }, - }); - } - Ok(PatKind::Range(Some(begin), end, re)) } @@ -823,32 +840,13 @@ impl<'a> Parser<'a> { /// The form `...X` is prohibited to reduce confusion with the potential /// expression syntax `...expr` for splatting in expressions. fn parse_pat_range_to(&mut self, mut re: Spanned) -> PResult<'a, PatKind> { - // recover from `(` - let open_paren = (self.may_recover() - && self.token.kind == token::OpenDelim(Delimiter::Parenthesis)) - .then(|| { - self.bump(); - self.prev_token.span - }); + let end = self.maybe_recover_paren_around_range_bound(|that| that.parse_pat_range_end())?; - let end = self.parse_pat_range_end()?; if let RangeEnd::Included(syn @ RangeSyntax::DotDotDot) = &mut re.node { *syn = RangeSyntax::DotDotEq; self.dcx().emit_err(DotDotDotRangeToPatternNotAllowed { span: re.span }); } - if let Some(span) = open_paren { - self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; - - self.dcx().emit_err(UnexpectedParenInRangePat { - span: MultiSpan::from_spans(vec![span, self.prev_token.span]), - sugg: UnexpectedParenInRangePatSugg { - start_span: span, - end_span: self.prev_token.span, - }, - }); - } - Ok(PatKind::Range(None, Some(end), re)) } @@ -1013,7 +1011,7 @@ impl<'a> Parser<'a> { if self.isnt_pattern_start() { let descr = super::token_descr(&self.token); - self.dcx().emit_err(BoxNotPat { + self.dcx().emit_err(errors::BoxNotPat { span: self.token.span, kw: box_span, lo: box_span.shrink_to_lo(), diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs index 0e96cfe785857..1d7ac24a3019a 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs +++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs @@ -8,7 +8,7 @@ fn main() { for x in -9 + 1..=(9 - 2) { match x as i32 { 0..=(5+1) => errors_only.push(x), - //~^ error: expected `)`, found `+` + //~^ error: unexpected token: `(` 1 | -3..0 => first_or.push(x), y @ (0..5 | 6) => or_two.push(y), y @ 0..const { 5 + 1 } => assert_eq!(y, 5), diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr index a54f29a3b3263..ae6135c092c7e 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr @@ -1,8 +1,8 @@ -error: expected `)`, found `+` - --> $DIR/range_pat_interactions2.rs:10:19 +error: unexpected token: `(` + --> $DIR/range_pat_interactions2.rs:10:17 | LL | 0..=(5+1) => errors_only.push(x), - | ^ expected `)` + | ^ error: aborting due to 1 previous error