]> git.lizzy.rs Git - rust.git/blob - crates/hir-ty/src/autoderef.rs
Run cargo fix --edition-idioms
[rust.git] / crates / hir-ty / src / autoderef.rs
1 //! In certain situations, rust automatically inserts derefs as necessary: for
2 //! example, field accesses `foo.bar` still work when `foo` is actually a
3 //! reference to a type with the field `bar`. This is an approximation of the
4 //! logic in rustc (which lives in librustc_typeck/check/autoderef.rs).
5
6 use std::sync::Arc;
7
8 use chalk_ir::cast::Cast;
9 use hir_expand::name::name;
10 use limit::Limit;
11 use syntax::SmolStr;
12
13 use crate::{
14     db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt,
15     TraitEnvironment, Ty, TyBuilder, TyKind,
16 };
17
18 static AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10);
19
20 pub(crate) enum AutoderefKind {
21     Builtin,
22     Overloaded,
23 }
24
25 pub(crate) struct Autoderef<'a, 'db> {
26     pub(crate) table: &'a mut InferenceTable<'db>,
27     ty: Ty,
28     at_start: bool,
29     steps: Vec<(AutoderefKind, Ty)>,
30 }
31
32 impl<'a, 'db> Autoderef<'a, 'db> {
33     pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty) -> Self {
34         let ty = table.resolve_ty_shallow(&ty);
35         Autoderef { table, ty, at_start: true, steps: Vec::new() }
36     }
37
38     pub(crate) fn step_count(&self) -> usize {
39         self.steps.len()
40     }
41
42     pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] {
43         &self.steps
44     }
45
46     pub(crate) fn final_ty(&self) -> Ty {
47         self.ty.clone()
48     }
49 }
50
51 impl Iterator for Autoderef<'_, '_> {
52     type Item = (Ty, usize);
53
54     fn next(&mut self) -> Option<Self::Item> {
55         if self.at_start {
56             self.at_start = false;
57             return Some((self.ty.clone(), 0));
58         }
59
60         if AUTODEREF_RECURSION_LIMIT.check(self.steps.len() + 1).is_err() {
61             return None;
62         }
63
64         let (kind, new_ty) = autoderef_step(self.table, self.ty.clone())?;
65
66         self.steps.push((kind, self.ty.clone()));
67         self.ty = new_ty;
68
69         Some((self.ty.clone(), self.step_count()))
70     }
71 }
72
73 pub(crate) fn autoderef_step(table: &mut InferenceTable<'_>, ty: Ty) -> Option<(AutoderefKind, Ty)> {
74     if let Some(derefed) = builtin_deref(&ty) {
75         Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
76     } else {
77         Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
78     }
79 }
80
81 // FIXME: replace uses of this with Autoderef above
82 pub fn autoderef<'a>(
83     db: &'a dyn HirDatabase,
84     env: Arc<TraitEnvironment>,
85     ty: Canonical<Ty>,
86 ) -> impl Iterator<Item = Canonical<Ty>> + 'a {
87     let mut table = InferenceTable::new(db, env);
88     let ty = table.instantiate_canonical(ty);
89     let mut autoderef = Autoderef::new(&mut table, ty);
90     let mut v = Vec::new();
91     while let Some((ty, _steps)) = autoderef.next() {
92         v.push(autoderef.table.canonicalize(ty).value);
93     }
94     v.into_iter()
95 }
96
97 pub(crate) fn deref(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> {
98     let _p = profile::span("deref");
99     autoderef_step(table, ty).map(|(_, ty)| ty)
100 }
101
102 fn builtin_deref(ty: &Ty) -> Option<&Ty> {
103     match ty.kind(Interner) {
104         TyKind::Ref(.., ty) => Some(ty),
105         TyKind::Raw(.., ty) => Some(ty),
106         _ => None,
107     }
108 }
109
110 fn deref_by_trait(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> {
111     let _p = profile::span("deref_by_trait");
112     if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() {
113         // don't try to deref unknown variables
114         return None;
115     }
116
117     let db = table.db;
118     let deref_trait = db
119         .lang_item(table.trait_env.krate, SmolStr::new_inline("deref"))
120         .and_then(|l| l.as_trait())?;
121     let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?;
122
123     let projection = {
124         let b = TyBuilder::assoc_type_projection(db, target);
125         if b.remaining() != 1 {
126             // the Target type + Deref trait should only have one generic parameter,
127             // namely Deref's Self type
128             return None;
129         }
130         b.push(ty).build()
131     };
132
133     // Check that the type implements Deref at all
134     let trait_ref = projection.trait_ref(db);
135     let implements_goal: Goal = trait_ref.cast(Interner);
136     table.try_obligation(implements_goal.clone())?;
137
138     table.register_obligation(implements_goal);
139
140     let result = table.normalize_projection_ty(projection);
141     Some(table.resolve_ty_shallow(&result))
142 }