about summary refs log tree commit diff
diff options
context:
space:
mode:
authoryukang <moorekang@gmail.com>2022-09-29 13:16:47 +0800
committeryukang <moorekang@gmail.com>2022-10-04 17:30:52 +0800
commit5dd44d4d4c4545f65f15f890e93fac68214cfe54 (patch)
tree2c79664ff64bc841dd9c0e61fecd6b6789df22f2
parentd9f8b4b98503e3f88623eb59d4f20432161b840a (diff)
downloadrust-5dd44d4d4c4545f65f15f890e93fac68214cfe54.tar.gz
rust-5dd44d4d4c4545f65f15f890e93fac68214cfe54.zip
fix #102396, suggest parentheses for possible range methods
-rw-r--r--compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl5
-rw-r--r--compiler/rustc_hir_analysis/src/check/method/suggest.rs75
-rw-r--r--compiler/rustc_hir_analysis/src/errors.rs25
-rw-r--r--src/test/ui/methods/issues/issue-90315.rs73
-rw-r--r--src/test/ui/methods/issues/issue-90315.stderr176
5 files changed, 339 insertions, 15 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl
index c6a4ff6f0e0..827c3c93a73 100644
--- a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl
@@ -133,3 +133,8 @@ hir_analysis_extern_crate_not_idiomatic =
     .suggestion = convert it to a `{$msg_code}`
 
 hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
+hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
+
+hir_analysis_missing_parentheses_in_range = `{$ty_str}` is not an iterator
+
+hir_analysis_add_missing_parentheses_in_range = you must surround the range in parentheses to call the `{$func_name}` function
diff --git a/compiler/rustc_hir_analysis/src/check/method/suggest.rs b/compiler/rustc_hir_analysis/src/check/method/suggest.rs
index 0e82e4956c7..d7189e70d06 100644
--- a/compiler/rustc_hir_analysis/src/check/method/suggest.rs
+++ b/compiler/rustc_hir_analysis/src/check/method/suggest.rs
@@ -2,6 +2,7 @@
 //! found or is otherwise invalid.
 
 use crate::check::FnCtxt;
+use crate::errors;
 use rustc_ast::ast::Mutability;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{
@@ -12,7 +13,7 @@ use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
-use rustc_hir::{ExprKind, Node, QPath};
+use rustc_hir::{is_range_literal, ExprKind, Node, QPath};
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_middle::traits::util::supertraits;
 use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
@@ -271,9 +272,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     }
                 };
 
-                if self.suggest_constraining_numerical_ty(
-                    tcx, actual, source, span, item_kind, item_name, &ty_str,
-                ) {
+                if self.suggest_range_for_iter(tcx, actual, source, span, item_name, &ty_str)
+                    || self.suggest_constraining_numerical_ty(
+                        tcx, actual, source, span, item_kind, item_name, &ty_str,
+                    )
+                {
                     return None;
                 }
 
@@ -1201,6 +1204,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         false
     }
 
+    fn suggest_range_for_iter(
+        &self,
+        tcx: TyCtxt<'tcx>,
+        actual: Ty<'tcx>,
+        source: SelfSource<'tcx>,
+        span: Span,
+        item_name: Ident,
+        ty_str: &str,
+    ) -> bool {
+        if let SelfSource::MethodCall(expr) = source {
+            let mut search_limit = 5;
+            for (_, parent) in tcx.hir().parent_iter(expr.hir_id) {
+                search_limit -= 1;
+                if search_limit == 0 {
+                    break;
+                }
+
+                if let Node::Expr(parent_expr) = parent && is_range_literal(parent_expr) {
+                    let span_included = match parent_expr.kind {
+                            hir::ExprKind::Struct(_, eps, _) =>
+                                eps.len() > 0 && eps.last().map_or(false, |ep| ep.span.contains(span)),
+                            // `..=` desugars into `::std::ops::RangeInclusive::new(...)`.
+                            hir::ExprKind::Call(ref func, ..) => func.span.contains(span),
+                            _ => false,
+                    };
+
+                    if !span_included {
+                        continue;
+                    }
+
+                    let range_def_id = self.tcx.lang_items().range_struct().unwrap();
+                    let range_ty = self.tcx.bound_type_of(range_def_id).subst(self.tcx, &[actual.into()]);
+
+                    // avoid suggesting when the method name is not implemented for a `range`
+                    let pick =  self.lookup_probe(
+                        span,
+                        item_name,
+                        range_ty,
+                        expr,
+                        ProbeScope::AllTraits
+                    );
+
+                    if pick.is_ok() {
+                        let range_span = parent_expr.span.with_hi(expr.span.hi());
+                        tcx.sess.emit_err(errors::MissingParentheseInRange {
+                            span: span,
+                            ty_str: ty_str.to_string(),
+                            add_missing_parentheses: Some(
+                                errors::AddMissingParenthesesInRange {
+                                    func_name: item_name.name.as_str().to_string(),
+                                    left: range_span.shrink_to_lo(),
+                                    right: range_span.shrink_to_hi(),
+                                }
+                            )
+                        });
+                        return true;
+                    }
+                }
+            }
+        }
+        false
+    }
+
     fn suggest_constraining_numerical_ty(
         &self,
         tcx: TyCtxt<'tcx>,
@@ -1263,7 +1329,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     // If this is a floating point literal that ends with '.',
                     // get rid of it to stop this from becoming a member access.
                     let snippet = snippet.strip_suffix('.').unwrap_or(&snippet);
-
                     err.span_suggestion(
                         lit.span,
                         &format!(
diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs
index d891171b824..6634444c636 100644
--- a/compiler/rustc_hir_analysis/src/errors.rs
+++ b/compiler/rustc_hir_analysis/src/errors.rs
@@ -346,3 +346,28 @@ pub struct ExpectedUsedSymbol {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis::missing_parentheses_in_range, code = "E0599")]
+pub struct MissingParentheseInRange {
+    #[primary_span]
+    #[label(hir_analysis::missing_parentheses_in_range)]
+    pub span: Span,
+    pub ty_str: String,
+
+    #[subdiagnostic]
+    pub add_missing_parentheses: Option<AddMissingParenthesesInRange>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion_verbose(
+    hir_analysis::add_missing_parentheses_in_range,
+    applicability = "maybe-incorrect"
+)]
+pub struct AddMissingParenthesesInRange {
+    pub func_name: String,
+    #[suggestion_part(code = "(")]
+    pub left: Span,
+    #[suggestion_part(code = ")")]
+    pub right: Span,
+}
diff --git a/src/test/ui/methods/issues/issue-90315.rs b/src/test/ui/methods/issues/issue-90315.rs
index 01bf9f48402..74cd2b35834 100644
--- a/src/test/ui/methods/issues/issue-90315.rs
+++ b/src/test/ui/methods/issues/issue-90315.rs
@@ -1,7 +1,70 @@
+#![allow(unused)]
 fn main() {
-  let arr = &[0,1,2,3];
-  for _i in 0..arr.len().rev() { //~ERROR not an iterator
-     // The above error used to say “the method `rev` exists for type `usize`”.
-     // This regression test ensures it doesn't say that any more.
-  }
+    let arr = &[0, 1, 2, 3];
+    for _i in 0..arr.len().rev() {
+        //~^ ERROR not an iterator
+        //~| surround the range in parentheses
+        // The above error used to say “the method `rev` exists for type `usize`”.
+        // This regression test ensures it doesn't say that any more.
+    }
+
+    // Test for #102396
+    for i in 1..11.rev() {
+        //~^ ERROR not an iterator
+        //~| HELP surround the range in parentheses
+    }
+
+    let end: usize = 10;
+    for i in 1..end.rev() {
+        //~^ ERROR not an iterator
+        //~| HELP surround the range in parentheses
+    }
+
+    for i in 1..(end + 1).rev() {
+        //~^ ERROR not an iterator
+        //~| HELP surround the range in parentheses
+    }
+
+    if 1..(end + 1).is_empty() {
+        //~^ ERROR not an iterator
+        //~| ERROR mismatched types [E0308]
+        //~| HELP surround the range in parentheses
+    }
+
+    if 1..(end + 1).is_sorted() {
+        //~^ ERROR mismatched types [E0308]
+        //~| ERROR `usize` is not an iterator [E0599]
+        //~| HELP surround the range in parentheses
+    }
+
+    let _res: i32 = 3..6.take(2).sum();
+    //~^ ERROR `{integer}` is not an iterator [E0599]
+    //~| ERROR mismatched types [E0308]
+    //~| HELP surround the range in parentheses
+
+    let _sum: i32 = 3..6.sum();
+    //~^ ERROR `{integer}` is not an iterator [E0599]
+    //~| ERROR mismatched types [E0308]
+    //~| HELP surround the range in parentheses
+
+    let a = 1 as usize;
+    let b = 10 as usize;
+
+    for _a in a..=b.rev() {
+        //~^ ERROR not an iterator
+        //~| HELP surround the range in parentheses
+    }
+
+    let _res = ..10.contains(3);
+    //~^ ERROR not an iterator
+    //~| HELP surround the range in parentheses
+
+    if 1..end.error_method() {
+        //~^ ERROR no method named `error_method`
+        //~| ERROR mismatched types [E0308]
+        // Won't suggest
+    }
+
+    let _res = b.take(1)..a;
+    //~^ ERROR not an iterator
 }
diff --git a/src/test/ui/methods/issues/issue-90315.stderr b/src/test/ui/methods/issues/issue-90315.stderr
index c6a76c9e790..f2084b593c2 100644
--- a/src/test/ui/methods/issues/issue-90315.stderr
+++ b/src/test/ui/methods/issues/issue-90315.stderr
@@ -1,13 +1,179 @@
 error[E0599]: `usize` is not an iterator
-  --> $DIR/issue-90315.rs:3:26
+  --> $DIR/issue-90315.rs:4:28
    |
-LL |   for _i in 0..arr.len().rev() {
-   |                          ^^^ `usize` is not an iterator
+LL |     for _i in 0..arr.len().rev() {
+   |                            ^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `rev` function
+   |
+LL |     for _i in (0..arr.len()).rev() {
+   |               +            +
+
+error[E0599]: `{integer}` is not an iterator
+  --> $DIR/issue-90315.rs:12:20
+   |
+LL |     for i in 1..11.rev() {
+   |                    ^^^ `{integer}` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `rev` function
+   |
+LL |     for i in (1..11).rev() {
+   |              +     +
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:18:21
+   |
+LL |     for i in 1..end.rev() {
+   |                     ^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `rev` function
+   |
+LL |     for i in (1..end).rev() {
+   |              +      +
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:23:27
+   |
+LL |     for i in 1..(end + 1).rev() {
+   |                           ^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `rev` function
+   |
+LL |     for i in (1..(end + 1)).rev() {
+   |              +            +
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:28:21
+   |
+LL |     if 1..(end + 1).is_empty() {
+   |                     ^^^^^^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `is_empty` function
+   |
+LL |     if (1..(end + 1)).is_empty() {
+   |        +            +
+
+error[E0308]: mismatched types
+  --> $DIR/issue-90315.rs:28:8
+   |
+LL |     if 1..(end + 1).is_empty() {
+   |        ^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found struct `std::ops::Range`
+   |
+   = note: expected type `bool`
+            found struct `std::ops::Range<{integer}>`
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:34:21
+   |
+LL |     if 1..(end + 1).is_sorted() {
+   |                     ^^^^^^^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `is_sorted` function
+   |
+LL |     if (1..(end + 1)).is_sorted() {
+   |        +            +
+
+error[E0308]: mismatched types
+  --> $DIR/issue-90315.rs:34:8
+   |
+LL |     if 1..(end + 1).is_sorted() {
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found struct `std::ops::Range`
+   |
+   = note: expected type `bool`
+            found struct `std::ops::Range<{integer}>`
+
+error[E0599]: `{integer}` is not an iterator
+  --> $DIR/issue-90315.rs:40:26
+   |
+LL |     let _res: i32 = 3..6.take(2).sum();
+   |                          ^^^^ `{integer}` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `take` function
+   |
+LL |     let _res: i32 = (3..6).take(2).sum();
+   |                     +    +
+
+error[E0308]: mismatched types
+  --> $DIR/issue-90315.rs:40:21
+   |
+LL |     let _res: i32 = 3..6.take(2).sum();
+   |               ---   ^^^^^^^^^^^^^^^^^^ expected `i32`, found struct `std::ops::Range`
+   |               |
+   |               expected due to this
+   |
+   = note: expected type `i32`
+            found struct `std::ops::Range<{integer}>`
+
+error[E0599]: `{integer}` is not an iterator
+  --> $DIR/issue-90315.rs:45:26
+   |
+LL |     let _sum: i32 = 3..6.sum();
+   |                          ^^^ `{integer}` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `sum` function
+   |
+LL |     let _sum: i32 = (3..6).sum();
+   |                     +    +
+
+error[E0308]: mismatched types
+  --> $DIR/issue-90315.rs:45:21
+   |
+LL |     let _sum: i32 = 3..6.sum();
+   |               ---   ^^^^^^^^^^ expected `i32`, found struct `std::ops::Range`
+   |               |
+   |               expected due to this
+   |
+   = note: expected type `i32`
+            found struct `std::ops::Range<{integer}>`
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:53:21
+   |
+LL |     for _a in a..=b.rev() {
+   |                     ^^^ `usize` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `rev` function
+   |
+LL |     for _a in (a..=b).rev() {
+   |               +     +
+
+error[E0599]: `{integer}` is not an iterator
+  --> $DIR/issue-90315.rs:58:21
+   |
+LL |     let _res = ..10.contains(3);
+   |                     ^^^^^^^^ `{integer}` is not an iterator
+   |
+help: you must surround the range in parentheses to call the `contains` function
+   |
+LL |     let _res = (..10).contains(3);
+   |                +    +
+
+error[E0599]: no method named `error_method` found for type `usize` in the current scope
+  --> $DIR/issue-90315.rs:62:15
+   |
+LL |     if 1..end.error_method() {
+   |               ^^^^^^^^^^^^ method not found in `usize`
+
+error[E0308]: mismatched types
+  --> $DIR/issue-90315.rs:62:8
+   |
+LL |     if 1..end.error_method() {
+   |        ^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found struct `std::ops::Range`
+   |
+   = note: expected type `bool`
+            found struct `std::ops::Range<{integer}>`
+
+error[E0599]: `usize` is not an iterator
+  --> $DIR/issue-90315.rs:68:18
+   |
+LL |     let _res = b.take(1)..a;
+   |                  ^^^^ `usize` is not an iterator
    |
    = note: the following trait bounds were not satisfied:
            `usize: Iterator`
            which is required by `&mut usize: Iterator`
 
-error: aborting due to previous error
+error: aborting due to 17 previous errors
 
-For more information about this error, try `rustc --explain E0599`.
+Some errors have detailed explanations: E0308, E0599.
+For more information about an error, try `rustc --explain E0308`.