Skip to main content

rustc_hir_analysis/
hir_wf_check.rs

1use rustc_hir::def::DefKind;
2use rustc_hir::intravisit::{self, Visitor};
3use rustc_hir::{self as hir, AmbigArg, ForeignItem, ForeignItemKind};
4use rustc_infer::infer::TyCtxtInferExt;
5use rustc_infer::traits::{ObligationCause, ObligationCauseCode, WellFormedLoc};
6use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode, fold_regions};
7use rustc_span::bug;
8use rustc_span::def_id::LocalDefId;
9use rustc_trait_selection::traits::{self, ObligationCtxt};
10use tracing::debug;
11
12use crate::collect::ItemCtxt;
13
14// Ideally, this would be in `rustc_trait_selection`, but we
15// need access to `ItemCtxt`
16pub(super) fn diagnostic_hir_wf_check<'tcx>(
17    tcx: TyCtxt<'tcx>,
18    (predicate, loc): (ty::Predicate<'tcx>, WellFormedLoc),
19) -> Option<ObligationCause<'tcx>> {
20    let def_id = match loc {
21        WellFormedLoc::Ty(def_id) => def_id,
22        WellFormedLoc::Param { function, .. } => function,
23        WellFormedLoc::HirId(_) | WellFormedLoc::None => return None,
24    };
25    let hir_id = tcx.local_def_id_to_hir_id(def_id);
26
27    // HIR wfcheck should only ever happen as part of improving an existing error
28    tcx.dcx()
29        .span_delayed_bug(tcx.def_span(def_id), "Performed HIR wfcheck without an existing error!");
30
31    let icx = ItemCtxt::new(tcx, def_id);
32
33    // To perform HIR-based WF checking, we iterate over all HIR types
34    // that occur 'inside' the item we're checking. For example,
35    // given the type `Option<MyStruct<u8>>`, we will check
36    // `Option<MyStruct<u8>>`, `MyStruct<u8>`, and `u8`.
37    // For each type, we perform a well-formed check, and see if we get
38    // an error that matches our expected predicate. We save
39    // the `ObligationCause` corresponding to the *innermost* type,
40    // which is the most specific type that we can point to.
41    // In general, the different components of an `hir::Ty` may have
42    // completely different spans due to macro invocations. Pointing
43    // to the most accurate part of the type can be the difference
44    // between a useless span (e.g. the macro invocation site)
45    // and a useful span (e.g. a user-provided type passed into the macro).
46    //
47    // This approach is quite inefficient - we redo a lot of work done
48    // by the normal WF checker. However, this code is run at most once
49    // per reported error - it will have no impact when compilation succeeds,
50    // and should only have an impact if a very large number of errors is
51    // displayed to the user.
52    struct HirWfCheck<'tcx> {
53        tcx: TyCtxt<'tcx>,
54        predicate: ty::Predicate<'tcx>,
55        cause: Option<ObligationCause<'tcx>> = None,
56        cause_depth: usize = 0,
57        icx: ItemCtxt<'tcx>,
58        def_id: LocalDefId,
59        param_env: ty::ParamEnv<'tcx>,
60        depth: usize = 0,
61    }
62
63    impl<'tcx> Visitor<'tcx> for HirWfCheck<'tcx> {
64        fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, AmbigArg>) {
65            let infcx = self.tcx.infer_ctxt().build(TypingMode::non_body_analysis());
66            let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
67
68            // We don't handle infer vars but we wouldn't handle them anyway as we're creating a
69            // fresh `InferCtxt` in this function.
70            let tcx_ty = self.icx.lower_ty(ty.as_unambig_ty());
71            // This visitor can walk into binders, resulting in the `tcx_ty` to
72            // potentially reference escaping bound variables. We simply erase
73            // those here.
74            let tcx_ty = fold_regions(self.tcx, tcx_ty, |r, _| {
75                if r.is_bound() { self.tcx.lifetimes.re_erased } else { r }
76            });
77
78            // We may be checking the WFness of a type in an opaque with a non-lifetime bound.
79            // Perhaps we could rebind all the escaping bound vars, but they're coming from
80            // arbitrary debruijn indices and aren't particularly important anyways, since they
81            // are only coming from `feature(non_lifetime_binders)` anyways.
82            if tcx_ty.has_escaping_bound_vars() {
83                return;
84            }
85
86            let cause = traits::ObligationCause::new(
87                ty.span,
88                self.def_id,
89                traits::ObligationCauseCode::WellFormed(WellFormedLoc::HirId(ty.hir_id)),
90            );
91
92            ocx.register_obligation(traits::Obligation::new(
93                self.tcx,
94                cause,
95                self.param_env,
96                ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(tcx_ty.into())),
97            ));
98
99            for error in ocx.evaluate_obligations_error_on_ambiguity() {
100                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_hir_analysis/src/hir_wf_check.rs:100",
                        "rustc_hir_analysis::hir_wf_check", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_hir_analysis/src/hir_wf_check.rs"),
                        ::tracing_core::__macro_support::Option::Some(100u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_hir_analysis::hir_wf_check"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Wf-check got error for {0:?}: {1:?}",
                                                    ty, error) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("Wf-check got error for {:?}: {:?}", ty, error);
101                if error.obligation.predicate == self.predicate {
102                    // Save the cause from the greatest depth - this corresponds
103                    // to picking more-specific types (e.g. `MyStruct<u8>`)
104                    // over less-specific types (e.g. `Option<MyStruct<u8>>`)
105                    if self.depth >= self.cause_depth {
106                        self.cause = Some(error.obligation.cause);
107                        if let hir::TyKind::TraitObject(..) = ty.kind
108                            && let DefKind::AssocTy | DefKind::AssocConst | DefKind::AssocFn =
109                                self.tcx.def_kind(self.def_id)
110                        {
111                            self.cause = Some(ObligationCause::new(
112                                ty.span,
113                                self.def_id,
114                                ObligationCauseCode::DynCompatible(ty.span),
115                            ));
116                        }
117                        self.cause_depth = self.depth
118                    }
119                }
120            }
121
122            self.depth += 1;
123            intravisit::walk_ty(self, ty);
124            self.depth -= 1;
125        }
126    }
127
128    let param_env = tcx.param_env(def_id.to_def_id());
129    let mut visitor = HirWfCheck { tcx, predicate, icx, def_id, param_env, .. };
130
131    // Get the starting `hir::Ty` using our `WellFormedLoc`.
132    // We will walk 'into' this type to try to find
133    // a more precise span for our predicate.
134    let tys = match loc {
135        WellFormedLoc::Ty(_) => match tcx.hir_node(hir_id) {
136            hir::Node::ImplItem(item) => match item.kind {
137                hir::ImplItemKind::Type(ty) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty],
138                hir::ImplItemKind::Const(ty, _) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty],
139                ref item => ::rustc_span::macros::bug_impl(None,
    format_args!("Unexpected ImplItem {0:?}", item), Location::caller())bug!("Unexpected ImplItem {:?}", item),
140            },
141            hir::Node::TraitItem(item) => match item.kind {
142                hir::TraitItemKind::Type(_, ty) => ty.into_iter().collect(),
143                hir::TraitItemKind::Const(ty, _) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty],
144                ref item => ::rustc_span::macros::bug_impl(None,
    format_args!("Unexpected TraitItem {0:?}", item), Location::caller())bug!("Unexpected TraitItem {:?}", item),
145            },
146            hir::Node::Item(item) => match item.kind {
147                hir::ItemKind::TyAlias(_, _, ty)
148                | hir::ItemKind::Static(_, _, ty, _)
149                | hir::ItemKind::Const(_, _, ty, _) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty],
150                hir::ItemKind::Impl(impl_) => match impl_.of_trait {
151                    Some(of_trait) => of_trait
152                        .trait_ref
153                        .path
154                        .segments
155                        .last()
156                        .iter()
157                        .flat_map(|seg| seg.args().args)
158                        .filter_map(|arg| {
159                            if let hir::GenericArg::Type(ty) = arg {
160                                Some(ty.as_unambig_ty())
161                            } else {
162                                None
163                            }
164                        })
165                        .chain([impl_.self_ty])
166                        .collect(),
167                    None => {
168                        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [impl_.self_ty]))vec![impl_.self_ty]
169                    }
170                },
171                ref item => ::rustc_span::macros::bug_impl(None,
    format_args!("Unexpected item {0:?}", item), Location::caller())bug!("Unexpected item {:?}", item),
172            },
173            hir::Node::Field(field) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [field.ty]))vec![field.ty],
174            hir::Node::ForeignItem(ForeignItem {
175                kind: ForeignItemKind::Static(ty, _, _), ..
176            }) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [*ty]))vec![*ty],
177            hir::Node::GenericParam(hir::GenericParam {
178                kind: hir::GenericParamKind::Type { default: Some(ty), .. },
179                ..
180            }) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [*ty]))vec![*ty],
181            hir::Node::AnonConst(_) => {
182                if let Some(const_param_id) = tcx.hir_opt_const_param_default_param_def_id(hir_id)
183                    && let hir::Node::GenericParam(hir::GenericParam {
184                        kind: hir::GenericParamKind::Const { ty, .. },
185                        ..
186                    }) = tcx.hir_node_by_def_id(const_param_id)
187                {
188                    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [*ty]))vec![*ty]
189                } else {
190                    ::alloc::vec::Vec::new()vec![]
191                }
192            }
193            ref node => ::rustc_span::macros::bug_impl(None,
    format_args!("Unexpected node {0:?}", node), Location::caller())bug!("Unexpected node {:?}", node),
194        },
195        WellFormedLoc::Param { param_idx, .. } => {
196            let fn_decl = tcx.hir_fn_decl_by_hir_id(hir_id).unwrap();
197            // Get return type
198            if param_idx as usize == fn_decl.inputs.len() {
199                match fn_decl.output {
200                    hir::FnRetTy::Return(ty) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty],
201                    // The unit type `()` is always well-formed
202                    hir::FnRetTy::DefaultReturn(_span) => ::alloc::vec::Vec::new()vec![],
203                }
204            } else {
205                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [&fn_decl.inputs[param_idx as usize]]))vec![&fn_decl.inputs[param_idx as usize]]
206            }
207        }
208        WellFormedLoc::HirId(_) | WellFormedLoc::None => return None,
209    };
210    for ty in tys {
211        visitor.visit_ty_unambig(ty);
212    }
213    visitor.cause
214}