Skip to content

Commit 9235c0f

Browse files
authored
Rollup merge of rust-lang#57817 - davidtwco:issue-54521, r=estebank
Add error for trailing angle brackets. Fixes rust-lang#54521. This PR adds a error (and accompanying machine applicable suggestion) for trailing angle brackets on function calls with a turbofish. r? @estebank
2 parents 457420d + 914d142 commit 9235c0f

9 files changed

+303
-1
lines changed

src/libsyntax/ast.rs

+14
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ pub enum GenericArgs {
140140
}
141141

142142
impl GenericArgs {
143+
pub fn is_parenthesized(&self) -> bool {
144+
match *self {
145+
Parenthesized(..) => true,
146+
_ => false,
147+
}
148+
}
149+
150+
pub fn is_angle_bracketed(&self) -> bool {
151+
match *self {
152+
AngleBracketed(..) => true,
153+
_ => false,
154+
}
155+
}
156+
143157
pub fn span(&self) -> Span {
144158
match *self {
145159
AngleBracketed(ref data) => data.span,

src/libsyntax/parse/parser.rs

+133-1
Original file line numberDiff line numberDiff line change
@@ -2188,7 +2188,27 @@ impl<'a> Parser<'a> {
21882188
enable_warning: bool)
21892189
-> PResult<'a, ()> {
21902190
loop {
2191-
segments.push(self.parse_path_segment(style, enable_warning)?);
2191+
let segment = self.parse_path_segment(style, enable_warning)?;
2192+
if style == PathStyle::Expr {
2193+
// In order to check for trailing angle brackets, we must have finished
2194+
// recursing (`parse_path_segment` can indirectly call this function),
2195+
// that is, the next token must be the highlighted part of the below example:
2196+
//
2197+
// `Foo::<Bar as Baz<T>>::Qux`
2198+
// ^ here
2199+
//
2200+
// As opposed to the below highlight (if we had only finished the first
2201+
// recursion):
2202+
//
2203+
// `Foo::<Bar as Baz<T>>::Qux`
2204+
// ^ here
2205+
//
2206+
// `PathStyle::Expr` is only provided at the root invocation and never in
2207+
// `parse_path_segment` to recurse and therefore can be checked to maintain
2208+
// this invariant.
2209+
self.check_trailing_angle_brackets(&segment, token::ModSep);
2210+
}
2211+
segments.push(segment);
21922212

21932213
if self.is_import_coupler() || !self.eat(&token::ModSep) {
21942214
return Ok(());
@@ -2821,6 +2841,8 @@ impl<'a> Parser<'a> {
28212841
// Assuming we have just parsed `.`, continue parsing into an expression.
28222842
fn parse_dot_suffix(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
28232843
let segment = self.parse_path_segment(PathStyle::Expr, true)?;
2844+
self.check_trailing_angle_brackets(&segment, token::OpenDelim(token::Paren));
2845+
28242846
Ok(match self.token {
28252847
token::OpenDelim(token::Paren) => {
28262848
// Method call `expr.f()`
@@ -2848,6 +2870,116 @@ impl<'a> Parser<'a> {
28482870
})
28492871
}
28502872

2873+
/// This function checks if there are trailing angle brackets and produces
2874+
/// a diagnostic to suggest removing them.
2875+
///
2876+
/// ```ignore (diagnostic)
2877+
/// let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
2878+
/// ^^ help: remove extra angle brackets
2879+
/// ```
2880+
fn check_trailing_angle_brackets(&mut self, segment: &PathSegment, end: token::Token) {
2881+
// This function is intended to be invoked after parsing a path segment where there are two
2882+
// cases:
2883+
//
2884+
// 1. A specific token is expected after the path segment.
2885+
// eg. `x.foo(`, `x.foo::<u32>(` (parenthesis - method call),
2886+
// `Foo::`, or `Foo::<Bar>::` (mod sep - continued path).
2887+
// 2. No specific token is expected after the path segment.
2888+
// eg. `x.foo` (field access)
2889+
//
2890+
// This function is called after parsing `.foo` and before parsing the token `end` (if
2891+
// present). This includes any angle bracket arguments, such as `.foo::<u32>` or
2892+
// `Foo::<Bar>`.
2893+
2894+
// We only care about trailing angle brackets if we previously parsed angle bracket
2895+
// arguments. This helps stop us incorrectly suggesting that extra angle brackets be
2896+
// removed in this case:
2897+
//
2898+
// `x.foo >> (3)` (where `x.foo` is a `u32` for example)
2899+
//
2900+
// This case is particularly tricky as we won't notice it just looking at the tokens -
2901+
// it will appear the same (in terms of upcoming tokens) as below (since the `::<u32>` will
2902+
// have already been parsed):
2903+
//
2904+
// `x.foo::<u32>>>(3)`
2905+
let parsed_angle_bracket_args = segment.args
2906+
.as_ref()
2907+
.map(|args| args.is_angle_bracketed())
2908+
.unwrap_or(false);
2909+
2910+
debug!(
2911+
"check_trailing_angle_brackets: parsed_angle_bracket_args={:?}",
2912+
parsed_angle_bracket_args,
2913+
);
2914+
if !parsed_angle_bracket_args {
2915+
return;
2916+
}
2917+
2918+
// Keep the span at the start so we can highlight the sequence of `>` characters to be
2919+
// removed.
2920+
let lo = self.span;
2921+
2922+
// We need to look-ahead to see if we have `>` characters without moving the cursor forward
2923+
// (since we might have the field access case and the characters we're eating are
2924+
// actual operators and not trailing characters - ie `x.foo >> 3`).
2925+
let mut position = 0;
2926+
2927+
// We can encounter `>` or `>>` tokens in any order, so we need to keep track of how
2928+
// many of each (so we can correctly pluralize our error messages) and continue to
2929+
// advance.
2930+
let mut number_of_shr = 0;
2931+
let mut number_of_gt = 0;
2932+
while self.look_ahead(position, |t| {
2933+
trace!("check_trailing_angle_brackets: t={:?}", t);
2934+
if *t == token::BinOp(token::BinOpToken::Shr) {
2935+
number_of_shr += 1;
2936+
true
2937+
} else if *t == token::Gt {
2938+
number_of_gt += 1;
2939+
true
2940+
} else {
2941+
false
2942+
}
2943+
}) {
2944+
position += 1;
2945+
}
2946+
2947+
// If we didn't find any trailing `>` characters, then we have nothing to error about.
2948+
debug!(
2949+
"check_trailing_angle_brackets: number_of_gt={:?} number_of_shr={:?}",
2950+
number_of_gt, number_of_shr,
2951+
);
2952+
if number_of_gt < 1 && number_of_shr < 1 {
2953+
return;
2954+
}
2955+
2956+
// Finally, double check that we have our end token as otherwise this is the
2957+
// second case.
2958+
if self.look_ahead(position, |t| {
2959+
trace!("check_trailing_angle_brackets: t={:?}", t);
2960+
*t == end
2961+
}) {
2962+
// Eat from where we started until the end token so that parsing can continue
2963+
// as if we didn't have those extra angle brackets.
2964+
self.eat_to_tokens(&[&end]);
2965+
let span = lo.until(self.span);
2966+
2967+
let plural = number_of_gt > 1 || number_of_shr >= 1;
2968+
self.diagnostic()
2969+
.struct_span_err(
2970+
span,
2971+
&format!("unmatched angle bracket{}", if plural { "s" } else { "" }),
2972+
)
2973+
.span_suggestion_with_applicability(
2974+
span,
2975+
&format!("remove extra angle bracket{}", if plural { "s" } else { "" }),
2976+
String::new(),
2977+
Applicability::MachineApplicable,
2978+
)
2979+
.emit();
2980+
}
2981+
}
2982+
28512983
fn parse_dot_or_call_expr_with_(&mut self, e0: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
28522984
let mut e = e0;
28532985
let mut hi;

src/test/ui/issues/issue-54521-1.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// compile-pass
2+
3+
// This test checks that the `remove extra angle brackets` error doesn't happen for some
4+
// potential edge-cases..
5+
6+
struct X {
7+
len: u32,
8+
}
9+
10+
fn main() {
11+
let x = X { len: 3 };
12+
13+
let _ = x.len > (3);
14+
15+
let _ = x.len >> (3);
16+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
3+
// This test checks that the following error is emitted and the suggestion works:
4+
//
5+
// ```
6+
// let _ = Vec::<usize>>>::new();
7+
// ^^ help: remove extra angle brackets
8+
// ```
9+
10+
fn main() {
11+
let _ = Vec::<usize>::new();
12+
//~^ ERROR unmatched angle bracket
13+
14+
let _ = Vec::<usize>::new();
15+
//~^ ERROR unmatched angle bracket
16+
17+
let _ = Vec::<usize>::new();
18+
//~^ ERROR unmatched angle bracket
19+
20+
let _ = Vec::<usize>::new();
21+
//~^ ERROR unmatched angle bracket
22+
}

src/test/ui/issues/issue-54521-2.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
3+
// This test checks that the following error is emitted and the suggestion works:
4+
//
5+
// ```
6+
// let _ = Vec::<usize>>>::new();
7+
// ^^ help: remove extra angle brackets
8+
// ```
9+
10+
fn main() {
11+
let _ = Vec::<usize>>>>>::new();
12+
//~^ ERROR unmatched angle bracket
13+
14+
let _ = Vec::<usize>>>>::new();
15+
//~^ ERROR unmatched angle bracket
16+
17+
let _ = Vec::<usize>>>::new();
18+
//~^ ERROR unmatched angle bracket
19+
20+
let _ = Vec::<usize>>::new();
21+
//~^ ERROR unmatched angle bracket
22+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: unmatched angle brackets
2+
--> $DIR/issue-54521-2.rs:11:25
3+
|
4+
LL | let _ = Vec::<usize>>>>>::new();
5+
| ^^^^ help: remove extra angle brackets
6+
7+
error: unmatched angle brackets
8+
--> $DIR/issue-54521-2.rs:14:25
9+
|
10+
LL | let _ = Vec::<usize>>>>::new();
11+
| ^^^ help: remove extra angle brackets
12+
13+
error: unmatched angle brackets
14+
--> $DIR/issue-54521-2.rs:17:25
15+
|
16+
LL | let _ = Vec::<usize>>>::new();
17+
| ^^ help: remove extra angle brackets
18+
19+
error: unmatched angle bracket
20+
--> $DIR/issue-54521-2.rs:20:25
21+
|
22+
LL | let _ = Vec::<usize>>::new();
23+
| ^ help: remove extra angle bracket
24+
25+
error: aborting due to 4 previous errors
26+

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

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
3+
// This test checks that the following error is emitted and the suggestion works:
4+
//
5+
// ```
6+
// let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
7+
// ^^ help: remove extra angle brackets
8+
// ```
9+
10+
fn main() {
11+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
12+
//~^ ERROR unmatched angle bracket
13+
14+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
15+
//~^ ERROR unmatched angle bracket
16+
17+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
18+
//~^ ERROR unmatched angle bracket
19+
20+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
21+
//~^ ERROR unmatched angle bracket
22+
}

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

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// run-rustfix
2+
3+
// This test checks that the following error is emitted and the suggestion works:
4+
//
5+
// ```
6+
// let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
7+
// ^^ help: remove extra angle brackets
8+
// ```
9+
10+
fn main() {
11+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>>();
12+
//~^ ERROR unmatched angle bracket
13+
14+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>();
15+
//~^ ERROR unmatched angle bracket
16+
17+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
18+
//~^ ERROR unmatched angle bracket
19+
20+
let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>();
21+
//~^ ERROR unmatched angle bracket
22+
}

src/test/ui/issues/issue-54521.stderr

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: unmatched angle brackets
2+
--> $DIR/issue-54521.rs:11:60
3+
|
4+
LL | let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>>();
5+
| ^^^^ help: remove extra angle brackets
6+
7+
error: unmatched angle brackets
8+
--> $DIR/issue-54521.rs:14:60
9+
|
10+
LL | let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>();
11+
| ^^^ help: remove extra angle brackets
12+
13+
error: unmatched angle brackets
14+
--> $DIR/issue-54521.rs:17:60
15+
|
16+
LL | let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
17+
| ^^ help: remove extra angle brackets
18+
19+
error: unmatched angle bracket
20+
--> $DIR/issue-54521.rs:20:60
21+
|
22+
LL | let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>();
23+
| ^ help: remove extra angle bracket
24+
25+
error: aborting due to 4 previous errors
26+

0 commit comments

Comments
 (0)