aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/infer/unify.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_ty/src/infer/unify.rs')
-rw-r--r--crates/ra_hir_ty/src/infer/unify.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs
new file mode 100644
index 000000000..f3a875678
--- /dev/null
+++ b/crates/ra_hir_ty/src/infer/unify.rs
@@ -0,0 +1,162 @@
1//! Unification and canonicalization logic.
2
3use super::{InferenceContext, Obligation};
4use crate::{
5 db::HirDatabase, utils::make_mut_slice, Canonical, InEnvironment, InferTy, ProjectionPredicate,
6 ProjectionTy, Substs, TraitRef, Ty, TypeWalk,
7};
8
9impl<'a, D: HirDatabase> InferenceContext<'a, D> {
10 pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D>
11 where
12 'a: 'b,
13 {
14 Canonicalizer { ctx: self, free_vars: Vec::new(), var_stack: Vec::new() }
15 }
16}
17
18pub(super) struct Canonicalizer<'a, 'b, D: HirDatabase>
19where
20 'a: 'b,
21{
22 ctx: &'b mut InferenceContext<'a, D>,
23 free_vars: Vec<InferTy>,
24 /// A stack of type variables that is used to detect recursive types (which
25 /// are an error, but we need to protect against them to avoid stack
26 /// overflows).
27 var_stack: Vec<super::TypeVarId>,
28}
29
30pub(super) struct Canonicalized<T> {
31 pub value: Canonical<T>,
32 free_vars: Vec<InferTy>,
33}
34
35impl<'a, 'b, D: HirDatabase> Canonicalizer<'a, 'b, D>
36where
37 'a: 'b,
38{
39 fn add(&mut self, free_var: InferTy) -> usize {
40 self.free_vars.iter().position(|&v| v == free_var).unwrap_or_else(|| {
41 let next_index = self.free_vars.len();
42 self.free_vars.push(free_var);
43 next_index
44 })
45 }
46
47 fn do_canonicalize_ty(&mut self, ty: Ty) -> Ty {
48 ty.fold(&mut |ty| match ty {
49 Ty::Infer(tv) => {
50 let inner = tv.to_inner();
51 if self.var_stack.contains(&inner) {
52 // recursive type
53 return tv.fallback_value();
54 }
55 if let Some(known_ty) =
56 self.ctx.var_unification_table.inlined_probe_value(inner).known()
57 {
58 self.var_stack.push(inner);
59 let result = self.do_canonicalize_ty(known_ty.clone());
60 self.var_stack.pop();
61 result
62 } else {
63 let root = self.ctx.var_unification_table.find(inner);
64 let free_var = match tv {
65 InferTy::TypeVar(_) => InferTy::TypeVar(root),
66 InferTy::IntVar(_) => InferTy::IntVar(root),
67 InferTy::FloatVar(_) => InferTy::FloatVar(root),
68 InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root),
69 };
70 let position = self.add(free_var);
71 Ty::Bound(position as u32)
72 }
73 }
74 _ => ty,
75 })
76 }
77
78 fn do_canonicalize_trait_ref(&mut self, mut trait_ref: TraitRef) -> TraitRef {
79 for ty in make_mut_slice(&mut trait_ref.substs.0) {
80 *ty = self.do_canonicalize_ty(ty.clone());
81 }
82 trait_ref
83 }
84
85 fn into_canonicalized<T>(self, result: T) -> Canonicalized<T> {
86 Canonicalized {
87 value: Canonical { value: result, num_vars: self.free_vars.len() },
88 free_vars: self.free_vars,
89 }
90 }
91
92 fn do_canonicalize_projection_ty(&mut self, mut projection_ty: ProjectionTy) -> ProjectionTy {
93 for ty in make_mut_slice(&mut projection_ty.parameters.0) {
94 *ty = self.do_canonicalize_ty(ty.clone());
95 }
96 projection_ty
97 }
98
99 fn do_canonicalize_projection_predicate(
100 &mut self,
101 projection: ProjectionPredicate,
102 ) -> ProjectionPredicate {
103 let ty = self.do_canonicalize_ty(projection.ty);
104 let projection_ty = self.do_canonicalize_projection_ty(projection.projection_ty);
105
106 ProjectionPredicate { ty, projection_ty }
107 }
108
109 // FIXME: add some point, we need to introduce a `Fold` trait that abstracts
110 // over all the things that can be canonicalized (like Chalk and rustc have)
111
112 pub(crate) fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized<Ty> {
113 let result = self.do_canonicalize_ty(ty);
114 self.into_canonicalized(result)
115 }
116
117 pub(crate) fn canonicalize_obligation(
118 mut self,
119 obligation: InEnvironment<Obligation>,
120 ) -> Canonicalized<InEnvironment<Obligation>> {
121 let result = match obligation.value {
122 Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize_trait_ref(tr)),
123 Obligation::Projection(pr) => {
124 Obligation::Projection(self.do_canonicalize_projection_predicate(pr))
125 }
126 };
127 self.into_canonicalized(InEnvironment {
128 value: result,
129 environment: obligation.environment,
130 })
131 }
132}
133
134impl<T> Canonicalized<T> {
135 pub fn decanonicalize_ty(&self, mut ty: Ty) -> Ty {
136 ty.walk_mut_binders(
137 &mut |ty, binders| match ty {
138 &mut Ty::Bound(idx) => {
139 if idx as usize >= binders && (idx as usize - binders) < self.free_vars.len() {
140 *ty = Ty::Infer(self.free_vars[idx as usize - binders]);
141 }
142 }
143 _ => {}
144 },
145 0,
146 );
147 ty
148 }
149
150 pub fn apply_solution(
151 &self,
152 ctx: &mut InferenceContext<'_, impl HirDatabase>,
153 solution: Canonical<Vec<Ty>>,
154 ) {
155 // the solution may contain new variables, which we need to convert to new inference vars
156 let new_vars = Substs((0..solution.num_vars).map(|_| ctx.new_type_var()).collect());
157 for (i, ty) in solution.value.into_iter().enumerate() {
158 let var = self.free_vars[i];
159 ctx.unify(&Ty::Infer(var), &ty.subst_bound_vars(&new_vars));
160 }
161 }
162}