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..357c6900a70 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,7 @@ 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_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}` + +hir_analysis_add_missing_parentheses_in_range = you must surround the range in parentheses to call its `{$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 e276c4f7d84..bfa5b68168f 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::{ @@ -271,7 +272,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - if self.suggest_constraining_numerical_ty( + if self.suggest_wrapping_range_with_parens( + 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; @@ -1202,6 +1205,89 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } + /// Suggest possible range with adding parentheses, for example: + /// when encountering `0..1.map(|i| i + 1)` suggest `(0..1).map(|i| i + 1)`. + fn suggest_wrapping_range_with_parens( + &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 { + for (_, parent) in tcx.hir().parent_iter(expr.hir_id).take(5) { + if let Node::Expr(parent_expr) = parent { + let lang_item = match parent_expr.kind { + ExprKind::Struct(ref qpath, _, _) => match **qpath { + QPath::LangItem(LangItem::Range, ..) => Some(LangItem::Range), + QPath::LangItem(LangItem::RangeTo, ..) => Some(LangItem::RangeTo), + QPath::LangItem(LangItem::RangeToInclusive, ..) => { + Some(LangItem::RangeToInclusive) + } + _ => None, + }, + ExprKind::Call(ref func, _) => match func.kind { + // `..=` desugars into `::std::ops::RangeInclusive::new(...)`. + ExprKind::Path(QPath::LangItem(LangItem::RangeInclusiveNew, ..)) => { + Some(LangItem::RangeInclusiveStruct) + } + _ => None, + }, + _ => None, + }; + + if lang_item.is_none() { + continue; + } + + 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.require_lang_item(lang_item.unwrap(), None); + let range_ty = + self.tcx.bound_type_of(range_def_id).subst(self.tcx, &[actual.into()]); + + let pick = self.probe_for_name( + span, + Mode::MethodCall, + item_name, + IsSuggestion(true), + range_ty, + expr.hir_id, + ProbeScope::AllTraits, + ); + if pick.is_ok() { + let range_span = parent_expr.span.with_hi(expr.span.hi()); + tcx.sess.emit_err(errors::MissingParentheseInRange { + span, + ty_str: ty_str.to_string(), + method_name: item_name.as_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>, @@ -1264,7 +1350,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..41f73323d9a 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -346,3 +346,29 @@ pub struct ExpectedUsedSymbol { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(hir_analysis::missing_parentheses_in_range, code = "E0689")] +pub struct MissingParentheseInRange { + #[primary_span] + #[label(hir_analysis::missing_parentheses_in_range)] + pub span: Span, + pub ty_str: String, + pub method_name: String, + + #[subdiagnostic] + pub add_missing_parentheses: Option, +} + +#[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..79cdc41959a 100644 --- a/src/test/ui/methods/issues/issue-90315.rs +++ b/src/test/ui/methods/issues/issue-90315.rs @@ -1,7 +1,76 @@ +#![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 can't call method + //~| 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 can't call method + //~| HELP surround the range in parentheses + } + + let end: usize = 10; + for i in 1..end.rev() { + //~^ ERROR can't call method + //~| HELP surround the range in parentheses + } + + for i in 1..(end + 1).rev() { + //~^ ERROR can't call method + //~| HELP surround the range in parentheses + } + + if 1..(end + 1).is_empty() { + //~^ ERROR can't call method + //~| ERROR mismatched types [E0308] + //~| HELP surround the range in parentheses + } + + if 1..(end + 1).is_sorted() { + //~^ ERROR mismatched types [E0308] + //~| ERROR can't call method + //~| HELP surround the range in parentheses + } + + let _res: i32 = 3..6.take(2).sum(); + //~^ ERROR can't call method + //~| ERROR mismatched types [E0308] + //~| HELP surround the range in parentheses + + let _sum: i32 = 3..6.sum(); + //~^ ERROR can't call method + //~| 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 can't call method + //~| HELP surround the range in parentheses + } + + let _res = ..10.contains(3); + //~^ ERROR can't call method + //~| 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 `usize` is not an iterator + + let _res: i32 = ..6.take(2).sum(); + //~^ can't call method `take` on ambiguous numeric type + //~| ERROR mismatched types [E0308] + //~| HELP you must specify a concrete type for this numeric value + // Won't suggest because `RangeTo` dest not implemented `take` } diff --git a/src/test/ui/methods/issues/issue-90315.stderr b/src/test/ui/methods/issues/issue-90315.stderr index c6a76c9e790..070cd305436 100644 --- a/src/test/ui/methods/issues/issue-90315.stderr +++ b/src/test/ui/methods/issues/issue-90315.stderr @@ -1,13 +1,201 @@ -error[E0599]: `usize` is not an iterator - --> $DIR/issue-90315.rs:3:26 +error[E0689]: can't call method `rev` on type `usize` + --> $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() { + | ^^^ can't call method `rev` on type `usize` + | +help: you must surround the range in parentheses to call its `rev` function + | +LL | for _i in (0..arr.len()).rev() { + | + + + +error[E0689]: can't call method `rev` on type `{integer}` + --> $DIR/issue-90315.rs:12:20 + | +LL | for i in 1..11.rev() { + | ^^^ can't call method `rev` on type `{integer}` + | +help: you must surround the range in parentheses to call its `rev` function + | +LL | for i in (1..11).rev() { + | + + + +error[E0689]: can't call method `rev` on type `usize` + --> $DIR/issue-90315.rs:18:21 + | +LL | for i in 1..end.rev() { + | ^^^ can't call method `rev` on type `usize` + | +help: you must surround the range in parentheses to call its `rev` function + | +LL | for i in (1..end).rev() { + | + + + +error[E0689]: can't call method `rev` on type `usize` + --> $DIR/issue-90315.rs:23:27 + | +LL | for i in 1..(end + 1).rev() { + | ^^^ can't call method `rev` on type `usize` + | +help: you must surround the range in parentheses to call its `rev` function + | +LL | for i in (1..(end + 1)).rev() { + | + + + +error[E0689]: can't call method `is_empty` on type `usize` + --> $DIR/issue-90315.rs:28:21 + | +LL | if 1..(end + 1).is_empty() { + | ^^^^^^^^ can't call method `is_empty` on type `usize` + | +help: you must surround the range in parentheses to call its `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[E0689]: can't call method `is_sorted` on type `usize` + --> $DIR/issue-90315.rs:34:21 + | +LL | if 1..(end + 1).is_sorted() { + | ^^^^^^^^^ can't call method `is_sorted` on type `usize` + | +help: you must surround the range in parentheses to call its `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[E0689]: can't call method `take` on type `{integer}` + --> $DIR/issue-90315.rs:40:26 + | +LL | let _res: i32 = 3..6.take(2).sum(); + | ^^^^ can't call method `take` on type `{integer}` + | +help: you must surround the range in parentheses to call its `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[E0689]: can't call method `sum` on type `{integer}` + --> $DIR/issue-90315.rs:45:26 + | +LL | let _sum: i32 = 3..6.sum(); + | ^^^ can't call method `sum` on type `{integer}` + | +help: you must surround the range in parentheses to call its `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[E0689]: can't call method `rev` on type `usize` + --> $DIR/issue-90315.rs:53:21 + | +LL | for _a in a..=b.rev() { + | ^^^ can't call method `rev` on type `usize` + | +help: you must surround the range in parentheses to call its `rev` function + | +LL | for _a in (a..=b).rev() { + | + + + +error[E0689]: can't call method `contains` on type `{integer}` + --> $DIR/issue-90315.rs:58:21 + | +LL | let _res = ..10.contains(3); + | ^^^^^^^^ can't call method `contains` on type `{integer}` + | +help: you must surround the range in parentheses to call its `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[E0689]: can't call method `take` on ambiguous numeric type `{integer}` + --> $DIR/issue-90315.rs:71:25 + | +LL | let _res: i32 = ..6.take(2).sum(); + | ^^^^ + | +help: you must specify a concrete type for this numeric value, like `i32` + | +LL | let _res: i32 = ..6_i32.take(2).sum(); + | ~~~~~ -For more information about this error, try `rustc --explain E0599`. +error[E0308]: mismatched types + --> $DIR/issue-90315.rs:71:21 + | +LL | let _res: i32 = ..6.take(2).sum(); + | --- ^^^^^^^^^^^^^^^^^ expected `i32`, found struct `RangeTo` + | | + | expected due to this + | + = note: expected type `i32` + found struct `RangeTo<_>` + +error: aborting due to 19 previous errors + +Some errors have detailed explanations: E0308, E0599, E0689. +For more information about an error, try `rustc --explain E0308`.