diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index ce39d07656f2a..55af2c9ddd32f 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1,6 +1,9 @@ +use super::pat::Expected; use super::ty::AllowPlus; -use super::TokenType; -use super::{BlockMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenExpectType}; +use super::{ + BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep, + TokenExpectType, TokenType, +}; use rustc_ast as ast; use rustc_ast::ptr::P; @@ -19,6 +22,8 @@ use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, Ident}; use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP}; +use std::mem::take; + use tracing::{debug, trace}; const TURBOFISH_SUGGESTION_STR: &str = @@ -2075,4 +2080,177 @@ impl<'a> Parser<'a> { ); err } + + /// Some special error handling for the "top-level" patterns in a match arm, + /// `for` loop, `let`, &c. (in contrast to subpatterns within such). + crate fn maybe_recover_colon_colon_in_pat_typo( + &mut self, + mut first_pat: P, + ra: RecoverColon, + expected: Expected, + ) -> P { + if RecoverColon::Yes != ra || token::Colon != self.token.kind { + return first_pat; + } + if !matches!(first_pat.kind, PatKind::Ident(_, _, None) | PatKind::Path(..)) + || !self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident()) + { + return first_pat; + } + // The pattern looks like it might be a path with a `::` -> `:` typo: + // `match foo { bar:baz => {} }` + let span = self.token.span; + // We only emit "unexpected `:`" error here if we can successfully parse the + // whole pattern correctly in that case. + let snapshot = self.clone(); + + // Create error for "unexpected `:`". + match self.expected_one_of_not_found(&[], &[]) { + Err(mut err) => { + self.bump(); // Skip the `:`. + match self.parse_pat_no_top_alt(expected) { + Err(mut inner_err) => { + // Carry on as if we had not done anything, callers will emit a + // reasonable error. + inner_err.cancel(); + err.cancel(); + *self = snapshot; + } + Ok(mut pat) => { + // We've parsed the rest of the pattern. + let new_span = first_pat.span.to(pat.span); + let mut show_sugg = false; + // Try to construct a recovered pattern. + match &mut pat.kind { + PatKind::Struct(qself @ None, path, ..) + | PatKind::TupleStruct(qself @ None, path, _) + | PatKind::Path(qself @ None, path) => match &first_pat.kind { + PatKind::Ident(_, ident, _) => { + path.segments.insert(0, PathSegment::from_ident(ident.clone())); + path.span = new_span; + show_sugg = true; + first_pat = pat; + } + PatKind::Path(old_qself, old_path) => { + path.segments = old_path + .segments + .iter() + .cloned() + .chain(take(&mut path.segments)) + .collect(); + path.span = new_span; + *qself = old_qself.clone(); + first_pat = pat; + show_sugg = true; + } + _ => {} + }, + PatKind::Ident(BindingMode::ByValue(Mutability::Not), ident, None) => { + match &first_pat.kind { + PatKind::Ident(_, old_ident, _) => { + let path = PatKind::Path( + None, + Path { + span: new_span, + segments: vec![ + PathSegment::from_ident(old_ident.clone()), + PathSegment::from_ident(ident.clone()), + ], + tokens: None, + }, + ); + first_pat = self.mk_pat(new_span, path); + show_sugg = true; + } + PatKind::Path(old_qself, old_path) => { + let mut segments = old_path.segments.clone(); + segments.push(PathSegment::from_ident(ident.clone())); + let path = PatKind::Path( + old_qself.clone(), + Path { span: new_span, segments, tokens: None }, + ); + first_pat = self.mk_pat(new_span, path); + show_sugg = true; + } + _ => {} + } + } + _ => {} + } + if show_sugg { + err.span_suggestion( + span, + "maybe write a path separator here", + "::".to_string(), + Applicability::MaybeIncorrect, + ); + } else { + first_pat = self.mk_pat(new_span, PatKind::Wild); + } + err.emit(); + } + } + } + _ => { + // Carry on as if we had not done anything. This should be unreachable. + *self = snapshot; + } + }; + first_pat + } + + /// Some special error handling for the "top-level" patterns in a match arm, + /// `for` loop, `let`, &c. (in contrast to subpatterns within such). + crate fn maybe_recover_unexpected_comma( + &mut self, + lo: Span, + rc: RecoverComma, + ) -> PResult<'a, ()> { + if rc == RecoverComma::No || self.token != token::Comma { + return Ok(()); + } + + // An unexpected comma after a top-level pattern is a clue that the + // user (perhaps more accustomed to some other language) forgot the + // parentheses in what should have been a tuple pattern; return a + // suggestion-enhanced error here rather than choking on the comma later. + let comma_span = self.token.span; + self.bump(); + if let Err(mut err) = self.skip_pat_list() { + // We didn't expect this to work anyway; we just wanted to advance to the + // end of the comma-sequence so we know the span to suggest parenthesizing. + err.cancel(); + } + let seq_span = lo.to(self.prev_token.span); + let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern"); + if let Ok(seq_snippet) = self.span_to_snippet(seq_span) { + const MSG: &str = "try adding parentheses to match on a tuple..."; + + err.span_suggestion( + seq_span, + MSG, + format!("({})", seq_snippet), + Applicability::MachineApplicable, + ); + err.span_suggestion( + seq_span, + "...or a vertical bar to match on multiple alternatives", + seq_snippet.replace(",", " |"), + Applicability::MachineApplicable, + ); + } + Err(err) + } + + /// Parse and throw away a parenthesized comma separated + /// sequence of patterns until `)` is reached. + fn skip_pat_list(&mut self) -> PResult<'a, ()> { + while !self.check(&token::CloseDelim(token::Paren)) { + self.parse_pat_no_top_alt(None)?; + if !self.eat(&token::Comma) { + return Ok(()); + } + } + Ok(()) + } } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index bb3947bb47a25..ac3123c40e3d9 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -3,14 +3,16 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor}; use rustc_ast::ptr::P; use rustc_ast::token; -use rustc_ast::{self as ast, AttrVec, Attribute, MacCall, Pat, PatField, PatKind, RangeEnd}; -use rustc_ast::{BindingMode, Expr, ExprKind, Mutability, Path, QSelf, RangeSyntax}; +use rustc_ast::{ + self as ast, AttrVec, Attribute, BindingMode, Expr, ExprKind, MacCall, Mutability, Pat, + PatField, PatKind, Path, QSelf, RangeEnd, RangeSyntax, +}; use rustc_ast_pretty::pprust; use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, PResult}; use rustc_span::source_map::{respan, Span, Spanned}; use rustc_span::symbol::{kw, sym, Ident}; -type Expected = Option<&'static str>; +pub(super) type Expected = Option<&'static str>; /// `Expected` for function and lambda parameter patterns. pub(super) const PARAM_EXPECTED: Expected = Some("parameter name"); @@ -98,55 +100,9 @@ impl<'a> Parser<'a> { // If we parsed a leading `|` which should be gated, // then we should really gate the leading `|`. // This complicated procedure is done purely for diagnostics UX. - let mut first_pat = first_pat; - - if let (RecoverColon::Yes, token::Colon) = (ra, &self.token.kind) { - if matches!( - first_pat.kind, - PatKind::Ident(BindingMode::ByValue(Mutability::Not), _, None) - | PatKind::Path(..) - ) && self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident()) - { - // The pattern looks like it might be a path with a `::` -> `:` typo: - // `match foo { bar:baz => {} }` - let span = self.token.span; - // We only emit "unexpected `:`" error here if we can successfully parse the - // whole pattern correctly in that case. - let snapshot = self.clone(); - - // Create error for "unexpected `:`". - match self.expected_one_of_not_found(&[], &[]) { - Err(mut err) => { - self.bump(); // Skip the `:`. - match self.parse_pat_no_top_alt(expected) { - Err(mut inner_err) => { - // Carry on as if we had not done anything, callers will emit a - // reasonable error. - inner_err.cancel(); - err.cancel(); - *self = snapshot; - } - Ok(pat) => { - // We've parsed the rest of the pattern. - err.span_suggestion( - span, - "maybe write a path separator here", - "::".to_string(), - Applicability::MachineApplicable, - ); - err.emit(); - first_pat = - self.mk_pat(first_pat.span.to(pat.span), PatKind::Wild); - } - } - } - _ => { - // Carry on as if we had not done anything. This should be unreachable. - *self = snapshot; - } - }; - } - } + + // Check if the user wrote `foo:bar` instead of `foo::bar`. + let first_pat = self.maybe_recover_colon_colon_in_pat_typo(first_pat, ra, expected); if let Some(leading_vert_span) = leading_vert_span { // If there was a leading vert, treat this as an or-pattern. This improves @@ -321,57 +277,6 @@ impl<'a> Parser<'a> { err.emit(); } - /// Some special error handling for the "top-level" patterns in a match arm, - /// `for` loop, `let`, &c. (in contrast to subpatterns within such). - fn maybe_recover_unexpected_comma(&mut self, lo: Span, rc: RecoverComma) -> PResult<'a, ()> { - if rc == RecoverComma::No || self.token != token::Comma { - return Ok(()); - } - - // An unexpected comma after a top-level pattern is a clue that the - // user (perhaps more accustomed to some other language) forgot the - // parentheses in what should have been a tuple pattern; return a - // suggestion-enhanced error here rather than choking on the comma later. - let comma_span = self.token.span; - self.bump(); - if let Err(mut err) = self.skip_pat_list() { - // We didn't expect this to work anyway; we just wanted to advance to the - // end of the comma-sequence so we know the span to suggest parenthesizing. - err.cancel(); - } - let seq_span = lo.to(self.prev_token.span); - let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern"); - if let Ok(seq_snippet) = self.span_to_snippet(seq_span) { - const MSG: &str = "try adding parentheses to match on a tuple..."; - - err.span_suggestion( - seq_span, - MSG, - format!("({})", seq_snippet), - Applicability::MachineApplicable, - ); - err.span_suggestion( - seq_span, - "...or a vertical bar to match on multiple alternatives", - seq_snippet.replace(",", " |"), - Applicability::MachineApplicable, - ); - } - Err(err) - } - - /// Parse and throw away a parenthesized comma separated - /// sequence of patterns until `)` is reached. - fn skip_pat_list(&mut self) -> PResult<'a, ()> { - while !self.check(&token::CloseDelim(token::Paren)) { - self.parse_pat_no_top_alt(None)?; - if !self.eat(&token::Comma) { - return Ok(()); - } - } - Ok(()) - } - /// A `|` or possibly `||` token shouldn't be here. Ban it. fn ban_illegal_vert(&mut self, lo: Option, pos: &str, ctx: &str) { let span = self.token.span; @@ -1168,7 +1073,7 @@ impl<'a> Parser<'a> { self.mk_pat(span, PatKind::Ident(bm, ident, None)) } - fn mk_pat(&self, span: Span, kind: PatKind) -> P { + pub(super) fn mk_pat(&self, span: Span, kind: PatKind) -> P { P(Pat { kind, span, id: ast::DUMMY_NODE_ID, tokens: None }) } } diff --git a/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs b/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs index 4ee0b2054ff77..0b7b67496d6f3 100644 --- a/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs +++ b/src/test/ui/parser/issues/issue-87086-colon-path-sep.rs @@ -1,11 +1,15 @@ // Tests that a suggestion is issued if the user wrote a colon instead of // a path separator in a match arm. -enum Foo { - Bar, - Baz, +mod qux { + pub enum Foo { + Bar, + Baz, + } } +use qux::Foo; + fn f() -> Foo { Foo::Bar } fn g1() { @@ -16,24 +20,24 @@ fn g1() { _ => {} } match f() { - Foo::Bar:Baz => {} + qux::Foo:Bar => {} //~^ ERROR: expected one of //~| HELP: maybe write a path separator here _ => {} } match f() { - Foo:Bar::Baz => {} + qux:Foo::Baz => {} //~^ ERROR: expected one of //~| HELP: maybe write a path separator here _ => {} } match f() { - Foo: Bar::Baz if true => {} + qux: Foo::Baz if true => {} //~^ ERROR: expected one of //~| HELP: maybe write a path separator here _ => {} } - if let Bar:Baz = f() { + if let Foo:Bar = f() { //~^ ERROR: expected one of //~| HELP: maybe write a path separator here } @@ -41,16 +45,18 @@ fn g1() { fn g1_neg() { match f() { - ref Foo: Bar::Baz => {} + ref qux: Foo::Baz => {} //~^ ERROR: expected one of + //~| HELP: maybe write a path separator here _ => {} } } fn g2_neg() { match f() { - mut Foo: Bar::Baz => {} + mut qux: Foo::Baz => {} //~^ ERROR: expected one of + //~| HELP: maybe write a path separator here _ => {} } } @@ -62,5 +68,12 @@ fn main() { Foo:Bar::Baz => {} //~^ ERROR: expected one of //~| HELP: maybe write a path separator here + //~| ERROR: failed to resolve: `Bar` is a variant, not a module + } + match myfoo { + Foo::Bar => {} + Foo:Bar => {} + //~^ ERROR: expected one of + //~| HELP: maybe write a path separator here } } diff --git a/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr b/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr index 8f93661a62646..2050a16beb349 100644 --- a/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr +++ b/src/test/ui/parser/issues/issue-87086-colon-path-sep.stderr @@ -1,5 +1,5 @@ error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:13:12 + --> $DIR/issue-87086-colon-path-sep.rs:17:12 | LL | Foo:Bar => {} | ^ @@ -8,55 +8,61 @@ LL | Foo:Bar => {} | help: maybe write a path separator here: `::` error: expected one of `!`, `(`, `...`, `..=`, `..`, `::`, `{`, or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:19:17 + --> $DIR/issue-87086-colon-path-sep.rs:23:17 | -LL | Foo::Bar:Baz => {} +LL | qux::Foo:Bar => {} | ^ | | | expected one of 8 possible tokens | help: maybe write a path separator here: `::` error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:25:12 + --> $DIR/issue-87086-colon-path-sep.rs:29:12 | -LL | Foo:Bar::Baz => {} +LL | qux:Foo::Baz => {} | ^ | | | expected one of `@` or `|` | help: maybe write a path separator here: `::` error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:31:12 + --> $DIR/issue-87086-colon-path-sep.rs:35:12 | -LL | Foo: Bar::Baz if true => {} +LL | qux: Foo::Baz if true => {} | ^ | | | expected one of `@` or `|` | help: maybe write a path separator here: `::` error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:36:15 + --> $DIR/issue-87086-colon-path-sep.rs:40:15 | -LL | if let Bar:Baz = f() { +LL | if let Foo:Bar = f() { | ^ | | | expected one of `@` or `|` | help: maybe write a path separator here: `::` -error: expected one of `=>`, `@`, `if`, or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:44:16 +error: expected one of `@` or `|`, found `:` + --> $DIR/issue-87086-colon-path-sep.rs:48:16 | -LL | ref Foo: Bar::Baz => {} - | ^ expected one of `=>`, `@`, `if`, or `|` +LL | ref qux: Foo::Baz => {} + | ^ + | | + | expected one of `@` or `|` + | help: maybe write a path separator here: `::` -error: expected one of `=>`, `@`, `if`, or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:52:16 +error: expected one of `@` or `|`, found `:` + --> $DIR/issue-87086-colon-path-sep.rs:57:16 | -LL | mut Foo: Bar::Baz => {} - | ^ expected one of `=>`, `@`, `if`, or `|` +LL | mut qux: Foo::Baz => {} + | ^ + | | + | expected one of `@` or `|` + | help: maybe write a path separator here: `::` error: expected one of `@` or `|`, found `:` - --> $DIR/issue-87086-colon-path-sep.rs:62:12 + --> $DIR/issue-87086-colon-path-sep.rs:68:12 | LL | Foo:Bar::Baz => {} | ^ @@ -64,5 +70,21 @@ LL | Foo:Bar::Baz => {} | expected one of `@` or `|` | help: maybe write a path separator here: `::` -error: aborting due to 8 previous errors +error: expected one of `@` or `|`, found `:` + --> $DIR/issue-87086-colon-path-sep.rs:75:12 + | +LL | Foo:Bar => {} + | ^ + | | + | expected one of `@` or `|` + | help: maybe write a path separator here: `::` + +error[E0433]: failed to resolve: `Bar` is a variant, not a module + --> $DIR/issue-87086-colon-path-sep.rs:68:13 + | +LL | Foo:Bar::Baz => {} + | ^^^ `Bar` is a variant, not a module + +error: aborting due to 10 previous errors +For more information about this error, try `rustc --explain E0433`.