]> git.lizzy.rs Git - rust.git/commitdiff
Tweak chained operators diagnostic
authorEsteban Küber <esteban@kuber.com.ar>
Tue, 10 Mar 2020 03:21:37 +0000 (20:21 -0700)
committerEsteban Küber <esteban@kuber.com.ar>
Thu, 26 Mar 2020 00:13:04 +0000 (17:13 -0700)
Use more selective spans
Improve suggestion output
Be more selective when displaying suggestions
Silence some knock-down type errors

src/librustc_parse/parser/diagnostics.rs
src/test/ui/did_you_mean/issue-40396.stderr
src/test/ui/parser/chained-comparison-suggestion.rs
src/test/ui/parser/chained-comparison-suggestion.stderr
src/test/ui/parser/require-parens-for-chained-comparison.rs
src/test/ui/parser/require-parens-for-chained-comparison.stderr

index 87255386b9e6602b101f6dbbf069cf43dd2b5c22..4771031984d1e4a041d4cfcb1d9e941fda378bff 100644 (file)
@@ -459,9 +459,18 @@ fn attempt_chained_comparison_suggestion(
         err: &mut DiagnosticBuilder<'_>,
         inner_op: &Expr,
         outer_op: &Spanned<AssocOp>,
-    ) {
+    ) -> bool /* recover */ {
         if let ExprKind::Binary(op, ref l1, ref r1) = inner_op.kind {
-            match (op.node, &outer_op.node) {
+            if let ExprKind::Field(_, ident) = l1.kind {
+                if ident.as_str().parse::<i32>().is_err() && !matches!(r1.kind, ExprKind::Lit(_)) {
+                    // The parser has encountered `foo.bar<baz`, the likelihood of the turbofish
+                    // suggestion being the only one to apply is high.
+                    return false;
+                }
+            }
+            return match (op.node, &outer_op.node) {
+                // `x == y == z`
+                (BinOpKind::Eq, AssocOp::Equal) |
                 // `x < y < z` and friends.
                 (BinOpKind::Lt, AssocOp::Less) | (BinOpKind::Lt, AssocOp::LessEqual) |
                 (BinOpKind::Le, AssocOp::LessEqual) | (BinOpKind::Le, AssocOp::Less) |
@@ -472,35 +481,65 @@ fn attempt_chained_comparison_suggestion(
                         self.span_to_snippet(e.span)
                             .unwrap_or_else(|_| pprust::expr_to_string(&e))
                     };
-                    err.span_suggestion(
-                        inner_op.span.to(outer_op.span),
-                        "split the comparison into two...",
-                        format!(
-                            "{} {} {} && {} {}",
-                            expr_to_str(&l1),
-                            op.node.to_string(),
-                            expr_to_str(&r1),
-                            expr_to_str(&r1),
-                            outer_op.node.to_ast_binop().unwrap().to_string(),
-                        ),
+                    err.span_suggestion_verbose(
+                            inner_op.span.shrink_to_hi(),
+                        "split the comparison into two",
+                        format!(" && {}", expr_to_str(&r1)),
                         Applicability::MaybeIncorrect,
                     );
-                    err.span_suggestion(
-                        inner_op.span.to(outer_op.span),
-                        "...or parenthesize one of the comparisons",
-                        format!(
-                            "({} {} {}) {}",
-                            expr_to_str(&l1),
-                            op.node.to_string(),
-                            expr_to_str(&r1),
-                            outer_op.node.to_ast_binop().unwrap().to_string(),
-                        ),
+                    false // Keep the current parse behavior, where the AST is `(x < y) < z`.
+                }
+                // `x == y < z`
+                (BinOpKind::Eq, AssocOp::Less) | (BinOpKind::Eq, AssocOp::LessEqual) |
+                (BinOpKind::Eq, AssocOp::Greater) | (BinOpKind::Eq, AssocOp::GreaterEqual) => {
+                    // Consume `/`z`/outer-op-rhs.
+                    let snapshot = self.clone();
+                    match self.parse_expr() {
+                        Ok(r2) => {
+                            err.multipart_suggestion(
+                                "parenthesize the comparison",
+                                vec![
+                                    (r1.span.shrink_to_lo(), "(".to_string()),
+                                    (r2.span.shrink_to_hi(), ")".to_string()),
+                                ],
+                                Applicability::MaybeIncorrect,
+                            );
+                            true
+                        }
+                        Err(mut expr_err) => {
+                            expr_err.cancel();
+                            mem::replace(self, snapshot);
+                            false
+                        }
+                    }
+                }
+                // `x > y == z`
+                (BinOpKind::Lt, AssocOp::Equal) | (BinOpKind::Le, AssocOp::Equal) |
+                (BinOpKind::Gt, AssocOp::Equal) | (BinOpKind::Ge, AssocOp::Equal) => {
+                    let snapshot = self.clone();
+                    err.multipart_suggestion(
+                        "parenthesize the comparison",
+                        vec![
+                            (l1.span.shrink_to_lo(), "(".to_string()),
+                            (r1.span.shrink_to_hi(), ")".to_string()),
+                        ],
                         Applicability::MaybeIncorrect,
                     );
+                    match self.parse_expr() {
+                        Ok(_) => {
+                            true
+                        }
+                        Err(mut expr_err) => {
+                            expr_err.cancel();
+                            mem::replace(self, snapshot);
+                            false
+                        }
+                    }
                 }
-                _ => {}
-            }
+                _ => false,
+            };
         }
+        false
     }
 
     /// Produces an error if comparison operators are chained (RFC #558).
@@ -534,31 +573,26 @@ pub(super) fn check_no_chained_comparison(
             |this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err, AttrVec::new())));
 
         match inner_op.kind {
-            ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
-                // Respan to include both operators.
-                let op_span = op.span.to(self.prev_token.span);
-                let mut err =
-                    self.struct_span_err(op_span, "comparison operators cannot be chained");
-
-                // If it looks like a genuine attempt to chain operators (as opposed to a
-                // misformatted turbofish, for instance), suggest a correct form.
-                self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
+            ExprKind::Binary(op, ref l1, ref r1) if op.node.is_comparison() => {
+                let mut err = self.struct_span_err(
+                    vec![op.span, self.prev_token.span],
+                    "comparison operators cannot be chained",
+                );
 
                 let suggest = |err: &mut DiagnosticBuilder<'_>| {
                     err.span_suggestion_verbose(
-                        op_span.shrink_to_lo(),
+                        op.span.shrink_to_lo(),
                         TURBOFISH,
                         "::".to_string(),
                         Applicability::MaybeIncorrect,
                     );
                 };
 
-                if op.node == BinOpKind::Lt &&
-                    outer_op.node == AssocOp::Less ||  // Include `<` to provide this recommendation
-                    outer_op.node == AssocOp::Greater
-                // even in a case like the following:
+                if op.node == BinOpKind::Lt && outer_op.node == AssocOp::Less
+                    || outer_op.node == AssocOp::Greater
                 {
-                    //     Foo<Bar<Baz<Qux, ()>>>
+                    // Include `<` to provide this recommendation
+                    // even in a case like `Foo<Bar<Baz<Qux, ()>>>`
                     if outer_op.node == AssocOp::Less {
                         let snapshot = self.clone();
                         self.bump();
@@ -617,15 +651,33 @@ pub(super) fn check_no_chained_comparison(
                             }
                         }
                     } else {
-                        // All we know is that this is `foo < bar >` and *nothing* else. Try to
-                        // be helpful, but don't attempt to recover.
-                        err.help(TURBOFISH);
-                        err.help("or use `(...)` if you meant to specify fn arguments");
-                        // These cases cause too many knock-down errors, bail out (#61329).
-                        Err(err)
+                        if !matches!(l1.kind, ExprKind::Lit(_))
+                            && !matches!(r1.kind, ExprKind::Lit(_))
+                        {
+                            // All we know is that this is `foo < bar >` and *nothing* else. Try to
+                            // be helpful, but don't attempt to recover.
+                            err.help(TURBOFISH);
+                            err.help("or use `(...)` if you meant to specify fn arguments");
+                        }
+
+                        // If it looks like a genuine attempt to chain operators (as opposed to a
+                        // misformatted turbofish, for instance), suggest a correct form.
+                        if self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op)
+                        {
+                            err.emit();
+                            mk_err_expr(self, inner_op.span.to(self.prev_token.span))
+                        } else {
+                            // These cases cause too many knock-down errors, bail out (#61329).
+                            Err(err)
+                        }
                     };
                 }
+                let recover =
+                    self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
                 err.emit();
+                if recover {
+                    return mk_err_expr(self, inner_op.span.to(self.prev_token.span));
+                }
             }
             _ => {}
         }
index f952136a7bfe36b8b1031acbf17606dfa9259e4f..10972697f9fcdc7af0373f94d1493e80b785329e 100644 (file)
@@ -2,16 +2,8 @@ error: comparison operators cannot be chained
   --> $DIR/issue-40396.rs:2:20
    |
 LL |     (0..13).collect<Vec<i32>>();
-   |                    ^^^^^
+   |                    ^   ^
    |
-help: split the comparison into two...
-   |
-LL |     (0..13).collect < Vec && Vec <i32>>();
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     ((0..13).collect < Vec) <i32>>();
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
 help: use `::<...>` instead of `<...>` to specify type arguments
    |
 LL |     (0..13).collect::<Vec<i32>>();
@@ -21,7 +13,7 @@ error: comparison operators cannot be chained
   --> $DIR/issue-40396.rs:4:8
    |
 LL |     Vec<i32>::new();
-   |        ^^^^^
+   |        ^   ^
    |
 help: use `::<...>` instead of `<...>` to specify type arguments
    |
@@ -32,16 +24,8 @@ error: comparison operators cannot be chained
   --> $DIR/issue-40396.rs:6:20
    |
 LL |     (0..13).collect<Vec<i32>();
-   |                    ^^^^^
-   |
-help: split the comparison into two...
-   |
-LL |     (0..13).collect < Vec && Vec <i32>();
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
+   |                    ^   ^
    |
-LL |     ((0..13).collect < Vec) <i32>();
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
 help: use `::<...>` instead of `<...>` to specify type arguments
    |
 LL |     (0..13).collect::<Vec<i32>();
index 0431196f1744e064b78ff66937a00b14bb3f1d3b..bbd46082c9f901bcc6c22f8de14f1759029bb188 100644 (file)
@@ -37,4 +37,17 @@ fn comp8() {
     //~^ ERROR mismatched types
 }
 
+fn comp9() {
+    1 == 2 < 3; //~ ERROR comparison operators cannot be chained
+}
+
+fn comp10() {
+    1 > 2 == false; //~ ERROR comparison operators cannot be chained
+}
+
+fn comp11() {
+    1 == 2 == 3; //~ ERROR comparison operators cannot be chained
+    //~^ ERROR mismatched types
+}
+
 fn main() {}
index 5c10a4599dd032e984b9f1fa11cf8e1f98a8ac16..067920d12f486c6595229d8eef6c896ba9a055b6 100644 (file)
@@ -2,127 +2,122 @@ error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:4:7
    |
 LL |     1 < 2 <= 3;
-   |       ^^^^^^
+   |       ^   ^^
    |
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 < 2 && 2 <= 3;
-   |     ^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 < 2) <= 3;
-   |     ^^^^^^^^^^
+   |           ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:9:7
    |
 LL |     1 < 2 < 3;
-   |       ^^^^^
+   |       ^   ^
    |
-   = help: use `::<...>` instead of `<...>` to specify type arguments
-   = help: or use `(...)` if you meant to specify fn arguments
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 < 2 && 2 < 3;
-   |     ^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 < 2) < 3;
-   |     ^^^^^^^^^
+   |           ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:13:7
    |
 LL |     1 <= 2 < 3;
-   |       ^^^^^^
+   |       ^^   ^
    |
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 <= 2 && 2 < 3;
-   |     ^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 <= 2) < 3;
-   |     ^^^^^^^^^^
+   |            ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:18:7
    |
 LL |     1 <= 2 <= 3;
-   |       ^^^^^^^
+   |       ^^   ^^
    |
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 <= 2 && 2 <= 3;
-   |     ^^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 <= 2) <= 3;
-   |     ^^^^^^^^^^^
+   |            ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:23:7
    |
 LL |     1 > 2 >= 3;
-   |       ^^^^^^
+   |       ^   ^^
    |
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 > 2 && 2 >= 3;
-   |     ^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 > 2) >= 3;
-   |     ^^^^^^^^^^
+   |           ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:28:7
    |
 LL |     1 > 2 > 3;
-   |       ^^^^^
+   |       ^   ^
    |
-   = help: use `::<...>` instead of `<...>` to specify type arguments
-   = help: or use `(...)` if you meant to specify fn arguments
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 > 2 && 2 > 3;
-   |     ^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 > 2) > 3;
-   |     ^^^^^^^^^
+   |           ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:32:7
    |
 LL |     1 >= 2 > 3;
-   |       ^^^^^^
+   |       ^^   ^
    |
-   = help: use `::<...>` instead of `<...>` to specify type arguments
-   = help: or use `(...)` if you meant to specify fn arguments
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 >= 2 && 2 > 3;
-   |     ^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
-   |
-LL |     (1 >= 2) > 3;
-   |     ^^^^^^^^^^
+   |            ^^^^
 
 error: comparison operators cannot be chained
   --> $DIR/chained-comparison-suggestion.rs:36:7
    |
 LL |     1 >= 2 >= 3;
-   |       ^^^^^^^
+   |       ^^   ^^
    |
-help: split the comparison into two...
+help: split the comparison into two
    |
 LL |     1 >= 2 && 2 >= 3;
-   |     ^^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
+   |            ^^^^
+
+error: comparison operators cannot be chained
+  --> $DIR/chained-comparison-suggestion.rs:41:7
+   |
+LL |     1 == 2 < 3;
+   |       ^^   ^
    |
-LL |     (1 >= 2) >= 3;
-   |     ^^^^^^^^^^^
+help: parenthesize the comparison
+   |
+LL |     1 == (2 < 3);
+   |          ^     ^
+
+error: comparison operators cannot be chained
+  --> $DIR/chained-comparison-suggestion.rs:45:7
+   |
+LL |     1 > 2 == false;
+   |       ^   ^^
+   |
+help: parenthesize the comparison
+   |
+LL |     (1 > 2) == false;
+   |     ^     ^
+
+error: comparison operators cannot be chained
+  --> $DIR/chained-comparison-suggestion.rs:49:7
+   |
+LL |     1 == 2 == 3;
+   |       ^^   ^^
+   |
+help: split the comparison into two
+   |
+LL |     1 == 2 && 2 == 3;
+   |            ^^^^
 
 error[E0308]: mismatched types
   --> $DIR/chained-comparison-suggestion.rs:4:14
@@ -154,6 +149,12 @@ error[E0308]: mismatched types
 LL |     1 >= 2 >= 3;
    |               ^ expected `bool`, found integer
 
-error: aborting due to 13 previous errors
+error[E0308]: mismatched types
+  --> $DIR/chained-comparison-suggestion.rs:49:15
+   |
+LL |     1 == 2 == 3;
+   |               ^ expected `bool`, found integer
+
+error: aborting due to 17 previous errors
 
 For more information about this error, try `rustc --explain E0308`.
index e27b03dddc5be56882eaf84089585bd4ceb9e98d..4e97904ed6d5f9f178aa6954b3673368724c1239 100644 (file)
@@ -4,11 +4,11 @@ fn f<T>() {}
 fn main() {
     false == false == false;
     //~^ ERROR comparison operators cannot be chained
+    //~| HELP split the comparison into two
 
     false == 0 < 2;
     //~^ ERROR comparison operators cannot be chained
-    //~| ERROR mismatched types
-    //~| ERROR mismatched types
+    //~| HELP parenthesize the comparison
 
     f<X>();
     //~^ ERROR comparison operators cannot be chained
@@ -16,8 +16,6 @@ fn main() {
 
     f<Result<Option<X>, Option<Option<X>>>(1, 2);
     //~^ ERROR comparison operators cannot be chained
-    //~| HELP split the comparison into two...
-    //~| ...or parenthesize one of the comparisons
     //~| HELP use `::<...>` instead of `<...>` to specify type arguments
 
     use std::convert::identity;
index 44edf2de7f8de22adccbaf1aa6c0168f4671c671..7001aa8e8a1d8c279707db8c396f5637ddb2fb71 100644 (file)
@@ -2,19 +2,29 @@ error: comparison operators cannot be chained
   --> $DIR/require-parens-for-chained-comparison.rs:5:11
    |
 LL |     false == false == false;
-   |           ^^^^^^^^^^^
+   |           ^^       ^^
+   |
+help: split the comparison into two
+   |
+LL |     false == false && false == false;
+   |                    ^^^^^^^^
 
 error: comparison operators cannot be chained
-  --> $DIR/require-parens-for-chained-comparison.rs:8:11
+  --> $DIR/require-parens-for-chained-comparison.rs:9:11
    |
 LL |     false == 0 < 2;
-   |           ^^^^^^
+   |           ^^   ^
+   |
+help: parenthesize the comparison
+   |
+LL |     false == (0 < 2);
+   |              ^     ^
 
 error: comparison operators cannot be chained
   --> $DIR/require-parens-for-chained-comparison.rs:13:6
    |
 LL |     f<X>();
-   |      ^^^
+   |      ^ ^
    |
 help: use `::<...>` instead of `<...>` to specify type arguments
    |
@@ -25,42 +35,21 @@ error: comparison operators cannot be chained
   --> $DIR/require-parens-for-chained-comparison.rs:17:6
    |
 LL |     f<Result<Option<X>, Option<Option<X>>>(1, 2);
-   |      ^^^^^^^^
-   |
-help: split the comparison into two...
-   |
-LL |     f < Result && Result <Option<X>, Option<Option<X>>>(1, 2);
-   |     ^^^^^^^^^^^^^^^^^^^^^^
-help: ...or parenthesize one of the comparisons
+   |      ^      ^
    |
-LL |     (f < Result) <Option<X>, Option<Option<X>>>(1, 2);
-   |     ^^^^^^^^^^^^^^
 help: use `::<...>` instead of `<...>` to specify type arguments
    |
 LL |     f::<Result<Option<X>, Option<Option<X>>>(1, 2);
    |      ^^
 
 error: comparison operators cannot be chained
-  --> $DIR/require-parens-for-chained-comparison.rs:24:21
+  --> $DIR/require-parens-for-chained-comparison.rs:22:21
    |
 LL |     let _ = identity<u8>;
-   |                     ^^^^
+   |                     ^  ^
    |
    = help: use `::<...>` instead of `<...>` to specify type arguments
    = help: or use `(...)` if you meant to specify fn arguments
 
-error[E0308]: mismatched types
-  --> $DIR/require-parens-for-chained-comparison.rs:8:14
-   |
-LL |     false == 0 < 2;
-   |              ^ expected `bool`, found integer
-
-error[E0308]: mismatched types
-  --> $DIR/require-parens-for-chained-comparison.rs:8:18
-   |
-LL |     false == 0 < 2;
-   |                  ^ expected `bool`, found integer
-
-error: aborting due to 7 previous errors
+error: aborting due to 5 previous errors
 
-For more information about this error, try `rustc --explain E0308`.