1
Fork 0

Rework point-at-arg

This commit is contained in:
Michael Goulet 2022-08-16 06:27:22 +00:00
parent fb80d2bfe4
commit c005e760f5
155 changed files with 1276 additions and 789 deletions

View file

@ -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)
};

View file

@ -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,
) {

View file

@ -247,17 +247,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 +302,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
@ -1183,7 +1164,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 {
@ -1626,179 +1607,174 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// 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(
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;
}
'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() {
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;
}
}
for error in errors {
self.adjust_fulfillment_error_for_expr_obligation(error);
}
}
/// 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>,
error: &mut traits::FulfillmentError<'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())
let (traits::ExprItemObligation(def_id, hir_id, idx) | traits::ExprBindingObligation(def_id, _, hir_id, idx))
= *error.obligation.cause.code().peel_derives() else { return; };
let Some(unsubstituted_pred) =
self.tcx.predicates_of(def_id).instantiate_identity(self.tcx).predicates.into_iter().nth(idx) else { return; };
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 param_to_point_at = 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()
// Look for a param ty that is local to this method/fn
// and not inherited from an impl, for example.
&& self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) == def_id
{
Some(arg)
} else {
None
}
})
});
let fallback_param_to_point_at = 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()
&& self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) != def_id
{
Some(arg)
} else {
None
}
})
});
let hir = self.tcx.hir();
match hir.get(hir_id) {
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Path(hir::QPath::Resolved(_, path)), hir_id, .. }) => {
if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(callee, args), hir_id: call_hir_id, .. })
= hir.get(hir.get_parent_node(*hir_id))
&& callee.hir_id == *hir_id
{
if let Some(param_to_point_at) = param_to_point_at
&& self.point_at_args_if_possible(error, def_id, param_to_point_at, *call_hir_id, callee.span, args) {
return;
}
if let Some(fallback_param_to_point_at) = fallback_param_to_point_at
&& self.point_at_args_if_possible(error, def_id, fallback_param_to_point_at, *call_hir_id, callee.span, args)
{
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
{
error.obligation.cause.span = hir_ty.span;
break;
}
return;
}
if let Some(param_to_point_at) = param_to_point_at
&& let Some(segment) = path.segments.last()
&& self.point_at_generics_if_possible(error, def_id, param_to_point_at, segment)
{
return;
}
}
}
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::MethodCall(segment, args, ..), .. }) => {
if let Some(param_to_point_at) = param_to_point_at
&& self.point_at_args_if_possible(error, def_id, param_to_point_at, hir_id, segment.ident.span, args)
{
return;
}
if let Some(fallback_param_to_point_at) = fallback_param_to_point_at
&& self.point_at_args_if_possible(error, def_id, fallback_param_to_point_at, hir_id, segment.ident.span, args)
{
return;
}
if let Some(param_to_point_at) = param_to_point_at
&& self.point_at_generics_if_possible(error, def_id, param_to_point_at, segment)
{
return;
}
}
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Struct(..), .. }) => {
// fixme
}
_ => {}
}
}
fn point_at_args_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)| ty.walk().any(|arg| arg == param_to_point_at))
.collect();
if let [(idx, _)] = args_referencing_param.as_slice()
&& let Some(arg) = args.get(*idx)
{
error.obligation.cause.span = arg.span;
error.obligation.cause.map_code(|parent_code| {
ObligationCauseCode::FunctionArgumentObligation {
arg_hir_id: arg.hir_id,
call_hir_id,
parent_code,
}
});
true
} else if args_referencing_param.len() > 0 {
// If more than one argument applies, then point to the callee
// We have chance to fix this up further in `point_at_generics_if_possible`
error.obligation.cause.span = callee_span;
false
} else {
false
}
}
fn point_at_generics_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();
true
}
fn label_fn_like(
&self,
err: &mut Diagnostic,

View file

@ -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,
) {

View file

@ -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));

View file

@ -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())