Auto merge of #100654 - compiler-errors:rework-point-at-arg, r=estebank
Rework "point at arg" suggestions to be more accurate Fixes #100560 Introduce a new set of `ObligationCauseCode`s which have additional bookeeping for what expression caused the obligation, and which predicate caused the obligation. This allows us to look at the _unsubstituted_ signature to find out which parameter or generic type argument caused an obligaton to fail. This means that (in most cases) we significantly improve the likelihood of pointing out the right argument that causes a fulfillment error. Also, since this logic isn't happening in just the `select_where_possible_and_mutate_fulfillment()` calls in the argument checking code, but instead during all trait selection in `FnCtxt`, we are also able to point out the correct argument even if inference means that we don't know whether an obligation has failed until well after a call expression has been checked. r? `@ghost`
This commit is contained in:
commit
0b71ffca18
161 changed files with 1379 additions and 790 deletions
|
@ -1463,7 +1463,7 @@ pub fn check_type_bounds<'tcx>(
|
|||
);
|
||||
let mk_cause = |span: Span| {
|
||||
let code = if span.is_dummy() {
|
||||
traits::MiscObligation
|
||||
traits::ItemObligation(trait_ty.def_id)
|
||||
} else {
|
||||
traits::BindingObligation(trait_ty.def_id, span)
|
||||
};
|
||||
|
|
|
@ -607,9 +607,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
pub(in super::super) fn select_all_obligations_or_error(&self) {
|
||||
let errors = self.fulfillment_cx.borrow_mut().select_all_or_error(&self);
|
||||
let mut errors = self.fulfillment_cx.borrow_mut().select_all_or_error(&self);
|
||||
|
||||
if !errors.is_empty() {
|
||||
self.adjust_fulfillment_errors_for_expr_obligation(&mut errors);
|
||||
self.report_fulfillment_errors(&errors, self.inh.body_id, false);
|
||||
}
|
||||
}
|
||||
|
@ -623,6 +624,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
let mut result = self.fulfillment_cx.borrow_mut().select_where_possible(self);
|
||||
if !result.is_empty() {
|
||||
mutate_fulfillment_errors(&mut result);
|
||||
self.adjust_fulfillment_errors_for_expr_obligation(&mut result);
|
||||
self.report_fulfillment_errors(&result, self.inh.body_id, fallback_has_occurred);
|
||||
}
|
||||
}
|
||||
|
@ -820,23 +822,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
let ty = item_ty.subst(self.tcx, substs);
|
||||
|
||||
self.write_resolution(hir_id, Ok((def_kind, def_id)));
|
||||
self.add_required_obligations_with_code(
|
||||
span,
|
||||
def_id,
|
||||
&substs,
|
||||
match lang_item {
|
||||
hir::LangItem::IntoFutureIntoFuture => {
|
||||
ObligationCauseCode::AwaitableExpr(expr_hir_id)
|
||||
}
|
||||
hir::LangItem::IteratorNext | hir::LangItem::IntoIterIntoIter => {
|
||||
ObligationCauseCode::ForLoopIterator
|
||||
}
|
||||
hir::LangItem::TryTraitFromOutput
|
||||
| hir::LangItem::TryTraitFromResidual
|
||||
| hir::LangItem::TryTraitBranch => ObligationCauseCode::QuestionMark,
|
||||
_ => traits::ItemObligation(def_id),
|
||||
},
|
||||
);
|
||||
|
||||
let code = match lang_item {
|
||||
hir::LangItem::IntoFutureIntoFuture => {
|
||||
Some(ObligationCauseCode::AwaitableExpr(expr_hir_id))
|
||||
}
|
||||
hir::LangItem::IteratorNext | hir::LangItem::IntoIterIntoIter => {
|
||||
Some(ObligationCauseCode::ForLoopIterator)
|
||||
}
|
||||
hir::LangItem::TryTraitFromOutput
|
||||
| hir::LangItem::TryTraitFromResidual
|
||||
| hir::LangItem::TryTraitBranch => Some(ObligationCauseCode::QuestionMark),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(code) = code {
|
||||
self.add_required_obligations_with_code(span, def_id, substs, move |_, _| code.clone());
|
||||
} else {
|
||||
self.add_required_obligations_for_hir(span, def_id, substs, hir_id);
|
||||
}
|
||||
|
||||
(Res::Def(def_kind, def_id), ty)
|
||||
}
|
||||
|
||||
|
@ -1348,7 +1352,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// First, store the "user substs" for later.
|
||||
self.write_user_type_annotation_from_substs(hir_id, def_id, substs, user_self_ty);
|
||||
|
||||
self.add_required_obligations(span, def_id, &substs);
|
||||
self.add_required_obligations_for_hir(span, def_id, &substs, hir_id);
|
||||
|
||||
// Substitute the values for the type parameters into the type of
|
||||
// the referenced item.
|
||||
|
@ -1385,32 +1389,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
|
||||
/// Add all the obligations that are required, substituting and normalized appropriately.
|
||||
pub(crate) fn add_required_obligations(
|
||||
pub(crate) fn add_required_obligations_for_hir(
|
||||
&self,
|
||||
span: Span,
|
||||
def_id: DefId,
|
||||
substs: &SubstsRef<'tcx>,
|
||||
substs: SubstsRef<'tcx>,
|
||||
hir_id: hir::HirId,
|
||||
) {
|
||||
self.add_required_obligations_with_code(
|
||||
span,
|
||||
def_id,
|
||||
substs,
|
||||
traits::ItemObligation(def_id),
|
||||
)
|
||||
self.add_required_obligations_with_code(span, def_id, substs, |idx, span| {
|
||||
if span.is_dummy() {
|
||||
ObligationCauseCode::ExprItemObligation(def_id, hir_id, idx)
|
||||
} else {
|
||||
ObligationCauseCode::ExprBindingObligation(def_id, span, hir_id, idx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, span, def_id, substs))]
|
||||
#[tracing::instrument(level = "debug", skip(self, code, span, def_id, substs))]
|
||||
fn add_required_obligations_with_code(
|
||||
&self,
|
||||
span: Span,
|
||||
def_id: DefId,
|
||||
substs: &SubstsRef<'tcx>,
|
||||
code: ObligationCauseCode<'tcx>,
|
||||
substs: SubstsRef<'tcx>,
|
||||
code: impl Fn(usize, Span) -> ObligationCauseCode<'tcx>,
|
||||
) {
|
||||
let (bounds, _) = self.instantiate_bounds(span, def_id, &substs);
|
||||
|
||||
for obligation in traits::predicates_for_generics(
|
||||
traits::ObligationCause::new(span, self.body_id, code),
|
||||
|idx, predicate_span| {
|
||||
traits::ObligationCause::new(span, self.body_id, code(idx, predicate_span))
|
||||
},
|
||||
self.param_env,
|
||||
bounds,
|
||||
) {
|
||||
|
|
|
@ -15,6 +15,7 @@ use crate::check::{
|
|||
use crate::structured_errors::StructuredDiagnostic;
|
||||
|
||||
use rustc_ast as ast;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::{pluralize, Applicability, Diagnostic, DiagnosticId, MultiSpan};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorOf, DefKind, Res};
|
||||
|
@ -27,13 +28,14 @@ use rustc_infer::infer::InferOk;
|
|||
use rustc_infer::infer::TypeTrace;
|
||||
use rustc_middle::ty::adjustment::AllowTwoPhase;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty};
|
||||
use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty, TypeSuperVisitable, TypeVisitor};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{self, Span};
|
||||
use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext};
|
||||
|
||||
use std::iter;
|
||||
use std::ops::ControlFlow;
|
||||
use std::slice;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
|
@ -247,17 +249,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// Cause selection errors caused by resolving a single argument to point at the
|
||||
// argument and not the call. This lets us customize the span pointed to in the
|
||||
// fulfillment error to be more accurate.
|
||||
let coerced_ty =
|
||||
self.resolve_vars_with_obligations_and_mutate_fulfillment(coerced_ty, |errors| {
|
||||
self.point_at_type_arg_instead_of_call_if_possible(errors, call_expr);
|
||||
self.point_at_arg_instead_of_call_if_possible(
|
||||
errors,
|
||||
call_expr,
|
||||
call_span,
|
||||
provided_args,
|
||||
&expected_input_tys,
|
||||
);
|
||||
});
|
||||
let coerced_ty = self.resolve_vars_with_obligations(coerced_ty);
|
||||
|
||||
let coerce_error = self
|
||||
.try_coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None)
|
||||
|
@ -312,16 +304,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// an "opportunistic" trait resolution of any trait bounds on
|
||||
// the call. This helps coercions.
|
||||
if check_closures {
|
||||
self.select_obligations_where_possible(false, |errors| {
|
||||
self.point_at_type_arg_instead_of_call_if_possible(errors, call_expr);
|
||||
self.point_at_arg_instead_of_call_if_possible(
|
||||
errors,
|
||||
call_expr,
|
||||
call_span,
|
||||
&provided_args,
|
||||
&expected_input_tys,
|
||||
);
|
||||
})
|
||||
self.select_obligations_where_possible(false, |_| {})
|
||||
}
|
||||
|
||||
// Check each argument, to satisfy the input it was provided for
|
||||
|
@ -674,7 +657,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
Applicability::MachineApplicable,
|
||||
);
|
||||
};
|
||||
self.label_fn_like(&mut err, fn_def_id, callee_ty, Some(mismatch_idx), is_method);
|
||||
self.label_fn_like(
|
||||
&mut err,
|
||||
fn_def_id,
|
||||
callee_ty,
|
||||
Some(mismatch_idx),
|
||||
is_method,
|
||||
);
|
||||
err.emit();
|
||||
return;
|
||||
}
|
||||
|
@ -1081,8 +1070,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
let suggestion_text = if let Some(provided_idx) = provided_idx
|
||||
&& let (_, provided_span) = provided_arg_tys[*provided_idx]
|
||||
&& let Ok(arg_text) =
|
||||
source_map.span_to_snippet(provided_span)
|
||||
&& let Ok(arg_text) = source_map.span_to_snippet(provided_span)
|
||||
{
|
||||
arg_text
|
||||
} else {
|
||||
|
@ -1183,7 +1171,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
self.write_user_type_annotation_from_substs(hir_id, did, substs, None);
|
||||
|
||||
// Check bounds on type arguments used in the path.
|
||||
self.add_required_obligations(path_span, did, substs);
|
||||
self.add_required_obligations_for_hir(path_span, did, substs, hir_id);
|
||||
|
||||
Some((variant, ty))
|
||||
} else {
|
||||
|
@ -1620,183 +1608,406 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a vec of evaluated `FulfillmentError`s and an `fn` call argument expressions, we walk
|
||||
/// the checked and coerced types for each argument to see if any of the `FulfillmentError`s
|
||||
/// reference a type argument. The reason to walk also the checked type is that the coerced type
|
||||
/// can be not easily comparable with predicate type (because of coercion). If the types match
|
||||
/// for either checked or coerced type, and there's only *one* argument that does, we point at
|
||||
/// the corresponding argument's expression span instead of the `fn` call path span.
|
||||
fn point_at_arg_instead_of_call_if_possible(
|
||||
/// Given a vector of fulfillment errors, try to adjust the spans of the
|
||||
/// errors to more accurately point at the cause of the failure.
|
||||
///
|
||||
/// This applies to calls, methods, and struct expressions. This will also
|
||||
/// try to deduplicate errors that are due to the same cause but might
|
||||
/// have been created with different [`ObligationCause`][traits::ObligationCause]s.
|
||||
pub(super) fn adjust_fulfillment_errors_for_expr_obligation(
|
||||
&self,
|
||||
errors: &mut Vec<traits::FulfillmentError<'tcx>>,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
call_sp: Span,
|
||||
args: &'tcx [hir::Expr<'tcx>],
|
||||
expected_tys: &[Ty<'tcx>],
|
||||
) {
|
||||
// We *do not* do this for desugared call spans to keep good diagnostics when involving
|
||||
// the `?` operator.
|
||||
if call_sp.desugaring_kind().is_some() {
|
||||
return;
|
||||
// Store a mapping from `(Span, Predicate) -> ObligationCause`, so that
|
||||
// other errors that have the same span and predicate can also get fixed,
|
||||
// even if their `ObligationCauseCode` isn't an `Expr*Obligation` kind.
|
||||
// This is important since if we adjust one span but not the other, then
|
||||
// we will have "duplicated" the error on the UI side.
|
||||
let mut remap_cause = FxHashSet::default();
|
||||
let mut not_adjusted = vec![];
|
||||
|
||||
for error in errors {
|
||||
let before_span = error.obligation.cause.span;
|
||||
if self.adjust_fulfillment_error_for_expr_obligation(error)
|
||||
|| before_span != error.obligation.cause.span
|
||||
{
|
||||
// Store both the predicate and the predicate *without constness*
|
||||
// since sometimes we instantiate and check both of these in a
|
||||
// method call, for example.
|
||||
remap_cause.insert((
|
||||
before_span,
|
||||
error.obligation.predicate,
|
||||
error.obligation.cause.clone(),
|
||||
));
|
||||
remap_cause.insert((
|
||||
before_span,
|
||||
error.obligation.predicate.without_const(self.tcx),
|
||||
error.obligation.cause.clone(),
|
||||
));
|
||||
} else {
|
||||
// If it failed to be adjusted once around, it may be adjusted
|
||||
// via the "remap cause" mapping the second time...
|
||||
not_adjusted.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
'outer: for error in errors {
|
||||
// Only if the cause is somewhere inside the expression we want try to point at arg.
|
||||
// Otherwise, it means that the cause is somewhere else and we should not change
|
||||
// anything because we can break the correct span.
|
||||
if !call_sp.contains(error.obligation.cause.span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Peel derived obligation, because it's the type that originally
|
||||
// started this inference chain that matters, not the one we wound
|
||||
// up with at the end.
|
||||
fn unpeel_to_top<'a, 'tcx>(
|
||||
mut code: &'a ObligationCauseCode<'tcx>,
|
||||
) -> &'a ObligationCauseCode<'tcx> {
|
||||
let mut result_code = code;
|
||||
loop {
|
||||
let parent = match code {
|
||||
ObligationCauseCode::ImplDerivedObligation(c) => &c.derived.parent_code,
|
||||
ObligationCauseCode::BuiltinDerivedObligation(c)
|
||||
| ObligationCauseCode::DerivedObligation(c) => &c.parent_code,
|
||||
_ => break result_code,
|
||||
};
|
||||
(result_code, code) = (code, parent);
|
||||
}
|
||||
}
|
||||
let self_: ty::subst::GenericArg<'_> =
|
||||
match unpeel_to_top(error.obligation.cause.code()) {
|
||||
ObligationCauseCode::BuiltinDerivedObligation(code)
|
||||
| ObligationCauseCode::DerivedObligation(code) => {
|
||||
code.parent_trait_pred.self_ty().skip_binder().into()
|
||||
}
|
||||
ObligationCauseCode::ImplDerivedObligation(code) => {
|
||||
code.derived.parent_trait_pred.self_ty().skip_binder().into()
|
||||
}
|
||||
_ => match error.obligation.predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(predicate) => predicate.self_ty().into(),
|
||||
ty::PredicateKind::Projection(predicate) => {
|
||||
predicate.projection_ty.self_ty().into()
|
||||
}
|
||||
_ => continue,
|
||||
},
|
||||
};
|
||||
let self_ = self.resolve_vars_if_possible(self_);
|
||||
let ty_matches_self = |ty: Ty<'tcx>| ty.walk().any(|arg| arg == self_);
|
||||
|
||||
let typeck_results = self.typeck_results.borrow();
|
||||
|
||||
for (idx, arg) in args.iter().enumerate() {
|
||||
// Don't adjust the span if we already have a more precise span
|
||||
// within one of the args.
|
||||
if arg.span.contains(error.obligation.cause.span) {
|
||||
let references_arg =
|
||||
typeck_results.expr_ty_opt(arg).map_or(false, &ty_matches_self)
|
||||
|| expected_tys.get(idx).copied().map_or(false, &ty_matches_self);
|
||||
if references_arg && !arg.span.from_expansion() {
|
||||
error.obligation.cause.map_code(|parent_code| {
|
||||
ObligationCauseCode::FunctionArgumentObligation {
|
||||
arg_hir_id: args[idx].hir_id,
|
||||
call_hir_id: expr.hir_id,
|
||||
parent_code,
|
||||
}
|
||||
})
|
||||
}
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the argument position for all arguments that could have caused this
|
||||
// `FulfillmentError`.
|
||||
let mut referenced_in: Vec<_> = std::iter::zip(expected_tys, args)
|
||||
.enumerate()
|
||||
.flat_map(|(idx, (expected_ty, arg))| {
|
||||
if let Some(arg_ty) = typeck_results.expr_ty_opt(arg) {
|
||||
vec![(idx, arg_ty), (idx, *expected_ty)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
.filter_map(|(i, ty)| {
|
||||
let ty = self.resolve_vars_if_possible(ty);
|
||||
// We walk the argument type because the argument's type could have
|
||||
// been `Option<T>`, but the `FulfillmentError` references `T`.
|
||||
if ty_matches_self(ty) { Some(i) } else { None }
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Both checked and coerced types could have matched, thus we need to remove
|
||||
// duplicates.
|
||||
|
||||
// We sort primitive type usize here and can use unstable sort
|
||||
referenced_in.sort_unstable();
|
||||
referenced_in.dedup();
|
||||
|
||||
if let &[idx] = &referenced_in[..] {
|
||||
// Do not point at the inside of a macro.
|
||||
// That would often result in poor error messages.
|
||||
if args[idx].span.from_expansion() {
|
||||
for error in not_adjusted {
|
||||
for (span, predicate, cause) in &remap_cause {
|
||||
if *predicate == error.obligation.predicate
|
||||
&& span.contains(error.obligation.cause.span)
|
||||
{
|
||||
error.obligation.cause = cause.clone();
|
||||
continue;
|
||||
}
|
||||
// We make sure that only *one* argument matches the obligation failure
|
||||
// and we assign the obligation's span to its expression's.
|
||||
error.obligation.cause.span = args[idx].span;
|
||||
error.obligation.cause.map_code(|parent_code| {
|
||||
ObligationCauseCode::FunctionArgumentObligation {
|
||||
arg_hir_id: args[idx].hir_id,
|
||||
call_hir_id: expr.hir_id,
|
||||
parent_code,
|
||||
}
|
||||
});
|
||||
} else if error.obligation.cause.span == call_sp {
|
||||
// Make function calls point at the callee, not the whole thing.
|
||||
if let hir::ExprKind::Call(callee, _) = expr.kind {
|
||||
error.obligation.cause.span = callee.span;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a vec of evaluated `FulfillmentError`s and an `fn` call expression, we walk the
|
||||
/// `PathSegment`s and resolve their type parameters to see if any of the `FulfillmentError`s
|
||||
/// were caused by them. If they were, we point at the corresponding type argument's span
|
||||
/// instead of the `fn` call path span.
|
||||
fn point_at_type_arg_instead_of_call_if_possible(
|
||||
fn adjust_fulfillment_error_for_expr_obligation(
|
||||
&self,
|
||||
errors: &mut Vec<traits::FulfillmentError<'tcx>>,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
) {
|
||||
if let hir::ExprKind::Call(path, _) = &call_expr.kind {
|
||||
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &path.kind {
|
||||
for error in errors {
|
||||
let self_ty = match error.obligation.predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(predicate) => predicate.self_ty(),
|
||||
ty::PredicateKind::Projection(predicate) => {
|
||||
predicate.projection_ty.self_ty()
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
// If any of the type arguments in this path segment caused the
|
||||
// `FulfillmentError`, point at its span (#61860).
|
||||
for arg in path
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| seg.args.as_ref())
|
||||
.flat_map(|a| a.args.iter())
|
||||
error: &mut traits::FulfillmentError<'tcx>,
|
||||
) -> bool {
|
||||
let (traits::ExprItemObligation(def_id, hir_id, idx) | traits::ExprBindingObligation(def_id, _, hir_id, idx))
|
||||
= *error.obligation.cause.code().peel_derives() else { return false; };
|
||||
let hir = self.tcx.hir();
|
||||
let hir::Node::Expr(expr) = hir.get(hir_id) else { return false; };
|
||||
|
||||
// Skip over mentioning async lang item
|
||||
if Some(def_id) == self.tcx.lang_items().from_generator_fn()
|
||||
&& error.obligation.cause.span.desugaring_kind()
|
||||
== Some(rustc_span::DesugaringKind::Async)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(unsubstituted_pred) =
|
||||
self.tcx.predicates_of(def_id).instantiate_identity(self.tcx).predicates.into_iter().nth(idx)
|
||||
else { return false; };
|
||||
|
||||
let generics = self.tcx.generics_of(def_id);
|
||||
let predicate_substs = match unsubstituted_pred.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(pred) => pred.trait_ref.substs,
|
||||
ty::PredicateKind::Projection(pred) => pred.projection_ty.substs,
|
||||
_ => ty::List::empty(),
|
||||
};
|
||||
|
||||
let find_param_matching = |matches: &dyn Fn(&ty::ParamTy) -> bool| {
|
||||
predicate_substs.types().find_map(|ty| {
|
||||
ty.walk().find_map(|arg| {
|
||||
if let ty::GenericArgKind::Type(ty) = arg.unpack()
|
||||
&& let ty::Param(param_ty) = ty.kind()
|
||||
&& matches(param_ty)
|
||||
{
|
||||
if let hir::GenericArg::Type(hir_ty) = &arg
|
||||
&& let Some(ty) =
|
||||
self.typeck_results.borrow().node_type_opt(hir_ty.hir_id)
|
||||
&& self.resolve_vars_if_possible(ty) == self_ty
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
// Prefer generics that are local to the fn item, since these are likely
|
||||
// to be the cause of the unsatisfied predicate.
|
||||
let mut param_to_point_at = find_param_matching(&|param_ty| {
|
||||
self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) == def_id
|
||||
});
|
||||
// Fall back to generic that isn't local to the fn item. This will come
|
||||
// from a trait or impl, for example.
|
||||
let mut fallback_param_to_point_at = find_param_matching(&|param_ty| {
|
||||
self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) != def_id
|
||||
&& param_ty.name != rustc_span::symbol::kw::SelfUpper
|
||||
});
|
||||
// Finally, the `Self` parameter is possibly the reason that the predicate
|
||||
// is unsatisfied. This is less likely to be true for methods, because
|
||||
// method probe means that we already kinda check that the predicates due
|
||||
// to the `Self` type are true.
|
||||
let mut self_param_to_point_at =
|
||||
find_param_matching(&|param_ty| param_ty.name == rustc_span::symbol::kw::SelfUpper);
|
||||
|
||||
// Finally, for ambiguity-related errors, we actually want to look
|
||||
// for a parameter that is the source of the inference type left
|
||||
// over in this predicate.
|
||||
if let traits::FulfillmentErrorCode::CodeAmbiguity = error.code {
|
||||
fallback_param_to_point_at = None;
|
||||
self_param_to_point_at = None;
|
||||
param_to_point_at =
|
||||
self.find_ambiguous_parameter_in(def_id, error.root_obligation.predicate);
|
||||
}
|
||||
|
||||
if self.closure_span_overlaps_error(error, expr.span) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Path(qpath) => {
|
||||
if let hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Call(callee, args),
|
||||
hir_id: call_hir_id,
|
||||
span: call_span,
|
||||
..
|
||||
}) = hir.get(hir.get_parent_node(expr.hir_id))
|
||||
&& callee.hir_id == expr.hir_id
|
||||
{
|
||||
if self.closure_span_overlaps_error(error, *call_span) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for param in
|
||||
[param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if self.point_at_arg_if_possible(
|
||||
error,
|
||||
def_id,
|
||||
param,
|
||||
*call_hir_id,
|
||||
callee.span,
|
||||
args,
|
||||
)
|
||||
{
|
||||
error.obligation.cause.span = hir_ty.span;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Notably, we only point to params that are local to the
|
||||
// item we're checking, since those are the ones we are able
|
||||
// to look in the final `hir::PathSegment` for. Everything else
|
||||
// would require a deeper search into the `qpath` than I think
|
||||
// is worthwhile.
|
||||
if let Some(param_to_point_at) = param_to_point_at
|
||||
&& self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ExprKind::MethodCall(segment, args, ..) => {
|
||||
for param in [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if self.point_at_arg_if_possible(
|
||||
error,
|
||||
def_id,
|
||||
param,
|
||||
hir_id,
|
||||
segment.ident.span,
|
||||
args,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Some(param_to_point_at) = param_to_point_at
|
||||
&& self.point_at_generic_if_possible(error, def_id, param_to_point_at, segment)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
hir::ExprKind::Struct(qpath, fields, ..) => {
|
||||
if let Res::Def(DefKind::Struct | DefKind::Variant, variant_def_id) =
|
||||
self.typeck_results.borrow().qpath_res(qpath, hir_id)
|
||||
{
|
||||
for param in
|
||||
[param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
|
||||
{
|
||||
if let Some(param) = param
|
||||
&& self.point_at_field_if_possible(
|
||||
error,
|
||||
def_id,
|
||||
param,
|
||||
variant_def_id,
|
||||
fields,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(param_to_point_at) = param_to_point_at
|
||||
&& self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn closure_span_overlaps_error(
|
||||
&self,
|
||||
error: &traits::FulfillmentError<'tcx>,
|
||||
span: Span,
|
||||
) -> bool {
|
||||
if let traits::FulfillmentErrorCode::CodeSelectionError(
|
||||
traits::SelectionError::OutputTypeParameterMismatch(_, expected, _),
|
||||
) = error.code
|
||||
&& let ty::Closure(def_id, _) | ty::Generator(def_id, ..) = expected.skip_binder().self_ty().kind()
|
||||
&& span.overlaps(self.tcx.def_span(*def_id))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn point_at_arg_if_possible(
|
||||
&self,
|
||||
error: &mut traits::FulfillmentError<'tcx>,
|
||||
def_id: DefId,
|
||||
param_to_point_at: ty::GenericArg<'tcx>,
|
||||
call_hir_id: hir::HirId,
|
||||
callee_span: Span,
|
||||
args: &[hir::Expr<'tcx>],
|
||||
) -> bool {
|
||||
let sig = self.tcx.fn_sig(def_id).skip_binder();
|
||||
let args_referencing_param: Vec<_> = sig
|
||||
.inputs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, ty)| find_param_in_ty(**ty, param_to_point_at))
|
||||
.collect();
|
||||
|
||||
// If there's one field that references the given generic, great!
|
||||
if let [(idx, _)] = args_referencing_param.as_slice() && let Some(arg) = args.get(*idx) {
|
||||
error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span);
|
||||
error.obligation.cause.map_code(|parent_code| {
|
||||
ObligationCauseCode::FunctionArgumentObligation {
|
||||
arg_hir_id: arg.hir_id,
|
||||
call_hir_id,
|
||||
parent_code,
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if args_referencing_param.len() > 0 {
|
||||
// If more than one argument applies, then point to the callee span at least...
|
||||
// We have chance to fix this up further in `point_at_generics_if_possible`
|
||||
error.obligation.cause.span = callee_span;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn point_at_field_if_possible(
|
||||
&self,
|
||||
error: &mut traits::FulfillmentError<'tcx>,
|
||||
def_id: DefId,
|
||||
param_to_point_at: ty::GenericArg<'tcx>,
|
||||
variant_def_id: DefId,
|
||||
expr_fields: &[hir::ExprField<'tcx>],
|
||||
) -> bool {
|
||||
let def = self.tcx.adt_def(def_id);
|
||||
|
||||
let identity_substs = ty::InternalSubsts::identity_for_item(self.tcx, def_id);
|
||||
let fields_referencing_param: Vec<_> = def
|
||||
.variant_with_id(variant_def_id)
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| {
|
||||
let field_ty = field.ty(self.tcx, identity_substs);
|
||||
find_param_in_ty(field_ty, param_to_point_at)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let [field] = fields_referencing_param.as_slice() {
|
||||
for expr_field in expr_fields {
|
||||
// Look for the ExprField that matches the field, using the
|
||||
// same rules that check_expr_struct uses for macro hygiene.
|
||||
if self.tcx.adjust_ident(expr_field.ident, variant_def_id) == field.ident(self.tcx)
|
||||
{
|
||||
error.obligation.cause.span = expr_field
|
||||
.expr
|
||||
.span
|
||||
.find_ancestor_in_same_ctxt(error.obligation.cause.span)
|
||||
.unwrap_or(expr_field.span);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn point_at_path_if_possible(
|
||||
&self,
|
||||
error: &mut traits::FulfillmentError<'tcx>,
|
||||
def_id: DefId,
|
||||
param: ty::GenericArg<'tcx>,
|
||||
qpath: &QPath<'tcx>,
|
||||
) -> bool {
|
||||
match qpath {
|
||||
hir::QPath::Resolved(_, path) => {
|
||||
if let Some(segment) = path.segments.last()
|
||||
&& self.point_at_generic_if_possible(error, def_id, param, segment)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
hir::QPath::TypeRelative(_, segment) => {
|
||||
if self.point_at_generic_if_possible(error, def_id, param, segment) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn point_at_generic_if_possible(
|
||||
&self,
|
||||
error: &mut traits::FulfillmentError<'tcx>,
|
||||
def_id: DefId,
|
||||
param_to_point_at: ty::GenericArg<'tcx>,
|
||||
segment: &hir::PathSegment<'tcx>,
|
||||
) -> bool {
|
||||
let own_substs = self
|
||||
.tcx
|
||||
.generics_of(def_id)
|
||||
.own_substs(ty::InternalSubsts::identity_for_item(self.tcx, def_id));
|
||||
let Some((index, _)) = own_substs
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg.unpack(), ty::GenericArgKind::Type(_)))
|
||||
.enumerate()
|
||||
.find(|(_, arg)| **arg == param_to_point_at) else { return false };
|
||||
let Some(arg) = segment
|
||||
.args()
|
||||
.args
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, hir::GenericArg::Type(_)))
|
||||
.nth(index) else { return false; };
|
||||
error.obligation.cause.span = arg
|
||||
.span()
|
||||
.find_ancestor_in_same_ctxt(error.obligation.cause.span)
|
||||
.unwrap_or(arg.span());
|
||||
true
|
||||
}
|
||||
|
||||
fn find_ambiguous_parameter_in<T: TypeVisitable<'tcx>>(
|
||||
&self,
|
||||
item_def_id: DefId,
|
||||
t: T,
|
||||
) -> Option<ty::GenericArg<'tcx>> {
|
||||
struct FindAmbiguousParameter<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, DefId);
|
||||
impl<'tcx> TypeVisitor<'tcx> for FindAmbiguousParameter<'_, 'tcx> {
|
||||
type BreakTy = ty::GenericArg<'tcx>;
|
||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
|
||||
if let Some(origin) = self.0.type_var_origin(ty)
|
||||
&& let TypeVariableOriginKind::TypeParameterDefinition(_, Some(def_id)) =
|
||||
origin.kind
|
||||
&& let generics = self.0.tcx.generics_of(self.1)
|
||||
&& let Some(index) = generics.param_def_id_to_index(self.0.tcx, def_id)
|
||||
&& let Some(subst) = ty::InternalSubsts::identity_for_item(self.0.tcx, self.1)
|
||||
.get(index as usize)
|
||||
{
|
||||
ControlFlow::Break(*subst)
|
||||
} else {
|
||||
ty.super_visit_with(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.visit_with(&mut FindAmbiguousParameter(self, item_def_id)).break_value()
|
||||
}
|
||||
|
||||
fn label_fn_like(
|
||||
|
@ -1864,14 +2075,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
let new_def_id = self.probe(|_| {
|
||||
let trait_ref = ty::TraitRef::new(
|
||||
call_kind.to_def_id(self.tcx),
|
||||
self.tcx.mk_substs([
|
||||
ty::GenericArg::from(callee_ty),
|
||||
self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: rustc_span::DUMMY_SP,
|
||||
})
|
||||
.into(),
|
||||
].into_iter()),
|
||||
self.tcx.mk_substs(
|
||||
[
|
||||
ty::GenericArg::from(callee_ty),
|
||||
self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: rustc_span::DUMMY_SP,
|
||||
})
|
||||
.into(),
|
||||
]
|
||||
.into_iter(),
|
||||
),
|
||||
);
|
||||
let obligation = traits::Obligation::new(
|
||||
traits::ObligationCause::dummy(),
|
||||
|
@ -1886,7 +2100,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
Ok(Some(traits::ImplSource::UserDefined(impl_source))) => {
|
||||
Some(impl_source.impl_def_id)
|
||||
}
|
||||
_ => None
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
if let Some(new_def_id) = new_def_id {
|
||||
|
@ -1940,3 +2154,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_param_in_ty<'tcx>(ty: Ty<'tcx>, param_to_point_at: ty::GenericArg<'tcx>) -> bool {
|
||||
let mut walk = ty.walk();
|
||||
while let Some(arg) = walk.next() {
|
||||
if arg == param_to_point_at {
|
||||
return true;
|
||||
} else if let ty::GenericArgKind::Type(ty) = arg.unpack()
|
||||
&& let ty::Projection(..) = ty.kind()
|
||||
{
|
||||
// This logic may seem a bit strange, but typically when
|
||||
// we have a projection type in a function signature, the
|
||||
// argument that's being passed into that signature is
|
||||
// not actually constraining that projection's substs in
|
||||
// a meaningful way. So we skip it, and see improvements
|
||||
// in some UI tests.
|
||||
walk.skip_current_subtree();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -491,7 +491,19 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> {
|
|||
// so we just call `predicates_for_generics` directly to avoid redoing work.
|
||||
// `self.add_required_obligations(self.span, def_id, &all_substs);`
|
||||
for obligation in traits::predicates_for_generics(
|
||||
traits::ObligationCause::new(self.span, self.body_id, traits::ItemObligation(def_id)),
|
||||
|idx, span| {
|
||||
let code = if span.is_dummy() {
|
||||
ObligationCauseCode::ExprItemObligation(def_id, self.call_expr.hir_id, idx)
|
||||
} else {
|
||||
ObligationCauseCode::ExprBindingObligation(
|
||||
def_id,
|
||||
span,
|
||||
self.call_expr.hir_id,
|
||||
idx,
|
||||
)
|
||||
};
|
||||
traits::ObligationCause::new(self.span, self.body_id, code)
|
||||
},
|
||||
self.param_env,
|
||||
method_predicates,
|
||||
) {
|
||||
|
|
|
@ -534,7 +534,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
} else {
|
||||
traits::ObligationCause::misc(span, self.body_id)
|
||||
};
|
||||
obligations.extend(traits::predicates_for_generics(cause.clone(), self.param_env, bounds));
|
||||
let predicates_cause = cause.clone();
|
||||
obligations.extend(traits::predicates_for_generics(
|
||||
move |_, _| predicates_cause.clone(),
|
||||
self.param_env,
|
||||
bounds,
|
||||
));
|
||||
|
||||
// Also add an obligation for the method type being well-formed.
|
||||
let method_ty = tcx.mk_fn_ptr(ty::Binder::dummy(fn_sig));
|
||||
|
|
|
@ -1514,8 +1514,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
|
|||
traits::normalize(selcx, self.param_env, cause.clone(), impl_bounds);
|
||||
|
||||
// Convert the bounds into obligations.
|
||||
let impl_obligations =
|
||||
traits::predicates_for_generics(cause, self.param_env, impl_bounds);
|
||||
let impl_obligations = traits::predicates_for_generics(
|
||||
move |_, _| cause.clone(),
|
||||
self.param_env,
|
||||
impl_bounds,
|
||||
);
|
||||
|
||||
let candidate_obligations = impl_obligations
|
||||
.chain(norm_obligations.into_iter())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue