diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 405cf612543f..e520ac3bdd49 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -140,6 +140,20 @@ pub enum GenericArgs { } impl GenericArgs { + pub fn is_parenthesized(&self) -> bool { + match *self { + Parenthesized(..) => true, + _ => false, + } + } + + pub fn is_angle_bracketed(&self) -> bool { + match *self { + AngleBracketed(..) => true, + _ => false, + } + } + pub fn span(&self) -> Span { match *self { AngleBracketed(ref data) => data.span, diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 439eec5b0c48..d7c209d12a8f 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -2757,6 +2757,8 @@ impl<'a> Parser<'a> { // Assuming we have just parsed `.`, continue parsing into an expression. fn parse_dot_suffix(&mut self, self_arg: P, lo: Span) -> PResult<'a, P> { let segment = self.parse_path_segment(PathStyle::Expr, true)?; + self.check_trailing_angle_brackets(&segment); + Ok(match self.token { token::OpenDelim(token::Paren) => { // Method call `expr.f()` @@ -2784,6 +2786,101 @@ impl<'a> Parser<'a> { }) } + /// This function checks if there are trailing angle brackets and produces + /// a diagnostic to suggest removing them. + /// + /// ```ignore (diagnostic) + /// let _ = vec![1, 2, 3].into_iter().collect::>>>(); + /// ^^ help: remove extra angle brackets + /// ``` + fn check_trailing_angle_brackets(&mut self, segment: &PathSegment) { + // This function is intended to be invoked from `parse_dot_suffix` where there are two + // cases: + // + // - A field access (eg. `x.foo`) + // - A method call (eg. `x.foo()`) + // + // This function is called after parsing `.foo` and before parsing any parenthesis (if + // present). This includes any angle bracket arguments, such as `.foo::`. + + // We only care about trailing angle brackets if we previously parsed angle bracket + // arguments. This helps stop us incorrectly suggesting that extra angle brackets be + // removed in this case: + // + // `x.foo >> (3)` (where `x.foo` is a `u32` for example) + // + // This case is particularly tricky as we won't notice it just looking at the tokens - + // it will appear the same (in terms of upcoming tokens) as below (since the `::` will + // have already been parsed): + // + // `x.foo::>>(3)` + let parsed_angle_bracket_args = segment.args + .as_ref() + .map(|args| args.is_angle_bracketed()) + .unwrap_or(false); + + debug!( + "check_trailing_angle_brackets: parsed_angle_bracket_args={:?}", + parsed_angle_bracket_args, + ); + if !parsed_angle_bracket_args { + return; + } + + // Keep the span at the start so we can highlight the sequence of `>` characters to be + // removed. + let lo = self.span; + + // We need to look-ahead to see if we have `>` characters without moving the cursor forward + // (since we might have the field access case and the characters we're eating are + // actual operators and not trailing characters - ie `x.foo >> 3`). + let mut position = 0; + + // The first tokens we will encounter are shift right tokens (`>>`) since pairs of `>` + // characters will have been grouped together by the tokenizer. + let mut number_of_shr = 0; + while self.look_ahead(position, |t| *t == token::BinOp(token::BinOpToken::Shr)) { + number_of_shr += 1; + position += 1; + } + + // Afterwards, there will be at most one `>` character remaining (more than one and it'd + // have shown up as a `>>`). + let encountered_gt = self.look_ahead(position, |t| *t == token::Gt); + if encountered_gt { + position += 1; + } + + // If we didn't find any trailing `>>` characters or a trailing `>`, then we have + // nothing to error about. + debug!( + "check_trailing_angle_brackets: encountered_gt={:?} number_of_shr={:?}", + encountered_gt, number_of_shr, + ); + if !encountered_gt && number_of_shr < 1 { + return; + } + + // Finally, double check that we have a left parenthesis next as otherwise this is the + // field case. + if self.look_ahead(position, |t| *t == token::OpenDelim(token::Paren)) { + // Eat from where we started until the left parenthesis so that parsing can continue + // as if we didn't have those extra angle brackets. + self.eat_to_tokens(&[&token::OpenDelim(token::Paren)]); + let span = lo.until(self.span); + + self.diagnostic() + .struct_span_err(span, "unmatched angle bracket") + .span_suggestion_with_applicability( + span, + "remove extra angle bracket", + String::new(), + Applicability::MachineApplicable, + ) + .emit(); + } + } + fn parse_dot_or_call_expr_with_(&mut self, e0: P, lo: Span) -> PResult<'a, P> { let mut e = e0; let mut hi; diff --git a/src/test/ui/issues/issue-54521-1.rs b/src/test/ui/issues/issue-54521-1.rs new file mode 100644 index 000000000000..d6a14a6e11f6 --- /dev/null +++ b/src/test/ui/issues/issue-54521-1.rs @@ -0,0 +1,16 @@ +// compile-pass + +// This test checks that the `remove extra angle brackets` error doesn't happen for some +// potential edge-cases.. + +struct X { + len: u32, +} + +fn main() { + let x = X { len: 3 }; + + let _ = x.len > (3); + + let _ = x.len >> (3); +} diff --git a/src/test/ui/issues/issue-54521.fixed b/src/test/ui/issues/issue-54521.fixed new file mode 100644 index 000000000000..84ab6866cf13 --- /dev/null +++ b/src/test/ui/issues/issue-54521.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +// This test checks that the following error is emitted and the suggestion works: +// +// ``` +// let _ = vec![1, 2, 3].into_iter().collect::>>>(); +// ^^ help: remove extra angle brackets +// ``` + +fn main() { + let _ = vec![1, 2, 3].into_iter().collect::>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>(); + //~^ ERROR unmatched angle bracket +} diff --git a/src/test/ui/issues/issue-54521.rs b/src/test/ui/issues/issue-54521.rs new file mode 100644 index 000000000000..f1d685041788 --- /dev/null +++ b/src/test/ui/issues/issue-54521.rs @@ -0,0 +1,22 @@ +// run-rustfix + +// This test checks that the following error is emitted and the suggestion works: +// +// ``` +// let _ = vec![1, 2, 3].into_iter().collect::>>>(); +// ^^ help: remove extra angle brackets +// ``` + +fn main() { + let _ = vec![1, 2, 3].into_iter().collect::>>>>>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>>>>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>>>(); + //~^ ERROR unmatched angle bracket + + let _ = vec![1, 2, 3].into_iter().collect::>>(); + //~^ ERROR unmatched angle bracket +} diff --git a/src/test/ui/issues/issue-54521.stderr b/src/test/ui/issues/issue-54521.stderr new file mode 100644 index 000000000000..a67e9ca8daf4 --- /dev/null +++ b/src/test/ui/issues/issue-54521.stderr @@ -0,0 +1,26 @@ +error: unmatched angle bracket + --> $DIR/issue-54521.rs:11:60 + | +LL | let _ = vec![1, 2, 3].into_iter().collect::>>>>>(); + | ^^^^ help: remove extra angle bracket + +error: unmatched angle bracket + --> $DIR/issue-54521.rs:14:60 + | +LL | let _ = vec![1, 2, 3].into_iter().collect::>>>>(); + | ^^^ help: remove extra angle bracket + +error: unmatched angle bracket + --> $DIR/issue-54521.rs:17:60 + | +LL | let _ = vec![1, 2, 3].into_iter().collect::>>>(); + | ^^ help: remove extra angle bracket + +error: unmatched angle bracket + --> $DIR/issue-54521.rs:20:60 + | +LL | let _ = vec![1, 2, 3].into_iter().collect::>>(); + | ^ help: remove extra angle bracket + +error: aborting due to 4 previous errors +