Skip to content

Commit e68e5d2

Browse files
authored
Rollup merge of #87160 - estebank:colon-recovery, r=nagisa
When recovering from a `:` in a pattern, use adequate AST pattern If the suggestion to use `::` instead of `:` in the pattern isn't correct, a second resolution error will be emitted.
2 parents 2446a21 + c027105 commit e68e5d2

File tree

4 files changed

+252
-134
lines changed

4 files changed

+252
-134
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+180-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use super::pat::Expected;
12
use super::ty::AllowPlus;
2-
use super::TokenType;
3-
use super::{BlockMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenExpectType};
3+
use super::{
4+
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
5+
TokenExpectType, TokenType,
6+
};
47

58
use rustc_ast as ast;
69
use rustc_ast::ptr::P;
@@ -19,6 +22,8 @@ use rustc_span::source_map::Spanned;
1922
use rustc_span::symbol::{kw, Ident};
2023
use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP};
2124

25+
use std::mem::take;
26+
2227
use tracing::{debug, trace};
2328

2429
const TURBOFISH_SUGGESTION_STR: &str =
@@ -2075,4 +2080,177 @@ impl<'a> Parser<'a> {
20752080
);
20762081
err
20772082
}
2083+
2084+
/// Some special error handling for the "top-level" patterns in a match arm,
2085+
/// `for` loop, `let`, &c. (in contrast to subpatterns within such).
2086+
crate fn maybe_recover_colon_colon_in_pat_typo(
2087+
&mut self,
2088+
mut first_pat: P<Pat>,
2089+
ra: RecoverColon,
2090+
expected: Expected,
2091+
) -> P<Pat> {
2092+
if RecoverColon::Yes != ra || token::Colon != self.token.kind {
2093+
return first_pat;
2094+
}
2095+
if !matches!(first_pat.kind, PatKind::Ident(_, _, None) | PatKind::Path(..))
2096+
|| !self.look_ahead(1, |token| token.is_ident() && !token.is_reserved_ident())
2097+
{
2098+
return first_pat;
2099+
}
2100+
// The pattern looks like it might be a path with a `::` -> `:` typo:
2101+
// `match foo { bar:baz => {} }`
2102+
let span = self.token.span;
2103+
// We only emit "unexpected `:`" error here if we can successfully parse the
2104+
// whole pattern correctly in that case.
2105+
let snapshot = self.clone();
2106+
2107+
// Create error for "unexpected `:`".
2108+
match self.expected_one_of_not_found(&[], &[]) {
2109+
Err(mut err) => {
2110+
self.bump(); // Skip the `:`.
2111+
match self.parse_pat_no_top_alt(expected) {
2112+
Err(mut inner_err) => {
2113+
// Carry on as if we had not done anything, callers will emit a
2114+
// reasonable error.
2115+
inner_err.cancel();
2116+
err.cancel();
2117+
*self = snapshot;
2118+
}
2119+
Ok(mut pat) => {
2120+
// We've parsed the rest of the pattern.
2121+
let new_span = first_pat.span.to(pat.span);
2122+
let mut show_sugg = false;
2123+
// Try to construct a recovered pattern.
2124+
match &mut pat.kind {
2125+
PatKind::Struct(qself @ None, path, ..)
2126+
| PatKind::TupleStruct(qself @ None, path, _)
2127+
| PatKind::Path(qself @ None, path) => match &first_pat.kind {
2128+
PatKind::Ident(_, ident, _) => {
2129+
path.segments.insert(0, PathSegment::from_ident(ident.clone()));
2130+
path.span = new_span;
2131+
show_sugg = true;
2132+
first_pat = pat;
2133+
}
2134+
PatKind::Path(old_qself, old_path) => {
2135+
path.segments = old_path
2136+
.segments
2137+
.iter()
2138+
.cloned()
2139+
.chain(take(&mut path.segments))
2140+
.collect();
2141+
path.span = new_span;
2142+
*qself = old_qself.clone();
2143+
first_pat = pat;
2144+
show_sugg = true;
2145+
}
2146+
_ => {}
2147+
},
2148+
PatKind::Ident(BindingMode::ByValue(Mutability::Not), ident, None) => {
2149+
match &first_pat.kind {
2150+
PatKind::Ident(_, old_ident, _) => {
2151+
let path = PatKind::Path(
2152+
None,
2153+
Path {
2154+
span: new_span,
2155+
segments: vec![
2156+
PathSegment::from_ident(old_ident.clone()),
2157+
PathSegment::from_ident(ident.clone()),
2158+
],
2159+
tokens: None,
2160+
},
2161+
);
2162+
first_pat = self.mk_pat(new_span, path);
2163+
show_sugg = true;
2164+
}
2165+
PatKind::Path(old_qself, old_path) => {
2166+
let mut segments = old_path.segments.clone();
2167+
segments.push(PathSegment::from_ident(ident.clone()));
2168+
let path = PatKind::Path(
2169+
old_qself.clone(),
2170+
Path { span: new_span, segments, tokens: None },
2171+
);
2172+
first_pat = self.mk_pat(new_span, path);
2173+
show_sugg = true;
2174+
}
2175+
_ => {}
2176+
}
2177+
}
2178+
_ => {}
2179+
}
2180+
if show_sugg {
2181+
err.span_suggestion(
2182+
span,
2183+
"maybe write a path separator here",
2184+
"::".to_string(),
2185+
Applicability::MaybeIncorrect,
2186+
);
2187+
} else {
2188+
first_pat = self.mk_pat(new_span, PatKind::Wild);
2189+
}
2190+
err.emit();
2191+
}
2192+
}
2193+
}
2194+
_ => {
2195+
// Carry on as if we had not done anything. This should be unreachable.
2196+
*self = snapshot;
2197+
}
2198+
};
2199+
first_pat
2200+
}
2201+
2202+
/// Some special error handling for the "top-level" patterns in a match arm,
2203+
/// `for` loop, `let`, &c. (in contrast to subpatterns within such).
2204+
crate fn maybe_recover_unexpected_comma(
2205+
&mut self,
2206+
lo: Span,
2207+
rc: RecoverComma,
2208+
) -> PResult<'a, ()> {
2209+
if rc == RecoverComma::No || self.token != token::Comma {
2210+
return Ok(());
2211+
}
2212+
2213+
// An unexpected comma after a top-level pattern is a clue that the
2214+
// user (perhaps more accustomed to some other language) forgot the
2215+
// parentheses in what should have been a tuple pattern; return a
2216+
// suggestion-enhanced error here rather than choking on the comma later.
2217+
let comma_span = self.token.span;
2218+
self.bump();
2219+
if let Err(mut err) = self.skip_pat_list() {
2220+
// We didn't expect this to work anyway; we just wanted to advance to the
2221+
// end of the comma-sequence so we know the span to suggest parenthesizing.
2222+
err.cancel();
2223+
}
2224+
let seq_span = lo.to(self.prev_token.span);
2225+
let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern");
2226+
if let Ok(seq_snippet) = self.span_to_snippet(seq_span) {
2227+
const MSG: &str = "try adding parentheses to match on a tuple...";
2228+
2229+
err.span_suggestion(
2230+
seq_span,
2231+
MSG,
2232+
format!("({})", seq_snippet),
2233+
Applicability::MachineApplicable,
2234+
);
2235+
err.span_suggestion(
2236+
seq_span,
2237+
"...or a vertical bar to match on multiple alternatives",
2238+
seq_snippet.replace(",", " |"),
2239+
Applicability::MachineApplicable,
2240+
);
2241+
}
2242+
Err(err)
2243+
}
2244+
2245+
/// Parse and throw away a parenthesized comma separated
2246+
/// sequence of patterns until `)` is reached.
2247+
fn skip_pat_list(&mut self) -> PResult<'a, ()> {
2248+
while !self.check(&token::CloseDelim(token::Paren)) {
2249+
self.parse_pat_no_top_alt(None)?;
2250+
if !self.eat(&token::Comma) {
2251+
return Ok(());
2252+
}
2253+
}
2254+
Ok(())
2255+
}
20782256
}

compiler/rustc_parse/src/parser/pat.rs

+9-104
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
33
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
44
use rustc_ast::ptr::P;
55
use rustc_ast::token;
6-
use rustc_ast::{self as ast, AttrVec, Attribute, MacCall, Pat, PatField, PatKind, RangeEnd};
7-
use rustc_ast::{BindingMode, Expr, ExprKind, Mutability, Path, QSelf, RangeSyntax};
6+
use rustc_ast::{
7+
self as ast, AttrVec, Attribute, BindingMode, Expr, ExprKind, MacCall, Mutability, Pat,
8+
PatField, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
9+
};
810
use rustc_ast_pretty::pprust;
911
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, PResult};
1012
use rustc_span::source_map::{respan, Span, Spanned};
1113
use rustc_span::symbol::{kw, sym, Ident};
1214

13-
type Expected = Option<&'static str>;
15+
pub(super) type Expected = Option<&'static str>;
1416

1517
/// `Expected` for function and lambda parameter patterns.
1618
pub(super) const PARAM_EXPECTED: Expected = Some("parameter name");
@@ -98,55 +100,9 @@ impl<'a> Parser<'a> {
98100
// If we parsed a leading `|` which should be gated,
99101
// then we should really gate the leading `|`.
100102
// 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-
}
103+
104+
// Check if the user wrote `foo:bar` instead of `foo::bar`.
105+
let first_pat = self.maybe_recover_colon_colon_in_pat_typo(first_pat, ra, expected);
150106

151107
if let Some(leading_vert_span) = leading_vert_span {
152108
// If there was a leading vert, treat this as an or-pattern. This improves
@@ -321,57 +277,6 @@ impl<'a> Parser<'a> {
321277
err.emit();
322278
}
323279

324-
/// Some special error handling for the "top-level" patterns in a match arm,
325-
/// `for` loop, `let`, &c. (in contrast to subpatterns within such).
326-
fn maybe_recover_unexpected_comma(&mut self, lo: Span, rc: RecoverComma) -> PResult<'a, ()> {
327-
if rc == RecoverComma::No || self.token != token::Comma {
328-
return Ok(());
329-
}
330-
331-
// An unexpected comma after a top-level pattern is a clue that the
332-
// user (perhaps more accustomed to some other language) forgot the
333-
// parentheses in what should have been a tuple pattern; return a
334-
// suggestion-enhanced error here rather than choking on the comma later.
335-
let comma_span = self.token.span;
336-
self.bump();
337-
if let Err(mut err) = self.skip_pat_list() {
338-
// We didn't expect this to work anyway; we just wanted to advance to the
339-
// end of the comma-sequence so we know the span to suggest parenthesizing.
340-
err.cancel();
341-
}
342-
let seq_span = lo.to(self.prev_token.span);
343-
let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern");
344-
if let Ok(seq_snippet) = self.span_to_snippet(seq_span) {
345-
const MSG: &str = "try adding parentheses to match on a tuple...";
346-
347-
err.span_suggestion(
348-
seq_span,
349-
MSG,
350-
format!("({})", seq_snippet),
351-
Applicability::MachineApplicable,
352-
);
353-
err.span_suggestion(
354-
seq_span,
355-
"...or a vertical bar to match on multiple alternatives",
356-
seq_snippet.replace(",", " |"),
357-
Applicability::MachineApplicable,
358-
);
359-
}
360-
Err(err)
361-
}
362-
363-
/// Parse and throw away a parenthesized comma separated
364-
/// sequence of patterns until `)` is reached.
365-
fn skip_pat_list(&mut self) -> PResult<'a, ()> {
366-
while !self.check(&token::CloseDelim(token::Paren)) {
367-
self.parse_pat_no_top_alt(None)?;
368-
if !self.eat(&token::Comma) {
369-
return Ok(());
370-
}
371-
}
372-
Ok(())
373-
}
374-
375280
/// A `|` or possibly `||` token shouldn't be here. Ban it.
376281
fn ban_illegal_vert(&mut self, lo: Option<Span>, pos: &str, ctx: &str) {
377282
let span = self.token.span;
@@ -1168,7 +1073,7 @@ impl<'a> Parser<'a> {
11681073
self.mk_pat(span, PatKind::Ident(bm, ident, None))
11691074
}
11701075

1171-
fn mk_pat(&self, span: Span, kind: PatKind) -> P<Pat> {
1076+
pub(super) fn mk_pat(&self, span: Span, kind: PatKind) -> P<Pat> {
11721077
P(Pat { kind, span, id: ast::DUMMY_NODE_ID, tokens: None })
11731078
}
11741079
}

0 commit comments

Comments
 (0)