Skip to content

Commit 22f794b

Browse files
committed
Suggest removing leading left angle brackets.
This commit adds errors and accompanying suggestions as below: ``` bar::<<<<<T as Foo>::Output>(); ^^^ help: remove extra angle brackets ```
1 parent 7001537 commit 22f794b

File tree

4 files changed

+339
-8
lines changed

4 files changed

+339
-8
lines changed

src/libsyntax/parse/parser.rs

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

247253

@@ -563,6 +569,7 @@ impl<'a> Parser<'a> {
563569
},
564570
desugar_doc_comments,
565571
cfg_mods: true,
572+
unmatched_angle_bracket_count: 0,
566573
};
567574

568575
let tok = parser.next_tok();
@@ -1027,7 +1034,7 @@ impl<'a> Parser<'a> {
10271034
/// starting token.
10281035
fn eat_lt(&mut self) -> bool {
10291036
self.expected_tokens.push(TokenType::Token(token::Lt));
1030-
match self.token {
1037+
let ate = match self.token {
10311038
token::Lt => {
10321039
self.bump();
10331040
true
@@ -1038,7 +1045,15 @@ impl<'a> Parser<'a> {
10381045
true
10391046
}
10401047
_ => false,
1048+
};
1049+
1050+
if ate {
1051+
// See doc comment for `unmatched_angle_bracket_count`.
1052+
self.unmatched_angle_bracket_count += 1;
1053+
debug!("eat_lt: (increment) count={:?}", self.unmatched_angle_bracket_count);
10411054
}
1055+
1056+
ate
10421057
}
10431058

10441059
fn expect_lt(&mut self) -> PResult<'a, ()> {
@@ -1054,24 +1069,35 @@ impl<'a> Parser<'a> {
10541069
/// signal an error.
10551070
fn expect_gt(&mut self) -> PResult<'a, ()> {
10561071
self.expected_tokens.push(TokenType::Token(token::Gt));
1057-
match self.token {
1072+
let ate = match self.token {
10581073
token::Gt => {
10591074
self.bump();
1060-
Ok(())
1075+
Some(())
10611076
}
10621077
token::BinOp(token::Shr) => {
10631078
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1064-
Ok(self.bump_with(token::Gt, span))
1079+
Some(self.bump_with(token::Gt, span))
10651080
}
10661081
token::BinOpEq(token::Shr) => {
10671082
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1068-
Ok(self.bump_with(token::Ge, span))
1083+
Some(self.bump_with(token::Ge, span))
10691084
}
10701085
token::Ge => {
10711086
let span = self.span.with_lo(self.span.lo() + BytePos(1));
1072-
Ok(self.bump_with(token::Eq, span))
1087+
Some(self.bump_with(token::Eq, span))
10731088
}
1074-
_ => self.unexpected()
1089+
_ => None,
1090+
};
1091+
1092+
match ate {
1093+
Some(x) => {
1094+
// See doc comment for `unmatched_angle_bracket_count`.
1095+
self.unmatched_angle_bracket_count -= 1;
1096+
debug!("expect_gt: (decrement) count={:?}", self.unmatched_angle_bracket_count);
1097+
1098+
Ok(x)
1099+
},
1100+
None => self.unexpected(),
10751101
}
10761102
}
10771103

@@ -2079,7 +2105,11 @@ impl<'a> Parser<'a> {
20792105
path_span = self.span.to(self.span);
20802106
}
20812107

2108+
// See doc comment for `unmatched_angle_bracket_count`.
20822109
self.expect(&token::Gt)?;
2110+
self.unmatched_angle_bracket_count -= 1;
2111+
debug!("parse_qpath: (decrement) count={:?}", self.unmatched_angle_bracket_count);
2112+
20832113
self.expect(&token::ModSep)?;
20842114

20852115
let qself = QSelf { ty, path_span, position: path.segments.len() };
@@ -2182,9 +2212,15 @@ impl<'a> Parser<'a> {
21822212
}
21832213
let lo = self.span;
21842214

2215+
// We use `style == PathStyle::Expr` to check if this is in a recursion or not. If
2216+
// it isn't, then we reset the unmatched angle bracket count as we're about to start
2217+
// parsing a new path.
2218+
if style == PathStyle::Expr { self.unmatched_angle_bracket_count = 0; }
2219+
21852220
let args = if self.eat_lt() {
21862221
// `<'a, T, A = U>`
2187-
let (args, bindings) = self.parse_generic_args()?;
2222+
let (args, bindings) =
2223+
self.parse_generic_args_with_leaning_angle_bracket_recovery(style, lo)?;
21882224
self.expect_gt()?;
21892225
let span = lo.to(self.prev_span);
21902226
AngleBracketedArgs { args, bindings, span }.into()
@@ -5319,6 +5355,163 @@ impl<'a> Parser<'a> {
53195355
}
53205356
}
53215357

5358+
/// Parse generic args (within a path segment) with recovery for extra leading angle brackets.
5359+
/// For the purposes of understanding the parsing logic of generic arguments, this function
5360+
/// can be thought of being the same as just calling `self.parse_generic_args()` if the source
5361+
/// had the correct amount of leading angle brackets.
5362+
///
5363+
/// ```ignore (diagnostics)
5364+
/// bar::<<<<T as Foo>::Output>();
5365+
/// ^^ help: remove extra angle brackets
5366+
/// ```
5367+
fn parse_generic_args_with_leaning_angle_bracket_recovery(
5368+
&mut self,
5369+
style: PathStyle,
5370+
lo: Span,
5371+
) -> PResult<'a, (Vec<GenericArg>, Vec<TypeBinding>)> {
5372+
// We need to detect whether there are extra leading left angle brackets and produce an
5373+
// appropriate error and suggestion. This cannot be implemented by looking ahead at
5374+
// upcoming tokens for a matching `>` character - if there are unmatched `<` tokens
5375+
// then there won't be matching `>` tokens to find.
5376+
//
5377+
// To explain how this detection works, consider the following example:
5378+
//
5379+
// ```ignore (diagnostics)
5380+
// bar::<<<<T as Foo>::Output>();
5381+
// ^^ help: remove extra angle brackets
5382+
// ```
5383+
//
5384+
// Parsing of the left angle brackets starts in this function. We start by parsing the
5385+
// `<` token (incrementing the counter of unmatched angle brackets on `Parser` via
5386+
// `eat_lt`):
5387+
//
5388+
// *Upcoming tokens:* `<<<<T as Foo>::Output>;`
5389+
// *Unmatched count:* 1
5390+
// *`parse_path_segment` calls deep:* 0
5391+
//
5392+
// This has the effect of recursing as this function is called if a `<` character
5393+
// is found within the expected generic arguments:
5394+
//
5395+
// *Upcoming tokens:* `<<<T as Foo>::Output>;`
5396+
// *Unmatched count:* 2
5397+
// *`parse_path_segment` calls deep:* 1
5398+
//
5399+
// Eventually we will have recursed until having consumed all of the `<` tokens and
5400+
// this will be reflected in the count:
5401+
//
5402+
// *Upcoming tokens:* `T as Foo>::Output>;`
5403+
// *Unmatched count:* 4
5404+
// `parse_path_segment` calls deep:* 3
5405+
//
5406+
// The parser will continue until reaching the first `>` - this will decrement the
5407+
// unmatched angle bracket count and return to the parent invocation of this function
5408+
// having succeeded in parsing:
5409+
//
5410+
// *Upcoming tokens:* `::Output>;`
5411+
// *Unmatched count:* 3
5412+
// *`parse_path_segment` calls deep:* 2
5413+
//
5414+
// This will continue until the next `>` character which will also return successfully
5415+
// to the parent invocation of this function and decrement the count:
5416+
//
5417+
// *Upcoming tokens:* `;`
5418+
// *Unmatched count:* 2
5419+
// *`parse_path_segment` calls deep:* 1
5420+
//
5421+
// At this point, this function will expect to find another matching `>` character but
5422+
// won't be able to and will return an error. This will continue all the way up the
5423+
// call stack until the first invocation:
5424+
//
5425+
// *Upcoming tokens:* `;`
5426+
// *Unmatched count:* 2
5427+
// *`parse_path_segment` calls deep:* 0
5428+
//
5429+
// In doing this, we have managed to work out how many unmatched leading left angle
5430+
// brackets there are, but we cannot recover as the unmatched angle brackets have
5431+
// already been consumed. To remedy this, whenever `parse_generic_args` is invoked, we
5432+
// make a snapshot of the current parser state and invoke it on that and inspect
5433+
// the result:
5434+
//
5435+
// - If success (ie. when it found a matching `>` character) then the snapshot state
5436+
// is kept (this is required to propagate the count upwards).
5437+
//
5438+
// - If error and in was in a recursive call, then the snapshot state is kept (this is
5439+
// required to propagate the count upwards).
5440+
//
5441+
// - If error and this was the first invocation (before any recursion had taken place)
5442+
// then we choose not to keep the snapshot state - that way we haven't actually
5443+
// consumed any of the `<` characters, but can still inspect the count from the
5444+
// snapshot to know how many `<` characters to remove. Using this information, we can
5445+
// emit an error and consume the extra `<` characters before attempting to parse
5446+
// the generic arguments again (this time hopefullt successfully as the unmatched `<`
5447+
// characters are gone).
5448+
//
5449+
// In practice, the recursion of this function is indirect and there will be other
5450+
// locations that consume some `<` characters - as long as we update the count when
5451+
// this happens, it isn't an issue.
5452+
let mut snapshot = self.clone();
5453+
debug!("parse_generic_args_with_leading_angle_bracket_recovery: (snapshotting)");
5454+
match snapshot.parse_generic_args() {
5455+
Ok(value) => {
5456+
debug!(
5457+
"parse_generic_args_with_leading_angle_bracket_recovery: (snapshot success) \
5458+
snapshot.count={:?}",
5459+
snapshot.unmatched_angle_bracket_count,
5460+
);
5461+
mem::replace(self, snapshot);
5462+
Ok(value)
5463+
},
5464+
Err(mut e) => {
5465+
debug!(
5466+
"parse_generic_args_with_leading_angle_bracket_recovery: (snapshot failure) \
5467+
snapshot.count={:?}",
5468+
snapshot.unmatched_angle_bracket_count,
5469+
);
5470+
if style == PathStyle::Expr && snapshot.unmatched_angle_bracket_count > 0 {
5471+
// Cancel error from being unable to find `>`. We know the error
5472+
// must have been this due to a non-zero unmatched angle bracket
5473+
// count.
5474+
e.cancel();
5475+
5476+
// Eat the unmatched angle brackets.
5477+
for _ in 0..snapshot.unmatched_angle_bracket_count {
5478+
self.eat_lt();
5479+
}
5480+
5481+
// Make a span over ${unmatched angle bracket count} characters.
5482+
let span = lo.with_hi(
5483+
lo.lo() + BytePos(snapshot.unmatched_angle_bracket_count)
5484+
);
5485+
let plural = snapshot.unmatched_angle_bracket_count > 1;
5486+
self.diagnostic()
5487+
.struct_span_err(
5488+
span,
5489+
&format!(
5490+
"unmatched angle bracket{}",
5491+
if plural { "s" } else { "" }
5492+
),
5493+
)
5494+
.span_suggestion_with_applicability(
5495+
span,
5496+
&format!(
5497+
"remove extra angle bracket{}",
5498+
if plural { "s" } else { "" }
5499+
),
5500+
String::new(),
5501+
Applicability::MachineApplicable,
5502+
)
5503+
.emit();
5504+
5505+
// Try again without unmatched angle bracket characters.
5506+
self.parse_generic_args()
5507+
} else {
5508+
mem::replace(self, snapshot);
5509+
Err(e)
5510+
}
5511+
},
5512+
}
5513+
}
5514+
53225515
/// Parses (possibly empty) list of lifetime and type arguments and associated type bindings,
53235516
/// possibly including trailing comma.
53245517
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)