diff options
Diffstat (limited to 'compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs')
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs | 864 |
1 files changed, 864 insertions, 0 deletions
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs new file mode 100644 index 000000000..b09886fe3 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs @@ -0,0 +1,864 @@ +use crate::FnCtxt; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::def_id::DefId; +use rustc_infer::traits::ObligationCauseCode; +use rustc_middle::ty::{ + self, DefIdTree, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, +}; +use rustc_span::{self, Span}; +use rustc_trait_selection::traits; + +use std::ops::ControlFlow; + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn adjust_fulfillment_error_for_expr_obligation( + &self, + 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; }; + + 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::Clause(ty::Clause::Trait(pred)) => pred.trait_ref.substs, + ty::PredicateKind::Clause(ty::Clause::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) + { + 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_parent(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.blame_specific_arg_if_possible( + error, + def_id, + param, + *call_hir_id, + callee.span, + None, + args, + ) + { + 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, receiver, args, ..) => { + for param in [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at] + .into_iter() + .flatten() + { + if self.blame_specific_arg_if_possible( + error, + def_id, + param, + hir_id, + segment.ident.span, + Some(receiver), + 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( + hir::def::DefKind::Struct | hir::def::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 { + let refined_expr = self.point_at_field_if_possible( + def_id, + param, + variant_def_id, + fields, + ); + + match refined_expr { + None => {} + Some((refined_expr, _)) => { + error.obligation.cause.span = refined_expr + .span + .find_ancestor_in_same_ctxt(error.obligation.cause.span) + .unwrap_or(refined_expr.span); + 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 point_at_path_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + def_id: DefId, + param: ty::GenericArg<'tcx>, + qpath: &hir::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<TyCtxt<'tcx>>>( + &self, + item_def_id: DefId, + t: T, + ) -> Option<ty::GenericArg<'tcx>> { + struct FindAmbiguousParameter<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, DefId); + impl<'tcx> TypeVisitor<TyCtxt<'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 rustc_infer::infer::type_variable::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 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_field_if_possible( + &self, + def_id: DefId, + param_to_point_at: ty::GenericArg<'tcx>, + variant_def_id: DefId, + expr_fields: &[hir::ExprField<'tcx>], + ) -> Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)> { + 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); + Self::find_param_in_ty(field_ty.into(), 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) + { + return Some((expr_field.expr, self.tcx.type_of(field.did).subst_identity())); + } + } + } + + None + } + + /// - `blame_specific_*` means that the function will recursively traverse the expression, + /// looking for the most-specific-possible span to blame. + /// + /// - `point_at_*` means that the function will only go "one level", pointing at the specific + /// expression mentioned. + /// + /// `blame_specific_arg_if_possible` will find the most-specific expression anywhere inside + /// the provided function call expression, and mark it as responsible for the fullfillment + /// error. + fn blame_specific_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, + receiver: Option<&'tcx hir::Expr<'tcx>>, + args: &'tcx [hir::Expr<'tcx>], + ) -> bool { + let ty = self.tcx.type_of(def_id).subst_identity(); + if !ty.is_fn() { + return false; + } + let sig = ty.fn_sig(self.tcx).skip_binder(); + let args_referencing_param: Vec<_> = sig + .inputs() + .iter() + .enumerate() + .filter(|(_, ty)| Self::find_param_in_ty((**ty).into(), 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) = receiver + .map_or(args.get(*idx), |rcvr| if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }) { + + error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span); + + if let hir::Node::Expr(arg_expr) = self.tcx.hir().get(arg.hir_id) { + // This is more specific than pointing at the entire argument. + self.blame_specific_expr_if_possible(error, arg_expr) + } + + 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 + } + + /** + * Recursively searches for the most-specific blamable expression. + * For example, if you have a chain of constraints like: + * - want `Vec<i32>: Copy` + * - because `Option<Vec<i32>>: Copy` needs `Vec<i32>: Copy` because `impl <T: Copy> Copy for Option<T>` + * - because `(Option<Vec<i32>, bool)` needs `Option<Vec<i32>>: Copy` because `impl <A: Copy, B: Copy> Copy for (A, B)` + * then if you pass in `(Some(vec![1, 2, 3]), false)`, this helper `point_at_specific_expr_if_possible` + * will find the expression `vec![1, 2, 3]` as the "most blameable" reason for this missing constraint. + * + * This function only updates the error span. + */ + pub fn blame_specific_expr_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) { + // Whether it succeeded or failed, it likely made some amount of progress. + // In the very worst case, it's just the same `expr` we originally passed in. + let expr = match self.blame_specific_expr_if_possible_for_obligation_cause_code( + &error.obligation.cause.code(), + expr, + ) { + Ok(expr) => expr, + Err(expr) => expr, + }; + + // Either way, use this expression to update the error span. + // If it doesn't overlap the existing span at all, use the original span. + // FIXME: It would possibly be better to do this more continuously, at each level... + error.obligation.cause.span = expr + .span + .find_ancestor_in_same_ctxt(error.obligation.cause.span) + .unwrap_or(error.obligation.cause.span); + } + + fn blame_specific_expr_if_possible_for_obligation_cause_code( + &self, + obligation_cause_code: &traits::ObligationCauseCode<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> { + match obligation_cause_code { + traits::ObligationCauseCode::ExprBindingObligation(_, _, _, _) => { + // This is the "root"; we assume that the `expr` is already pointing here. + // Therefore, we return `Ok` so that this `expr` can be refined further. + Ok(expr) + } + traits::ObligationCauseCode::ImplDerivedObligation(impl_derived) => self + .blame_specific_expr_if_possible_for_derived_predicate_obligation( + impl_derived, + expr, + ), + _ => { + // We don't recognize this kind of constraint, so we cannot refine the expression + // any further. + Err(expr) + } + } + } + + /// We want to achieve the error span in the following example: + /// + /// ```ignore (just for demonstration) + /// struct Burrito<Filling> { + /// filling: Filling, + /// } + /// impl <Filling: Delicious> Delicious for Burrito<Filling> {} + /// fn eat_delicious_food<Food: Delicious>(_food: Food) {} + /// + /// fn will_type_error() { + /// eat_delicious_food(Burrito { filling: Kale }); + /// } // ^--- The trait bound `Kale: Delicious` + /// // is not satisfied + /// ``` + /// + /// Without calling this function, the error span will cover the entire argument expression. + /// + /// Before we do any of this logic, we recursively call `point_at_specific_expr_if_possible` on the parent + /// obligation. Hence we refine the `expr` "outwards-in" and bail at the first kind of expression/impl we don't recognize. + /// + /// This function returns a `Result<&Expr, &Expr>` - either way, it returns the `Expr` whose span should be + /// reported as an error. If it is `Ok`, then it means it refined successfull. If it is `Err`, then it may be + /// only a partial success - but it cannot be refined even further. + fn blame_specific_expr_if_possible_for_derived_predicate_obligation( + &self, + obligation: &traits::ImplDerivedObligationCause<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> { + // First, we attempt to refine the `expr` for our span using the parent obligation. + // If this cannot be done, then we are already stuck, so we stop early (hence the use + // of the `?` try operator here). + let expr = self.blame_specific_expr_if_possible_for_obligation_cause_code( + &*obligation.derived.parent_code, + expr, + )?; + + // This is the "trait" (meaning, the predicate "proved" by this `impl`) which provides the `Self` type we care about. + // For the purposes of this function, we hope that it is a `struct` type, and that our current `expr` is a literal of + // that struct type. + let impl_trait_self_ref = if self.tcx.is_trait_alias(obligation.impl_or_alias_def_id) { + self.tcx.mk_trait_ref( + obligation.impl_or_alias_def_id, + ty::InternalSubsts::identity_for_item(self.tcx, obligation.impl_or_alias_def_id), + ) + } else { + self.tcx + .impl_trait_ref(obligation.impl_or_alias_def_id) + .map(|impl_def| impl_def.skip_binder()) + // It is possible that this is absent. In this case, we make no progress. + .ok_or(expr)? + }; + + // We only really care about the `Self` type itself, which we extract from the ref. + let impl_self_ty: Ty<'tcx> = impl_trait_self_ref.self_ty(); + + let impl_predicates: ty::GenericPredicates<'tcx> = + self.tcx.predicates_of(obligation.impl_or_alias_def_id); + let Some(impl_predicate_index) = obligation.impl_def_predicate_index else { + // We don't have the index, so we can only guess. + return Err(expr); + }; + + if impl_predicate_index >= impl_predicates.predicates.len() { + // This shouldn't happen, but since this is only a diagnostic improvement, avoid breaking things. + return Err(expr); + } + let relevant_broken_predicate: ty::PredicateKind<'tcx> = + impl_predicates.predicates[impl_predicate_index].0.kind().skip_binder(); + + match relevant_broken_predicate { + ty::PredicateKind::Clause(ty::Clause::Trait(broken_trait)) => { + // ... + self.blame_specific_part_of_expr_corresponding_to_generic_param( + broken_trait.trait_ref.self_ty().into(), + expr, + impl_self_ty.into(), + ) + } + _ => Err(expr), + } + } + + /// Drills into `expr` to arrive at the equivalent location of `find_generic_param` in `in_ty`. + /// For example, given + /// - expr: `(Some(vec![1, 2, 3]), false)` + /// - param: `T` + /// - in_ty: `(Option<Vec<T>, bool)` + /// we would drill until we arrive at `vec![1, 2, 3]`. + /// + /// If successful, we return `Ok(refined_expr)`. If unsuccesful, we return `Err(partially_refined_expr`), + /// which will go as far as possible. For example, given `(foo(), false)` instead, we would drill to + /// `foo()` and then return `Err("foo()")`. + /// + /// This means that you can (and should) use the `?` try operator to chain multiple calls to this + /// function with different types, since you can only continue drilling the second time if you + /// succeeded the first time. + fn blame_specific_part_of_expr_corresponding_to_generic_param( + &self, + param: ty::GenericArg<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + in_ty: ty::GenericArg<'tcx>, + ) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> { + if param == in_ty { + // The types match exactly, so we have drilled as far as we can. + return Ok(expr); + } + + let ty::GenericArgKind::Type(in_ty) = in_ty.unpack() else { + return Err(expr); + }; + + if let ( + hir::ExprKind::AddrOf(_borrow_kind, _borrow_mutability, borrowed_expr), + ty::Ref(_ty_region, ty_ref_type, _ty_mutability), + ) = (&expr.kind, in_ty.kind()) + { + // We can "drill into" the borrowed expression. + return self.blame_specific_part_of_expr_corresponding_to_generic_param( + param, + borrowed_expr, + (*ty_ref_type).into(), + ); + } + + if let (hir::ExprKind::Tup(expr_elements), ty::Tuple(in_ty_elements)) = + (&expr.kind, in_ty.kind()) + { + if in_ty_elements.len() != expr_elements.len() { + return Err(expr); + } + // Find out which of `in_ty_elements` refer to `param`. + // FIXME: It may be better to take the first if there are multiple, + // just so that the error points to a smaller expression. + let Some((drill_expr, drill_ty)) = Self::is_iterator_singleton(expr_elements.iter().zip( in_ty_elements.iter()).filter(|(_expr_elem, in_ty_elem)| { + Self::find_param_in_ty((*in_ty_elem).into(), param) + })) else { + // The param is not mentioned, or it is mentioned in multiple indexes. + return Err(expr); + }; + + return self.blame_specific_part_of_expr_corresponding_to_generic_param( + param, + drill_expr, + drill_ty.into(), + ); + } + + if let ( + hir::ExprKind::Struct(expr_struct_path, expr_struct_fields, _expr_struct_rest), + ty::Adt(in_ty_adt, in_ty_adt_generic_args), + ) = (&expr.kind, in_ty.kind()) + { + // First, confirm that this struct is the same one as in the types, and if so, + // find the right variant. + let Res::Def(expr_struct_def_kind, expr_struct_def_id) = self.typeck_results.borrow().qpath_res(expr_struct_path, expr.hir_id) else { + return Err(expr); + }; + + let variant_def_id = match expr_struct_def_kind { + hir::def::DefKind::Struct => { + if in_ty_adt.did() != expr_struct_def_id { + // FIXME: Deal with type aliases? + return Err(expr); + } + expr_struct_def_id + } + hir::def::DefKind::Variant => { + // If this is a variant, its parent is the type definition. + if in_ty_adt.did() != self.tcx.parent(expr_struct_def_id) { + // FIXME: Deal with type aliases? + return Err(expr); + } + expr_struct_def_id + } + _ => { + return Err(expr); + } + }; + + // We need to know which of the generic parameters mentions our target param. + // We expect that at least one of them does, since it is expected to be mentioned. + let Some((drill_generic_index, generic_argument_type)) = + Self::is_iterator_singleton( + in_ty_adt_generic_args.iter().enumerate().filter( + |(_index, in_ty_generic)| { + Self::find_param_in_ty(*in_ty_generic, param) + }, + ), + ) else { + return Err(expr); + }; + + let struct_generic_parameters: &ty::Generics = self.tcx.generics_of(in_ty_adt.did()); + if drill_generic_index >= struct_generic_parameters.params.len() { + return Err(expr); + } + + let param_to_point_at_in_struct = self.tcx.mk_param_from_def( + struct_generic_parameters.param_at(drill_generic_index, self.tcx), + ); + + // We make 3 steps: + // Suppose we have a type like + // ```ignore (just for demonstration) + // struct ExampleStruct<T> { + // enabled: bool, + // item: Option<(usize, T, bool)>, + // } + // + // f(ExampleStruct { + // enabled: false, + // item: Some((0, Box::new(String::new()), 1) }, true)), + // }); + // ``` + // Here, `f` is passed a `ExampleStruct<Box<String>>`, but it wants + // for `String: Copy`, which isn't true here. + // + // (1) First, we drill into `.item` and highlight that expression + // (2) Then we use the template type `Option<(usize, T, bool)>` to + // drill into the `T`, arriving at a `Box<String>` expression. + // (3) Then we keep going, drilling into this expression using our + // outer contextual information. + + // (1) Find the (unique) field which mentions the type in our constraint: + let (field_expr, field_type) = self + .point_at_field_if_possible( + in_ty_adt.did(), + param_to_point_at_in_struct, + variant_def_id, + expr_struct_fields, + ) + .ok_or(expr)?; + + // (2) Continue drilling into the struct, ignoring the struct's + // generic argument types. + let expr = self.blame_specific_part_of_expr_corresponding_to_generic_param( + param_to_point_at_in_struct, + field_expr, + field_type.into(), + )?; + + // (3) Continue drilling into the expression, having "passed + // through" the struct entirely. + return self.blame_specific_part_of_expr_corresponding_to_generic_param( + param, + expr, + generic_argument_type, + ); + } + + if let ( + hir::ExprKind::Call(expr_callee, expr_args), + ty::Adt(in_ty_adt, in_ty_adt_generic_args), + ) = (&expr.kind, in_ty.kind()) + { + let hir::ExprKind::Path(expr_callee_path) = &expr_callee.kind else { + // FIXME: This case overlaps with another one worth handling, + // which should happen above since it applies to non-ADTs: + // we can drill down into regular generic functions. + return Err(expr); + }; + // This is (possibly) a constructor call, like `Some(...)` or `MyStruct(a, b, c)`. + + let Res::Def(expr_struct_def_kind, expr_ctor_def_id) = self.typeck_results.borrow().qpath_res(expr_callee_path, expr_callee.hir_id) else { + return Err(expr); + }; + + let variant_def_id = match expr_struct_def_kind { + hir::def::DefKind::Ctor(hir::def::CtorOf::Struct, hir::def::CtorKind::Fn) => { + if in_ty_adt.did() != self.tcx.parent(expr_ctor_def_id) { + // FIXME: Deal with type aliases? + return Err(expr); + } + self.tcx.parent(expr_ctor_def_id) + } + hir::def::DefKind::Ctor(hir::def::CtorOf::Variant, hir::def::CtorKind::Fn) => { + // For a typical enum like + // `enum Blah<T> { Variant(T) }` + // we get the following resolutions: + // - expr_ctor_def_id ::: DefId(0:29 ~ source_file[b442]::Blah::Variant::{constructor#0}) + // - self.tcx.parent(expr_ctor_def_id) ::: DefId(0:28 ~ source_file[b442]::Blah::Variant) + // - self.tcx.parent(self.tcx.parent(expr_ctor_def_id)) ::: DefId(0:26 ~ source_file[b442]::Blah) + + // Therefore, we need to go up once to obtain the variant and up twice to obtain the type. + // Note that this pattern still holds even when we `use` a variant or `use` an enum type to rename it, or chain `use` expressions + // together; this resolution is handled automatically by `qpath_res`. + + // FIXME: Deal with type aliases? + if in_ty_adt.did() == self.tcx.parent(self.tcx.parent(expr_ctor_def_id)) { + // The constructor definition refers to the "constructor" of the variant: + // For example, `Some(5)` triggers this case. + self.tcx.parent(expr_ctor_def_id) + } else { + // FIXME: Deal with type aliases? + return Err(expr); + } + } + _ => { + return Err(expr); + } + }; + + // We need to know which of the generic parameters mentions our target param. + // We expect that at least one of them does, since it is expected to be mentioned. + let Some((drill_generic_index, generic_argument_type)) = + Self::is_iterator_singleton( + in_ty_adt_generic_args.iter().enumerate().filter( + |(_index, in_ty_generic)| { + Self::find_param_in_ty(*in_ty_generic, param) + }, + ), + ) else { + return Err(expr); + }; + + let struct_generic_parameters: &ty::Generics = self.tcx.generics_of(in_ty_adt.did()); + if drill_generic_index >= struct_generic_parameters.params.len() { + return Err(expr); + } + + let param_to_point_at_in_struct = self.tcx.mk_param_from_def( + struct_generic_parameters.param_at(drill_generic_index, self.tcx), + ); + + // We make 3 steps: + // Suppose we have a type like + // ```ignore (just for demonstration) + // struct ExampleStruct<T> { + // enabled: bool, + // item: Option<(usize, T, bool)>, + // } + // + // f(ExampleStruct { + // enabled: false, + // item: Some((0, Box::new(String::new()), 1) }, true)), + // }); + // ``` + // Here, `f` is passed a `ExampleStruct<Box<String>>`, but it wants + // for `String: Copy`, which isn't true here. + // + // (1) First, we drill into `.item` and highlight that expression + // (2) Then we use the template type `Option<(usize, T, bool)>` to + // drill into the `T`, arriving at a `Box<String>` expression. + // (3) Then we keep going, drilling into this expression using our + // outer contextual information. + + // (1) Find the (unique) field index which mentions the type in our constraint: + let Some((field_index, field_type)) = Self::is_iterator_singleton( + in_ty_adt + .variant_with_id(variant_def_id) + .fields + .iter() + .map(|field| field.ty(self.tcx, *in_ty_adt_generic_args)) + .enumerate() + .filter(|(_index, field_type)| Self::find_param_in_ty((*field_type).into(), param)) + ) else { + return Err(expr); + }; + + if field_index >= expr_args.len() { + return Err(expr); + } + + // (2) Continue drilling into the struct, ignoring the struct's + // generic argument types. + let expr = self.blame_specific_part_of_expr_corresponding_to_generic_param( + param_to_point_at_in_struct, + &expr_args[field_index], + field_type.into(), + )?; + + // (3) Continue drilling into the expression, having "passed + // through" the struct entirely. + return self.blame_specific_part_of_expr_corresponding_to_generic_param( + param, + expr, + generic_argument_type, + ); + } + + // At this point, none of the basic patterns matched. + // One major possibility which remains is that we have a function call. + // In this case, it's often possible to dive deeper into the call to find something to blame, + // but this is not always possible. + + Err(expr) + } + + // FIXME: This can be made into a private, non-impl function later. + /// Traverses the given ty (either a `ty::Ty` or a `ty::GenericArg`) and searches for references + /// to the given `param_to_point_at`. Returns `true` if it finds any use of the param. + pub fn find_param_in_ty( + ty: ty::GenericArg<'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; + } + if let ty::GenericArgKind::Type(ty) = arg.unpack() + && let ty::Alias(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 + } + + // FIXME: This can be made into a private, non-impl function later. + /// Returns `Some(iterator.next())` if it has exactly one item, and `None` otherwise. + pub fn is_iterator_singleton<T>(mut iterator: impl Iterator<Item = T>) -> Option<T> { + match (iterator.next(), iterator.next()) { + (_, Some(_)) => None, + (first, _) => first, + } + } +} |