diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_typeck/src/hir_wf_check.rs | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/compiler/rustc_typeck/src/hir_wf_check.rs b/compiler/rustc_typeck/src/hir_wf_check.rs new file mode 100644 index 000000000..55c7a15f9 --- /dev/null +++ b/compiler/rustc_typeck/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::TraitEngine; +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::{self, TraitEngineExt}; + +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 mut fulfill = <dyn TraitEngine<'tcx>>::new(self.tcx); + 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), + ); + fulfill.register_predicate_obligation( + &infcx, + traits::Obligation::new( + cause, + self.param_env, + ty::Binder::dummy(ty::PredicateKind::WellFormed(tcx_ty.into())) + .to_predicate(self.tcx), + ), + ); + + let errors = fulfill.select_all_or_error(&infcx); + 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), + 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 } + } +} |