PredicateObligation,
};
-use crate::autoderef::Autoderef;
use crate::infer::InferCtxt;
use crate::traits::{NormalizeExt, ObligationCtxt};
TypeSuperFoldable, TypeVisitable, TypeckResults,
};
use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span, DUMMY_SP};
+use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span, DUMMY_SP};
use rustc_target::spec::abi;
use std::ops::Deref;
trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> bool;
+ fn extract_callable_info(
+ &self,
+ hir_id: HirId,
+ param_env: ty::ParamEnv<'tcx>,
+ found: Ty<'tcx>,
+ ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)>;
+
fn suggest_add_reference_to_arg(
&self,
obligation: &PredicateObligation<'tcx>,
}
if let ty::Ref(region, base_ty, mutbl) = *real_ty.skip_binder().kind() {
- let mut autoderef = Autoderef::new(
- self,
- obligation.param_env,
- obligation.cause.body_id,
- span,
- base_ty,
- );
- if let Some(steps) = autoderef.find_map(|(ty, steps)| {
- // Re-add the `&`
- let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl });
-
- // Remapping bound vars here
- let real_trait_pred_and_ty =
- real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
- let obligation = self.mk_trait_obligation_with_new_self_ty(
- obligation.param_env,
- real_trait_pred_and_ty,
- );
- Some(steps).filter(|_| self.predicate_may_hold(&obligation))
- }) {
+ let autoderef = (self.autoderef_steps)(base_ty);
+ if let Some(steps) =
+ autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| {
+ // Re-add the `&`
+ let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl });
+
+ // Remapping bound vars here
+ let real_trait_pred_and_ty =
+ real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
+ let obligation = self.mk_trait_obligation_with_new_self_ty(
+ obligation.param_env,
+ real_trait_pred_and_ty,
+ );
+ if obligations
+ .iter()
+ .chain([&obligation])
+ .all(|obligation| self.predicate_may_hold(obligation))
+ {
+ Some(steps)
+ } else {
+ None
+ }
+ })
+ {
if steps > 0 {
// Don't care about `&mut` because `DerefMut` is used less
// often and user will not expect autoderef happens.
err: &mut Diagnostic,
trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> bool {
+ // It doesn't make sense to make this suggestion outside of typeck...
+ // (also autoderef will ICE...)
+ if self.typeck_results.is_none() {
+ return false;
+ }
+
if let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = obligation.predicate.kind().skip_binder()
&& Some(trait_pred.def_id()) == self.tcx.lang_items().sized_trait()
{
return false;
}
- // This is duplicated from `extract_callable_info` in typeck, which
- // relies on autoderef, so we can't use it here.
- let found = trait_pred.self_ty().skip_binder().peel_refs();
- let Some((def_id_or_name, output, inputs)) = (match *found.kind()
- {
- ty::FnPtr(fn_sig) => {
- Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs()))
- }
- ty::FnDef(def_id, _) => {
- let fn_sig = found.fn_sig(self.tcx);
- Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs()))
- }
- ty::Closure(def_id, substs) => {
- let fn_sig = substs.as_closure().sig();
- Some((
- DefIdOrName::DefId(def_id),
- fn_sig.output(),
- fn_sig.inputs().map_bound(|inputs| &inputs[1..]),
- ))
- }
- ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => {
- self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
- if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
- && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output()
- // args tuple will always be substs[1]
- && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
- {
- Some((
- DefIdOrName::DefId(def_id),
- pred.kind().rebind(proj.term.ty().unwrap()),
- pred.kind().rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- ty::Dynamic(data, _, ty::Dyn) => {
- data.iter().find_map(|pred| {
- if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder()
- && Some(proj.def_id) == self.tcx.lang_items().fn_once_output()
- // for existential projection, substs are shifted over by 1
- && let ty::Tuple(args) = proj.substs.type_at(0).kind()
- {
- Some((
- DefIdOrName::Name("trait object"),
- pred.rebind(proj.term.ty().unwrap()),
- pred.rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- ty::Param(_) => {
- obligation.param_env.caller_bounds().iter().find_map(|pred| {
- if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
- && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output()
- && proj.projection_ty.self_ty() == found
- // args tuple will always be substs[1]
- && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
- {
- Some((
- DefIdOrName::Name("type parameter"),
- pred.kind().rebind(proj.term.ty().unwrap()),
- pred.kind().rebind(args.as_slice()),
- ))
- } else {
- None
- }
- })
- }
- _ => None,
- }) else { return false; };
- let output = self.replace_bound_vars_with_fresh_vars(
- obligation.cause.span,
+ let self_ty = self.replace_bound_vars_with_fresh_vars(
+ DUMMY_SP,
LateBoundRegionConversionTime::FnCall,
- output,
+ trait_pred.self_ty(),
);
- let inputs = inputs.skip_binder().iter().map(|ty| {
- self.replace_bound_vars_with_fresh_vars(
- obligation.cause.span,
- LateBoundRegionConversionTime::FnCall,
- inputs.rebind(*ty),
- )
- });
+
+ let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(
+ obligation.cause.body_id,
+ obligation.param_env,
+ self_ty,
+ ) else { return false; };
// Remapping bound vars here
let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, output));
};
let args = inputs
+ .into_iter()
.map(|ty| {
if ty.is_suggestable(self.tcx, false) {
format!("/* {ty} */")
false
}
+ /// Extracts information about a callable type for diagnostics. This is a
+ /// heuristic -- it doesn't necessarily mean that a type is always callable,
+ /// because the callable type must also be well-formed to be called.
+ fn extract_callable_info(
+ &self,
+ hir_id: HirId,
+ param_env: ty::ParamEnv<'tcx>,
+ found: Ty<'tcx>,
+ ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> {
+ // Autoderef is useful here because sometimes we box callables, etc.
+ let Some((def_id_or_name, output, inputs)) = (self.autoderef_steps)(found).into_iter().find_map(|(found, _)| {
+ match *found.kind() {
+ ty::FnPtr(fn_sig) =>
+ Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())),
+ ty::FnDef(def_id, _) => {
+ let fn_sig = found.fn_sig(self.tcx);
+ Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs()))
+ }
+ ty::Closure(def_id, substs) => {
+ let fn_sig = substs.as_closure().sig();
+ Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..])))
+ }
+ ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => {
+ self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| {
+ if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
+ && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output()
+ // args tuple will always be substs[1]
+ && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
+ {
+ Some((
+ DefIdOrName::DefId(def_id),
+ pred.kind().rebind(proj.term.ty().unwrap()),
+ pred.kind().rebind(args.as_slice()),
+ ))
+ } else {
+ None
+ }
+ })
+ }
+ ty::Dynamic(data, _, ty::Dyn) => {
+ data.iter().find_map(|pred| {
+ if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder()
+ && Some(proj.def_id) == self.tcx.lang_items().fn_once_output()
+ // for existential projection, substs are shifted over by 1
+ && let ty::Tuple(args) = proj.substs.type_at(0).kind()
+ {
+ Some((
+ DefIdOrName::Name("trait object"),
+ pred.rebind(proj.term.ty().unwrap()),
+ pred.rebind(args.as_slice()),
+ ))
+ } else {
+ None
+ }
+ })
+ }
+ ty::Param(param) => {
+ let generics = self.tcx.generics_of(hir_id.owner.to_def_id());
+ let name = if generics.count() > param.index as usize
+ && let def = generics.param_at(param.index as usize, self.tcx)
+ && matches!(def.kind, ty::GenericParamDefKind::Type { .. })
+ && def.name == param.name
+ {
+ DefIdOrName::DefId(def.def_id)
+ } else {
+ DefIdOrName::Name("type parameter")
+ };
+ param_env.caller_bounds().iter().find_map(|pred| {
+ if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder()
+ && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output()
+ && proj.projection_ty.self_ty() == found
+ // args tuple will always be substs[1]
+ && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind()
+ {
+ Some((
+ name,
+ pred.kind().rebind(proj.term.ty().unwrap()),
+ pred.kind().rebind(args.as_slice()),
+ ))
+ } else {
+ None
+ }
+ })
+ }
+ _ => None,
+ }
+ }) else { return None; };
+
+ let output = self.replace_bound_vars_with_fresh_vars(
+ DUMMY_SP,
+ LateBoundRegionConversionTime::FnCall,
+ output,
+ );
+ let inputs = inputs
+ .skip_binder()
+ .iter()
+ .map(|ty| {
+ self.replace_bound_vars_with_fresh_vars(
+ DUMMY_SP,
+ LateBoundRegionConversionTime::FnCall,
+ inputs.rebind(*ty),
+ )
+ })
+ .collect();
+
+ // We don't want to register any extra obligations, which should be
+ // implied by wf, but also because that would possibly result in
+ // erroneous errors later on.
+ let InferOk { value: output, obligations: _ } =
+ self.at(&ObligationCause::dummy(), param_env).normalize(output);
+
+ if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) }
+ }
+
fn suggest_add_reference_to_arg(
&self,
obligation: &PredicateObligation<'tcx>,
err: &mut Diagnostic,
trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> bool {
- let span = obligation.cause.span;
+ let mut span = obligation.cause.span;
+ let mut trait_pred = trait_pred;
+ let mut code = obligation.cause.code();
+ while let Some((c, Some(parent_trait_pred))) = code.parent() {
+ // We want the root obligation, in order to detect properly handle
+ // `for _ in &mut &mut vec![] {}`.
+ code = c;
+ trait_pred = parent_trait_pred;
+ }
+ while span.desugaring_kind().is_some() {
+ // Remove all the hir desugaring contexts while maintaining the macro contexts.
+ span.remove_mark();
+ }
+ let mut expr_finder = super::FindExprBySpan::new(span);
+ let Some(hir::Node::Expr(body)) = self.tcx.hir().find(obligation.cause.body_id) else {
+ return false;
+ };
+ expr_finder.visit_expr(&body);
+ let mut maybe_suggest = |suggested_ty, count, suggestions| {
+ // Remapping bound vars here
+ let trait_pred_and_suggested_ty =
+ trait_pred.map_bound(|trait_pred| (trait_pred, suggested_ty));
+
+ let new_obligation = self.mk_trait_obligation_with_new_self_ty(
+ obligation.param_env,
+ trait_pred_and_suggested_ty,
+ );
- let mut suggested = false;
- if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
- let refs_number =
- snippet.chars().filter(|c| !c.is_whitespace()).take_while(|c| *c == '&').count();
- if let Some('\'') = snippet.chars().filter(|c| !c.is_whitespace()).nth(refs_number) {
- // Do not suggest removal of borrow from type arguments.
- return false;
+ if self.predicate_may_hold(&new_obligation) {
+ let msg = if count == 1 {
+ "consider removing the leading `&`-reference".to_string()
+ } else {
+ format!("consider removing {count} leading `&`-references")
+ };
+
+ err.multipart_suggestion_verbose(
+ &msg,
+ suggestions,
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
}
+ };
- // Skipping binder here, remapping below
- let mut suggested_ty = trait_pred.self_ty().skip_binder();
+ // Maybe suggest removal of borrows from types in type parameters, like in
+ // `src/test/ui/not-panic/not-panic-safe.rs`.
+ let mut count = 0;
+ let mut suggestions = vec![];
+ // Skipping binder here, remapping below
+ let mut suggested_ty = trait_pred.self_ty().skip_binder();
+ if let Some(mut hir_ty) = expr_finder.ty_result {
+ while let hir::TyKind::Ref(_, mut_ty) = &hir_ty.kind {
+ count += 1;
+ let span = hir_ty.span.until(mut_ty.ty.span);
+ suggestions.push((span, String::new()));
- for refs_remaining in 0..refs_number {
let ty::Ref(_, inner_ty, _) = suggested_ty.kind() else {
break;
};
suggested_ty = *inner_ty;
- // Remapping bound vars here
- let trait_pred_and_suggested_ty =
- trait_pred.map_bound(|trait_pred| (trait_pred, suggested_ty));
+ hir_ty = mut_ty.ty;
- let new_obligation = self.mk_trait_obligation_with_new_self_ty(
- obligation.param_env,
- trait_pred_and_suggested_ty,
- );
+ if maybe_suggest(suggested_ty, count, suggestions.clone()) {
+ return true;
+ }
+ }
+ }
- if self.predicate_may_hold(&new_obligation) {
- let sp = self
- .tcx
- .sess
- .source_map()
- .span_take_while(span, |c| c.is_whitespace() || *c == '&');
+ // Maybe suggest removal of borrows from expressions, like in `for i in &&&foo {}`.
+ let Some(mut expr) = expr_finder.result else { return false; };
+ let mut count = 0;
+ let mut suggestions = vec![];
+ // Skipping binder here, remapping below
+ let mut suggested_ty = trait_pred.self_ty().skip_binder();
+ 'outer: loop {
+ while let hir::ExprKind::AddrOf(_, _, borrowed) = expr.kind {
+ count += 1;
+ let span = if expr.span.eq_ctxt(borrowed.span) {
+ expr.span.until(borrowed.span)
+ } else {
+ expr.span.with_hi(expr.span.lo() + BytePos(1))
+ };
+ suggestions.push((span, String::new()));
- let remove_refs = refs_remaining + 1;
+ let ty::Ref(_, inner_ty, _) = suggested_ty.kind() else {
+ break 'outer;
+ };
+ suggested_ty = *inner_ty;
- let msg = if remove_refs == 1 {
- "consider removing the leading `&`-reference".to_string()
- } else {
- format!("consider removing {} leading `&`-references", remove_refs)
- };
+ expr = borrowed;
- err.span_suggestion_short(sp, &msg, "", Applicability::MachineApplicable);
- suggested = true;
- break;
+ if maybe_suggest(suggested_ty, count, suggestions.clone()) {
+ return true;
}
}
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
+ && let hir::def::Res::Local(hir_id) = path.res
+ && let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(hir_id)
+ && let Some(hir::Node::Local(local)) = self.tcx.hir().find_parent(binding.hir_id)
+ && let None = local.ty
+ && let Some(binding_expr) = local.init
+ {
+ expr = binding_expr;
+ } else {
+ break 'outer;
+ }
}
- suggested
+ false
}
fn suggest_remove_await(&self, obligation: &PredicateObligation<'tcx>, err: &mut Diagnostic) {
// Find another predicate whose self-type is equal to the expected self type,
// but whose substs don't match.
- let other_pred = std::iter::zip(&predicates.predicates, &predicates.spans)
+ let other_pred = predicates.into_iter()
.enumerate()
.find(|(other_idx, (pred, _))| match pred.kind().skip_binder() {
ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred))
// If we found one, then it's very likely the cause of the error.
if let Some((_, (_, other_pred_span))) = other_pred {
err.span_note(
- *other_pred_span,
+ other_pred_span,
"closure inferred to have a different signature due to this bound",
);
}
}
}
ObligationCauseCode::ObjectCastObligation(concrete_ty, object_ty) => {
- err.note(&format!(
- "required for the cast from `{}` to the object type `{}`",
- self.ty_to_string(concrete_ty),
- self.ty_to_string(object_ty)
- ));
+ let (concrete_ty, concrete_file) =
+ self.tcx.short_ty_string(self.resolve_vars_if_possible(concrete_ty));
+ let (object_ty, object_file) =
+ self.tcx.short_ty_string(self.resolve_vars_if_possible(object_ty));
+ err.note(&with_forced_trimmed_paths!(format!(
+ "required for the cast from `{concrete_ty}` to the object type `{object_ty}`",
+ )));
+ if let Some(file) = concrete_file {
+ err.note(&format!(
+ "the full name for the casted type has been written to '{}'",
+ file.display(),
+ ));
+ }
+ if let Some(file) = object_file {
+ err.note(&format!(
+ "the full name for the object type has been written to '{}'",
+ file.display(),
+ ));
+ }
}
ObligationCauseCode::Coercion { source: _, target } => {
err.note(&format!("required by cast to type `{}`", self.ty_to_string(target)));
// FIXME: we should do something else so that it works even on crate foreign
// auto traits.
is_auto_trait = matches!(is_auto, hir::IsAuto::Yes);
- err.span_note(ident.span, &msg)
+ err.span_note(ident.span, &msg);
}
Some(Node::Item(hir::Item {
kind: hir::ItemKind::Impl(hir::Impl { of_trait, self_ty, .. }),
spans.push(trait_ref.path.span);
}
spans.push(self_ty.span);
- err.span_note(spans, &msg)
+ let mut spans: MultiSpan = spans.into();
+ if matches!(
+ self_ty.span.ctxt().outer_expn_data().kind,
+ ExpnKind::Macro(MacroKind::Derive, _)
+ ) || matches!(
+ of_trait.as_ref().map(|t| t.path.span.ctxt().outer_expn_data().kind),
+ Some(ExpnKind::Macro(MacroKind::Derive, _))
+ ) {
+ spans.push_span_label(
+ data.span,
+ "unsatisfied trait bound introduced in this `derive` macro",
+ );
+ } else if !data.span.is_dummy() && !data.span.overlaps(self_ty.span) {
+ spans.push_span_label(
+ data.span,
+ "unsatisfied trait bound introduced here",
+ );
+ }
+ err.span_note(spans, &msg);
+ }
+ _ => {
+ err.note(&msg);
}
- _ => err.note(&msg),
};
if let Some(file) = file {