diff options
Diffstat (limited to 'crates/ra_hir_ty/src/autoderef.rs')
-rw-r--r-- | crates/ra_hir_ty/src/autoderef.rs | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/crates/ra_hir_ty/src/autoderef.rs b/crates/ra_hir_ty/src/autoderef.rs new file mode 100644 index 000000000..9d1d4e48c --- /dev/null +++ b/crates/ra_hir_ty/src/autoderef.rs | |||
@@ -0,0 +1,108 @@ | |||
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 hir_def::lang_item::LangItemTarget; | ||
9 | use hir_expand::name; | ||
10 | use log::{info, warn}; | ||
11 | use ra_db::CrateId; | ||
12 | |||
13 | use crate::db::HirDatabase; | ||
14 | |||
15 | use super::{ | ||
16 | traits::{InEnvironment, Solution}, | ||
17 | Canonical, Substs, Ty, TypeWalk, | ||
18 | }; | ||
19 | |||
20 | const AUTODEREF_RECURSION_LIMIT: usize = 10; | ||
21 | |||
22 | pub fn autoderef<'a>( | ||
23 | db: &'a impl HirDatabase, | ||
24 | krate: Option<CrateId>, | ||
25 | ty: InEnvironment<Canonical<Ty>>, | ||
26 | ) -> impl Iterator<Item = Canonical<Ty>> + 'a { | ||
27 | let InEnvironment { value: ty, environment } = ty; | ||
28 | successors(Some(ty), move |ty| { | ||
29 | deref(db, krate?, InEnvironment { value: ty, environment: environment.clone() }) | ||
30 | }) | ||
31 | .take(AUTODEREF_RECURSION_LIMIT) | ||
32 | } | ||
33 | |||
34 | pub(crate) fn deref( | ||
35 | db: &impl HirDatabase, | ||
36 | krate: CrateId, | ||
37 | ty: InEnvironment<&Canonical<Ty>>, | ||
38 | ) -> Option<Canonical<Ty>> { | ||
39 | if let Some(derefed) = ty.value.value.builtin_deref() { | ||
40 | Some(Canonical { value: derefed, num_vars: ty.value.num_vars }) | ||
41 | } else { | ||
42 | deref_by_trait(db, krate, ty) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | fn deref_by_trait( | ||
47 | db: &impl HirDatabase, | ||
48 | krate: CrateId, | ||
49 | ty: InEnvironment<&Canonical<Ty>>, | ||
50 | ) -> Option<Canonical<Ty>> { | ||
51 | let deref_trait = match db.lang_item(krate.into(), "deref".into())? { | ||
52 | LangItemTarget::TraitId(it) => it, | ||
53 | _ => return None, | ||
54 | }; | ||
55 | let target = db.trait_data(deref_trait).associated_type_by_name(&name::TARGET_TYPE)?; | ||
56 | |||
57 | let generic_params = db.generic_params(target.into()); | ||
58 | if generic_params.count_params_including_parent() != 1 { | ||
59 | // the Target type + Deref trait should only have one generic parameter, | ||
60 | // namely Deref's Self type | ||
61 | return None; | ||
62 | } | ||
63 | |||
64 | // FIXME make the Canonical handling nicer | ||
65 | |||
66 | let parameters = Substs::build_for_generics(&generic_params) | ||
67 | .push(ty.value.value.clone().shift_bound_vars(1)) | ||
68 | .build(); | ||
69 | |||
70 | let projection = super::traits::ProjectionPredicate { | ||
71 | ty: Ty::Bound(0), | ||
72 | projection_ty: super::ProjectionTy { associated_ty: target, parameters }, | ||
73 | }; | ||
74 | |||
75 | let obligation = super::Obligation::Projection(projection); | ||
76 | |||
77 | let in_env = InEnvironment { value: obligation, environment: ty.environment }; | ||
78 | |||
79 | let canonical = super::Canonical { num_vars: 1 + ty.value.num_vars, value: in_env }; | ||
80 | |||
81 | let solution = db.trait_solve(krate.into(), canonical)?; | ||
82 | |||
83 | match &solution { | ||
84 | Solution::Unique(vars) => { | ||
85 | // FIXME: vars may contain solutions for any inference variables | ||
86 | // that happened to be inside ty. To correctly handle these, we | ||
87 | // would have to pass the solution up to the inference context, but | ||
88 | // that requires a larger refactoring (especially if the deref | ||
89 | // happens during method resolution). So for the moment, we just | ||
90 | // check that we're not in the situation we're we would actually | ||
91 | // need to handle the values of the additional variables, i.e. | ||
92 | // they're just being 'passed through'. In the 'standard' case where | ||
93 | // we have `impl<T> Deref for Foo<T> { Target = T }`, that should be | ||
94 | // the case. | ||
95 | for i in 1..vars.0.num_vars { | ||
96 | if vars.0.value[i] != Ty::Bound((i - 1) as u32) { | ||
97 | warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.value, solution); | ||
98 | return None; | ||
99 | } | ||
100 | } | ||
101 | Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars }) | ||
102 | } | ||
103 | Solution::Ambig(_) => { | ||
104 | info!("Ambiguous solution for derefing {:?}: {:?}", ty.value, solution); | ||
105 | None | ||
106 | } | ||
107 | } | ||
108 | } | ||