diff options
author | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/hir_ty/src/autoderef.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/hir_ty/src/autoderef.rs')
-rw-r--r-- | crates/hir_ty/src/autoderef.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/crates/hir_ty/src/autoderef.rs b/crates/hir_ty/src/autoderef.rs new file mode 100644 index 000000000..ece68183e --- /dev/null +++ b/crates/hir_ty/src/autoderef.rs | |||
@@ -0,0 +1,131 @@ | |||
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 hir_def::lang_item::LangItemTarget; | ||
10 | use hir_expand::name::name; | ||
11 | use log::{info, warn}; | ||
12 | |||
13 | use crate::{ | ||
14 | db::HirDatabase, | ||
15 | traits::{InEnvironment, Solution}, | ||
16 | utils::generics, | ||
17 | BoundVar, Canonical, DebruijnIndex, Obligation, Substs, TraitRef, Ty, | ||
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 { 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: &dyn 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, kinds: ty.value.kinds.clone() }) | ||
41 | } else { | ||
42 | deref_by_trait(db, krate, ty) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | fn deref_by_trait( | ||
47 | db: &dyn HirDatabase, | ||
48 | krate: CrateId, | ||
49 | ty: InEnvironment<&Canonical<Ty>>, | ||
50 | ) -> Option<Canonical<Ty>> { | ||
51 | let deref_trait = match db.lang_item(krate, "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])?; | ||
56 | |||
57 | let generic_params = generics(db.upcast(), target.into()); | ||
58 | if generic_params.len() != 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 / bound var handling nicer | ||
65 | |||
66 | let parameters = | ||
67 | Substs::build_for_generics(&generic_params).push(ty.value.value.clone()).build(); | ||
68 | |||
69 | // Check that the type implements Deref at all | ||
70 | let trait_ref = TraitRef { trait_: deref_trait, substs: parameters.clone() }; | ||
71 | let implements_goal = Canonical { | ||
72 | kinds: ty.value.kinds.clone(), | ||
73 | value: InEnvironment { | ||
74 | value: Obligation::Trait(trait_ref), | ||
75 | environment: ty.environment.clone(), | ||
76 | }, | ||
77 | }; | ||
78 | if db.trait_solve(krate, implements_goal).is_none() { | ||
79 | return None; | ||
80 | } | ||
81 | |||
82 | // Now do the assoc type projection | ||
83 | let projection = super::traits::ProjectionPredicate { | ||
84 | ty: Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, ty.value.kinds.len())), | ||
85 | projection_ty: super::ProjectionTy { associated_ty: target, parameters }, | ||
86 | }; | ||
87 | |||
88 | let obligation = super::Obligation::Projection(projection); | ||
89 | |||
90 | let in_env = InEnvironment { value: obligation, environment: ty.environment }; | ||
91 | |||
92 | let canonical = | ||
93 | Canonical::new(in_env, ty.value.kinds.iter().copied().chain(Some(super::TyKind::General))); | ||
94 | |||
95 | let solution = db.trait_solve(krate, canonical)?; | ||
96 | |||
97 | match &solution { | ||
98 | Solution::Unique(vars) => { | ||
99 | // FIXME: vars may contain solutions for any inference variables | ||
100 | // that happened to be inside ty. To correctly handle these, we | ||
101 | // would have to pass the solution up to the inference context, but | ||
102 | // that requires a larger refactoring (especially if the deref | ||
103 | // happens during method resolution). So for the moment, we just | ||
104 | // check that we're not in the situation we're we would actually | ||
105 | // need to handle the values of the additional variables, i.e. | ||
106 | // they're just being 'passed through'. In the 'standard' case where | ||
107 | // we have `impl<T> Deref for Foo<T> { Target = T }`, that should be | ||
108 | // the case. | ||
109 | |||
110 | // FIXME: if the trait solver decides to truncate the type, these | ||
111 | // assumptions will be broken. We would need to properly introduce | ||
112 | // new variables in that case | ||
113 | |||
114 | for i in 1..vars.0.kinds.len() { | ||
115 | if vars.0.value[i - 1] != Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, i - 1)) | ||
116 | { | ||
117 | warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.value, solution); | ||
118 | return None; | ||
119 | } | ||
120 | } | ||
121 | Some(Canonical { | ||
122 | value: vars.0.value[vars.0.value.len() - 1].clone(), | ||
123 | kinds: vars.0.kinds.clone(), | ||
124 | }) | ||
125 | } | ||
126 | Solution::Ambig(_) => { | ||
127 | info!("Ambiguous solution for derefing {:?}: {:?}", ty.value, solution); | ||
128 | None | ||
129 | } | ||
130 | } | ||
131 | } | ||