//! In certain situations, rust automatically inserts derefs as necessary: for //! example, field accesses `foo.bar` still work when `foo` is actually a //! reference to a type with the field `bar`. This is an approximation of the //! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs). use std::mem; use chalk_ir::cast::Cast; use hir_def::lang_item::LangItem; use hir_expand::name::Name; use intern::sym; use limit::Limit; use triomphe::Arc; use crate::{ db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt, TraitEnvironment, Ty, TyBuilder, TyKind, }; static AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10); #[derive(Debug)] pub(crate) enum AutoderefKind { Builtin, Overloaded, } /// Returns types that `ty` transitively dereferences to. This function is only meant to be used /// outside `hir-ty`. /// /// It is guaranteed that: /// - the yielded types don't contain inference variables (but may contain `TyKind::Error`). /// - a type won't be yielded more than once; in other words, the returned iterator will stop if it /// detects a cycle in the deref chain. pub fn autoderef( db: &dyn HirDatabase, env: Arc, ty: Canonical, ) -> impl Iterator { let mut table = InferenceTable::new(db, env); let ty = table.instantiate_canonical(ty); let mut autoderef = Autoderef::new_no_tracking(&mut table, ty, false); let mut v = Vec::new(); while let Some((ty, _steps)) = autoderef.next() { // `ty` may contain unresolved inference variables. Since there's no chance they would be // resolved, just replace with fallback type. let resolved = autoderef.table.resolve_completely(ty); // If the deref chain contains a cycle (e.g. `A` derefs to `B` and `B` derefs to `A`), we // would revisit some already visited types. Stop here to avoid duplication. // // XXX: The recursion limit for `Autoderef` is currently 10, so `Vec::contains()` shouldn't // be too expensive. Replace this duplicate check with `FxHashSet` if it proves to be more // performant. if v.contains(&resolved) { break; } v.push(resolved); } v.into_iter() } trait TrackAutoderefSteps { fn len(&self) -> usize; fn push(&mut self, kind: AutoderefKind, ty: &Ty); } impl TrackAutoderefSteps for usize { fn len(&self) -> usize { *self } fn push(&mut self, _: AutoderefKind, _: &Ty) { *self += 1; } } impl TrackAutoderefSteps for Vec<(AutoderefKind, Ty)> { fn len(&self) -> usize { self.len() } fn push(&mut self, kind: AutoderefKind, ty: &Ty) { self.push((kind, ty.clone())); } } #[derive(Debug)] pub(crate) struct Autoderef<'table, 'db, T = Vec<(AutoderefKind, Ty)>> { pub(crate) table: &'table mut InferenceTable<'db>, ty: Ty, at_start: bool, steps: T, explicit: bool, } impl<'table, 'db> Autoderef<'table, 'db> { pub(crate) fn new(table: &'table mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self { let ty = table.resolve_ty_shallow(&ty); Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit } } pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] { &self.steps } } impl<'table, 'db> Autoderef<'table, 'db, usize> { pub(crate) fn new_no_tracking( table: &'table mut InferenceTable<'db>, ty: Ty, explicit: bool, ) -> Self { let ty = table.resolve_ty_shallow(&ty); Autoderef { table, ty, at_start: true, steps: 0, explicit } } } #[allow(private_bounds)] impl<'table, 'db, T: TrackAutoderefSteps> Autoderef<'table, 'db, T> { pub(crate) fn step_count(&self) -> usize { self.steps.len() } pub(crate) fn final_ty(&self) -> Ty { self.ty.clone() } } impl Iterator for Autoderef<'_, '_, T> { type Item = (Ty, usize); #[tracing::instrument(skip_all)] fn next(&mut self) -> Option { if mem::take(&mut self.at_start) { return Some((self.ty.clone(), 0)); } if AUTODEREF_RECURSION_LIMIT.check(self.steps.len() + 1).is_err() { return None; } let (kind, new_ty) = autoderef_step(self.table, self.ty.clone(), self.explicit)?; self.steps.push(kind, &self.ty); self.ty = new_ty; Some((self.ty.clone(), self.step_count())) } } pub(crate) fn autoderef_step( table: &mut InferenceTable<'_>, ty: Ty, explicit: bool, ) -> Option<(AutoderefKind, Ty)> { if let Some(derefed) = builtin_deref(table.db, &ty, explicit) { Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed))) } else { Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?)) } } pub(crate) fn builtin_deref<'ty>( db: &dyn HirDatabase, ty: &'ty Ty, explicit: bool, ) -> Option<&'ty Ty> { match ty.kind(Interner) { TyKind::Ref(.., ty) => Some(ty), TyKind::Raw(.., ty) if explicit => Some(ty), &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) if crate::lang_items::is_box(db, adt) => { substs.at(Interner, 0).ty(Interner) } _ => None, } } pub(crate) fn deref_by_trait( table @ &mut InferenceTable { db, .. }: &mut InferenceTable<'_>, ty: Ty, ) -> Option { let _p = tracing::info_span!("deref_by_trait").entered(); if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() { // don't try to deref unknown variables return None; } let deref_trait = db.lang_item(table.trait_env.krate, LangItem::Deref).and_then(|l| l.as_trait())?; let target = db .trait_data(deref_trait) .associated_type_by_name(&Name::new_symbol_root(sym::Target.clone()))?; let projection = { let b = TyBuilder::subst_for_def(db, deref_trait, None); if b.remaining() != 1 { // the Target type + Deref trait should only have one generic parameter, // namely Deref's Self type return None; } let deref_subst = b.push(ty).build(); TyBuilder::assoc_type_projection(db, target, Some(deref_subst)).build() }; // Check that the type implements Deref at all let trait_ref = projection.trait_ref(db); let implements_goal: Goal = trait_ref.cast(Interner); table.try_obligation(implements_goal.clone())?; table.register_obligation(implements_goal); let result = table.normalize_projection_ty(projection); Some(table.resolve_ty_shallow(&result)) }