Skip to content

Commit 46a43dc

Browse files
committed
Auto merge of #57852 - davidtwco:issue-57819, r=estebank
Suggest removing leading left angle brackets. Fixes #57819. This PR adds errors and accompanying suggestions as below: ``` bar::<<<<<T as Foo>::Output>(); ^^^ help: remove extra angle brackets ``` r? @estebank
2 parents ccd428b + 8ab12f6 commit 46a43dc

File tree

4 files changed

+328
-8
lines changed

4 files changed

+328
-8
lines changed

src/libsyntax/parse/parser.rs

+190-8
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ pub struct Parser<'a> {
243243
desugar_doc_comments: bool,
244244
/// Whether we should configure out of line modules as we parse.
245245
pub cfg_mods: bool,
246+
/// This field is used to keep track of how many left angle brackets we have seen. This is
247+
/// required in order to detect extra leading left angle brackets (`<` characters) and error
248+
/// appropriately.
249+
///
250+
/// See the comments in the `parse_path_segment` function for more details.
251+
crate unmatched_angle_bracket_count: u32,
246252
}
247253

248254

@@ -564,6 +570,7 @@ impl<'a> Parser<'a> {
564570
},
565571
desugar_doc_comments,
566572
cfg_mods: true,
573+
unmatched_angle_bracket_count: 0,
567574
};
568575

569576
let tok = parser.next_tok();
@@ -1028,7 +1035,7 @@ impl<'a> Parser<'a> {
10281035
/// starting token.
10291036
fn eat_lt(&mut self) -> bool {
10301037
self.expected_tokens.push(TokenType::Token(token::Lt));
1031-
match self.token {
1038+
let ate = match self.token {
10321039
token::Lt => {
10331040
self.bump();
10341041
true
@@ -1039,7 +1046,15 @@ impl<'a> Parser<'a> {
10391046
true
10401047
}
10411048
_ => false,
1049+
};
1050+
1051+
if ate {
1052+
// See doc comment for `unmatched_angle_bracket_count`.
1053+
self.unmatched_angle_bracket_count += 1;
1054+
debug!("eat_lt: (increment) count={:?}", self.unmatched_angle_bracket_count);
10421055
}
1056+
1057+
ate
10431058
}
10441059

10451060
fn expect_lt(&mut self) -> PResult<'a, ()> {
@@ -1055,24 +1070,35 @@ impl<'a> Parser<'a> {
10551070
/// signal an error.
10561071
fn expect_gt(&mut self) -> PResult<'a, ()> {
10571072
self.expected_tokens.push(TokenType::Token(token::Gt));
1058-
match self.token {
1073+
let ate = match self.token {
10591074
token::Gt => {
10601075
self.bump();
1061-
Ok(())
1076+
Some(())
10621077
}
10631078
token::BinOp(token::Shr) => {
10641079
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1065-
Ok(self.bump_with(token::Gt, span))
1080+
Some(self.bump_with(token::Gt, span))
10661081
}
10671082
token::BinOpEq(token::Shr) => {
10681083
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1069-
Ok(self.bump_with(token::Ge, span))
1084+
Some(self.bump_with(token::Ge, span))
10701085
}
10711086
token::Ge => {
10721087
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1073-
Ok(self.bump_with(token::Eq, span))
1088+
Some(self.bump_with(token::Eq, span))
10741089
}
1075-
_ => self.unexpected()
1090+
_ => None,
1091+
};
1092+
1093+
match ate {
1094+
Some(x) => {
1095+
// See doc comment for `unmatched_angle_bracket_count`.
1096+
self.unmatched_angle_bracket_count -= 1;
1097+
debug!("expect_gt: (decrement) count={:?}", self.unmatched_angle_bracket_count);
1098+
1099+
Ok(x)
1100+
},
1101+
None => self.unexpected(),
10761102
}
10771103
}
10781104

@@ -2115,7 +2141,11 @@ impl<'a> Parser<'a> {
21152141
path_span = self.span.to(self.span);
21162142
}
21172143

2144+
// See doc comment for `unmatched_angle_bracket_count`.
21182145
self.expect(&token::Gt)?;
2146+
self.unmatched_angle_bracket_count -= 1;
2147+
debug!("parse_qpath: (decrement) count={:?}", self.unmatched_angle_bracket_count);
2148+
21192149
self.expect(&token::ModSep)?;
21202150

21212151
let qself = QSelf { ty, path_span, position: path.segments.len() };
@@ -2238,9 +2268,15 @@ impl<'a> Parser<'a> {
22382268
}
22392269
let lo = self.span;
22402270

2271+
// We use `style == PathStyle::Expr` to check if this is in a recursion or not. If
2272+
// it isn't, then we reset the unmatched angle bracket count as we're about to start
2273+
// parsing a new path.
2274+
if style == PathStyle::Expr { self.unmatched_angle_bracket_count = 0; }
2275+
22412276
let args = if self.eat_lt() {
22422277
// `<'a, T, A = U>`
2243-
let (args, bindings) = self.parse_generic_args()?;
2278+
let (args, bindings) =
2279+
self.parse_generic_args_with_leaning_angle_bracket_recovery(style, lo)?;
22442280
self.expect_gt()?;
22452281
let span = lo.to(self.prev_span);
22462282
AngleBracketedArgs { args, bindings, span }.into()
@@ -5538,6 +5574,152 @@ impl<'a> Parser<'a> {
55385574
}
55395575
}
55405576

5577+
/// Parse generic args (within a path segment) with recovery for extra leading angle brackets.
5578+
/// For the purposes of understanding the parsing logic of generic arguments, this function
5579+
/// can be thought of being the same as just calling `self.parse_generic_args()` if the source
5580+
/// had the correct amount of leading angle brackets.
5581+
///
5582+
/// ```ignore (diagnostics)
5583+
/// bar::<<<<T as Foo>::Output>();
5584+
/// ^^ help: remove extra angle brackets
5585+
/// ```
5586+
fn parse_generic_args_with_leaning_angle_bracket_recovery(
5587+
&mut self,
5588+
style: PathStyle,
5589+
lo: Span,
5590+
) -> PResult<'a, (Vec<GenericArg>, Vec<TypeBinding>)> {
5591+
// We need to detect whether there are extra leading left angle brackets and produce an
5592+
// appropriate error and suggestion. This cannot be implemented by looking ahead at
5593+
// upcoming tokens for a matching `>` character - if there are unmatched `<` tokens
5594+
// then there won't be matching `>` tokens to find.
5595+
//
5596+
// To explain how this detection works, consider the following example:
5597+
//
5598+
// ```ignore (diagnostics)
5599+
// bar::<<<<T as Foo>::Output>();
5600+
// ^^ help: remove extra angle brackets
5601+
// ```
5602+
//
5603+
// Parsing of the left angle brackets starts in this function. We start by parsing the
5604+
// `<` token (incrementing the counter of unmatched angle brackets on `Parser` via
5605+
// `eat_lt`):
5606+
//
5607+
// *Upcoming tokens:* `<<<<T as Foo>::Output>;`
5608+
// *Unmatched count:* 1
5609+
// *`parse_path_segment` calls deep:* 0
5610+
//
5611+
// This has the effect of recursing as this function is called if a `<` character
5612+
// is found within the expected generic arguments:
5613+
//
5614+
// *Upcoming tokens:* `<<<T as Foo>::Output>;`
5615+
// *Unmatched count:* 2
5616+
// *`parse_path_segment` calls deep:* 1
5617+
//
5618+
// Eventually we will have recursed until having consumed all of the `<` tokens and
5619+
// this will be reflected in the count:
5620+
//
5621+
// *Upcoming tokens:* `T as Foo>::Output>;`
5622+
// *Unmatched count:* 4
5623+
// `parse_path_segment` calls deep:* 3
5624+
//
5625+
// The parser will continue until reaching the first `>` - this will decrement the
5626+
// unmatched angle bracket count and return to the parent invocation of this function
5627+
// having succeeded in parsing:
5628+
//
5629+
// *Upcoming tokens:* `::Output>;`
5630+
// *Unmatched count:* 3
5631+
// *`parse_path_segment` calls deep:* 2
5632+
//
5633+
// This will continue until the next `>` character which will also return successfully
5634+
// to the parent invocation of this function and decrement the count:
5635+
//
5636+
// *Upcoming tokens:* `;`
5637+
// *Unmatched count:* 2
5638+
// *`parse_path_segment` calls deep:* 1
5639+
//
5640+
// At this point, this function will expect to find another matching `>` character but
5641+
// won't be able to and will return an error. This will continue all the way up the
5642+
// call stack until the first invocation:
5643+
//
5644+
// *Upcoming tokens:* `;`
5645+
// *Unmatched count:* 2
5646+
// *`parse_path_segment` calls deep:* 0
5647+
//
5648+
// In doing this, we have managed to work out how many unmatched leading left angle
5649+
// brackets there are, but we cannot recover as the unmatched angle brackets have
5650+
// already been consumed. To remedy this, we keep a snapshot of the parser state
5651+
// before we do the above. We can then inspect whether we ended up with a parsing error
5652+
// and unmatched left angle brackets and if so, restore the parser state before we
5653+
// consumed any `<` characters to emit an error and consume the erroneous tokens to
5654+
// recover by attempting to parse again.
5655+
//
5656+
// In practice, the recursion of this function is indirect and there will be other
5657+
// locations that consume some `<` characters - as long as we update the count when
5658+
// this happens, it isn't an issue.
5659+
5660+
let is_first_invocation = style == PathStyle::Expr;
5661+
// Take a snapshot before attempting to parse - we can restore this later.
5662+
let snapshot = if is_first_invocation {
5663+
Some(self.clone())
5664+
} else {
5665+
None
5666+
};
5667+
5668+
debug!("parse_generic_args_with_leading_angle_bracket_recovery: (snapshotting)");
5669+
match self.parse_generic_args() {
5670+
Ok(value) => Ok(value),
5671+
Err(ref mut e) if is_first_invocation && self.unmatched_angle_bracket_count > 0 => {
5672+
// Cancel error from being unable to find `>`. We know the error
5673+
// must have been this due to a non-zero unmatched angle bracket
5674+
// count.
5675+
e.cancel();
5676+
5677+
// Swap `self` with our backup of the parser state before attempting to parse
5678+
// generic arguments.
5679+
let snapshot = mem::replace(self, snapshot.unwrap());
5680+
5681+
debug!(
5682+
"parse_generic_args_with_leading_angle_bracket_recovery: (snapshot failure) \
5683+
snapshot.count={:?}",
5684+
snapshot.unmatched_angle_bracket_count,
5685+
);
5686+
5687+
// Eat the unmatched angle brackets.
5688+
for _ in 0..snapshot.unmatched_angle_bracket_count {
5689+
self.eat_lt();
5690+
}
5691+
5692+
// Make a span over ${unmatched angle bracket count} characters.
5693+
let span = lo.with_hi(
5694+
lo.lo() + BytePos(snapshot.unmatched_angle_bracket_count)
5695+
);
5696+
let plural = snapshot.unmatched_angle_bracket_count > 1;
5697+
self.diagnostic()
5698+
.struct_span_err(
5699+
span,
5700+
&format!(
5701+
"unmatched angle bracket{}",
5702+
if plural { "s" } else { "" }
5703+
),
5704+
)
5705+
.span_suggestion_with_applicability(
5706+
span,
5707+
&format!(
5708+
"remove extra angle bracket{}",
5709+
if plural { "s" } else { "" }
5710+
),
5711+
String::new(),
5712+
Applicability::MachineApplicable,
5713+
)
5714+
.emit();
5715+
5716+
// Try again without unmatched angle bracket characters.
5717+
self.parse_generic_args()
5718+
},
5719+
Err(e) => Err(e),
5720+
}
5721+
}
5722+
55415723
/// Parses (possibly empty) list of lifetime and type arguments and associated type bindings,
55425724
/// possibly including trailing comma.
55435725
fn parse_generic_args(&mut self) -> PResult<'a, (Vec<GenericArg>, Vec<TypeBinding>)> {

src/test/ui/issues/issue-57819.fixed

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// run-rustfix
2+
3+
#![allow(warnings)]
4+
5+
// This test checks that the following error is emitted and the suggestion works:
6+
//
7+
// ```
8+
// let _ = vec![1, 2, 3].into_iter().collect::<<<Vec<usize>>();
9+
// ^^ help: remove extra angle brackets
10+
// ```
11+
12+
trait Foo {
13+
type Output;
14+
}
15+
16+
fn foo<T: Foo>() {
17+
// More complex cases with more than one correct leading `<` character:
18+
19+
bar::<<T as Foo>::Output>();
20+
//~^ ERROR unmatched angle bracket
21+
22+
bar::<<T as Foo>::Output>();
23+
//~^ ERROR unmatched angle bracket
24+
25+
bar::<<T as Foo>::Output>();
26+
//~^ ERROR unmatched angle bracket
27+
28+
bar::<<T as Foo>::Output>();
29+
}
30+
31+
fn bar<T>() {}
32+
33+
fn main() {
34+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
35+
//~^ ERROR unmatched angle bracket
36+
37+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
38+
//~^ ERROR unmatched angle bracket
39+
40+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
41+
//~^ ERROR unmatched angle bracket
42+
43+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
44+
//~^ ERROR unmatched angle bracket
45+
46+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
47+
}

src/test/ui/issues/issue-57819.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// run-rustfix
2+
3+
#![allow(warnings)]
4+
5+
// This test checks that the following error is emitted and the suggestion works:
6+
//
7+
// ```
8+
// let _ = vec![1, 2, 3].into_iter().collect::<<<Vec<usize>>();
9+
// ^^ help: remove extra angle brackets
10+
// ```
11+
12+
trait Foo {
13+
type Output;
14+
}
15+
16+
fn foo<T: Foo>() {
17+
// More complex cases with more than one correct leading `<` character:
18+
19+
bar::<<<<<T as Foo>::Output>();
20+
//~^ ERROR unmatched angle bracket
21+
22+
bar::<<<<T as Foo>::Output>();
23+
//~^ ERROR unmatched angle bracket
24+
25+
bar::<<<T as Foo>::Output>();
26+
//~^ ERROR unmatched angle bracket
27+
28+
bar::<<T as Foo>::Output>();
29+
}
30+
31+
fn bar<T>() {}
32+
33+
fn main() {
34+
let _ = vec![1, 2, 3].into_iter().collect::<<<<<Vec<usize>>();
35+
//~^ ERROR unmatched angle bracket
36+
37+
let _ = vec![1, 2, 3].into_iter().collect::<<<<Vec<usize>>();
38+
//~^ ERROR unmatched angle bracket
39+
40+
let _ = vec![1, 2, 3].into_iter().collect::<<<Vec<usize>>();
41+
//~^ ERROR unmatched angle bracket
42+
43+
let _ = vec![1, 2, 3].into_iter().collect::<<Vec<usize>>();
44+
//~^ ERROR unmatched angle bracket
45+
46+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
47+
}

0 commit comments

Comments
 (0)