rustc_hir_analysis/
hir_wf_check.rs

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