about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libsyntax/ast.rs14
-rw-r--r--src/libsyntax/parse/parser.rs97
-rw-r--r--src/test/ui/issues/issue-54521-1.rs16
-rw-r--r--src/test/ui/issues/issue-54521.fixed22
-rw-r--r--src/test/ui/issues/issue-54521.rs22
-rw-r--r--src/test/ui/issues/issue-54521.stderr26
6 files changed, 197 insertions, 0 deletions
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index 405cf612543..e520ac3bdd4 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 439eec5b0c4..d7c209d12a8 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<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
         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::<Vec<usize>>>>();
+    ///                                                        ^^ 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::<u32>`.
+
+        // 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 `::<u32>` will
+        // have already been parsed):
+        //
+        // `x.foo::<u32>>>(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<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
         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 00000000000..d6a14a6e11f
--- /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 00000000000..84ab6866cf1
--- /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::<Vec<usize>>>>();
+//                                                        ^^ help: remove extra angle brackets
+// ```
+
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>();
+    //~^ 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 00000000000..f1d68504178
--- /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::<Vec<usize>>>>();
+//                                                        ^^ help: remove extra angle brackets
+// ```
+
+fn main() {
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
+    //~^ ERROR unmatched angle bracket
+
+    let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>();
+    //~^ 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 00000000000..a67e9ca8daf
--- /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::<Vec<usize>>>>>>();
+   |                                                            ^^^^ help: remove extra angle bracket
+
+error: unmatched angle bracket
+  --> $DIR/issue-54521.rs:14:60
+   |
+LL |     let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>>();
+   |                                                            ^^^ help: remove extra angle bracket
+
+error: unmatched angle bracket
+  --> $DIR/issue-54521.rs:17:60
+   |
+LL |     let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>>();
+   |                                                            ^^ help: remove extra angle bracket
+
+error: unmatched angle bracket
+  --> $DIR/issue-54521.rs:20:60
+   |
+LL |     let _ = vec![1, 2, 3].into_iter().collect::<Vec<usize>>>();
+   |                                                            ^ help: remove extra angle bracket
+
+error: aborting due to 4 previous errors
+