rustc_next_trait_solver/
canonicalizer.rs

1use std::cmp::Ordering;
2
3use rustc_type_ir::data_structures::{HashMap, ensure_sufficient_stack};
4use rustc_type_ir::inherent::*;
5use rustc_type_ir::solve::{Goal, QueryInput};
6use rustc_type_ir::{
7    self as ty, Canonical, CanonicalTyVarKind, CanonicalVarInfo, CanonicalVarKind, InferCtxtLike,
8    Interner, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt,
9};
10
11use crate::delegate::SolverDelegate;
12
13/// Whether we're canonicalizing a query input or the query response.
14///
15/// When canonicalizing an input we're in the context of the caller
16/// while canonicalizing the response happens in the context of the
17/// query.
18#[derive(Debug, Clone, Copy)]
19enum CanonicalizeMode {
20    /// When canonicalizing the `param_env`, we keep `'static` as merging
21    /// trait candidates relies on it when deciding whether a where-bound
22    /// is trivial.
23    Input { keep_static: bool },
24    /// FIXME: We currently return region constraints referring to
25    /// placeholders and inference variables from a binder instantiated
26    /// inside of the query.
27    ///
28    /// In the long term we should eagerly deal with these constraints
29    /// inside of the query and only propagate constraints which are
30    /// actually nameable by the caller.
31    Response {
32        /// The highest universe nameable by the caller.
33        ///
34        /// All variables in a universe nameable by the caller get mapped
35        /// to the root universe in the response and then mapped back to
36        /// their correct universe when applying the query response in the
37        /// context of the caller.
38        ///
39        /// This doesn't work for universes created inside of the query so
40        /// we do remember their universe in the response.
41        max_input_universe: ty::UniverseIndex,
42    },
43}
44
45pub struct Canonicalizer<'a, D: SolverDelegate<Interner = I>, I: Interner> {
46    delegate: &'a D,
47
48    // Immutable field.
49    canonicalize_mode: CanonicalizeMode,
50
51    // Mutable fields.
52    variables: &'a mut Vec<I::GenericArg>,
53    primitive_var_infos: Vec<CanonicalVarInfo<I>>,
54    variable_lookup_table: HashMap<I::GenericArg, usize>,
55    binder_index: ty::DebruijnIndex,
56
57    /// We only use the debruijn index during lookup. We don't need to
58    /// track the `variables` as each generic arg only results in a single
59    /// bound variable regardless of how many times it is encountered.
60    cache: HashMap<(ty::DebruijnIndex, I::Ty), I::Ty>,
61}
62
63impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
64    pub fn canonicalize_response<T: TypeFoldable<I>>(
65        delegate: &'a D,
66        max_input_universe: ty::UniverseIndex,
67        variables: &'a mut Vec<I::GenericArg>,
68        value: T,
69    ) -> ty::Canonical<I, T> {
70        let mut canonicalizer = Canonicalizer {
71            delegate,
72            canonicalize_mode: CanonicalizeMode::Response { max_input_universe },
73
74            variables,
75            variable_lookup_table: Default::default(),
76            primitive_var_infos: Vec::new(),
77            binder_index: ty::INNERMOST,
78
79            cache: Default::default(),
80        };
81
82        let value = value.fold_with(&mut canonicalizer);
83        assert!(!value.has_infer(), "unexpected infer in {value:?}");
84        assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");
85        let (max_universe, variables) = canonicalizer.finalize();
86        Canonical { max_universe, variables, value }
87    }
88
89    /// When canonicalizing query inputs, we keep `'static` in the `param_env`
90    /// but erase it everywhere else. We generally don't want to depend on region
91    /// identity, so while it should not matter whether `'static` is kept in the
92    /// value or opaque type storage as well, this prevents us from accidentally
93    /// relying on it in the future.
94    ///
95    /// We want to keep the option of canonicalizing `'static` to an existential
96    /// variable in the future by changing the way we detect global where-bounds.
97    pub fn canonicalize_input<P: TypeFoldable<I>>(
98        delegate: &'a D,
99        variables: &'a mut Vec<I::GenericArg>,
100        input: QueryInput<I, P>,
101    ) -> ty::Canonical<I, QueryInput<I, P>> {
102        // First canonicalize the `param_env` while keeping `'static`
103        let mut env_canonicalizer = Canonicalizer {
104            delegate,
105            canonicalize_mode: CanonicalizeMode::Input { keep_static: true },
106
107            variables,
108            variable_lookup_table: Default::default(),
109            primitive_var_infos: Vec::new(),
110            binder_index: ty::INNERMOST,
111
112            cache: Default::default(),
113        };
114        let param_env = input.goal.param_env.fold_with(&mut env_canonicalizer);
115        debug_assert_eq!(env_canonicalizer.binder_index, ty::INNERMOST);
116        // Then canonicalize the rest of the input without keeping `'static`
117        // while *mostly* reusing the canonicalizer from above.
118        let mut rest_canonicalizer = Canonicalizer {
119            delegate,
120            canonicalize_mode: CanonicalizeMode::Input { keep_static: false },
121
122            variables: env_canonicalizer.variables,
123            // We're able to reuse the `variable_lookup_table` as whether or not
124            // it already contains an entry for `'static` does not matter.
125            variable_lookup_table: env_canonicalizer.variable_lookup_table,
126            primitive_var_infos: env_canonicalizer.primitive_var_infos,
127            binder_index: ty::INNERMOST,
128
129            // We do not reuse the cache as it may contain entries whose canonicalized
130            // value contains `'static`. While we could alternatively handle this by
131            // checking for `'static` when using cached entries, this does not
132            // feel worth the effort. I do not expect that a `ParamEnv` will ever
133            // contain large enough types for caching to be necessary.
134            cache: Default::default(),
135        };
136
137        let predicate = input.goal.predicate.fold_with(&mut rest_canonicalizer);
138        let goal = Goal { param_env, predicate };
139        let predefined_opaques_in_body =
140            input.predefined_opaques_in_body.fold_with(&mut rest_canonicalizer);
141        let value = QueryInput { goal, predefined_opaques_in_body };
142
143        assert!(!value.has_infer(), "unexpected infer in {value:?}");
144        assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");
145        let (max_universe, variables) = rest_canonicalizer.finalize();
146        Canonical { max_universe, variables, value }
147    }
148
149    fn get_or_insert_bound_var(
150        &mut self,
151        arg: impl Into<I::GenericArg>,
152        canonical_var_info: CanonicalVarInfo<I>,
153    ) -> ty::BoundVar {
154        // FIXME: 16 is made up and arbitrary. We should look at some
155        // perf data here.
156        let arg = arg.into();
157        let idx = if self.variables.len() > 16 {
158            if self.variable_lookup_table.is_empty() {
159                self.variable_lookup_table.extend(self.variables.iter().copied().zip(0..));
160            }
161
162            *self.variable_lookup_table.entry(arg).or_insert_with(|| {
163                let var = self.variables.len();
164                self.variables.push(arg);
165                self.primitive_var_infos.push(canonical_var_info);
166                var
167            })
168        } else {
169            self.variables.iter().position(|&v| v == arg).unwrap_or_else(|| {
170                let var = self.variables.len();
171                self.variables.push(arg);
172                self.primitive_var_infos.push(canonical_var_info);
173                var
174            })
175        };
176
177        ty::BoundVar::from(idx)
178    }
179
180    fn finalize(self) -> (ty::UniverseIndex, I::CanonicalVars) {
181        let mut var_infos = self.primitive_var_infos;
182        // See the rustc-dev-guide section about how we deal with universes
183        // during canonicalization in the new solver.
184        match self.canonicalize_mode {
185            // We try to deduplicate as many query calls as possible and hide
186            // all information which should not matter for the solver.
187            //
188            // For this we compress universes as much as possible.
189            CanonicalizeMode::Input { .. } => {}
190            // When canonicalizing a response we map a universes already entered
191            // by the caller to the root universe and only return useful universe
192            // information for placeholders and inference variables created inside
193            // of the query.
194            CanonicalizeMode::Response { max_input_universe } => {
195                for var in var_infos.iter_mut() {
196                    let uv = var.universe();
197                    let new_uv = ty::UniverseIndex::from(
198                        uv.index().saturating_sub(max_input_universe.index()),
199                    );
200                    *var = var.with_updated_universe(new_uv);
201                }
202                let max_universe = var_infos
203                    .iter()
204                    .map(|info| info.universe())
205                    .max()
206                    .unwrap_or(ty::UniverseIndex::ROOT);
207
208                let var_infos = self.delegate.cx().mk_canonical_var_infos(&var_infos);
209                return (max_universe, var_infos);
210            }
211        }
212
213        // Given a `var_infos` with existentials `En` and universals `Un` in
214        // universes `n`, this algorithm compresses them in place so that:
215        //
216        // - the new universe indices are as small as possible
217        // - we create a new universe if we would otherwise
218        //   1. put existentials from a different universe into the same one
219        //   2. put a placeholder in the same universe as an existential which cannot name it
220        //
221        // Let's walk through an example:
222        // - var_infos: [E0, U1, E5, U2, E2, E6, U6], curr_compressed_uv: 0, next_orig_uv: 0
223        // - var_infos: [E0, U1, E5, U2, E2, E6, U6], curr_compressed_uv: 0, next_orig_uv: 1
224        // - var_infos: [E0, U1, E5, U2, E2, E6, U6], curr_compressed_uv: 1, next_orig_uv: 2
225        // - var_infos: [E0, U1, E5, U1, E1, E6, U6], curr_compressed_uv: 1, next_orig_uv: 5
226        // - var_infos: [E0, U1, E2, U1, E1, E6, U6], curr_compressed_uv: 2, next_orig_uv: 6
227        // - var_infos: [E0, U1, E1, U1, E1, E3, U3], curr_compressed_uv: 2, next_orig_uv: -
228        //
229        // This algorithm runs in `O(mn)` where `n` is the number of different universes and
230        // `m` the number of variables. This should be fine as both are expected to be small.
231        let mut curr_compressed_uv = ty::UniverseIndex::ROOT;
232        let mut existential_in_new_uv = None;
233        let mut next_orig_uv = Some(ty::UniverseIndex::ROOT);
234        while let Some(orig_uv) = next_orig_uv.take() {
235            let mut update_uv = |var: &mut CanonicalVarInfo<I>, orig_uv, is_existential| {
236                let uv = var.universe();
237                match uv.cmp(&orig_uv) {
238                    Ordering::Less => (), // Already updated
239                    Ordering::Equal => {
240                        if is_existential {
241                            if existential_in_new_uv.is_some_and(|uv| uv < orig_uv) {
242                                // Condition 1.
243                                //
244                                // We already put an existential from a outer universe
245                                // into the current compressed universe, so we need to
246                                // create a new one.
247                                curr_compressed_uv = curr_compressed_uv.next_universe();
248                            }
249
250                            // `curr_compressed_uv` will now contain an existential from
251                            // `orig_uv`. Trying to canonicalizing an existential from
252                            // a higher universe has to therefore use a new compressed
253                            // universe.
254                            existential_in_new_uv = Some(orig_uv);
255                        } else if existential_in_new_uv.is_some() {
256                            // Condition 2.
257                            //
258                            //  `var` is a placeholder from a universe which is not nameable
259                            // by an existential which we already put into the compressed
260                            // universe `curr_compressed_uv`. We therefore have to create a
261                            // new universe for `var`.
262                            curr_compressed_uv = curr_compressed_uv.next_universe();
263                            existential_in_new_uv = None;
264                        }
265
266                        *var = var.with_updated_universe(curr_compressed_uv);
267                    }
268                    Ordering::Greater => {
269                        // We can ignore this variable in this iteration. We only look at
270                        // universes which actually occur in the input for performance.
271                        //
272                        // For this we set `next_orig_uv` to the next smallest, not yet compressed,
273                        // universe of the input.
274                        if next_orig_uv.is_none_or(|curr_next_uv| uv.cannot_name(curr_next_uv)) {
275                            next_orig_uv = Some(uv);
276                        }
277                    }
278                }
279            };
280
281            // For each universe which occurs in the input, we first iterate over all
282            // placeholders and then over all inference variables.
283            //
284            // Whenever we compress the universe of a placeholder, no existential with
285            // an already compressed universe can name that placeholder.
286            for is_existential in [false, true] {
287                for var in var_infos.iter_mut() {
288                    // We simply put all regions from the input into the highest
289                    // compressed universe, so we only deal with them at the end.
290                    if !var.is_region() {
291                        if is_existential == var.is_existential() {
292                            update_uv(var, orig_uv, is_existential)
293                        }
294                    }
295                }
296            }
297        }
298
299        // We put all regions into a separate universe.
300        let mut first_region = true;
301        for var in var_infos.iter_mut() {
302            if var.is_region() {
303                if first_region {
304                    first_region = false;
305                    curr_compressed_uv = curr_compressed_uv.next_universe();
306                }
307                assert!(var.is_existential());
308                *var = var.with_updated_universe(curr_compressed_uv);
309            }
310        }
311
312        let var_infos = self.delegate.cx().mk_canonical_var_infos(&var_infos);
313        (curr_compressed_uv, var_infos)
314    }
315
316    fn cached_fold_ty(&mut self, t: I::Ty) -> I::Ty {
317        let kind = match t.kind() {
318            ty::Infer(i) => match i {
319                ty::TyVar(vid) => {
320                    assert_eq!(
321                        self.delegate.opportunistic_resolve_ty_var(vid),
322                        t,
323                        "ty vid should have been resolved fully before canonicalization"
324                    );
325
326                    CanonicalVarKind::Ty(CanonicalTyVarKind::General(
327                        self.delegate
328                            .universe_of_ty(vid)
329                            .unwrap_or_else(|| panic!("ty var should have been resolved: {t:?}")),
330                    ))
331                }
332                ty::IntVar(vid) => {
333                    assert_eq!(
334                        self.delegate.opportunistic_resolve_int_var(vid),
335                        t,
336                        "ty vid should have been resolved fully before canonicalization"
337                    );
338                    CanonicalVarKind::Ty(CanonicalTyVarKind::Int)
339                }
340                ty::FloatVar(vid) => {
341                    assert_eq!(
342                        self.delegate.opportunistic_resolve_float_var(vid),
343                        t,
344                        "ty vid should have been resolved fully before canonicalization"
345                    );
346                    CanonicalVarKind::Ty(CanonicalTyVarKind::Float)
347                }
348                ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => {
349                    panic!("fresh vars not expected in canonicalization")
350                }
351            },
352            ty::Placeholder(placeholder) => match self.canonicalize_mode {
353                CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderTy(
354                    PlaceholderLike::new(placeholder.universe(), self.variables.len().into()),
355                ),
356                CanonicalizeMode::Response { .. } => CanonicalVarKind::PlaceholderTy(placeholder),
357            },
358            ty::Param(_) => match self.canonicalize_mode {
359                CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderTy(
360                    PlaceholderLike::new(ty::UniverseIndex::ROOT, self.variables.len().into()),
361                ),
362                CanonicalizeMode::Response { .. } => panic!("param ty in response: {t:?}"),
363            },
364            ty::Bool
365            | ty::Char
366            | ty::Int(_)
367            | ty::Uint(_)
368            | ty::Float(_)
369            | ty::Adt(_, _)
370            | ty::Foreign(_)
371            | ty::Str
372            | ty::Array(_, _)
373            | ty::Slice(_)
374            | ty::RawPtr(_, _)
375            | ty::Ref(_, _, _)
376            | ty::Pat(_, _)
377            | ty::FnDef(_, _)
378            | ty::FnPtr(..)
379            | ty::UnsafeBinder(_)
380            | ty::Dynamic(_, _, _)
381            | ty::Closure(..)
382            | ty::CoroutineClosure(..)
383            | ty::Coroutine(_, _)
384            | ty::CoroutineWitness(..)
385            | ty::Never
386            | ty::Tuple(_)
387            | ty::Alias(_, _)
388            | ty::Bound(_, _)
389            | ty::Error(_) => {
390                return ensure_sufficient_stack(|| t.super_fold_with(self));
391            }
392        };
393
394        let var = self.get_or_insert_bound_var(t, CanonicalVarInfo { kind });
395
396        Ty::new_anon_bound(self.cx(), self.binder_index, var)
397    }
398}
399
400impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicalizer<'_, D, I> {
401    fn cx(&self) -> I {
402        self.delegate.cx()
403    }
404
405    fn fold_binder<T>(&mut self, t: ty::Binder<I, T>) -> ty::Binder<I, T>
406    where
407        T: TypeFoldable<I>,
408    {
409        self.binder_index.shift_in(1);
410        let t = t.super_fold_with(self);
411        self.binder_index.shift_out(1);
412        t
413    }
414
415    fn fold_region(&mut self, r: I::Region) -> I::Region {
416        let kind = match r.kind() {
417            ty::ReBound(..) => return r,
418
419            // We don't canonicalize `ReStatic` in the `param_env` as we use it
420            // when checking whether a `ParamEnv` candidate is global.
421            ty::ReStatic => match self.canonicalize_mode {
422                CanonicalizeMode::Input { keep_static: false } => {
423                    CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
424                }
425                CanonicalizeMode::Input { keep_static: true }
426                | CanonicalizeMode::Response { .. } => return r,
427            },
428
429            // `ReErased` should only be encountered in the hidden
430            // type of an opaque for regions that are ignored for the purposes of
431            // captures.
432            //
433            // FIXME: We should investigate the perf implications of not uniquifying
434            // `ReErased`. We may be able to short-circuit registering region
435            // obligations if we encounter a `ReErased` on one side, for example.
436            ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
437                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
438                CanonicalizeMode::Response { .. } => return r,
439            },
440
441            ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode {
442                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
443                CanonicalizeMode::Response { .. } => {
444                    panic!("unexpected region in response: {r:?}")
445                }
446            },
447
448            ty::RePlaceholder(placeholder) => match self.canonicalize_mode {
449                // We canonicalize placeholder regions as existentials in query inputs.
450                CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
451                CanonicalizeMode::Response { max_input_universe } => {
452                    // If we have a placeholder region inside of a query, it must be from
453                    // a new universe.
454                    if max_input_universe.can_name(placeholder.universe()) {
455                        panic!("new placeholder in universe {max_input_universe:?}: {r:?}");
456                    }
457                    CanonicalVarKind::PlaceholderRegion(placeholder)
458                }
459            },
460
461            ty::ReVar(vid) => {
462                assert_eq!(
463                    self.delegate.opportunistic_resolve_lt_var(vid),
464                    r,
465                    "region vid should have been resolved fully before canonicalization"
466                );
467                match self.canonicalize_mode {
468                    CanonicalizeMode::Input { keep_static: _ } => {
469                        CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
470                    }
471                    CanonicalizeMode::Response { .. } => {
472                        CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap())
473                    }
474                }
475            }
476        };
477
478        let var = self.get_or_insert_bound_var(r, CanonicalVarInfo { kind });
479
480        Region::new_anon_bound(self.cx(), self.binder_index, var)
481    }
482
483    fn fold_ty(&mut self, t: I::Ty) -> I::Ty {
484        if let Some(&ty) = self.cache.get(&(self.binder_index, t)) {
485            ty
486        } else {
487            let res = self.cached_fold_ty(t);
488            assert!(self.cache.insert((self.binder_index, t), res).is_none());
489            res
490        }
491    }
492
493    fn fold_const(&mut self, c: I::Const) -> I::Const {
494        let kind = match c.kind() {
495            ty::ConstKind::Infer(i) => match i {
496                ty::InferConst::Var(vid) => {
497                    assert_eq!(
498                        self.delegate.opportunistic_resolve_ct_var(vid),
499                        c,
500                        "const vid should have been resolved fully before canonicalization"
501                    );
502                    CanonicalVarKind::Const(self.delegate.universe_of_ct(vid).unwrap())
503                }
504                ty::InferConst::Fresh(_) => todo!(),
505            },
506            ty::ConstKind::Placeholder(placeholder) => match self.canonicalize_mode {
507                CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderConst(
508                    PlaceholderLike::new(placeholder.universe(), self.variables.len().into()),
509                ),
510                CanonicalizeMode::Response { .. } => {
511                    CanonicalVarKind::PlaceholderConst(placeholder)
512                }
513            },
514            ty::ConstKind::Param(_) => match self.canonicalize_mode {
515                CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderConst(
516                    PlaceholderLike::new(ty::UniverseIndex::ROOT, self.variables.len().into()),
517                ),
518                CanonicalizeMode::Response { .. } => panic!("param ty in response: {c:?}"),
519            },
520            // FIXME: See comment above -- we could fold the region separately or something.
521            ty::ConstKind::Bound(_, _)
522            | ty::ConstKind::Unevaluated(_)
523            | ty::ConstKind::Value(_)
524            | ty::ConstKind::Error(_)
525            | ty::ConstKind::Expr(_) => return c.super_fold_with(self),
526        };
527
528        let var = self.get_or_insert_bound_var(c, CanonicalVarInfo { kind });
529
530        Const::new_anon_bound(self.cx(), self.binder_index, var)
531    }
532}