about summary refs log tree commit diff
path: root/compiler/rustc_hir_analysis/src/hir_wf_check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_hir_analysis/src/hir_wf_check.rs')
-rw-r--r--compiler/rustc_hir_analysis/src/hir_wf_check.rs188
1 files changed, 188 insertions, 0 deletions
diff --git a/compiler/rustc_hir_analysis/src/hir_wf_check.rs b/compiler/rustc_hir_analysis/src/hir_wf_check.rs
new file mode 100644
index 00000000000..7b080dc2942
--- /dev/null
+++ b/compiler/rustc_hir_analysis/src/hir_wf_check.rs
@@ -0,0 +1,188 @@
+use crate::collect::ItemCtxt;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{ForeignItem, ForeignItemKind, HirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::{ObligationCause, WellFormedLoc};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, Region, ToPredicate, TyCtxt, TypeFoldable, TypeFolder};
+use rustc_trait_selection::traits;
+
+pub fn provide(providers: &mut Providers) {
+    *providers = Providers { diagnostic_hir_wf_check, ..*providers };
+}
+
+// Ideally, this would be in `rustc_trait_selection`, but we
+// need access to `ItemCtxt`
+fn diagnostic_hir_wf_check<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    (predicate, loc): (ty::Predicate<'tcx>, WellFormedLoc),
+) -> Option<ObligationCause<'tcx>> {
+    let hir = tcx.hir();
+
+    let def_id = match loc {
+        WellFormedLoc::Ty(def_id) => def_id,
+        WellFormedLoc::Param { function, param_idx: _ } => function,
+    };
+    let hir_id = hir.local_def_id_to_hir_id(def_id);
+
+    // HIR wfcheck should only ever happen as part of improving an existing error
+    tcx.sess
+        .delay_span_bug(tcx.def_span(def_id), "Performed HIR wfcheck without an existing error!");
+
+    let icx = ItemCtxt::new(tcx, def_id.to_def_id());
+
+    // To perform HIR-based WF checking, we iterate over all HIR types
+    // that occur 'inside' the item we're checking. For example,
+    // given the type `Option<MyStruct<u8>>`, we will check
+    // `Option<MyStruct<u8>>`, `MyStruct<u8>`, and `u8`.
+    // For each type, we perform a well-formed check, and see if we get
+    // an error that matches our expected predicate. We save
+    // the `ObligationCause` corresponding to the *innermost* type,
+    // which is the most specific type that we can point to.
+    // In general, the different components of an `hir::Ty` may have
+    // completely different spans due to macro invocations. Pointing
+    // to the most accurate part of the type can be the difference
+    // between a useless span (e.g. the macro invocation site)
+    // and a useful span (e.g. a user-provided type passed into the macro).
+    //
+    // This approach is quite inefficient - we redo a lot of work done
+    // by the normal WF checker. However, this code is run at most once
+    // per reported error - it will have no impact when compilation succeeds,
+    // and should only have an impact if a very large number of errors is
+    // displayed to the user.
+    struct HirWfCheck<'tcx> {
+        tcx: TyCtxt<'tcx>,
+        predicate: ty::Predicate<'tcx>,
+        cause: Option<ObligationCause<'tcx>>,
+        cause_depth: usize,
+        icx: ItemCtxt<'tcx>,
+        hir_id: HirId,
+        param_env: ty::ParamEnv<'tcx>,
+        depth: usize,
+    }
+
+    impl<'tcx> Visitor<'tcx> for HirWfCheck<'tcx> {
+        fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) {
+            self.tcx.infer_ctxt().enter(|infcx| {
+                let tcx_ty =
+                    self.icx.to_ty(ty).fold_with(&mut EraseAllBoundRegions { tcx: self.tcx });
+                let cause = traits::ObligationCause::new(
+                    ty.span,
+                    self.hir_id,
+                    traits::ObligationCauseCode::WellFormed(None),
+                );
+                let errors = traits::fully_solve_obligation(
+                    &infcx,
+                    traits::Obligation::new(
+                        cause,
+                        self.param_env,
+                        ty::Binder::dummy(ty::PredicateKind::WellFormed(tcx_ty.into()))
+                            .to_predicate(self.tcx),
+                    ),
+                );
+                if !errors.is_empty() {
+                    debug!("Wf-check got errors for {:?}: {:?}", ty, errors);
+                    for error in errors {
+                        if error.obligation.predicate == self.predicate {
+                            // Save the cause from the greatest depth - this corresponds
+                            // to picking more-specific types (e.g. `MyStruct<u8>`)
+                            // over less-specific types (e.g. `Option<MyStruct<u8>>`)
+                            if self.depth >= self.cause_depth {
+                                self.cause = Some(error.obligation.cause);
+                                self.cause_depth = self.depth
+                            }
+                        }
+                    }
+                }
+            });
+            self.depth += 1;
+            intravisit::walk_ty(self, ty);
+            self.depth -= 1;
+        }
+    }
+
+    let mut visitor = HirWfCheck {
+        tcx,
+        predicate,
+        cause: None,
+        cause_depth: 0,
+        icx,
+        hir_id,
+        param_env: tcx.param_env(def_id.to_def_id()),
+        depth: 0,
+    };
+
+    // Get the starting `hir::Ty` using our `WellFormedLoc`.
+    // We will walk 'into' this type to try to find
+    // a more precise span for our predicate.
+    let ty = match loc {
+        WellFormedLoc::Ty(_) => match hir.get(hir_id) {
+            hir::Node::ImplItem(item) => match item.kind {
+                hir::ImplItemKind::TyAlias(ty) => Some(ty),
+                hir::ImplItemKind::Const(ty, _) => Some(ty),
+                ref item => bug!("Unexpected ImplItem {:?}", item),
+            },
+            hir::Node::TraitItem(item) => match item.kind {
+                hir::TraitItemKind::Type(_, ty) => ty,
+                hir::TraitItemKind::Const(ty, _) => Some(ty),
+                ref item => bug!("Unexpected TraitItem {:?}", item),
+            },
+            hir::Node::Item(item) => match item.kind {
+                hir::ItemKind::Static(ty, _, _) | hir::ItemKind::Const(ty, _) => Some(ty),
+                hir::ItemKind::Impl(ref impl_) => {
+                    assert!(impl_.of_trait.is_none(), "Unexpected trait impl: {:?}", impl_);
+                    Some(impl_.self_ty)
+                }
+                ref item => bug!("Unexpected item {:?}", item),
+            },
+            hir::Node::Field(field) => Some(field.ty),
+            hir::Node::ForeignItem(ForeignItem {
+                kind: ForeignItemKind::Static(ty, _), ..
+            }) => Some(*ty),
+            hir::Node::GenericParam(hir::GenericParam {
+                kind: hir::GenericParamKind::Type { default: Some(ty), .. },
+                ..
+            }) => Some(*ty),
+            ref node => bug!("Unexpected node {:?}", node),
+        },
+        WellFormedLoc::Param { function: _, param_idx } => {
+            let fn_decl = hir.fn_decl_by_hir_id(hir_id).unwrap();
+            // Get return type
+            if param_idx as usize == fn_decl.inputs.len() {
+                match fn_decl.output {
+                    hir::FnRetTy::Return(ty) => Some(ty),
+                    // The unit type `()` is always well-formed
+                    hir::FnRetTy::DefaultReturn(_span) => None,
+                }
+            } else {
+                Some(&fn_decl.inputs[param_idx as usize])
+            }
+        }
+    };
+    if let Some(ty) = ty {
+        visitor.visit_ty(ty);
+    }
+    visitor.cause
+}
+
+struct EraseAllBoundRegions<'tcx> {
+    tcx: TyCtxt<'tcx>,
+}
+
+// Higher ranked regions are complicated.
+// To make matters worse, the HIR WF check can instantiate them
+// outside of a `Binder`, due to the way we (ab)use
+// `ItemCtxt::to_ty`. To make things simpler, we just erase all
+// of them, regardless of depth. At worse, this will give
+// us an inaccurate span for an error message, but cannot
+// lead to unsoundness (we call `delay_span_bug` at the start
+// of `diagnostic_hir_wf_check`).
+impl<'tcx> TypeFolder<'tcx> for EraseAllBoundRegions<'tcx> {
+    fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
+        self.tcx
+    }
+    fn fold_region(&mut self, r: Region<'tcx>) -> Region<'tcx> {
+        if r.is_late_bound() { self.tcx.lifetimes.re_erased } else { r }
+    }
+}