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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Up @@ -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::{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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!(
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
}
Loading