aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/infer
diff options
context:
space:
mode:
authorFlorian Diebold <[email protected]>2020-02-21 17:24:18 +0000
committerFlorian Diebold <[email protected]>2020-02-22 10:09:21 +0000
commitde39d221a15c0a146ed8adbdb1616692180948bb (patch)
treec88cddbadedb021365e518d68502e8c2f9c21077 /crates/ra_hir_ty/src/infer
parentbaf832d6d903afbc39e3a01c752a1aa5218c020e (diff)
Implement unsize coercion using proper trait solving
Diffstat (limited to 'crates/ra_hir_ty/src/infer')
-rw-r--r--crates/ra_hir_ty/src/infer/coerce.rs205
1 files changed, 26 insertions, 179 deletions
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs
index fb6a51b12..95ac3c713 100644
--- a/crates/ra_hir_ty/src/infer/coerce.rs
+++ b/crates/ra_hir_ty/src/infer/coerce.rs
@@ -4,11 +4,12 @@
4//! 4//!
5//! See: https://doc.rust-lang.org/nomicon/coercions.html 5//! See: https://doc.rust-lang.org/nomicon/coercions.html
6 6
7use hir_def::{lang_item::LangItemTarget, resolver::Resolver, type_ref::Mutability, AdtId}; 7use hir_def::{lang_item::LangItemTarget, type_ref::Mutability};
8use rustc_hash::FxHashMap;
9use test_utils::tested_by; 8use test_utils::tested_by;
10 9
11use crate::{autoderef, db::HirDatabase, Substs, Ty, TypeCtor, TypeWalk}; 10use crate::{
11 autoderef, db::HirDatabase, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor,
12};
12 13
13use super::{unify::TypeVarValue, InEnvironment, InferTy, InferenceContext}; 14use super::{unify::TypeVarValue, InEnvironment, InferTy, InferenceContext};
14 15
@@ -39,44 +40,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
39 } 40 }
40 } 41 }
41 42
42 pub(super) fn init_coerce_unsized_map(
43 db: &'a D,
44 resolver: &Resolver,
45 ) -> FxHashMap<(TypeCtor, TypeCtor), usize> {
46 let krate = resolver.krate().unwrap();
47 let impls = match db.lang_item(krate, "coerce_unsized".into()) {
48 Some(LangItemTarget::TraitId(trait_)) => db.impls_for_trait(krate, trait_),
49 _ => return FxHashMap::default(),
50 };
51
52 impls
53 .iter()
54 .filter_map(|&impl_id| {
55 let trait_ref = db.impl_trait(impl_id)?;
56
57 // `CoerseUnsized` has one generic parameter for the target type.
58 let cur_from_ty = trait_ref.value.substs.0.get(0)?;
59 let cur_to_ty = trait_ref.value.substs.0.get(1)?;
60
61 match (&cur_from_ty, cur_to_ty) {
62 (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => {
63 // FIXME: We return the first non-equal bound as the type parameter to coerce to unsized type.
64 // This works for smart-pointer-like coercion, which covers all impls from std.
65 st1.iter().zip(st2.iter()).enumerate().find_map(|(i, (ty1, ty2))| {
66 match (ty1, ty2) {
67 (Ty::Bound(idx1), Ty::Bound(idx2)) if idx1 != idx2 => {
68 Some(((*ctor1, *ctor2), i))
69 }
70 _ => None,
71 }
72 })
73 }
74 _ => None,
75 }
76 })
77 .collect()
78 }
79
80 fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool { 43 fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
81 match (&from_ty, to_ty) { 44 match (&from_ty, to_ty) {
82 // Never type will make type variable to fallback to Never Type instead of Unknown. 45 // Never type will make type variable to fallback to Never Type instead of Unknown.
@@ -157,154 +120,38 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
157 /// 120 ///
158 /// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html 121 /// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html
159 fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option<bool> { 122 fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option<bool> {
160 let (ctor1, st1, ctor2, st2) = match (from_ty, to_ty) { 123 let krate = self.resolver.krate().unwrap();
161 (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => (ctor1, st1, ctor2, st2), 124 let coerce_unsized_trait = match self.db.lang_item(krate, "coerce_unsized".into()) {
125 Some(LangItemTarget::TraitId(trait_)) => trait_,
162 _ => return None, 126 _ => return None,
163 }; 127 };
164 128
165 let coerce_generic_index = *self.coerce_unsized_map.get(&(*ctor1, *ctor2))?; 129 let generic_params = crate::utils::generics(self.db, coerce_unsized_trait.into());
166 130 if generic_params.len() != 2 {
167 // Check `Unsize` first 131 // The CoerceUnsized trait should have two generic params: Self and T.
168 match self.check_unsize_and_coerce( 132 return None;
169 st1.0.get(coerce_generic_index)?,
170 st2.0.get(coerce_generic_index)?,
171 0,
172 ) {
173 Some(true) => {}
174 ret => return ret,
175 } 133 }
176 134
177 let ret = st1 135 let substs = Substs::build_for_generics(&generic_params)
178 .iter() 136 .push(from_ty.clone())
179 .zip(st2.iter()) 137 .push(to_ty.clone())
180 .enumerate() 138 .build();
181 .filter(|&(idx, _)| idx != coerce_generic_index) 139 let trait_ref = TraitRef { trait_: coerce_unsized_trait, substs };
182 .all(|(_, (ty1, ty2))| self.unify(ty1, ty2)); 140 let goal = InEnvironment::new(self.trait_env.clone(), Obligation::Trait(trait_ref));
183 141
184 Some(ret) 142 let canonicalizer = self.canonicalizer();
185 } 143 let canonicalized = canonicalizer.canonicalize_obligation(goal);
186 144
187 /// Check if `from_ty: Unsize<to_ty>`, and coerce to `to_ty` if it holds. 145 let solution = self.db.trait_solve(krate, canonicalized.value.clone())?;
188 ///
189 /// It should not be directly called. It is only used by `try_coerce_unsized`.
190 ///
191 /// See: https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html
192 fn check_unsize_and_coerce(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> Option<bool> {
193 if depth > 1000 {
194 panic!("Infinite recursion in coercion");
195 }
196
197 match (&from_ty, &to_ty) {
198 // `[T; N]` -> `[T]`
199 (ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => {
200 Some(self.unify(&st1[0], &st2[0]))
201 }
202 146
203 // `T` -> `dyn Trait` when `T: Trait` 147 match solution {
204 (_, Ty::Dyn(_)) => { 148 Solution::Unique(v) => {
205 // FIXME: Check predicates 149 canonicalized.apply_solution(self, v.0);
206 Some(true)
207 }
208
209 // `(..., T)` -> `(..., U)` when `T: Unsize<U>`
210 (
211 ty_app!(TypeCtor::Tuple { cardinality: len1 }, st1),
212 ty_app!(TypeCtor::Tuple { cardinality: len2 }, st2),
213 ) => {
214 if len1 != len2 || *len1 == 0 {
215 return None;
216 }
217
218 match self.check_unsize_and_coerce(
219 st1.last().unwrap(),
220 st2.last().unwrap(),
221 depth + 1,
222 ) {
223 Some(true) => {}
224 ret => return ret,
225 }
226
227 let ret = st1[..st1.len() - 1]
228 .iter()
229 .zip(&st2[..st2.len() - 1])
230 .all(|(ty1, ty2)| self.unify(ty1, ty2));
231
232 Some(ret)
233 }
234
235 // Foo<..., T, ...> is Unsize<Foo<..., U, ...>> if:
236 // - T: Unsize<U>
237 // - Foo is a struct
238 // - Only the last field of Foo has a type involving T
239 // - T is not part of the type of any other fields
240 // - Bar<T>: Unsize<Bar<U>>, if the last field of Foo has type Bar<T>
241 (
242 ty_app!(TypeCtor::Adt(AdtId::StructId(struct1)), st1),
243 ty_app!(TypeCtor::Adt(AdtId::StructId(struct2)), st2),
244 ) if struct1 == struct2 => {
245 let field_tys = self.db.field_types((*struct1).into());
246 let struct_data = self.db.struct_data(*struct1);
247
248 let mut fields = struct_data.variant_data.fields().iter();
249 let (last_field_id, _data) = fields.next_back()?;
250
251 // Get the generic parameter involved in the last field.
252 let unsize_generic_index = {
253 let mut index = None;
254 let mut multiple_param = false;
255 field_tys[last_field_id].value.walk(&mut |ty| {
256 if let &Ty::Bound(idx) = ty {
257 if index.is_none() {
258 index = Some(idx);
259 } else if Some(idx) != index {
260 multiple_param = true;
261 }
262 }
263 });
264
265 if multiple_param {
266 return None;
267 }
268 index?
269 };
270
271 // Check other fields do not involve it.
272 let mut multiple_used = false;
273 fields.for_each(|(field_id, _data)| {
274 field_tys[field_id].value.walk(&mut |ty| match ty {
275 &Ty::Bound(idx) if idx == unsize_generic_index => multiple_used = true,
276 _ => {}
277 })
278 });
279 if multiple_used {
280 return None;
281 }
282
283 let unsize_generic_index = unsize_generic_index as usize;
284
285 // Check `Unsize` first
286 match self.check_unsize_and_coerce(
287 st1.get(unsize_generic_index)?,
288 st2.get(unsize_generic_index)?,
289 depth + 1,
290 ) {
291 Some(true) => {}
292 ret => return ret,
293 }
294
295 // Then unify other parameters
296 let ret = st1
297 .iter()
298 .zip(st2.iter())
299 .enumerate()
300 .filter(|&(idx, _)| idx != unsize_generic_index)
301 .all(|(_, (ty1, ty2))| self.unify(ty1, ty2));
302
303 Some(ret)
304 } 150 }
151 _ => return None,
152 };
305 153
306 _ => None, 154 Some(true)
307 }
308 } 155 }
309 156
310 /// Unify `from_ty` to `to_ty` with optional auto Deref 157 /// Unify `from_ty` to `to_ty` with optional auto Deref