use crate::FnCtxt;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def_id::DefId;
use rustc_infer::{infer::type_variable::TypeVariableOriginKind, traits::ObligationCauseCode};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
use rustc_span::{self, symbol::kw, 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_args = match unsubstituted_pred.kind().skip_binder() {
            ty::ClauseKind::Trait(pred) => pred.trait_ref.args.to_vec(),
            ty::ClauseKind::Projection(pred) => pred.projection_ty.args.to_vec(),
            ty::ClauseKind::ConstArgHasType(arg, ty) => {
                vec![ty.into(), arg.into()]
            }
            ty::ClauseKind::ConstEvaluatable(e) => vec![e.into()],
            _ => return false,
        };

        let find_param_matching = |matches: &dyn Fn(ty::ParamTerm) -> bool| {
            predicate_args.iter().find_map(|arg| {
                arg.walk().find_map(|arg| {
                    if let ty::GenericArgKind::Type(ty) = arg.unpack()
                        && let ty::Param(param_ty) = *ty.kind()
                        && matches(ty::ParamTerm::Ty(param_ty))
                    {
                        Some(arg)
                    } else if let ty::GenericArgKind::Const(ct) = arg.unpack()
                        && let ty::ConstKind::Param(param_ct) = ct.kind()
                        && matches(ty::ParamTerm::Const(param_ct))
                    {
                        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_term| {
            self.tcx.parent(generics.param_at(param_term.index(), 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_term| {
            self.tcx.parent(generics.param_at(param_term.index(), self.tcx).def_id) != def_id
                && !matches!(param_term, ty::ParamTerm::Ty(ty) if ty.name == 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_term| matches!(param_term, ty::ParamTerm::Ty(ty) if ty.name == 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]
                            .into_iter()
                            .flatten()
                    {
                        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_args = self
            .tcx
            .generics_of(def_id)
            .own_args(ty::GenericArgs::identity_for_item(self.tcx, def_id));
        let Some((index, _)) =
            own_args.iter().enumerate().find(|(_, arg)| **arg == param_to_point_at)
        else {
            return false;
        };
        let Some(arg) = segment.args().args.get(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 TypeVariableOriginKind::TypeParameterDefinition(_, 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::GenericArgs::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(box traits::SelectionOutputTypeParameterMismatch{
                expected_trait_ref, ..
            }),
        ) = error.code
            && let ty::Closure(def_id, _) | ty::Generator(def_id, ..) = expected_trait_ref.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_args = ty::GenericArgs::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_args);
                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).instantiate_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 fulfillment
    /// 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).instantiate_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)| 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 successful. 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) {
            ty::TraitRef::new(
                self.tcx,
                obligation.impl_or_alias_def_id,
                ty::GenericArgs::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);
        }

        match impl_predicates.predicates[impl_predicate_index].0.kind().skip_binder() {
            ty::ClauseKind::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 unsuccessful, 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)) =
                is_iterator_singleton(expr_elements.iter().zip(in_ty_elements.iter()).filter(
                    |(_expr_elem, in_ty_elem)| 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)) = is_iterator_singleton(
                in_ty_adt_generic_args
                    .iter()
                    .enumerate()
                    .filter(|(_index, in_ty_generic)| 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)) = is_iterator_singleton(
                in_ty_adt_generic_args
                    .iter()
                    .enumerate()
                    .filter(|(_index, in_ty_generic)| 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)) = 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)| 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)
    }
}

/// 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.
fn find_param_in_ty<'tcx>(
    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::Inherent, ..) = 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 args in
                // a meaningful way. So we skip it, and see improvements
                // in some UI tests.
                walk.skip_current_subtree();
            }
    }
    false
}

/// Returns `Some(iterator.next())` if it has exactly one item, and `None` otherwise.
fn is_iterator_singleton<T>(mut iterator: impl Iterator<Item = T>) -> Option<T> {
    match (iterator.next(), iterator.next()) {
        (_, Some(_)) => None,
        (first, _) => first,
    }
}
