rustc_infer/infer/snapshot/
undo_log.rs

1use std::marker::PhantomData;
2
3use rustc_data_structures::undo_log::{Rollback, UndoLogs};
4use rustc_data_structures::{snapshot_vec as sv, unify as ut};
5use rustc_middle::ty::{self, OpaqueHiddenType, OpaqueTypeKey};
6use tracing::debug;
7
8use crate::infer::unify_key::{ConstVidKey, RegionVidKey};
9use crate::infer::{InferCtxtInner, region_constraints, type_variable};
10use crate::traits;
11
12pub struct Snapshot<'tcx> {
13    pub(crate) undo_len: usize,
14    _marker: PhantomData<&'tcx ()>,
15}
16
17/// Records the "undo" data for a single operation that affects some form of inference variable.
18#[derive(Clone)]
19pub(crate) enum UndoLog<'tcx> {
20    DuplicateOpaqueType,
21    OpaqueTypes(OpaqueTypeKey<'tcx>, Option<OpaqueHiddenType<'tcx>>),
22    TypeVariables(sv::UndoLog<ut::Delegate<type_variable::TyVidEqKey<'tcx>>>),
23    ConstUnificationTable(sv::UndoLog<ut::Delegate<ConstVidKey<'tcx>>>),
24    IntUnificationTable(sv::UndoLog<ut::Delegate<ty::IntVid>>),
25    FloatUnificationTable(sv::UndoLog<ut::Delegate<ty::FloatVid>>),
26    RegionConstraintCollector(region_constraints::UndoLog<'tcx>),
27    RegionUnificationTable(sv::UndoLog<ut::Delegate<RegionVidKey<'tcx>>>),
28    ProjectionCache(traits::UndoLog<'tcx>),
29    PushRegionObligation,
30}
31
32macro_rules! impl_from {
33    ($($ctor:ident ($ty:ty),)*) => {
34        $(
35        impl<'tcx> From<$ty> for UndoLog<'tcx> {
36            fn from(x: $ty) -> Self {
37                UndoLog::$ctor(x.into())
38            }
39        }
40        )*
41    }
42}
43
44// Upcast from a single kind of "undoable action" to the general enum
45impl_from! {
46    RegionConstraintCollector(region_constraints::UndoLog<'tcx>),
47
48    TypeVariables(sv::UndoLog<ut::Delegate<type_variable::TyVidEqKey<'tcx>>>),
49    IntUnificationTable(sv::UndoLog<ut::Delegate<ty::IntVid>>),
50    FloatUnificationTable(sv::UndoLog<ut::Delegate<ty::FloatVid>>),
51
52    ConstUnificationTable(sv::UndoLog<ut::Delegate<ConstVidKey<'tcx>>>),
53
54    RegionUnificationTable(sv::UndoLog<ut::Delegate<RegionVidKey<'tcx>>>),
55    ProjectionCache(traits::UndoLog<'tcx>),
56}
57
58/// The Rollback trait defines how to rollback a particular action.
59impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
60    fn reverse(&mut self, undo: UndoLog<'tcx>) {
61        match undo {
62            UndoLog::DuplicateOpaqueType => self.opaque_type_storage.pop_duplicate_entry(),
63            UndoLog::OpaqueTypes(key, idx) => self.opaque_type_storage.remove(key, idx),
64            UndoLog::TypeVariables(undo) => self.type_variable_storage.reverse(undo),
65            UndoLog::ConstUnificationTable(undo) => self.const_unification_storage.reverse(undo),
66            UndoLog::IntUnificationTable(undo) => self.int_unification_storage.reverse(undo),
67            UndoLog::FloatUnificationTable(undo) => self.float_unification_storage.reverse(undo),
68            UndoLog::RegionConstraintCollector(undo) => {
69                self.region_constraint_storage.as_mut().unwrap().reverse(undo)
70            }
71            UndoLog::RegionUnificationTable(undo) => {
72                self.region_constraint_storage.as_mut().unwrap().unification_table.reverse(undo)
73            }
74            UndoLog::ProjectionCache(undo) => self.projection_cache.reverse(undo),
75            UndoLog::PushRegionObligation => {
76                self.region_obligations.pop();
77            }
78        }
79    }
80}
81
82/// The combined undo log for all the various unification tables. For each change to the storage
83/// for any kind of inference variable, we record an UndoLog entry in the vector here.
84#[derive(Clone, Default)]
85pub(crate) struct InferCtxtUndoLogs<'tcx> {
86    logs: Vec<UndoLog<'tcx>>,
87    num_open_snapshots: usize,
88}
89
90/// The UndoLogs trait defines how we undo a particular kind of action (of type T). We can undo any
91/// action that is convertible into an UndoLog (per the From impls above).
92impl<'tcx, T> UndoLogs<T> for InferCtxtUndoLogs<'tcx>
93where
94    UndoLog<'tcx>: From<T>,
95{
96    #[inline]
97    fn num_open_snapshots(&self) -> usize {
98        self.num_open_snapshots
99    }
100
101    #[inline]
102    fn push(&mut self, undo: T) {
103        if self.in_snapshot() {
104            self.logs.push(undo.into())
105        }
106    }
107
108    fn clear(&mut self) {
109        self.logs.clear();
110        self.num_open_snapshots = 0;
111    }
112
113    fn extend<J>(&mut self, undos: J)
114    where
115        Self: Sized,
116        J: IntoIterator<Item = T>,
117    {
118        if self.in_snapshot() {
119            self.logs.extend(undos.into_iter().map(UndoLog::from))
120        }
121    }
122}
123
124impl<'tcx> InferCtxtInner<'tcx> {
125    pub fn rollback_to(&mut self, snapshot: Snapshot<'tcx>) {
126        debug!("rollback_to({})", snapshot.undo_len);
127        self.undo_log.assert_open_snapshot(&snapshot);
128
129        while self.undo_log.logs.len() > snapshot.undo_len {
130            let undo = self.undo_log.logs.pop().unwrap();
131            self.reverse(undo);
132        }
133
134        self.type_variable_storage.finalize_rollback();
135
136        if self.undo_log.num_open_snapshots == 1 {
137            // After the root snapshot the undo log should be empty.
138            assert!(snapshot.undo_len == 0);
139            assert!(self.undo_log.logs.is_empty());
140        }
141
142        self.undo_log.num_open_snapshots -= 1;
143    }
144
145    pub fn commit(&mut self, snapshot: Snapshot<'tcx>) {
146        debug!("commit({})", snapshot.undo_len);
147
148        if self.undo_log.num_open_snapshots == 1 {
149            // The root snapshot. It's safe to clear the undo log because
150            // there's no snapshot further out that we might need to roll back
151            // to.
152            assert!(snapshot.undo_len == 0);
153            self.undo_log.logs.clear();
154        }
155
156        self.undo_log.num_open_snapshots -= 1;
157    }
158}
159
160impl<'tcx> InferCtxtUndoLogs<'tcx> {
161    pub(crate) fn start_snapshot(&mut self) -> Snapshot<'tcx> {
162        self.num_open_snapshots += 1;
163        Snapshot { undo_len: self.logs.len(), _marker: PhantomData }
164    }
165
166    pub(crate) fn region_constraints_in_snapshot(
167        &self,
168        s: &Snapshot<'tcx>,
169    ) -> impl Iterator<Item = &'_ region_constraints::UndoLog<'tcx>> + Clone {
170        self.logs[s.undo_len..].iter().filter_map(|log| match log {
171            UndoLog::RegionConstraintCollector(log) => Some(log),
172            _ => None,
173        })
174    }
175
176    pub(crate) fn opaque_types_in_snapshot(&self, s: &Snapshot<'tcx>) -> bool {
177        self.logs[s.undo_len..].iter().any(|log| matches!(log, UndoLog::OpaqueTypes(..)))
178    }
179
180    fn assert_open_snapshot(&self, snapshot: &Snapshot<'tcx>) {
181        // Failures here may indicate a failure to follow a stack discipline.
182        assert!(self.logs.len() >= snapshot.undo_len);
183        assert!(self.num_open_snapshots > 0);
184    }
185}
186
187impl<'tcx> std::ops::Index<usize> for InferCtxtUndoLogs<'tcx> {
188    type Output = UndoLog<'tcx>;
189
190    fn index(&self, key: usize) -> &Self::Output {
191        &self.logs[key]
192    }
193}
194
195impl<'tcx> std::ops::IndexMut<usize> for InferCtxtUndoLogs<'tcx> {
196    fn index_mut(&mut self, key: usize) -> &mut Self::Output {
197        &mut self.logs[key]
198    }
199}