Skip to content

Commit d8943a7

Browse files
authored
Rollup merge of #87101 - FabianWolff:issue-87086, r=estebank
Suggest a path separator if a stray colon is found in a match arm Attempts to fix #87086. r? `@estebank`
2 parents 4ec7b48 + 2362450 commit d8943a7

File tree

7 files changed

+219
-19
lines changed

7 files changed

+219
-19
lines changed

compiler/rustc_expand/src/expand.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
2222
use rustc_data_structures::sync::Lrc;
2323
use rustc_errors::{Applicability, FatalError, PResult};
2424
use rustc_feature::Features;
25-
use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, Parser, RecoverComma};
25+
use rustc_parse::parser::{
26+
AttemptLocalParseRecovery, ForceCollect, Parser, RecoverColon, RecoverComma,
27+
};
2628
use rustc_parse::validate_attr;
2729
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
2830
use rustc_session::lint::BuiltinLintDiagnostics;
@@ -930,9 +932,11 @@ pub fn parse_ast_fragment<'a>(
930932
}
931933
}
932934
AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
933-
AstFragmentKind::Pat => {
934-
AstFragment::Pat(this.parse_pat_allow_top_alt(None, RecoverComma::No)?)
935-
}
935+
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_alt(
936+
None,
937+
RecoverComma::No,
938+
RecoverColon::Yes,
939+
)?),
936940
AstFragmentKind::Arms
937941
| AstFragmentKind::Fields
938942
| AstFragmentKind::FieldPats

compiler/rustc_parse/src/parser/expr.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::pat::{RecoverComma, PARAM_EXPECTED};
1+
use super::pat::{RecoverColon, RecoverComma, PARAM_EXPECTED};
22
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
33
use super::{AttrWrapper, BlockMode, ForceCollect, Parser, PathStyle, Restrictions, TokenType};
44
use super::{SemiColonMode, SeqSep, TokenExpectType, TrailingToken};
@@ -1813,7 +1813,7 @@ impl<'a> Parser<'a> {
18131813
/// The `let` token has already been eaten.
18141814
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
18151815
let lo = self.prev_token.span;
1816-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes)?;
1816+
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
18171817
self.expect(&token::Eq)?;
18181818
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
18191819
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
@@ -1876,7 +1876,7 @@ impl<'a> Parser<'a> {
18761876
_ => None,
18771877
};
18781878

1879-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes)?;
1879+
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
18801880
if !self.eat_keyword(kw::In) {
18811881
self.error_missing_in_for_loop();
18821882
}
@@ -2083,7 +2083,7 @@ impl<'a> Parser<'a> {
20832083
let attrs = self.parse_outer_attributes()?;
20842084
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
20852085
let lo = this.token.span;
2086-
let pat = this.parse_pat_allow_top_alt(None, RecoverComma::Yes)?;
2086+
let pat = this.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
20872087
let guard = if this.eat_keyword(kw::If) {
20882088
let if_span = this.prev_token.span;
20892089
let cond = this.parse_expr()?;

compiler/rustc_parse/src/parser/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::lexer::UnmatchedBrace;
1414
pub use attr_wrapper::AttrWrapper;
1515
pub use diagnostics::AttemptLocalParseRecovery;
1616
use diagnostics::Error;
17-
pub use pat::RecoverComma;
17+
pub use pat::{RecoverColon, RecoverComma};
1818
pub use path::PathStyle;
1919

2020
use rustc_ast::ptr::P;

compiler/rustc_parse/src/parser/nonterminal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_ast_pretty::pprust;
55
use rustc_errors::PResult;
66
use rustc_span::symbol::{kw, Ident};
77

8-
use crate::parser::pat::RecoverComma;
8+
use crate::parser::pat::{RecoverColon, RecoverComma};
99
use crate::parser::{FollowedByType, ForceCollect, Parser, PathStyle};
1010

1111
impl<'a> Parser<'a> {
@@ -125,7 +125,7 @@ impl<'a> Parser<'a> {
125125
token::NtPat(self.collect_tokens_no_attrs(|this| match kind {
126126
NonterminalKind::PatParam { .. } => this.parse_pat_no_top_alt(None),
127127
NonterminalKind::PatWithOr { .. } => {
128-
this.parse_pat_allow_top_alt(None, RecoverComma::No)
128+
this.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
129129
}
130130
_ => unreachable!(),
131131
})?)

compiler/rustc_parse/src/parser/pat.rs

+70-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ pub enum RecoverComma {
2424
No,
2525
}
2626

27+
/// Whether or not to recover a `:` when parsing patterns that were meant to be paths.
28+
#[derive(PartialEq, Copy, Clone)]
29+
pub enum RecoverColon {
30+
Yes,
31+
No,
32+
}
33+
2734
/// The result of `eat_or_separator`. We want to distinguish which case we are in to avoid
2835
/// emitting duplicate diagnostics.
2936
#[derive(Debug, Clone, Copy)]
@@ -58,8 +65,9 @@ impl<'a> Parser<'a> {
5865
&mut self,
5966
expected: Expected,
6067
rc: RecoverComma,
68+
ra: RecoverColon,
6169
) -> PResult<'a, P<Pat>> {
62-
self.parse_pat_allow_top_alt_inner(expected, rc).map(|(pat, _)| pat)
70+
self.parse_pat_allow_top_alt_inner(expected, rc, ra).map(|(pat, _)| pat)
6371
}
6472

6573
/// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true =
@@ -68,6 +76,7 @@ impl<'a> Parser<'a> {
6876
&mut self,
6977
expected: Expected,
7078
rc: RecoverComma,
79+
ra: RecoverColon,
7180
) -> PResult<'a, (P<Pat>, bool)> {
7281
// Keep track of whether we recovered from a trailing vert so that we can avoid duplicated
7382
// suggestions (which bothers rustfix).
@@ -89,6 +98,56 @@ impl<'a> Parser<'a> {
8998
// If we parsed a leading `|` which should be gated,
9099
// then we should really gate the leading `|`.
91100
// This complicated procedure is done purely for diagnostics UX.
101+
let mut first_pat = first_pat;
102+
103+
if let (RecoverColon::Yes, token::Colon) = (ra, &self.token.kind) {
104+
if matches!(
105+
first_pat.kind,
106+
PatKind::Ident(BindingMode::ByValue(Mutability::Not), _, None)
107+
| PatKind::Path(..)
108+
) && self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident())
109+
{
110+
// The pattern looks like it might be a path with a `::` -> `:` typo:
111+
// `match foo { bar:baz => {} }`
112+
let span = self.token.span;
113+
// We only emit "unexpected `:`" error here if we can successfully parse the
114+
// whole pattern correctly in that case.
115+
let snapshot = self.clone();
116+
117+
// Create error for "unexpected `:`".
118+
match self.expected_one_of_not_found(&[], &[]) {
119+
Err(mut err) => {
120+
self.bump(); // Skip the `:`.
121+
match self.parse_pat_no_top_alt(expected) {
122+
Err(mut inner_err) => {
123+
// Carry on as if we had not done anything, callers will emit a
124+
// reasonable error.
125+
inner_err.cancel();
126+
err.cancel();
127+
*self = snapshot;
128+
}
129+
Ok(pat) => {
130+
// We've parsed the rest of the pattern.
131+
err.span_suggestion(
132+
span,
133+
"maybe write a path separator here",
134+
"::".to_string(),
135+
Applicability::MachineApplicable,
136+
);
137+
err.emit();
138+
first_pat =
139+
self.mk_pat(first_pat.span.to(pat.span), PatKind::Wild);
140+
}
141+
}
142+
}
143+
_ => {
144+
// Carry on as if we had not done anything. This should be unreachable.
145+
*self = snapshot;
146+
}
147+
};
148+
}
149+
}
150+
92151
if let Some(leading_vert_span) = leading_vert_span {
93152
// If there was a leading vert, treat this as an or-pattern. This improves
94153
// diagnostics.
@@ -140,7 +199,8 @@ impl<'a> Parser<'a> {
140199
// We use `parse_pat_allow_top_alt` regardless of whether we actually want top-level
141200
// or-patterns so that we can detect when a user tries to use it. This allows us to print a
142201
// better error message.
143-
let (pat, trailing_vert) = self.parse_pat_allow_top_alt_inner(expected, rc)?;
202+
let (pat, trailing_vert) =
203+
self.parse_pat_allow_top_alt_inner(expected, rc, RecoverColon::No)?;
144204
let colon = self.eat(&token::Colon);
145205

146206
if let PatKind::Or(pats) = &pat.kind {
@@ -350,7 +410,7 @@ impl<'a> Parser<'a> {
350410
} else if self.check(&token::OpenDelim(token::Bracket)) {
351411
// Parse `[pat, pat,...]` as a slice pattern.
352412
let (pats, _) = self.parse_delim_comma_seq(token::Bracket, |p| {
353-
p.parse_pat_allow_top_alt(None, RecoverComma::No)
413+
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
354414
})?;
355415
PatKind::Slice(pats)
356416
} else if self.check(&token::DotDot) && !self.is_pat_range_end_start(1) {
@@ -563,8 +623,9 @@ impl<'a> Parser<'a> {
563623

564624
/// Parse a tuple or parenthesis pattern.
565625
fn parse_pat_tuple_or_parens(&mut self) -> PResult<'a, PatKind> {
566-
let (fields, trailing_comma) =
567-
self.parse_paren_comma_seq(|p| p.parse_pat_allow_top_alt(None, RecoverComma::No))?;
626+
let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
627+
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
628+
})?;
568629

569630
// Here, `(pat,)` is a tuple pattern.
570631
// For backward compatibility, `(..)` is a tuple pattern as well.
@@ -873,8 +934,9 @@ impl<'a> Parser<'a> {
873934

874935
/// Parse tuple struct or tuple variant pattern (e.g. `Foo(...)` or `Foo::Bar(...)`).
875936
fn parse_pat_tuple_struct(&mut self, qself: Option<QSelf>, path: Path) -> PResult<'a, PatKind> {
876-
let (fields, _) =
877-
self.parse_paren_comma_seq(|p| p.parse_pat_allow_top_alt(None, RecoverComma::No))?;
937+
let (fields, _) = self.parse_paren_comma_seq(|p| {
938+
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
939+
})?;
878940
if qself.is_some() {
879941
self.sess.gated_spans.gate(sym::more_qualified_paths, path.span);
880942
}
@@ -1033,7 +1095,7 @@ impl<'a> Parser<'a> {
10331095
// Parsing a pattern of the form `fieldname: pat`.
10341096
let fieldname = self.parse_field_name()?;
10351097
self.bump();
1036-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::No)?;
1098+
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)?;
10371099
hi = pat.span;
10381100
(pat, fieldname, false)
10391101
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Tests that a suggestion is issued if the user wrote a colon instead of
2+
// a path separator in a match arm.
3+
4+
enum Foo {
5+
Bar,
6+
Baz,
7+
}
8+
9+
fn f() -> Foo { Foo::Bar }
10+
11+
fn g1() {
12+
match f() {
13+
Foo:Bar => {}
14+
//~^ ERROR: expected one of
15+
//~| HELP: maybe write a path separator here
16+
_ => {}
17+
}
18+
match f() {
19+
Foo::Bar:Baz => {}
20+
//~^ ERROR: expected one of
21+
//~| HELP: maybe write a path separator here
22+
_ => {}
23+
}
24+
match f() {
25+
Foo:Bar::Baz => {}
26+
//~^ ERROR: expected one of
27+
//~| HELP: maybe write a path separator here
28+
_ => {}
29+
}
30+
match f() {
31+
Foo: Bar::Baz if true => {}
32+
//~^ ERROR: expected one of
33+
//~| HELP: maybe write a path separator here
34+
_ => {}
35+
}
36+
if let Bar:Baz = f() {
37+
//~^ ERROR: expected one of
38+
//~| HELP: maybe write a path separator here
39+
}
40+
}
41+
42+
fn g1_neg() {
43+
match f() {
44+
ref Foo: Bar::Baz => {}
45+
//~^ ERROR: expected one of
46+
_ => {}
47+
}
48+
}
49+
50+
fn g2_neg() {
51+
match f() {
52+
mut Foo: Bar::Baz => {}
53+
//~^ ERROR: expected one of
54+
_ => {}
55+
}
56+
}
57+
58+
fn main() {
59+
let myfoo = Foo::Bar;
60+
match myfoo {
61+
Foo::Bar => {}
62+
Foo:Bar::Baz => {}
63+
//~^ ERROR: expected one of
64+
//~| HELP: maybe write a path separator here
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
error: expected one of `@` or `|`, found `:`
2+
--> $DIR/issue-87086-colon-path-sep.rs:13:12
3+
|
4+
LL | Foo:Bar => {}
5+
| ^
6+
| |
7+
| expected one of `@` or `|`
8+
| help: maybe write a path separator here: `::`
9+
10+
error: expected one of `!`, `(`, `...`, `..=`, `..`, `::`, `{`, or `|`, found `:`
11+
--> $DIR/issue-87086-colon-path-sep.rs:19:17
12+
|
13+
LL | Foo::Bar:Baz => {}
14+
| ^
15+
| |
16+
| expected one of 8 possible tokens
17+
| help: maybe write a path separator here: `::`
18+
19+
error: expected one of `@` or `|`, found `:`
20+
--> $DIR/issue-87086-colon-path-sep.rs:25:12
21+
|
22+
LL | Foo:Bar::Baz => {}
23+
| ^
24+
| |
25+
| expected one of `@` or `|`
26+
| help: maybe write a path separator here: `::`
27+
28+
error: expected one of `@` or `|`, found `:`
29+
--> $DIR/issue-87086-colon-path-sep.rs:31:12
30+
|
31+
LL | Foo: Bar::Baz if true => {}
32+
| ^
33+
| |
34+
| expected one of `@` or `|`
35+
| help: maybe write a path separator here: `::`
36+
37+
error: expected one of `@` or `|`, found `:`
38+
--> $DIR/issue-87086-colon-path-sep.rs:36:15
39+
|
40+
LL | if let Bar:Baz = f() {
41+
| ^
42+
| |
43+
| expected one of `@` or `|`
44+
| help: maybe write a path separator here: `::`
45+
46+
error: expected one of `=>`, `@`, `if`, or `|`, found `:`
47+
--> $DIR/issue-87086-colon-path-sep.rs:44:16
48+
|
49+
LL | ref Foo: Bar::Baz => {}
50+
| ^ expected one of `=>`, `@`, `if`, or `|`
51+
52+
error: expected one of `=>`, `@`, `if`, or `|`, found `:`
53+
--> $DIR/issue-87086-colon-path-sep.rs:52:16
54+
|
55+
LL | mut Foo: Bar::Baz => {}
56+
| ^ expected one of `=>`, `@`, `if`, or `|`
57+
58+
error: expected one of `@` or `|`, found `:`
59+
--> $DIR/issue-87086-colon-path-sep.rs:62:12
60+
|
61+
LL | Foo:Bar::Baz => {}
62+
| ^
63+
| |
64+
| expected one of `@` or `|`
65+
| help: maybe write a path separator here: `::`
66+
67+
error: aborting due to 8 previous errors
68+

0 commit comments

Comments
 (0)