]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_parse/parser/diagnostics.rs
Auto merge of #68267 - estebank:lt-sugg, r=petrochenkov
[rust.git] / src / librustc_parse / parser / diagnostics.rs
index d321383424cabc85a87b6dd10c28deeaa842860c..80bc5c158a64f947bb25984211ef21e8b9c858cd 100644 (file)
@@ -1,9 +1,9 @@
 use super::{BlockMode, Parser, PathStyle, SemiColonMode, SeqSep, TokenExpectType, TokenType};
 
 use rustc_data_structures::fx::FxHashSet;
-use rustc_error_codes::*;
 use rustc_errors::{pluralize, struct_span_err};
 use rustc_errors::{Applicability, DiagnosticBuilder, Handler, PResult};
+use rustc_span::source_map::Spanned;
 use rustc_span::symbol::kw;
 use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP};
 use syntax::ast::{
@@ -259,10 +259,7 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
             };
             (
                 format!("expected one of {}, found {}", expect, actual),
-                (
-                    self.sess.source_map().next_point(self.prev_span),
-                    format!("expected one of {}", short_expect),
-                ),
+                (self.prev_span.shrink_to_hi(), format!("expected one of {}", short_expect)),
             )
         } else if expected.is_empty() {
             (
@@ -272,7 +269,7 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
         } else {
             (
                 format!("expected {}, found {}", expect, actual),
-                (self.sess.source_map().next_point(self.prev_span), format!("expected {}", expect)),
+                (self.prev_span.shrink_to_hi(), format!("expected {}", expect)),
             )
         };
         self.last_unexpected_token_span = Some(self.token.span);
@@ -494,6 +491,58 @@ pub(super) fn check_trailing_angle_brackets(&mut self, segment: &PathSegment, en
         }
     }
 
+    /// Check to see if a pair of chained operators looks like an attempt at chained comparison,
+    /// e.g. `1 < x <= 3`. If so, suggest either splitting the comparison into two, or
+    /// parenthesising the leftmost comparison.
+    fn attempt_chained_comparison_suggestion(
+        &mut self,
+        err: &mut DiagnosticBuilder<'_>,
+        inner_op: &Expr,
+        outer_op: &Spanned<AssocOp>,
+    ) {
+        if let ExprKind::Binary(op, ref l1, ref r1) = inner_op.kind {
+            match (op.node, &outer_op.node) {
+                // `x < y < z` and friends.
+                (BinOpKind::Lt, AssocOp::Less) | (BinOpKind::Lt, AssocOp::LessEqual) |
+                (BinOpKind::Le, AssocOp::LessEqual) | (BinOpKind::Le, AssocOp::Less) |
+                // `x > y > z` and friends.
+                (BinOpKind::Gt, AssocOp::Greater) | (BinOpKind::Gt, AssocOp::GreaterEqual) |
+                (BinOpKind::Ge, AssocOp::GreaterEqual) | (BinOpKind::Ge, AssocOp::Greater) => {
+                    let expr_to_str = |e: &Expr| {
+                        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(),
+                        ),
+                        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(),
+                        ),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+                _ => {}
+            }
+        }
+    }
+
     /// Produces an error if comparison operators are chained (RFC #558).
     /// We only need to check the LHS, not the RHS, because all comparison ops have same
     /// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
@@ -509,27 +558,31 @@ pub(super) fn check_trailing_angle_brackets(&mut self, segment: &PathSegment, en
     ///           /   \
     ///     inner_op   r2
     ///        /  \
-    ///     l1    r1
+    ///      l1    r1
     pub(super) fn check_no_chained_comparison(
         &mut self,
-        lhs: &Expr,
-        outer_op: &AssocOp,
+        inner_op: &Expr,
+        outer_op: &Spanned<AssocOp>,
     ) -> PResult<'a, Option<P<Expr>>> {
         debug_assert!(
-            outer_op.is_comparison(),
+            outer_op.node.is_comparison(),
             "check_no_chained_comparison: {:?} is not comparison",
-            outer_op,
+            outer_op.node,
         );
 
         let mk_err_expr =
             |this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err, AttrVec::new())));
 
-        match lhs.kind {
+        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_span);
-                let mut err = self
-                    .struct_span_err(op_span, "chained comparison operators require parentheses");
+                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);
 
                 let suggest = |err: &mut DiagnosticBuilder<'_>| {
                     err.span_suggestion_verbose(
@@ -541,12 +594,12 @@ pub(super) fn check_no_chained_comparison(
                 };
 
                 if op.node == BinOpKind::Lt &&
-                    *outer_op == AssocOp::Less ||  // Include `<` to provide this recommendation
-                    *outer_op == AssocOp::Greater
+                    outer_op.node == AssocOp::Less ||  // Include `<` to provide this recommendation
+                    outer_op.node == AssocOp::Greater
                 // even in a case like the following:
                 {
                     //     Foo<Bar<Baz<Qux, ()>>>
-                    if *outer_op == AssocOp::Less {
+                    if outer_op.node == AssocOp::Less {
                         let snapshot = self.clone();
                         self.bump();
                         // So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -578,7 +631,7 @@ pub(super) fn check_no_chained_comparison(
                                 // FIXME: actually check that the two expressions in the binop are
                                 // paths and resynthesize new fn call expression instead of using
                                 // `ExprKind::Err` placeholder.
-                                mk_err_expr(self, lhs.span.to(self.prev_span))
+                                mk_err_expr(self, inner_op.span.to(self.prev_span))
                             }
                             Err(mut expr_err) => {
                                 expr_err.cancel();
@@ -600,7 +653,7 @@ pub(super) fn check_no_chained_comparison(
                                 // FIXME: actually check that the two expressions in the binop are
                                 // paths and resynthesize new fn call expression instead of using
                                 // `ExprKind::Err` placeholder.
-                                mk_err_expr(self, lhs.span.to(self.prev_span))
+                                mk_err_expr(self, inner_op.span.to(self.prev_span))
                             }
                         }
                     } else {
@@ -803,7 +856,7 @@ pub(super) fn unexpected_try_recover(
             _ if self.prev_span == DUMMY_SP => (self.token.span, self.token.span),
             // EOF, don't want to point at the following char, but rather the last token.
             (token::Eof, None) => (self.prev_span, self.token.span),
-            _ => (self.sess.source_map().next_point(self.prev_span), self.token.span),
+            _ => (self.prev_span.shrink_to_hi(), self.token.span),
         };
         let msg = format!(
             "expected `{}`, found {}",
@@ -1126,7 +1179,7 @@ pub(super) fn recover_closing_delimiter(
                     err.span_label(sp, "unclosed delimiter");
                 }
                 err.span_suggestion_short(
-                    self.sess.source_map().next_point(self.prev_span),
+                    self.prev_span.shrink_to_hi(),
                     &format!("{} may belong here", delim.to_string()),
                     delim.to_string(),
                     Applicability::MaybeIncorrect,