Skip to content

Suggest parentheses for possible range method calling #102454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 17, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl
Original file line number Diff line number Diff line change
@@ -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
89 changes: 87 additions & 2 deletions compiler/rustc_hir_analysis/src/check/method/suggest.rs
Original file line number Diff line number Diff line change
@@ -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;
@@ -1201,6 +1204,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>,
@@ -1263,7 +1349,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!(
26 changes: 26 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
@@ -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<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,
}
79 changes: 74 additions & 5 deletions src/test/ui/methods/issues/issue-90315.rs
Original file line number Diff line number Diff line change
@@ -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`
}
198 changes: 193 additions & 5 deletions src/test/ui/methods/issues/issue-90315.stderr
Original file line number Diff line number Diff line change
@@ -1,13 +1,201 @@
error[E0689]: can't call method `rev` on type `usize`
--> $DIR/issue-90315.rs:4:28
|
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:3:26
--> $DIR/issue-90315.rs:68:18
|
LL | for _i in 0..arr.len().rev() {
| ^^^ `usize` is not an iterator
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();
| ~~~~~

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

For more information about this error, try `rustc --explain E0599`.
Some errors have detailed explanations: E0308, E0599, E0689.
For more information about an error, try `rustc --explain E0308`.