diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 8b0e7794f92ac..da54ad06e048b 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1886,6 +1886,15 @@ impl<'tcx> Ty<'tcx> { } } + #[inline] + pub fn is_array_slice(self) -> bool { + match self.kind() { + Slice(_) => true, + RawPtr(TypeAndMut { ty, .. }) | Ref(_, ty, _) => matches!(ty.kind(), Slice(_)), + _ => false, + } + } + #[inline] pub fn is_array(self) -> bool { matches!(self.kind(), Array(..)) diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs index 6dfbdace8e2a0..59c88b6603c22 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs @@ -11,7 +11,7 @@ use std::iter; use super::InferCtxtPrivExt; -crate trait InferCtxtExt<'tcx> { +pub trait InferCtxtExt<'tcx> { /*private*/ fn impl_similar_to( &self, @@ -204,6 +204,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { flags.push((sym::_Self, Some("{integral}".to_owned()))); } + if self_ty.is_array_slice() { + flags.push((sym::_Self, Some("&[]".to_owned()))); + } + if let ty::Array(aty, len) = self_ty.kind() { flags.push((sym::_Self, Some("[]".to_owned()))); flags.push((sym::_Self, Some(format!("[{}]", aty)))); diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs index 0ae2dfa180b9e..504e1ce8c9f51 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_typeck/src/check/method/suggest.rs @@ -19,9 +19,10 @@ use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable}; use rustc_span::lev_distance; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{source_map, FileName, MultiSpan, Span}; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::on_unimplemented::InferCtxtExt as _; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{ - FulfillmentError, Obligation, ObligationCause, ObligationCauseCode, + FulfillmentError, Obligation, ObligationCause, ObligationCauseCode, OnUnimplementedNote, }; use std::cmp::Ordering; @@ -483,150 +484,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - let mut label_span_not_found = || { - if unsatisfied_predicates.is_empty() { - err.span_label(span, format!("{item_kind} not found in `{ty_str}`")); - let is_string_or_ref_str = match actual.kind() { - ty::Ref(_, ty, _) => { - ty.is_str() - || matches!( - ty.kind(), - ty::Adt(adt, _) if self.tcx.is_diagnostic_item(sym::String, adt.did) - ) - } - ty::Adt(adt, _) => self.tcx.is_diagnostic_item(sym::String, adt.did), - _ => false, - }; - if is_string_or_ref_str && item_name.name == sym::iter { - err.span_suggestion_verbose( - item_name.span, - "because of the in-memory representation of `&str`, to obtain \ - an `Iterator` over each of its codepoint use method `chars`", - String::from("chars"), - Applicability::MachineApplicable, - ); - } - if let ty::Adt(adt, _) = rcvr_ty.kind() { - let mut inherent_impls_candidate = self - .tcx - .inherent_impls(adt.did) - .iter() - .copied() - .filter(|def_id| { - if let Some(assoc) = self.associated_value(*def_id, item_name) { - // Check for both mode is the same so we avoid suggesting - // incorrect associated item. - match (mode, assoc.fn_has_self_parameter, source) { - (Mode::MethodCall, true, SelfSource::MethodCall(_)) => { - // We check that the suggest type is actually - // different from the received one - // So we avoid suggestion method with Box - // for instance - self.tcx.at(span).type_of(*def_id) != actual - && self.tcx.at(span).type_of(*def_id) != rcvr_ty - } - (Mode::Path, false, _) => true, - _ => false, - } - } else { - false - } - }) - .collect::>(); - if !inherent_impls_candidate.is_empty() { - inherent_impls_candidate.sort(); - inherent_impls_candidate.dedup(); - - // number of type to shows at most. - let limit = if inherent_impls_candidate.len() == 5 { 5 } else { 4 }; - let type_candidates = inherent_impls_candidate - .iter() - .take(limit) - .map(|impl_item| { - format!("- `{}`", self.tcx.at(span).type_of(*impl_item)) - }) - .collect::>() - .join("\n"); - let additional_types = if inherent_impls_candidate.len() > limit { - format!( - "\nand {} more types", - inherent_impls_candidate.len() - limit - ) - } else { - "".to_string() - }; - err.note(&format!( - "the {item_kind} was found for\n{}{}", - type_candidates, additional_types - )); - } - } - } else { - err.span_label(span, format!("{item_kind} cannot be called on `{ty_str}` due to unsatisfied trait bounds")); - } - }; - - // If the method name is the name of a field with a function or closure type, - // give a helping note that it has to be called as `(x.f)(...)`. - if let SelfSource::MethodCall(expr) = source { - let field_receiver = - self.autoderef(span, rcvr_ty).find_map(|(ty, _)| match ty.kind() { - ty::Adt(def, substs) if !def.is_enum() => { - let variant = &def.non_enum_variant(); - self.tcx.find_field_index(item_name, variant).map(|index| { - let field = &variant.fields[index]; - let field_ty = field.ty(tcx, substs); - (field, field_ty) - }) - } - _ => None, - }); - - if let Some((field, field_ty)) = field_receiver { - let scope = self.tcx.parent_module(self.body_id).to_def_id(); - let is_accessible = field.vis.is_accessible_from(scope, self.tcx); - - if is_accessible { - if self.is_fn_ty(field_ty, span) { - let expr_span = expr.span.to(item_name.span); - err.multipart_suggestion( - &format!( - "to call the function stored in `{}`, \ - surround the field access with parentheses", - item_name, - ), - vec![ - (expr_span.shrink_to_lo(), '('.to_string()), - (expr_span.shrink_to_hi(), ')'.to_string()), - ], - Applicability::MachineApplicable, - ); - } else { - let call_expr = self - .tcx - .hir() - .expect_expr(self.tcx.hir().get_parent_node(expr.hir_id)); - - if let Some(span) = call_expr.span.trim_start(item_name.span) { - err.span_suggestion( - span, - "remove the arguments", - String::new(), - Applicability::MaybeIncorrect, - ); - } - } - } - - let field_kind = if is_accessible { "field" } else { "private field" }; - err.span_label(item_name.span, format!("{}, not a method", field_kind)); - } else if lev_candidate.is_none() && static_sources.is_empty() { - label_span_not_found(); - } - } else { - label_span_not_found(); - } - if self.is_fn_ty(rcvr_ty, span) { fn report_function(err: &mut Diagnostic, name: T) { err.note( @@ -645,12 +502,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + let mut custom_span_label = false; + if !static_sources.is_empty() { err.note( "found the following associated functions; to be used as methods, \ functions must have a `self` parameter", ); err.span_label(span, "this is an associated function, not a method"); + custom_span_label = true; } if static_sources.len() == 1 { let ty_str = if let Some(CandidateSource::ImplSource(impl_did)) = @@ -686,6 +546,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { report_candidates(span, &mut err, static_sources, sugg_span); } + let mut bound_spans = vec![]; let mut restrict_type_params = false; let mut unsatisfied_bounds = false; if item_name.name == sym::count && self.is_slice_ty(actual, span) { @@ -709,7 +570,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.sess.source_map().guess_head_span(self.tcx.def_span(def_id)) }; let mut type_params = FxHashMap::default(); - let mut bound_spans = vec![]; + + // Pick out the list of unimplemented traits on the receiver. + // This is used for custom error messages with the `#[rustc_on_unimplemented]` attribute. + let mut unimplemented_traits = FxHashMap::default(); + for (predicate, _parent_pred, cause) in &unsatisfied_predicates { + if let (ty::PredicateKind::Trait(p), Some(cause)) = + (predicate.kind().skip_binder(), cause.as_ref()) + { + if p.trait_ref.self_ty() != rcvr_ty { + // This is necessary, not just to keep the errors clean, but also + // because our derived obligations can wind up with a trait ref that + // requires a different param_env to be correctly compared. + continue; + } + unimplemented_traits.entry(p.trait_ref.def_id).or_insert(( + predicate.kind().rebind(p.trait_ref), + Obligation { + cause: cause.clone(), + param_env: self.param_env, + predicate: predicate.clone(), + recursion_depth: 0, + }, + )); + } + } let mut collect_type_param_suggestions = |self_ty: Ty<'tcx>, parent_pred: ty::Predicate<'tcx>, obligation: &str| { @@ -945,11 +830,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { bound_list.sort_by(|(_, a), (_, b)| a.cmp(b)); // Sort alphabetically. bound_list.dedup_by(|(_, a), (_, b)| a == b); // #35677 bound_list.sort_by_key(|(pos, _)| *pos); // Keep the original predicate order. - bound_spans.sort(); - bound_spans.dedup(); - for (span, msg) in bound_spans.into_iter() { - err.span_label(span, &msg); - } + if !bound_list.is_empty() || !skip_list.is_empty() { let bound_list = bound_list .into_iter() @@ -957,9 +838,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .collect::>() .join("\n"); let actual_prefix = actual.prefix_string(self.tcx); - err.set_primary_message(&format!( + info!("unimplemented_traits.len() == {}", unimplemented_traits.len()); + let (primary_message, label) = if unimplemented_traits.len() == 1 { + unimplemented_traits + .into_iter() + .next() + .map(|(_, (trait_ref, obligation))| { + if trait_ref.self_ty().references_error() + || actual.references_error() + { + // Avoid crashing. + return (None, None); + } + let OnUnimplementedNote { message, label, .. } = + self.infcx.on_unimplemented_note(trait_ref, &obligation); + (message, label) + }) + .unwrap_or((None, None)) + } else { + (None, None) + }; + let primary_message = primary_message.unwrap_or_else(|| format!( "the {item_kind} `{item_name}` exists for {actual_prefix} `{ty_str}`, but its trait bounds were not satisfied" )); + err.set_primary_message(&primary_message); + if let Some(label) = label { + custom_span_label = true; + err.span_label(span, label); + } if !bound_list.is_empty() { err.note(&format!( "the following trait bounds were not satisfied:\n{bound_list}" @@ -971,6 +877,156 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + let mut label_span_not_found = || { + if unsatisfied_predicates.is_empty() { + err.span_label(span, format!("{item_kind} not found in `{ty_str}`")); + let is_string_or_ref_str = match actual.kind() { + ty::Ref(_, ty, _) => { + ty.is_str() + || matches!( + ty.kind(), + ty::Adt(adt, _) if self.tcx.is_diagnostic_item(sym::String, adt.did) + ) + } + ty::Adt(adt, _) => self.tcx.is_diagnostic_item(sym::String, adt.did), + _ => false, + }; + if is_string_or_ref_str && item_name.name == sym::iter { + err.span_suggestion_verbose( + item_name.span, + "because of the in-memory representation of `&str`, to obtain \ + an `Iterator` over each of its codepoint use method `chars`", + String::from("chars"), + Applicability::MachineApplicable, + ); + } + if let ty::Adt(adt, _) = rcvr_ty.kind() { + let mut inherent_impls_candidate = self + .tcx + .inherent_impls(adt.did) + .iter() + .copied() + .filter(|def_id| { + if let Some(assoc) = self.associated_value(*def_id, item_name) { + // Check for both mode is the same so we avoid suggesting + // incorrect associated item. + match (mode, assoc.fn_has_self_parameter, source) { + (Mode::MethodCall, true, SelfSource::MethodCall(_)) => { + // We check that the suggest type is actually + // different from the received one + // So we avoid suggestion method with Box + // for instance + self.tcx.at(span).type_of(*def_id) != actual + && self.tcx.at(span).type_of(*def_id) != rcvr_ty + } + (Mode::Path, false, _) => true, + _ => false, + } + } else { + false + } + }) + .collect::>(); + if !inherent_impls_candidate.is_empty() { + inherent_impls_candidate.sort(); + inherent_impls_candidate.dedup(); + + // number of type to shows at most. + let limit = if inherent_impls_candidate.len() == 5 { 5 } else { 4 }; + let type_candidates = inherent_impls_candidate + .iter() + .take(limit) + .map(|impl_item| { + format!("- `{}`", self.tcx.at(span).type_of(*impl_item)) + }) + .collect::>() + .join("\n"); + let additional_types = if inherent_impls_candidate.len() > limit { + format!( + "\nand {} more types", + inherent_impls_candidate.len() - limit + ) + } else { + "".to_string() + }; + err.note(&format!( + "the {item_kind} was found for\n{}{}", + type_candidates, additional_types + )); + } + } + } else { + err.span_label(span, format!("{item_kind} cannot be called on `{ty_str}` due to unsatisfied trait bounds")); + } + }; + + // If the method name is the name of a field with a function or closure type, + // give a helping note that it has to be called as `(x.f)(...)`. + if let SelfSource::MethodCall(expr) = source { + let field_receiver = + self.autoderef(span, rcvr_ty).find_map(|(ty, _)| match ty.kind() { + ty::Adt(def, substs) if !def.is_enum() => { + let variant = &def.non_enum_variant(); + self.tcx.find_field_index(item_name, variant).map(|index| { + let field = &variant.fields[index]; + let field_ty = field.ty(tcx, substs); + (field, field_ty) + }) + } + _ => None, + }); + + if let Some((field, field_ty)) = field_receiver { + let scope = self.tcx.parent_module(self.body_id).to_def_id(); + let is_accessible = field.vis.is_accessible_from(scope, self.tcx); + + if is_accessible { + if self.is_fn_ty(field_ty, span) { + let expr_span = expr.span.to(item_name.span); + err.multipart_suggestion( + &format!( + "to call the function stored in `{}`, \ + surround the field access with parentheses", + item_name, + ), + vec![ + (expr_span.shrink_to_lo(), '('.to_string()), + (expr_span.shrink_to_hi(), ')'.to_string()), + ], + Applicability::MachineApplicable, + ); + } else { + let call_expr = self + .tcx + .hir() + .expect_expr(self.tcx.hir().get_parent_node(expr.hir_id)); + + if let Some(span) = call_expr.span.trim_start(item_name.span) { + err.span_suggestion( + span, + "remove the arguments", + String::new(), + Applicability::MaybeIncorrect, + ); + } + } + } + + let field_kind = if is_accessible { "field" } else { "private field" }; + err.span_label(item_name.span, format!("{}, not a method", field_kind)); + } else if lev_candidate.is_none() && !custom_span_label { + label_span_not_found(); + } + } else if !custom_span_label { + label_span_not_found(); + } + + bound_spans.sort(); + bound_spans.dedup(); + for (span, msg) in bound_spans.into_iter() { + err.span_label(span, &msg); + } + if actual.is_numeric() && actual.is_fresh() || restrict_type_params { } else { self.suggest_traits_to_import( diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index b38df1c2d0228..c35d0784dd5a4 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -34,6 +34,11 @@ fn _assert_is_object_safe(_: &dyn Iterator) {} note = "`..=end` is a `RangeToInclusive`, which cannot be iterated on; you might have meant \ to have a bounded `RangeInclusive`: `0..=end`" ), + on( + _Self = "[]", + label = "`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`" + ), + on(_Self = "&[]", label = "`{Self}` is not an iterator; try calling `.iter()`"), on( _Self = "&str", label = "`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`" diff --git a/src/test/ui/issues/issue-21596.stderr b/src/test/ui/issues/issue-21596.stderr index b0524f056ef7f..a336d1b0ed55d 100644 --- a/src/test/ui/issues/issue-21596.stderr +++ b/src/test/ui/issues/issue-21596.stderr @@ -1,8 +1,8 @@ -error[E0599]: the method `to_string` exists for raw pointer `*const u8`, but its trait bounds were not satisfied +error[E0599]: `*const u8` doesn't implement `std::fmt::Display` --> $DIR/issue-21596.rs:4:22 | LL | println!("{}", z.to_string()); - | ^^^^^^^^^ method cannot be called on `*const u8` due to unsatisfied trait bounds + | ^^^^^^^^^ `*const u8` cannot be formatted with the default formatter | = note: try using `<*const T>::as_ref()` to get a reference to the type behind the pointer: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_ref = note: using `<*const T>::as_ref()` on a pointer which is unaligned or points to invalid or uninitialized memory is undefined behavior diff --git a/src/test/ui/methods/issues/issue-94581.rs b/src/test/ui/methods/issues/issue-94581.rs new file mode 100644 index 0000000000000..df393e91db084 --- /dev/null +++ b/src/test/ui/methods/issues/issue-94581.rs @@ -0,0 +1,7 @@ +fn get_slice() -> &'static [i32] { + &[1, 2, 3, 4] +} + +fn main() { + let sqsum = get_slice().map(|i| i * i).sum(); //~ ERROR [E0599] +} diff --git a/src/test/ui/methods/issues/issue-94581.stderr b/src/test/ui/methods/issues/issue-94581.stderr new file mode 100644 index 0000000000000..d6be29cf582c3 --- /dev/null +++ b/src/test/ui/methods/issues/issue-94581.stderr @@ -0,0 +1,15 @@ +error[E0599]: `&'static [i32]` is not an iterator + --> $DIR/issue-94581.rs:6:29 + | +LL | let sqsum = get_slice().map(|i| i * i).sum(); + | ^^^ `&'static [i32]` is not an iterator; try calling `.iter()` + | + = note: the following trait bounds were not satisfied: + `&'static [i32]: Iterator` + which is required by `&mut &'static [i32]: Iterator` + `[i32]: Iterator` + which is required by `&mut [i32]: Iterator` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`. diff --git a/src/test/ui/methods/method-call-err-msg.rs b/src/test/ui/methods/method-call-err-msg.rs index 86d00ca376026..d53ef445afc1b 100644 --- a/src/test/ui/methods/method-call-err-msg.rs +++ b/src/test/ui/methods/method-call-err-msg.rs @@ -16,7 +16,7 @@ fn main() { let y = Foo; y.zero() - .take() //~ ERROR the method + .take() //~ ERROR not an iterator .one(0); y.three::(); //~ ERROR this function takes 3 arguments but 0 arguments were supplied } diff --git a/src/test/ui/methods/method-call-err-msg.stderr b/src/test/ui/methods/method-call-err-msg.stderr index c1183e053eb43..c410e076dde26 100644 --- a/src/test/ui/methods/method-call-err-msg.stderr +++ b/src/test/ui/methods/method-call-err-msg.stderr @@ -40,7 +40,7 @@ note: associated function defined here LL | fn two(self, _: isize, _: isize) -> Foo { self } | ^^^ ---- -------- -------- -error[E0599]: the method `take` exists for struct `Foo`, but its trait bounds were not satisfied +error[E0599]: `Foo` is not an iterator --> $DIR/method-call-err-msg.rs:19:7 | LL | pub struct Foo; @@ -50,7 +50,7 @@ LL | pub struct Foo; | doesn't satisfy `Foo: Iterator` ... LL | .take() - | ^^^^ method cannot be called on `Foo` due to unsatisfied trait bounds + | ^^^^ `Foo` is not an iterator | = note: the following trait bounds were not satisfied: `Foo: Iterator` diff --git a/src/test/ui/mismatched_types/issue-36053-2.rs b/src/test/ui/mismatched_types/issue-36053-2.rs index 17d2292baaf68..0252ba2eb0460 100644 --- a/src/test/ui/mismatched_types/issue-36053-2.rs +++ b/src/test/ui/mismatched_types/issue-36053-2.rs @@ -5,6 +5,6 @@ use std::iter::once; fn main() { once::<&str>("str").fuse().filter(|a: &str| true).count(); - //~^ ERROR the method + //~^ ERROR not an iterator //~| ERROR type mismatch in closure arguments } diff --git a/src/test/ui/mismatched_types/issue-36053-2.stderr b/src/test/ui/mismatched_types/issue-36053-2.stderr index a8bcdf5efe91e..b1010171eb235 100644 --- a/src/test/ui/mismatched_types/issue-36053-2.stderr +++ b/src/test/ui/mismatched_types/issue-36053-2.stderr @@ -12,11 +12,11 @@ note: required by a bound in `filter` LL | P: FnMut(&Self::Item) -> bool, | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `filter` -error[E0599]: the method `count` exists for struct `Filter>, [closure@$DIR/issue-36053-2.rs:7:39: 7:53]>`, but its trait bounds were not satisfied +error[E0599]: `Filter>, [closure@$DIR/issue-36053-2.rs:7:39: 7:53]>` is not an iterator --> $DIR/issue-36053-2.rs:7:55 | LL | once::<&str>("str").fuse().filter(|a: &str| true).count(); - | -------------- ^^^^^ method cannot be called on `Filter>, [closure@$DIR/issue-36053-2.rs:7:39: 7:53]>` due to unsatisfied trait bounds + | -------------- ^^^^^ `Filter>, [closure@$DIR/issue-36053-2.rs:7:39: 7:53]>` is not an iterator | | | doesn't satisfy `<_ as FnOnce<(&&str,)>>::Output = bool` | doesn't satisfy `_: FnMut<(&&str,)>`