Skip to main content

hir_ty/next_solver/infer/snapshot/
fudge.rs

1use std::ops::Range;
2
3use ena::{
4    snapshot_vec as sv,
5    unify::{self as ut, UnifyKey},
6};
7use rustc_type_ir::{
8    ConstVid, FloatVid, IntVid, RegionVid, TyVid, TypeFoldable, TypeFolder, TypeSuperFoldable,
9    TypeVisitableExt, inherent::IntoKind,
10};
11
12use crate::{
13    Span,
14    next_solver::{
15        Const, ConstKind, DbInterner, Region, RegionKind, Ty, TyKind,
16        infer::{
17            InferCtxt, UnificationTable, iter_idx_range,
18            snapshot::VariableLengths,
19            unify_key::{ConstVariableValue, ConstVidKey},
20        },
21    },
22};
23
24fn vars_since_snapshot<'db, T>(
25    table: &UnificationTable<'_, 'db, T>,
26    snapshot_var_len: usize,
27) -> Range<T>
28where
29    T: UnifyKey,
30    super::UndoLog<'db>: From<sv::UndoLog<ut::Delegate<T>>>,
31{
32    T::from_index(snapshot_var_len as u32)..T::from_index(table.len() as u32)
33}
34
35fn const_vars_since_snapshot<'db>(
36    table: &mut UnificationTable<'_, 'db, ConstVidKey<'db>>,
37    snapshot_var_len: usize,
38) -> (Range<ConstVid>, Vec<Span>) {
39    let range = vars_since_snapshot(table, snapshot_var_len);
40    let range = range.start.vid..range.end.vid;
41
42    (
43        range.clone(),
44        iter_idx_range(range)
45            .map(|index| match table.probe_value(index) {
46                ConstVariableValue::Known { value: _ } => Span::Dummy,
47                ConstVariableValue::Unknown { span, universe: _ } => span,
48            })
49            .collect(),
50    )
51}
52
53impl<'db> InferCtxt<'db> {
54    /// This rather funky routine is used while processing expected
55    /// types. What happens here is that we want to propagate a
56    /// coercion through the return type of a fn to its
57    /// argument. Consider the type of `Option::Some`, which is
58    /// basically `for<T> fn(T) -> Option<T>`. So if we have an
59    /// expression `Some(&[1, 2, 3])`, and that has the expected type
60    /// `Option<&[u32]>`, we would like to type check `&[1, 2, 3]`
61    /// with the expectation of `&[u32]`. This will cause us to coerce
62    /// from `&[u32; 3]` to `&[u32]` and make the users life more
63    /// pleasant.
64    ///
65    /// The way we do this is using `fudge_inference_if_ok`. What the
66    /// routine actually does is to start a snapshot and execute the
67    /// closure `f`. In our example above, what this closure will do
68    /// is to unify the expectation (`Option<&[u32]>`) with the actual
69    /// return type (`Option<?T>`, where `?T` represents the variable
70    /// instantiated for `T`). This will cause `?T` to be unified
71    /// with `&?a [u32]`, where `?a` is a fresh lifetime variable. The
72    /// input type (`?T`) is then returned by `f()`.
73    ///
74    /// At this point, `fudge_inference_if_ok` will normalize all type
75    /// variables, converting `?T` to `&?a [u32]` and end the
76    /// snapshot. The problem is that we can't just return this type
77    /// out, because it references the region variable `?a`, and that
78    /// region variable was popped when we popped the snapshot.
79    ///
80    /// So what we do is to keep a list (`region_vars`, in the code below)
81    /// of region variables created during the snapshot (here, `?a`). We
82    /// fold the return value and replace any such regions with a *new*
83    /// region variable (e.g., `?b`) and return the result (`&?b [u32]`).
84    /// This can then be used as the expectation for the fn argument.
85    ///
86    /// The important point here is that, for soundness purposes, the
87    /// regions in question are not particularly important. We will
88    /// use the expected types to guide coercions, but we will still
89    /// type-check the resulting types from those coercions against
90    /// the actual types (`?T`, `Option<?T>`) -- and remember that
91    /// after the snapshot is popped, the variable `?T` is no longer
92    /// unified.
93    pub fn fudge_inference_if_ok<T, E, F>(&self, f: F) -> Result<T, E>
94    where
95        F: FnOnce() -> Result<T, E>,
96        T: TypeFoldable<DbInterner<'db>>,
97    {
98        let variable_lengths = self.variable_lengths();
99        let (snapshot_vars, value) = self.probe(|_| {
100            let value = f()?;
101            // At this point, `value` could in principle refer
102            // to inference variables that have been created during
103            // the snapshot. Once we exit `probe()`, those are
104            // going to be popped, so we will have to
105            // eliminate any references to them.
106            let snapshot_vars = SnapshotVarData::new(self, variable_lengths);
107            Ok((snapshot_vars, self.resolve_vars_if_possible(value)))
108        })?;
109
110        // At this point, we need to replace any of the now-popped
111        // type/region variables that appear in `value` with a fresh
112        // variable of the appropriate kind. We can't do this during
113        // the probe because they would just get popped then too. =)
114        Ok(self.fudge_inference(snapshot_vars, value))
115    }
116
117    fn fudge_inference<T: TypeFoldable<DbInterner<'db>>>(
118        &self,
119        snapshot_vars: SnapshotVarData,
120        value: T,
121    ) -> T {
122        // Micro-optimization: if no variables have been created, then
123        // `value` can't refer to any of them. =) So we can just return it.
124        if snapshot_vars.is_empty() {
125            value
126        } else {
127            value.fold_with(&mut InferenceFudger { infcx: self, snapshot_vars })
128        }
129    }
130}
131
132struct SnapshotVarData {
133    region_vars: (Range<RegionVid>, Vec<Span>),
134    type_vars: (Range<TyVid>, Vec<Span>),
135    int_vars: Range<IntVid>,
136    float_vars: Range<FloatVid>,
137    const_vars: (Range<ConstVid>, Vec<Span>),
138}
139
140impl SnapshotVarData {
141    fn new(infcx: &InferCtxt<'_>, vars_pre_snapshot: VariableLengths) -> SnapshotVarData {
142        let mut inner = infcx.inner.borrow_mut();
143        let region_vars = inner
144            .unwrap_region_constraints()
145            .vars_since_snapshot(vars_pre_snapshot.region_constraints_len);
146        let type_vars = inner.type_variables().vars_since_snapshot(vars_pre_snapshot.type_var_len);
147        let int_vars =
148            vars_since_snapshot(&inner.int_unification_table(), vars_pre_snapshot.int_var_len);
149        let float_vars =
150            vars_since_snapshot(&inner.float_unification_table(), vars_pre_snapshot.float_var_len);
151
152        let const_vars = const_vars_since_snapshot(
153            &mut inner.const_unification_table(),
154            vars_pre_snapshot.const_var_len,
155        );
156        SnapshotVarData { region_vars, type_vars, int_vars, float_vars, const_vars }
157    }
158
159    fn is_empty(&self) -> bool {
160        let SnapshotVarData { region_vars, type_vars, int_vars, float_vars, const_vars } = self;
161        region_vars.0.is_empty()
162            && type_vars.0.is_empty()
163            && int_vars.is_empty()
164            && float_vars.is_empty()
165            && const_vars.0.is_empty()
166    }
167}
168
169struct InferenceFudger<'a, 'db> {
170    infcx: &'a InferCtxt<'db>,
171    snapshot_vars: SnapshotVarData,
172}
173
174impl<'a, 'db> TypeFolder<DbInterner<'db>> for InferenceFudger<'a, 'db> {
175    fn cx(&self) -> DbInterner<'db> {
176        self.infcx.interner
177    }
178
179    fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> {
180        if let TyKind::Infer(infer_ty) = ty.kind() {
181            match infer_ty {
182                rustc_type_ir::TyVar(vid) => {
183                    if self.snapshot_vars.type_vars.0.contains(&vid) {
184                        // This variable was created during the fudging.
185                        // Recreate it with a fresh variable here.
186                        let idx = vid.as_usize() - self.snapshot_vars.type_vars.0.start.as_usize();
187                        let span = self.snapshot_vars.type_vars.1[idx];
188                        self.infcx.next_ty_var(span)
189                    } else {
190                        // This variable was created before the
191                        // "fudging". Since we refresh all type
192                        // variables to their binding anyhow, we know
193                        // that it is unbound, so we can just return
194                        // it.
195                        debug_assert!(
196                            self.infcx.inner.borrow_mut().type_variables().probe(vid).is_unknown()
197                        );
198                        ty
199                    }
200                }
201                rustc_type_ir::IntVar(vid) => {
202                    if self.snapshot_vars.int_vars.contains(&vid) {
203                        self.infcx.next_int_var()
204                    } else {
205                        ty
206                    }
207                }
208                rustc_type_ir::FloatVar(vid) => {
209                    if self.snapshot_vars.float_vars.contains(&vid) {
210                        self.infcx.next_float_var()
211                    } else {
212                        ty
213                    }
214                }
215                rustc_type_ir::FreshTy(_)
216                | rustc_type_ir::FreshIntTy(_)
217                | rustc_type_ir::FreshFloatTy(_) => {
218                    unreachable!("unexpected fresh infcx var")
219                }
220            }
221        } else if ty.has_infer() {
222            ty.super_fold_with(self)
223        } else {
224            ty
225        }
226    }
227
228    fn fold_region(&mut self, r: Region<'db>) -> Region<'db> {
229        if let RegionKind::ReVar(vid) = r.kind() {
230            if self.snapshot_vars.region_vars.0.contains(&vid) {
231                let idx = vid.index() - self.snapshot_vars.region_vars.0.start.index();
232                let span = self.snapshot_vars.region_vars.1[idx];
233                self.infcx.next_region_var(span)
234            } else {
235                r
236            }
237        } else {
238            r
239        }
240    }
241
242    fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> {
243        if let ConstKind::Infer(infer_ct) = ct.kind() {
244            match infer_ct {
245                rustc_type_ir::InferConst::Var(vid) => {
246                    if self.snapshot_vars.const_vars.0.contains(&vid) {
247                        let idx = vid.index() - self.snapshot_vars.const_vars.0.start.index();
248                        let span = self.snapshot_vars.const_vars.1[idx];
249                        self.infcx.next_const_var(span)
250                    } else {
251                        ct
252                    }
253                }
254                rustc_type_ir::InferConst::Fresh(_) => {
255                    unreachable!("unexpected fresh infcx var")
256                }
257            }
258        } else if ct.has_infer() {
259            ct.super_fold_with(self)
260        } else {
261            ct
262        }
263    }
264}