Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/autoderef.rs')
| -rw-r--r-- | crates/hir-ty/src/autoderef.rs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs new file mode 100644 index 0000000000..22cb856535 --- /dev/null +++ b/crates/hir-ty/src/autoderef.rs @@ -0,0 +1,142 @@ +//! 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 librustc_typeck/check/autoderef.rs). + +use std::sync::Arc; + +use chalk_ir::cast::Cast; +use hir_expand::name::name; +use limit::Limit; +use syntax::SmolStr; + +use crate::{ + db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt, + TraitEnvironment, Ty, TyBuilder, TyKind, +}; + +static AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10); + +pub(crate) enum AutoderefKind { + Builtin, + Overloaded, +} + +pub(crate) struct Autoderef<'a, 'db> { + pub(crate) table: &'a mut InferenceTable<'db>, + ty: Ty, + at_start: bool, + steps: Vec<(AutoderefKind, Ty)>, +} + +impl<'a, 'db> Autoderef<'a, 'db> { + pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty) -> Self { + let ty = table.resolve_ty_shallow(&ty); + Autoderef { table, ty, at_start: true, steps: Vec::new() } + } + + pub(crate) fn step_count(&self) -> usize { + self.steps.len() + } + + pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] { + &self.steps + } + + pub(crate) fn final_ty(&self) -> Ty { + self.ty.clone() + } +} + +impl Iterator for Autoderef<'_, '_> { + type Item = (Ty, usize); + + fn next(&mut self) -> Option<Self::Item> { + if self.at_start { + self.at_start = false; + 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.steps.push((kind, self.ty.clone())); + self.ty = new_ty; + + Some((self.ty.clone(), self.step_count())) + } +} + +pub(crate) fn autoderef_step(table: &mut InferenceTable, ty: Ty) -> Option<(AutoderefKind, Ty)> { + if let Some(derefed) = builtin_deref(&ty) { + Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed))) + } else { + Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?)) + } +} + +// FIXME: replace uses of this with Autoderef above +pub fn autoderef<'a>( + db: &'a dyn HirDatabase, + env: Arc<TraitEnvironment>, + ty: Canonical<Ty>, +) -> impl Iterator<Item = Canonical<Ty>> + 'a { + let mut table = InferenceTable::new(db, env); + let ty = table.instantiate_canonical(ty); + let mut autoderef = Autoderef::new(&mut table, ty); + let mut v = Vec::new(); + while let Some((ty, _steps)) = autoderef.next() { + v.push(autoderef.table.canonicalize(ty).value); + } + v.into_iter() +} + +pub(crate) fn deref(table: &mut InferenceTable, ty: Ty) -> Option<Ty> { + let _p = profile::span("deref"); + autoderef_step(table, ty).map(|(_, ty)| ty) +} + +fn builtin_deref(ty: &Ty) -> Option<&Ty> { + match ty.kind(Interner) { + TyKind::Ref(.., ty) => Some(ty), + TyKind::Raw(.., ty) => Some(ty), + _ => None, + } +} + +fn deref_by_trait(table: &mut InferenceTable, ty: Ty) -> Option<Ty> { + let _p = profile::span("deref_by_trait"); + if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() { + // don't try to deref unknown variables + return None; + } + + let db = table.db; + let deref_trait = db + .lang_item(table.trait_env.krate, SmolStr::new_inline("deref")) + .and_then(|l| l.as_trait())?; + let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?; + + let projection = { + let b = TyBuilder::assoc_type_projection(db, target); + if b.remaining() != 1 { + // the Target type + Deref trait should only have one generic parameter, + // namely Deref's Self type + return None; + } + b.push(ty).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)) +} |