aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/autoderef.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/autoderef.rs')
-rw-r--r--crates/hir_ty/src/autoderef.rs131
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
6use std::iter::successors;
7
8use base_db::CrateId;
9use hir_def::lang_item::LangItemTarget;
10use hir_expand::name::name;
11use log::{info, warn};
12
13use crate::{
14 db::HirDatabase,
15 traits::{InEnvironment, Solution},
16 utils::generics,
17 BoundVar, Canonical, DebruijnIndex, Obligation, Substs, TraitRef, Ty,
18};
19
20const AUTODEREF_RECURSION_LIMIT: usize = 10;
21
22pub 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
34pub(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
46fn 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}