]>
Commit | Line | Data |
---|---|---|
f035d41b XL |
1 | use crate::ext::*; |
2 | use crate::infer::InferenceTable; | |
3 | use crate::solve::slg::SlgContextOps; | |
4 | use crate::solve::slg::SubstitutionExt; | |
5 | use crate::solve::{Guidance, Solution}; | |
6 | use chalk_ir::cast::Cast; | |
7 | use chalk_ir::interner::Interner; | |
8 | use chalk_ir::*; | |
9 | ||
10 | use chalk_engine::context::{self, AnswerResult, ContextOps}; | |
11 | use chalk_engine::CompleteAnswer; | |
12 | use std::fmt::Debug; | |
13 | ||
14 | /// Methods for combining solutions to yield an aggregate solution. | |
15 | pub trait AggregateOps<I: Interner> { | |
16 | fn make_solution( | |
17 | &self, | |
18 | root_goal: &UCanonical<InEnvironment<Goal<I>>>, | |
19 | answers: impl context::AnswerStream<I>, | |
20 | should_continue: impl std::ops::Fn() -> bool, | |
21 | ) -> Option<Solution<I>>; | |
22 | } | |
23 | ||
24 | /// Draws as many answers as it needs from `answers` (but | |
25 | /// no more!) in order to come up with a solution. | |
26 | impl<I: Interner> AggregateOps<I> for SlgContextOps<'_, I> { | |
27 | fn make_solution( | |
28 | &self, | |
29 | root_goal: &UCanonical<InEnvironment<Goal<I>>>, | |
30 | mut answers: impl context::AnswerStream<I>, | |
31 | should_continue: impl std::ops::Fn() -> bool, | |
32 | ) -> Option<Solution<I>> { | |
33 | let interner = self.program.interner(); | |
34 | let CompleteAnswer { subst, ambiguous } = match answers.next_answer(|| should_continue()) { | |
35 | AnswerResult::NoMoreSolutions => { | |
36 | // No answers at all | |
37 | return None; | |
38 | } | |
39 | AnswerResult::Answer(answer) => answer, | |
40 | AnswerResult::Floundered => CompleteAnswer { | |
41 | subst: self.identity_constrained_subst(root_goal), | |
42 | ambiguous: true, | |
43 | }, | |
44 | AnswerResult::QuantumExceeded => { | |
45 | return Some(Solution::Ambig(Guidance::Unknown)); | |
46 | } | |
47 | }; | |
48 | ||
49 | // Exactly 1 unconditional answer? | |
50 | let next_answer = answers.peek_answer(|| should_continue()); | |
51 | if next_answer.is_quantum_exceeded() { | |
52 | if subst.value.subst.is_identity_subst(interner) { | |
53 | return Some(Solution::Ambig(Guidance::Unknown)); | |
54 | } else { | |
55 | return Some(Solution::Ambig(Guidance::Suggested( | |
56 | subst.map(interner, |cs| cs.subst), | |
57 | ))); | |
58 | } | |
59 | } | |
60 | if next_answer.is_no_more_solutions() && !ambiguous { | |
61 | return Some(Solution::Unique(subst)); | |
62 | } | |
63 | ||
64 | // Otherwise, we either have >1 answer, or else we have | |
65 | // ambiguity. Either way, we are only going to be giving back | |
66 | // **guidance**, and with guidance, the caller doesn't get | |
67 | // back any region constraints. So drop them from our `subst` | |
68 | // variable. | |
69 | // | |
70 | // FIXME-- there is actually a 3rd possibility. We could have | |
71 | // >1 answer where all the answers have the same substitution, | |
72 | // but different region constraints. We should collapse those | |
73 | // cases into an `OR` region constraint at some point, but I | |
74 | // leave that for future work. This is basically | |
75 | // rust-lang/rust#21974. | |
76 | let mut subst = subst.map(interner, |cs| cs.subst); | |
77 | ||
78 | // Extract answers and merge them into `subst`. Stop once we have | |
79 | // a trivial subst (or run out of answers). | |
80 | let mut num_answers = 1; | |
81 | let guidance = loop { | |
82 | if subst.value.is_empty(interner) || is_trivial(interner, &subst) { | |
83 | break Guidance::Unknown; | |
84 | } | |
85 | ||
86 | if !answers | |
87 | .any_future_answer(|ref mut new_subst| new_subst.may_invalidate(interner, &subst)) | |
88 | { | |
89 | break Guidance::Definite(subst); | |
90 | } | |
91 | ||
92 | if let Some(expected_answers) = self.expected_answers { | |
93 | if num_answers >= expected_answers { | |
94 | panic!("Too many answers for solution."); | |
95 | } | |
96 | } | |
97 | ||
98 | let new_subst = match answers.next_answer(|| should_continue()) { | |
99 | AnswerResult::Answer(answer1) => answer1.subst, | |
100 | AnswerResult::Floundered => { | |
101 | // FIXME: this doesn't trigger for any current tests | |
102 | self.identity_constrained_subst(root_goal) | |
103 | } | |
104 | AnswerResult::NoMoreSolutions => { | |
105 | break Guidance::Definite(subst); | |
106 | } | |
107 | AnswerResult::QuantumExceeded => { | |
108 | break Guidance::Suggested(subst); | |
109 | } | |
110 | }; | |
111 | subst = merge_into_guidance(interner, &root_goal.canonical, subst, &new_subst); | |
112 | num_answers += 1; | |
113 | }; | |
114 | ||
115 | if let Some(expected_answers) = self.expected_answers { | |
116 | assert_eq!( | |
117 | expected_answers, num_answers, | |
118 | "Not enough answers for solution." | |
119 | ); | |
120 | } | |
121 | Some(Solution::Ambig(guidance)) | |
122 | } | |
123 | } | |
124 | ||
125 | /// Given a current substitution used as guidance for `root_goal`, and | |
126 | /// a new possible answer to `root_goal`, returns a new set of | |
127 | /// guidance that encompasses both of them. This is often more general | |
128 | /// than the old guidance. For example, if we had a guidance of `?0 = | |
129 | /// u32` and the new answer is `?0 = i32`, then the guidance would | |
130 | /// become `?0 = ?X` (where `?X` is some fresh variable). | |
131 | fn merge_into_guidance<I: Interner>( | |
132 | interner: &I, | |
133 | root_goal: &Canonical<InEnvironment<Goal<I>>>, | |
134 | guidance: Canonical<Substitution<I>>, | |
135 | answer: &Canonical<ConstrainedSubst<I>>, | |
136 | ) -> Canonical<Substitution<I>> { | |
137 | let mut infer = InferenceTable::new(); | |
138 | let Canonical { | |
139 | value: ConstrainedSubst { | |
140 | subst: subst1, | |
141 | constraints: _, | |
142 | }, | |
143 | binders: _, | |
144 | } = answer; | |
145 | ||
146 | // Collect the types that the two substitutions have in | |
147 | // common. | |
148 | let aggr_generic_args: Vec<_> = guidance | |
149 | .value | |
150 | .iter(interner) | |
151 | .zip(subst1.iter(interner)) | |
152 | .enumerate() | |
153 | .map(|(index, (p1, p2))| { | |
154 | // We have two values for some variable X that | |
155 | // appears in the root goal. Find out the universe | |
156 | // of X. | |
157 | let universe = *root_goal.binders.as_slice(interner)[index].skip_kind(); | |
158 | ||
159 | match p1.data(interner) { | |
160 | GenericArgData::Ty(_) => (), | |
161 | GenericArgData::Lifetime(_) => { | |
162 | // Ignore the lifetimes from the substitution: we're just | |
163 | // creating guidance here anyway. | |
164 | return infer | |
165 | .new_variable(universe) | |
166 | .to_lifetime(interner) | |
167 | .cast(interner); | |
168 | } | |
169 | GenericArgData::Const(_) => (), | |
170 | }; | |
171 | ||
172 | // Combine the two types into a new type. | |
173 | let mut aggr = AntiUnifier { | |
174 | infer: &mut infer, | |
175 | universe, | |
176 | interner, | |
177 | }; | |
178 | aggr.aggregate_generic_args(p1, p2) | |
179 | }) | |
180 | .collect(); | |
181 | ||
182 | let aggr_subst = Substitution::from(interner, aggr_generic_args); | |
183 | ||
184 | infer.canonicalize(interner, &aggr_subst).quantified | |
185 | } | |
186 | ||
187 | fn is_trivial<I: Interner>(interner: &I, subst: &Canonical<Substitution<I>>) -> bool { | |
188 | // A subst is trivial if.. | |
189 | subst | |
190 | .value | |
191 | .iter(interner) | |
192 | .enumerate() | |
193 | .all(|(index, parameter)| { | |
194 | let is_trivial = |b: Option<BoundVar>| match b { | |
195 | None => false, | |
196 | Some(bound_var) => { | |
197 | if let Some(index1) = bound_var.index_if_innermost() { | |
198 | index == index1 | |
199 | } else { | |
200 | false | |
201 | } | |
202 | } | |
203 | }; | |
204 | ||
205 | match parameter.data(interner) { | |
206 | // All types and consts are mapped to distinct variables. Since this | |
207 | // has been canonicalized, those will also be the first N | |
208 | // variables. | |
209 | GenericArgData::Ty(t) => is_trivial(t.bound_var(interner)), | |
210 | GenericArgData::Const(t) => is_trivial(t.bound_var(interner)), | |
211 | ||
212 | // And no lifetime mappings. (This is too strict, but we never | |
213 | // product substs with lifetimes.) | |
214 | GenericArgData::Lifetime(_) => false, | |
215 | } | |
216 | }) | |
217 | } | |
218 | ||
219 | /// [Anti-unification] is the act of taking two things that do not | |
220 | /// unify and finding a minimal generalization of them. So for | |
221 | /// example `Vec<u32>` anti-unified with `Vec<i32>` might be | |
222 | /// `Vec<?X>`. This is a **very simplistic** anti-unifier. | |
223 | /// | |
224 | /// [Anti-unification]: https://en.wikipedia.org/wiki/Anti-unification_(computer_science) | |
225 | struct AntiUnifier<'infer, 'intern, I: Interner> { | |
226 | infer: &'infer mut InferenceTable<I>, | |
227 | universe: UniverseIndex, | |
228 | interner: &'intern I, | |
229 | } | |
230 | ||
231 | impl<I: Interner> AntiUnifier<'_, '_, I> { | |
232 | fn aggregate_tys(&mut self, ty0: &Ty<I>, ty1: &Ty<I>) -> Ty<I> { | |
233 | let interner = self.interner; | |
234 | match (ty0.data(interner), ty1.data(interner)) { | |
235 | // If we see bound things on either side, just drop in a | |
236 | // fresh variable. This means we will sometimes | |
237 | // overgeneralize. So for example if we have two | |
238 | // solutions that are both `(X, X)`, we just produce `(Y, | |
239 | // Z)` in all cases. | |
240 | (TyData::InferenceVar(_, _), TyData::InferenceVar(_, _)) => self.new_ty_variable(), | |
241 | ||
242 | // Ugh. Aggregating two types like `for<'a> fn(&'a u32, | |
243 | // &'a u32)` and `for<'a, 'b> fn(&'a u32, &'b u32)` seems | |
244 | // kinda hard. Don't try to be smart for now, just plop a | |
245 | // variable in there and be done with it. | |
246 | (TyData::BoundVar(_), TyData::BoundVar(_)) | |
247 | | (TyData::Function(_), TyData::Function(_)) | |
248 | | (TyData::Dyn(_), TyData::Dyn(_)) => self.new_ty_variable(), | |
249 | ||
250 | (TyData::Apply(apply1), TyData::Apply(apply2)) => { | |
251 | self.aggregate_application_tys(apply1, apply2) | |
252 | } | |
253 | ||
254 | ( | |
255 | TyData::Alias(AliasTy::Projection(proj1)), | |
256 | TyData::Alias(AliasTy::Projection(proj2)), | |
257 | ) => self.aggregate_projection_tys(proj1, proj2), | |
258 | ||
259 | ( | |
260 | TyData::Alias(AliasTy::Opaque(opaque_ty1)), | |
261 | TyData::Alias(AliasTy::Opaque(opaque_ty2)), | |
262 | ) => self.aggregate_opaque_ty_tys(opaque_ty1, opaque_ty2), | |
263 | ||
264 | (TyData::Placeholder(placeholder1), TyData::Placeholder(placeholder2)) => { | |
265 | self.aggregate_placeholder_tys(placeholder1, placeholder2) | |
266 | } | |
267 | ||
268 | // Mismatched base kinds. | |
269 | (TyData::InferenceVar(_, _), _) | |
270 | | (TyData::BoundVar(_), _) | |
271 | | (TyData::Dyn(_), _) | |
272 | | (TyData::Function(_), _) | |
273 | | (TyData::Apply(_), _) | |
274 | | (TyData::Alias(_), _) | |
275 | | (TyData::Placeholder(_), _) => self.new_ty_variable(), | |
276 | } | |
277 | } | |
278 | ||
279 | fn aggregate_application_tys( | |
280 | &mut self, | |
281 | apply1: &ApplicationTy<I>, | |
282 | apply2: &ApplicationTy<I>, | |
283 | ) -> Ty<I> { | |
284 | let interner = self.interner; | |
285 | let ApplicationTy { | |
286 | name: name1, | |
287 | substitution: substitution1, | |
288 | } = apply1; | |
289 | let ApplicationTy { | |
290 | name: name2, | |
291 | substitution: substitution2, | |
292 | } = apply2; | |
293 | ||
294 | self.aggregate_name_and_substs(name1, substitution1, name2, substitution2) | |
295 | .map(|(&name, substitution)| { | |
296 | TyData::Apply(ApplicationTy { name, substitution }).intern(interner) | |
297 | }) | |
298 | .unwrap_or_else(|| self.new_ty_variable()) | |
299 | } | |
300 | ||
301 | fn aggregate_placeholder_tys( | |
302 | &mut self, | |
303 | index1: &PlaceholderIndex, | |
304 | index2: &PlaceholderIndex, | |
305 | ) -> Ty<I> { | |
306 | let interner = self.interner; | |
307 | if index1 != index2 { | |
308 | self.new_ty_variable() | |
309 | } else { | |
310 | TyData::Placeholder(index1.clone()).intern(interner) | |
311 | } | |
312 | } | |
313 | ||
314 | fn aggregate_projection_tys( | |
315 | &mut self, | |
316 | proj1: &ProjectionTy<I>, | |
317 | proj2: &ProjectionTy<I>, | |
318 | ) -> Ty<I> { | |
319 | let interner = self.interner; | |
320 | let ProjectionTy { | |
321 | associated_ty_id: name1, | |
322 | substitution: substitution1, | |
323 | } = proj1; | |
324 | let ProjectionTy { | |
325 | associated_ty_id: name2, | |
326 | substitution: substitution2, | |
327 | } = proj2; | |
328 | ||
329 | self.aggregate_name_and_substs(name1, substitution1, name2, substitution2) | |
330 | .map(|(&associated_ty_id, substitution)| { | |
331 | TyData::Alias(AliasTy::Projection(ProjectionTy { | |
332 | associated_ty_id, | |
333 | substitution, | |
334 | })) | |
335 | .intern(interner) | |
336 | }) | |
337 | .unwrap_or_else(|| self.new_ty_variable()) | |
338 | } | |
339 | ||
340 | fn aggregate_opaque_ty_tys( | |
341 | &mut self, | |
342 | opaque_ty1: &OpaqueTy<I>, | |
343 | opaque_ty2: &OpaqueTy<I>, | |
344 | ) -> Ty<I> { | |
345 | let OpaqueTy { | |
346 | opaque_ty_id: name1, | |
347 | substitution: substitution1, | |
348 | } = opaque_ty1; | |
349 | let OpaqueTy { | |
350 | opaque_ty_id: name2, | |
351 | substitution: substitution2, | |
352 | } = opaque_ty2; | |
353 | ||
354 | self.aggregate_name_and_substs(name1, substitution1, name2, substitution2) | |
355 | .map(|(&opaque_ty_id, substitution)| { | |
356 | TyData::Alias(AliasTy::Opaque(OpaqueTy { | |
357 | opaque_ty_id, | |
358 | substitution, | |
359 | })) | |
360 | .intern(self.interner) | |
361 | }) | |
362 | .unwrap_or_else(|| self.new_ty_variable()) | |
363 | } | |
364 | ||
365 | fn aggregate_name_and_substs<N>( | |
366 | &mut self, | |
367 | name1: N, | |
368 | substitution1: &Substitution<I>, | |
369 | name2: N, | |
370 | substitution2: &Substitution<I>, | |
371 | ) -> Option<(N, Substitution<I>)> | |
372 | where | |
373 | N: Copy + Eq + Debug, | |
374 | { | |
375 | let interner = self.interner; | |
376 | if name1 != name2 { | |
377 | return None; | |
378 | } | |
379 | ||
380 | let name = name1; | |
381 | ||
382 | assert_eq!( | |
383 | substitution1.len(interner), | |
384 | substitution2.len(interner), | |
385 | "does {:?} take {} substitution or {}? can't both be right", | |
386 | name, | |
387 | substitution1.len(interner), | |
388 | substitution2.len(interner) | |
389 | ); | |
390 | ||
391 | let substitution = Substitution::from( | |
392 | interner, | |
393 | substitution1 | |
394 | .iter(interner) | |
395 | .zip(substitution2.iter(interner)) | |
396 | .map(|(p1, p2)| self.aggregate_generic_args(p1, p2)), | |
397 | ); | |
398 | ||
399 | Some((name, substitution)) | |
400 | } | |
401 | ||
402 | fn aggregate_generic_args(&mut self, p1: &GenericArg<I>, p2: &GenericArg<I>) -> GenericArg<I> { | |
403 | let interner = self.interner; | |
404 | match (p1.data(interner), p2.data(interner)) { | |
405 | (GenericArgData::Ty(ty1), GenericArgData::Ty(ty2)) => { | |
406 | self.aggregate_tys(ty1, ty2).cast(interner) | |
407 | } | |
408 | (GenericArgData::Lifetime(l1), GenericArgData::Lifetime(l2)) => { | |
409 | self.aggregate_lifetimes(l1, l2).cast(interner) | |
410 | } | |
411 | (GenericArgData::Const(c1), GenericArgData::Const(c2)) => { | |
412 | self.aggregate_consts(c1, c2).cast(interner) | |
413 | } | |
414 | (GenericArgData::Ty(_), _) | |
415 | | (GenericArgData::Lifetime(_), _) | |
416 | | (GenericArgData::Const(_), _) => { | |
417 | panic!("mismatched parameter kinds: p1={:?} p2={:?}", p1, p2) | |
418 | } | |
419 | } | |
420 | } | |
421 | ||
422 | fn aggregate_lifetimes(&mut self, l1: &Lifetime<I>, l2: &Lifetime<I>) -> Lifetime<I> { | |
423 | let interner = self.interner; | |
424 | match (l1.data(interner), l2.data(interner)) { | |
425 | (LifetimeData::InferenceVar(_), _) | (_, LifetimeData::InferenceVar(_)) => { | |
426 | self.new_lifetime_variable() | |
427 | } | |
428 | ||
429 | (LifetimeData::BoundVar(_), _) | (_, LifetimeData::BoundVar(_)) => { | |
430 | self.new_lifetime_variable() | |
431 | } | |
432 | ||
433 | (LifetimeData::Placeholder(_), LifetimeData::Placeholder(_)) => { | |
434 | if l1 == l2 { | |
435 | l1.clone() | |
436 | } else { | |
437 | self.new_lifetime_variable() | |
438 | } | |
439 | } | |
440 | ||
441 | (LifetimeData::Phantom(..), _) | (_, LifetimeData::Phantom(..)) => unreachable!(), | |
442 | } | |
443 | } | |
444 | ||
445 | fn aggregate_consts(&mut self, c1: &Const<I>, c2: &Const<I>) -> Const<I> { | |
446 | let interner = self.interner; | |
447 | ||
448 | // It would be nice to check that c1 and c2 have the same type, even though | |
449 | // on this stage of solving they should already have the same type. | |
450 | ||
451 | let ConstData { | |
452 | ty: c1_ty, | |
453 | value: c1_value, | |
454 | } = c1.data(interner); | |
455 | let ConstData { | |
456 | ty: _c2_ty, | |
457 | value: c2_value, | |
458 | } = c2.data(interner); | |
459 | ||
460 | let ty = c1_ty.clone(); | |
461 | ||
462 | match (c1_value, c2_value) { | |
463 | (ConstValue::InferenceVar(_), _) | (_, ConstValue::InferenceVar(_)) => { | |
464 | self.new_const_variable(ty) | |
465 | } | |
466 | ||
467 | (ConstValue::BoundVar(_), _) | (_, ConstValue::BoundVar(_)) => { | |
468 | self.new_const_variable(ty.clone()) | |
469 | } | |
470 | ||
471 | (ConstValue::Placeholder(_), ConstValue::Placeholder(_)) => { | |
472 | if c1 == c2 { | |
473 | c1.clone() | |
474 | } else { | |
475 | self.new_const_variable(ty) | |
476 | } | |
477 | } | |
478 | (ConstValue::Concrete(e1), ConstValue::Concrete(e2)) => { | |
479 | if e1.const_eq(&ty, e2, interner) { | |
480 | c1.clone() | |
481 | } else { | |
482 | self.new_const_variable(ty) | |
483 | } | |
484 | } | |
485 | ||
486 | (ConstValue::Placeholder(_), _) | (_, ConstValue::Placeholder(_)) => { | |
487 | self.new_const_variable(ty) | |
488 | } | |
489 | } | |
490 | } | |
491 | ||
492 | fn new_ty_variable(&mut self) -> Ty<I> { | |
493 | let interner = self.interner; | |
494 | self.infer.new_variable(self.universe).to_ty(interner) | |
495 | } | |
496 | ||
497 | fn new_lifetime_variable(&mut self) -> Lifetime<I> { | |
498 | let interner = self.interner; | |
499 | self.infer.new_variable(self.universe).to_lifetime(interner) | |
500 | } | |
501 | ||
502 | fn new_const_variable(&mut self, ty: Ty<I>) -> Const<I> { | |
503 | let interner = self.interner; | |
504 | self.infer | |
505 | .new_variable(self.universe) | |
506 | .to_const(interner, ty) | |
507 | } | |
508 | } | |
509 | ||
510 | /// Test the equivalent of `Vec<i32>` vs `Vec<u32>` | |
511 | #[test] | |
512 | fn vec_i32_vs_vec_u32() { | |
513 | use chalk_integration::interner::ChalkIr; | |
514 | let mut infer: InferenceTable<ChalkIr> = InferenceTable::new(); | |
515 | let mut anti_unifier = AntiUnifier { | |
516 | infer: &mut infer, | |
517 | universe: UniverseIndex::root(), | |
518 | interner: &ChalkIr, | |
519 | }; | |
520 | ||
521 | let ty = anti_unifier.aggregate_tys( | |
522 | &ty!(apply (item 0) (apply (item 1))), | |
523 | &ty!(apply (item 0) (apply (item 2))), | |
524 | ); | |
525 | assert_eq!(ty!(apply (item 0) (infer 0)), ty); | |
526 | } | |
527 | ||
528 | /// Test the equivalent of `Vec<i32>` vs `Vec<i32>` | |
529 | #[test] | |
530 | fn vec_i32_vs_vec_i32() { | |
531 | use chalk_integration::interner::ChalkIr; | |
532 | let interner = &ChalkIr; | |
533 | let mut infer: InferenceTable<ChalkIr> = InferenceTable::new(); | |
534 | let mut anti_unifier = AntiUnifier { | |
535 | interner, | |
536 | infer: &mut infer, | |
537 | universe: UniverseIndex::root(), | |
538 | }; | |
539 | ||
540 | let ty = anti_unifier.aggregate_tys( | |
541 | &ty!(apply (item 0) (apply (item 1))), | |
542 | &ty!(apply (item 0) (apply (item 1))), | |
543 | ); | |
544 | assert_eq!(ty!(apply (item 0) (apply (item 1))), ty); | |
545 | } | |
546 | ||
547 | /// Test the equivalent of `Vec<X>` vs `Vec<Y>` | |
548 | #[test] | |
549 | fn vec_x_vs_vec_y() { | |
550 | use chalk_integration::interner::ChalkIr; | |
551 | let interner = &ChalkIr; | |
552 | let mut infer: InferenceTable<ChalkIr> = InferenceTable::new(); | |
553 | let mut anti_unifier = AntiUnifier { | |
554 | interner, | |
555 | infer: &mut infer, | |
556 | universe: UniverseIndex::root(), | |
557 | }; | |
558 | ||
559 | // Note that the `var 0` and `var 1` in these types would be | |
560 | // referring to canonicalized free variables, not variables in | |
561 | // `infer`. | |
562 | let ty = anti_unifier.aggregate_tys( | |
563 | &ty!(apply (item 0) (infer 0)), | |
564 | &ty!(apply (item 0) (infer 1)), | |
565 | ); | |
566 | ||
567 | // But this `var 0` is from `infer. | |
568 | assert_eq!(ty!(apply (item 0) (infer 0)), ty); | |
569 | } |