Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 the `{$func_name}` function
85 changes: 81 additions & 4 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,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;
}

Expand Down Expand Up @@ -1201,6 +1204,81 @@ 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 {
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;
}

debug!("lang_item: {:?}", lang_item);
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.lookup_probe(span, item_name, range_ty, expr, ProbeScope::AllTraits);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you emit a comment for why we're using ProbeScope::AllTraits here, I remember that you mentioned the reason for that before but I forgot that again 😁

also, i think ideally we would use probe_for_name directly here with IsSuggestion(true). Not sure what IsSuggestion does but we are emitting a suggestion here, so setting that to true seems like it should be done 😅

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems IsSuggestion only used at here:

if !self.is_suggestion.0 && !unstable_candidates.is_empty() {

Use ProbeScope::TraitsInScope can not find the pick, but ProbeScope::AllTraits will find it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems helpful to not emit unstable_name_collision_hints while looking for diagnostics, so that should still be done.

ProbeScope::TraitsInScope that feels weird and shouldn't be the case as Iterator is in scope. Is this still the case with the current code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the code to use probe_for_name.

ProbeScope::TraitsInScope means find with the scope of expr,

probe_cx.assemble_extension_candidates_for_traits_in_scope(scope_expr_id)

othewise try to find methods in all traits.

At this function, we are at a point of error handling of range type checking, since error happened so the Iterator is not in the scope? it's only my guess😁

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 +1341,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