]> git.lizzy.rs Git - rust.git/blob - crates/hir_ty/src/autoderef.rs
clippy::redundant_closure
[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::iter::successors;
7
8 use base_db::CrateId;
9 use chalk_ir::{cast::Cast, fold::Fold, interner::HasInterner, VariableKind};
10 use hir_def::lang_item::LangItemTarget;
11 use hir_expand::name::name;
12 use log::{info, warn};
13
14 use crate::{
15     db::HirDatabase, static_lifetime, AliasEq, AliasTy, BoundVar, Canonical, CanonicalVarKinds,
16     DebruijnIndex, InEnvironment, Interner, ProjectionTyExt, Solution, Substitution, Ty, TyBuilder,
17     TyKind,
18 };
19
20 const AUTODEREF_RECURSION_LIMIT: usize = 10;
21
22 pub fn autoderef<'a>(
23     db: &'a dyn HirDatabase,
24     krate: Option<CrateId>,
25     ty: InEnvironment<Canonical<Ty>>,
26 ) -> impl Iterator<Item = Canonical<Ty>> + 'a {
27     let InEnvironment { goal: ty, environment } = ty;
28     successors(Some(ty), move |ty| {
29         deref(db, krate?, InEnvironment { goal: ty, environment: environment.clone() })
30     })
31     .take(AUTODEREF_RECURSION_LIMIT)
32 }
33
34 pub(crate) fn deref(
35     db: &dyn HirDatabase,
36     krate: CrateId,
37     ty: InEnvironment<&Canonical<Ty>>,
38 ) -> Option<Canonical<Ty>> {
39     let _p = profile::span("deref");
40     if let Some(derefed) = builtin_deref(&ty.goal.value) {
41         Some(Canonical { value: derefed, binders: ty.goal.binders.clone() })
42     } else {
43         deref_by_trait(db, krate, ty)
44     }
45 }
46
47 fn builtin_deref(ty: &Ty) -> Option<Ty> {
48     match ty.kind(&Interner) {
49         TyKind::Ref(.., ty) => Some(ty.clone()),
50         TyKind::Raw(.., ty) => Some(ty.clone()),
51         _ => None,
52     }
53 }
54
55 fn deref_by_trait(
56     db: &dyn HirDatabase,
57     krate: CrateId,
58     ty: InEnvironment<&Canonical<Ty>>,
59 ) -> Option<Canonical<Ty>> {
60     let _p = profile::span("deref_by_trait");
61     let deref_trait = match db.lang_item(krate, "deref".into())? {
62         LangItemTarget::TraitId(it) => it,
63         _ => return None,
64     };
65     let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?;
66
67     let projection = {
68         let b = TyBuilder::assoc_type_projection(db, target);
69         if b.remaining() != 1 {
70             // the Target type + Deref trait should only have one generic parameter,
71             // namely Deref's Self type
72             return None;
73         }
74         b.push(ty.goal.value.clone()).build()
75     };
76
77     // FIXME make the Canonical / bound var handling nicer
78
79     // Check that the type implements Deref at all
80     let trait_ref = projection.trait_ref(db);
81     let implements_goal = Canonical {
82         binders: ty.goal.binders.clone(),
83         value: InEnvironment {
84             goal: trait_ref.cast(&Interner),
85             environment: ty.environment.clone(),
86         },
87     };
88     if db.trait_solve(krate, implements_goal).is_none() {
89         return None;
90     }
91
92     // Now do the assoc type projection
93     let alias_eq = AliasEq {
94         alias: AliasTy::Projection(projection),
95         ty: TyKind::BoundVar(BoundVar::new(
96             DebruijnIndex::INNERMOST,
97             ty.goal.binders.len(&Interner),
98         ))
99         .intern(&Interner),
100     };
101
102     let in_env = InEnvironment { goal: alias_eq.cast(&Interner), environment: ty.environment };
103
104     let canonical = Canonical {
105         value: in_env,
106         binders: CanonicalVarKinds::from_iter(
107             &Interner,
108             ty.goal.binders.iter(&Interner).cloned().chain(Some(chalk_ir::WithKind::new(
109                 VariableKind::Ty(chalk_ir::TyVariableKind::General),
110                 chalk_ir::UniverseIndex::ROOT,
111             ))),
112         ),
113     };
114
115     let solution = db.trait_solve(krate, canonical)?;
116
117     match &solution {
118         Solution::Unique(vars) => {
119             // FIXME: vars may contain solutions for any inference variables
120             // that happened to be inside ty. To correctly handle these, we
121             // would have to pass the solution up to the inference context, but
122             // that requires a larger refactoring (especially if the deref
123             // happens during method resolution). So for the moment, we just
124             // check that we're not in the situation we're we would actually
125             // need to handle the values of the additional variables, i.e.
126             // they're just being 'passed through'. In the 'standard' case where
127             // we have `impl<T> Deref for Foo<T> { Target = T }`, that should be
128             // the case.
129
130             // FIXME: if the trait solver decides to truncate the type, these
131             // assumptions will be broken. We would need to properly introduce
132             // new variables in that case
133
134             for i in 1..vars.binders.len(&Interner) {
135                 if vars.value.subst.at(&Interner, i - 1).assert_ty_ref(&Interner).kind(&Interner)
136                     != &TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, i - 1))
137                 {
138                     warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.goal, solution);
139                     return None;
140                 }
141             }
142             // FIXME: we remove lifetime variables here since they can confuse
143             // the method resolution code later
144             Some(fixup_lifetime_variables(Canonical {
145                 value: vars
146                     .value
147                     .subst
148                     .at(&Interner, vars.value.subst.len(&Interner) - 1)
149                     .assert_ty_ref(&Interner)
150                     .clone(),
151                 binders: vars.binders.clone(),
152             }))
153         }
154         Solution::Ambig(_) => {
155             info!("Ambiguous solution for derefing {:?}: {:?}", ty.goal, solution);
156             None
157         }
158     }
159 }
160
161 fn fixup_lifetime_variables<T: Fold<Interner, Result = T> + HasInterner<Interner = Interner>>(
162     c: Canonical<T>,
163 ) -> Canonical<T> {
164     // Removes lifetime variables from the Canonical, replacing them by static lifetimes.
165     let mut i = 0;
166     let subst = Substitution::from_iter(
167         &Interner,
168         c.binders.iter(&Interner).map(|vk| match vk.kind {
169             VariableKind::Ty(_) => {
170                 let index = i;
171                 i += 1;
172                 BoundVar::new(DebruijnIndex::INNERMOST, index).to_ty(&Interner).cast(&Interner)
173             }
174             VariableKind::Lifetime => static_lifetime().cast(&Interner),
175             VariableKind::Const(_) => unimplemented!(),
176         }),
177     );
178     let binders = CanonicalVarKinds::from_iter(
179         &Interner,
180         c.binders.iter(&Interner).filter(|vk| match vk.kind {
181             VariableKind::Ty(_) => true,
182             VariableKind::Lifetime => false,
183             VariableKind::Const(_) => true,
184         }),
185     );
186     let value = subst.apply(c.value, &Interner);
187     Canonical { binders, value }
188 }