aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/infer/coerce.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-13 15:35:29 +0100
committerAleksey Kladov <[email protected]>2020-08-13 15:35:29 +0100
commit6a77ec7bbe6ddbf663dce9529d11d1bb56c5489a (patch)
treeb6e3d7e0109eb82bca75b5ebc35a7d723542b8dd /crates/hir_ty/src/infer/coerce.rs
parent50f8c1ebf23f634b68529603a917e3feeda457fa (diff)
Rename ra_hir_ty -> hir_ty
Diffstat (limited to 'crates/hir_ty/src/infer/coerce.rs')
-rw-r--r--crates/hir_ty/src/infer/coerce.rs197
1 files changed, 197 insertions, 0 deletions
diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs
new file mode 100644
index 000000000..32c7c57cd
--- /dev/null
+++ b/crates/hir_ty/src/infer/coerce.rs
@@ -0,0 +1,197 @@
1//! Coercion logic. Coercions are certain type conversions that can implicitly
2//! happen in certain places, e.g. weakening `&mut` to `&` or deref coercions
3//! like going from `&Vec<T>` to `&[T]`.
4//!
5//! See: https://doc.rust-lang.org/nomicon/coercions.html
6
7use hir_def::{lang_item::LangItemTarget, type_ref::Mutability};
8use test_utils::mark;
9
10use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor};
11
12use super::{unify::TypeVarValue, InEnvironment, InferTy, InferenceContext};
13
14impl<'a> InferenceContext<'a> {
15 /// Unify two types, but may coerce the first one to the second one
16 /// using "implicit coercion rules" if needed.
17 pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool {
18 let from_ty = self.resolve_ty_shallow(from_ty).into_owned();
19 let to_ty = self.resolve_ty_shallow(to_ty);
20 self.coerce_inner(from_ty, &to_ty)
21 }
22
23 /// Merge two types from different branches, with possible coercion.
24 ///
25 /// Mostly this means trying to coerce one to the other, but
26 /// - if we have two function types for different functions, we need to
27 /// coerce both to function pointers;
28 /// - if we were concerned with lifetime subtyping, we'd need to look for a
29 /// least upper bound.
30 pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
31 if self.coerce(ty1, ty2) {
32 ty2.clone()
33 } else if self.coerce(ty2, ty1) {
34 ty1.clone()
35 } else {
36 if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
37 mark::hit!(coerce_fn_reification);
38 // Special case: two function types. Try to coerce both to
39 // pointers to have a chance at getting a match. See
40 // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
41 let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig");
42 let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig");
43 let ptr_ty1 = Ty::fn_ptr(sig1);
44 let ptr_ty2 = Ty::fn_ptr(sig2);
45 self.coerce_merge_branch(&ptr_ty1, &ptr_ty2)
46 } else {
47 mark::hit!(coerce_merge_fail_fallback);
48 ty1.clone()
49 }
50 }
51 }
52
53 fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
54 match (&from_ty, to_ty) {
55 // Never type will make type variable to fallback to Never Type instead of Unknown.
56 (ty_app!(TypeCtor::Never), Ty::Infer(InferTy::TypeVar(tv))) => {
57 let var = self.table.new_maybe_never_type_var();
58 self.table.var_unification_table.union_value(*tv, TypeVarValue::Known(var));
59 return true;
60 }
61 (ty_app!(TypeCtor::Never), _) => return true,
62
63 // Trivial cases, this should go after `never` check to
64 // avoid infer result type to be never
65 _ => {
66 if self.table.unify_inner_trivial(&from_ty, &to_ty, 0) {
67 return true;
68 }
69 }
70 }
71
72 // Pointer weakening and function to pointer
73 match (&mut from_ty, to_ty) {
74 // `*mut T`, `&mut T, `&T`` -> `*const T`
75 // `&mut T` -> `&T`
76 // `&mut T` -> `*mut T`
77 (ty_app!(c1@TypeCtor::RawPtr(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared)))
78 | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared)))
79 | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::Ref(Mutability::Shared)))
80 | (ty_app!(c1@TypeCtor::Ref(Mutability::Mut)), ty_app!(c2@TypeCtor::RawPtr(_))) => {
81 *c1 = *c2;
82 }
83
84 // Illegal mutablity conversion
85 (
86 ty_app!(TypeCtor::RawPtr(Mutability::Shared)),
87 ty_app!(TypeCtor::RawPtr(Mutability::Mut)),
88 )
89 | (
90 ty_app!(TypeCtor::Ref(Mutability::Shared)),
91 ty_app!(TypeCtor::Ref(Mutability::Mut)),
92 ) => return false,
93
94 // `{function_type}` -> `fn()`
95 (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnPtr { .. })) => {
96 match from_ty.callable_sig(self.db) {
97 None => return false,
98 Some(sig) => {
99 from_ty = Ty::fn_ptr(sig);
100 }
101 }
102 }
103
104 (ty_app!(TypeCtor::Closure { .. }, params), ty_app!(TypeCtor::FnPtr { .. })) => {
105 from_ty = params[0].clone();
106 }
107
108 _ => {}
109 }
110
111 if let Some(ret) = self.try_coerce_unsized(&from_ty, &to_ty) {
112 return ret;
113 }
114
115 // Auto Deref if cannot coerce
116 match (&from_ty, to_ty) {
117 // FIXME: DerefMut
118 (ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => {
119 self.unify_autoderef_behind_ref(&st1[0], &st2[0])
120 }
121
122 // Otherwise, normal unify
123 _ => self.unify(&from_ty, to_ty),
124 }
125 }
126
127 /// Coerce a type using `from_ty: CoerceUnsized<ty_ty>`
128 ///
129 /// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html
130 fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option<bool> {
131 let krate = self.resolver.krate().unwrap();
132 let coerce_unsized_trait = match self.db.lang_item(krate, "coerce_unsized".into()) {
133 Some(LangItemTarget::TraitId(trait_)) => trait_,
134 _ => return None,
135 };
136
137 let generic_params = crate::utils::generics(self.db.upcast(), coerce_unsized_trait.into());
138 if generic_params.len() != 2 {
139 // The CoerceUnsized trait should have two generic params: Self and T.
140 return None;
141 }
142
143 let substs = Substs::build_for_generics(&generic_params)
144 .push(from_ty.clone())
145 .push(to_ty.clone())
146 .build();
147 let trait_ref = TraitRef { trait_: coerce_unsized_trait, substs };
148 let goal = InEnvironment::new(self.trait_env.clone(), Obligation::Trait(trait_ref));
149
150 let canonicalizer = self.canonicalizer();
151 let canonicalized = canonicalizer.canonicalize_obligation(goal);
152
153 let solution = self.db.trait_solve(krate, canonicalized.value.clone())?;
154
155 match solution {
156 Solution::Unique(v) => {
157 canonicalized.apply_solution(self, v.0);
158 }
159 _ => return None,
160 };
161
162 Some(true)
163 }
164
165 /// Unify `from_ty` to `to_ty` with optional auto Deref
166 ///
167 /// Note that the parameters are already stripped the outer reference.
168 fn unify_autoderef_behind_ref(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool {
169 let canonicalized = self.canonicalizer().canonicalize_ty(from_ty.clone());
170 let to_ty = self.resolve_ty_shallow(&to_ty);
171 // FIXME: Auto DerefMut
172 for derefed_ty in autoderef::autoderef(
173 self.db,
174 self.resolver.krate(),
175 InEnvironment {
176 value: canonicalized.value.clone(),
177 environment: self.trait_env.clone(),
178 },
179 ) {
180 let derefed_ty = canonicalized.decanonicalize_ty(derefed_ty.value);
181 match (&*self.resolve_ty_shallow(&derefed_ty), &*to_ty) {
182 // Stop when constructor matches.
183 (ty_app!(from_ctor, st1), ty_app!(to_ctor, st2)) if from_ctor == to_ctor => {
184 // It will not recurse to `coerce`.
185 return self.table.unify_substs(st1, st2, 0);
186 }
187 _ => {
188 if self.table.unify_inner_trivial(&derefed_ty, &to_ty, 0) {
189 return true;
190 }
191 }
192 }
193 }
194
195 false
196 }
197}