diff --git a/Cargo.lock b/Cargo.lock index dfe3db5907a..b46696dde7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4383,7 +4383,6 @@ dependencies = [ name = "rustc_typeck" version = "0.0.0" dependencies = [ - "itertools 0.9.0", "rustc_arena", "rustc_ast", "rustc_attr", @@ -4396,6 +4395,7 @@ dependencies = [ "rustc_lint", "rustc_macros", "rustc_middle", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_target", diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml index 1da106d7002..3279c331ade 100644 --- a/compiler/rustc_typeck/Cargo.toml +++ b/compiler/rustc_typeck/Cargo.toml @@ -8,7 +8,6 @@ test = false doctest = false [dependencies] -itertools = "0.9" rustc_arena = { path = "../rustc_arena" } tracing = "0.1" rustc_macros = { path = "../rustc_macros" } @@ -28,3 +27,4 @@ rustc_infer = { path = "../rustc_infer" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_ty_utils = { path = "../rustc_ty_utils" } rustc_lint = { path = "../rustc_lint" } +rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs index 30977293018..33533ab6189 100644 --- a/compiler/rustc_typeck/src/check/generator_interior.rs +++ b/compiler/rustc_typeck/src/check/generator_interior.rs @@ -3,13 +3,12 @@ //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the //! types computed here. -use std::mem; - use crate::expr_use_visitor::{self, ExprUseVisitor}; +use self::drop_ranges::DropRanges; + use super::FnCtxt; use hir::{HirIdMap, Node}; -use itertools::Itertools; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::pluralize; use rustc_hir as hir; @@ -30,6 +29,8 @@ use tracing::debug; #[cfg(test)] mod tests; +mod drop_ranges; + struct InteriorVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>, @@ -45,7 +46,7 @@ struct InteriorVisitor<'a, 'tcx> { guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>, guard_bindings_set: HirIdSet, linted_values: HirIdSet, - drop_ranges: HirIdMap<DropRange>, + drop_ranges: DropRanges, } impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { @@ -85,14 +86,10 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { yield_data.expr_and_pat_count, self.expr_count, source_span ); - match self.drop_ranges.get(&hir_id) { - Some(range) - if range.is_dropped_at(yield_data.expr_and_pat_count) => - { - debug!("value is dropped at yield point; not recording"); - return false; - } - _ => (), + if self.drop_ranges.is_dropped_at(hir_id, yield_data.expr_and_pat_count) + { + debug!("value is dropped at yield point; not recording"); + return false; } // If it is a borrowing happening in the guard, @@ -233,25 +230,27 @@ pub fn resolve_interior<'a, 'tcx>( let body = fcx.tcx.hir().body(body_id); let mut visitor = { - let mut drop_range_visitor = DropRangeVisitor { + let mut expr_use_visitor = ExprUseDelegate { hir: fcx.tcx.hir(), consumed_places: <_>::default(), borrowed_places: <_>::default(), - drop_ranges: <_>::default(), - expr_count: 0, }; // Run ExprUseVisitor to find where values are consumed. ExprUseVisitor::new( - &mut drop_range_visitor, + &mut expr_use_visitor, &fcx.infcx, def_id.expect_local(), fcx.param_env, &fcx.typeck_results.borrow(), ) .consume_body(body); + + let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor); intravisit::walk_body(&mut drop_range_visitor, body); + drop_range_visitor.drop_ranges.propagate_to_fixpoint(); + InteriorVisitor { fcx, types: FxIndexSet::default(), @@ -673,29 +672,46 @@ fn check_must_not_suspend_def( false } -/// This struct facilitates computing the ranges for which a place is uninitialized. -struct DropRangeVisitor<'tcx> { +struct ExprUseDelegate<'tcx> { hir: Map<'tcx>, /// Maps a HirId to a set of HirIds that are dropped by that node. consumed_places: HirIdMap<HirIdSet>, borrowed_places: HirIdSet, - drop_ranges: HirIdMap<DropRange>, - expr_count: usize, } -impl DropRangeVisitor<'tcx> { +impl<'tcx> ExprUseDelegate<'tcx> { fn mark_consumed(&mut self, consumer: HirId, target: HirId) { if !self.consumed_places.contains_key(&consumer) { self.consumed_places.insert(consumer, <_>::default()); } self.consumed_places.get_mut(&consumer).map(|places| places.insert(target)); } +} - fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange { - if !self.drop_ranges.contains_key(hir_id) { - self.drop_ranges.insert(*hir_id, DropRange::empty()); +/// This struct facilitates computing the ranges for which a place is uninitialized. +struct DropRangeVisitor<'tcx> { + hir: Map<'tcx>, + /// Maps a HirId to a set of HirIds that are dropped by that node. + consumed_places: HirIdMap<HirIdSet>, + borrowed_places: HirIdSet, + drop_ranges: DropRanges, + expr_count: usize, +} + +impl<'tcx> DropRangeVisitor<'tcx> { + fn from(uses: ExprUseDelegate<'tcx>) -> Self { + debug!("consumed_places: {:?}", uses.consumed_places); + let drop_ranges = DropRanges::new( + uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()), + &uses.hir, + ); + Self { + hir: uses.hir, + consumed_places: uses.consumed_places, + borrowed_places: uses.borrowed_places, + drop_ranges, + expr_count: 0, } - self.drop_ranges.get_mut(hir_id).unwrap() } fn record_drop(&mut self, hir_id: HirId) { @@ -704,41 +720,10 @@ impl DropRangeVisitor<'tcx> { } else { debug!("marking {:?} as dropped at {}", hir_id, self.expr_count); let count = self.expr_count; - self.drop_range(&hir_id).drop(count); + self.drop_ranges.drop_at(hir_id, count); } } - fn swap_drop_ranges(&mut self, mut other: HirIdMap<DropRange>) -> HirIdMap<DropRange> { - mem::swap(&mut self.drop_ranges, &mut other); - other - } - - fn fork_drop_ranges(&self) -> HirIdMap<DropRange> { - self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect() - } - - fn intersect_drop_ranges(&mut self, drops: HirIdMap<DropRange>) { - drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) { - Some(ranges) => *ranges = ranges.intersect(&v), - None => { - self.drop_ranges.insert(k, v); - } - }) - } - - fn merge_drop_ranges_at(&mut self, drops: HirIdMap<DropRange>, join_point: usize) { - drops.into_iter().for_each(|(k, v)| { - if !self.drop_ranges.contains_key(&k) { - self.drop_ranges.insert(k, DropRange { events: vec![] }); - } - self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point); - }); - } - - fn merge_drop_ranges(&mut self, drops: HirIdMap<DropRange>) { - self.merge_drop_ranges_at(drops, self.expr_count); - } - /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all /// expressions. This method consumes a little deeper into the expression when needed. fn consume_expr(&mut self, expr: &hir::Expr<'_>) { @@ -748,18 +733,7 @@ impl DropRangeVisitor<'tcx> { .get(&expr.hir_id) .map_or(vec![], |places| places.iter().cloned().collect()); for place in places { - self.record_drop(place); - if let Some(Node::Expr(expr)) = self.hir.find(place) { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { res: hir::def::Res::Local(hir_id), .. }, - )) => { - self.record_drop(*hir_id); - } - _ => (), - } - } + for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id)); } } @@ -771,13 +745,28 @@ impl DropRangeVisitor<'tcx> { { let location = self.expr_count; debug!("reinitializing {:?} at {}", hir_id, location); - self.drop_range(hir_id).reinit(location) + self.drop_ranges.reinit_at(*hir_id, location); } else { debug!("reinitializing {:?} is not supported", expr); } } } +fn for_each_consumable(place: HirId, node: Option<Node<'_>>, mut f: impl FnMut(HirId)) { + f(place); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(*hir_id); + } + _ => (), + } + } +} + fn place_hir_id(place: &Place<'_>) -> Option<HirId> { match place.base { PlaceBase::Rvalue | PlaceBase::StaticItem => None, @@ -786,7 +775,7 @@ fn place_hir_id(place: &Place<'_>) -> Option<HirId> { } } -impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> { +impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { fn consume( &mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>, @@ -839,58 +828,41 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { let mut reinit = None; match expr.kind { - ExprKind::AssignOp(_op, lhs, rhs) => { - // These operations are weird because their order of evaluation depends on whether - // the operator is overloaded. In a perfect world, we'd just ask the type checker - // whether this is a method call, but we also need to match the expression IDs - // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, - // so it runs both orders and picks the most conservative. We'll mirror that here. - let mut old_count = self.expr_count; - self.visit_expr(lhs); - self.visit_expr(rhs); + // ExprKind::AssignOp(_op, lhs, rhs) => { + // // These operations are weird because their order of evaluation depends on whether + // // the operator is overloaded. In a perfect world, we'd just ask the type checker + // // whether this is a method call, but we also need to match the expression IDs + // // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order, + // // so it runs both orders and picks the most conservative. We'll mirror that here. + // let mut old_count = self.expr_count; + // self.visit_expr(lhs); + // self.visit_expr(rhs); - let old_drops = self.swap_drop_ranges(<_>::default()); - std::mem::swap(&mut old_count, &mut self.expr_count); - self.visit_expr(rhs); - self.visit_expr(lhs); + // let old_drops = self.swap_drop_ranges(<_>::default()); + // std::mem::swap(&mut old_count, &mut self.expr_count); + // self.visit_expr(rhs); + // self.visit_expr(lhs); - // We should have visited the same number of expressions in either order. - assert_eq!(old_count, self.expr_count); + // // We should have visited the same number of expressions in either order. + // assert_eq!(old_count, self.expr_count); - self.intersect_drop_ranges(old_drops); - } + // self.intersect_drop_ranges(old_drops); + // } ExprKind::If(test, if_true, if_false) => { self.visit_expr(test); - match if_false { - Some(if_false) => { - let mut true_ranges = self.fork_drop_ranges(); - let mut false_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_true); + let true_end = self.expr_count - 1; - false_ranges = - self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count)); - self.visit_expr(if_false); - false_ranges = self.swap_drop_ranges(false_ranges); - - self.merge_drop_ranges(true_ranges); - self.merge_drop_ranges(false_ranges); - } - None => { - let mut true_ranges = self.fork_drop_ranges(); - debug!("true branch drop range fork: {:?}", true_ranges); - true_ranges = self.swap_drop_ranges(true_ranges); - self.visit_expr(if_true); - true_ranges = self.swap_drop_ranges(true_ranges); - debug!("true branch computed drop_ranges: {:?}", true_ranges); - debug!("drop ranges before merging: {:?}", self.drop_ranges); - self.merge_drop_ranges(true_ranges); - debug!("drop ranges after merging: {:?}", self.drop_ranges); - } + if let Some(if_false) = if_false { + self.drop_ranges.add_control_edge(fork, self.expr_count); + self.visit_expr(if_false); } + + self.drop_ranges.add_control_edge(true_end, self.expr_count); } ExprKind::Assign(lhs, rhs, _) => { self.visit_expr(lhs); @@ -899,27 +871,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { reinit = Some(lhs); } ExprKind::Loop(body, ..) => { - // FIXME: we probably need to iterate this to a fixpoint. - let body_drop_ranges = self.fork_drop_ranges(); - let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges); - - let join_point = self.expr_count; - + let loop_begin = self.expr_count; self.visit_block(body); - - let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges); - self.merge_drop_ranges_at(body_drop_ranges, join_point); + self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin); } ExprKind::Match(scrutinee, arms, ..) => { self.visit_expr(scrutinee); - let forked_ranges = self.fork_drop_ranges(); + let fork = self.expr_count - 1; let arm_drops = arms .iter() - .map(|Arm { hir_id, pat, body, guard, .. }| { - debug!("match arm {:?} starts at {}", hir_id, self.expr_count); - let old_ranges = self - .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count)); + .map(|Arm { pat, body, guard, .. }| { + self.drop_ranges.add_control_edge(fork, self.expr_count); self.visit_pat(pat); match guard { Some(Guard::If(expr)) => self.visit_expr(expr), @@ -930,10 +893,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { None => (), } self.visit_expr(body); - self.swap_drop_ranges(old_ranges) + self.expr_count - 1 }) .collect::<Vec<_>>(); - arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops)); + arm_drops.into_iter().for_each(|arm_end| { + self.drop_ranges.add_control_edge(arm_end, self.expr_count) + }); } _ => intravisit::walk_expr(self, expr), } @@ -952,160 +917,3 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> { self.expr_count += 1; } } - -fn trim_drop_ranges(drop_ranges: &HirIdMap<DropRange>, trim_from: usize) -> HirIdMap<DropRange> { - drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect() -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Event { - Drop(usize), - Reinit(usize), -} - -impl Event { - fn location(&self) -> usize { - match *self { - Event::Drop(i) | Event::Reinit(i) => i, - } - } -} - -impl PartialOrd for Event { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - self.location().partial_cmp(&other.location()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct DropRange { - events: Vec<Event>, -} - -impl DropRange { - fn empty() -> Self { - Self { events: vec![] } - } - - fn intersect(&self, other: &Self) -> Self { - let mut events = vec![]; - self.events - .iter() - .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap()) - .fold((false, false), |(left, right), event| match event { - itertools::EitherOrBoth::Both(_, _) => todo!(), - itertools::EitherOrBoth::Left(e) => match e { - Event::Drop(i) => { - if !left && right { - events.push(Event::Drop(*i)); - } - (true, right) - } - Event::Reinit(i) => { - if left && !right { - events.push(Event::Reinit(*i)); - } - (false, right) - } - }, - itertools::EitherOrBoth::Right(e) => match e { - Event::Drop(i) => { - if left && !right { - events.push(Event::Drop(*i)); - } - (left, true) - } - Event::Reinit(i) => { - if !left && right { - events.push(Event::Reinit(*i)); - } - (left, false) - } - }, - }); - Self { events } - } - - fn is_dropped_at(&self, id: usize) -> bool { - let dropped = match self.events.iter().try_fold(false, |is_dropped, event| { - if event.location() <= id { - Ok(match event { - Event::Drop(_) => true, - Event::Reinit(_) => false, - }) - } else { - Err(is_dropped) - } - }) { - Ok(is_dropped) | Err(is_dropped) => is_dropped, - }; - trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped); - dropped - } - - fn drop(&mut self, location: usize) { - self.events.push(Event::Drop(location)) - } - - fn reinit(&mut self, location: usize) { - self.events.push(Event::Reinit(location)); - } - - /// Merges another range with this one. Meant to be used at control flow join points. - /// - /// After merging, the value will be dead at the end of the range only if it was dead - /// at the end of both self and other. - #[tracing::instrument] - fn merge_with(&mut self, other: &DropRange, join_point: usize) { - let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) { - Event::Drop(join_point) - } else { - Event::Reinit(join_point) - }; - let events: Vec<_> = self - .events - .iter() - .merge([join_event].iter()) - .merge(other.events.iter()) - .dedup() - .cloned() - .collect(); - - trace!("events after merging: {:?}", events); - - self.events = events; - } - - /// Creates a new DropRange from this one at the split point. - /// - /// Used to model branching control flow. - fn fork_at(&self, split_point: usize) -> Self { - let result = Self { - events: vec![if self.is_dropped_at(split_point) { - Event::Drop(split_point) - } else { - Event::Reinit(split_point) - }], - }; - trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result); - result - } - - fn trimmed(&self, trim_from: usize) -> Self { - let start = if self.is_dropped_at(trim_from) { - Event::Drop(trim_from) - } else { - Event::Reinit(trim_from) - }; - - let result = Self { - events: [start] - .iter() - .chain(self.events.iter().skip_while(|event| event.location() <= trim_from)) - .cloned() - .collect(), - }; - trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result); - result - } -} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs new file mode 100644 index 00000000000..dadd9b8d753 --- /dev/null +++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs @@ -0,0 +1,173 @@ +use rustc_hir::{HirId, HirIdMap}; +use rustc_index::bit_set::BitSet; +use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; + +use super::for_each_consumable; + +rustc_index::newtype_index! { + pub struct PostOrderId { + DEBUG_FORMAT = "id({})", + } +} + +rustc_index::newtype_index! { + pub struct HirIdIndex { + DEBUG_FORMAT = "hidx({})", + } +} + +pub struct DropRanges { + hir_id_map: HirIdMap<HirIdIndex>, + nodes: IndexVec<PostOrderId, NodeInfo>, +} + +/// DropRanges keeps track of what values are definitely dropped at each point in the code. +/// +/// Values of interest are defined by the hir_id of their place. Locations in code are identified +/// by their index in the post-order traversal. At its core, DropRanges maps +/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely +/// dropped at the point of the node identified by post_order_id. +impl DropRanges { + pub fn new(hir_ids: impl Iterator<Item = HirId>, hir: &Map<'_>) -> Self { + let mut hir_id_map = HirIdMap::<HirIdIndex>::default(); + let mut next = <_>::from(0u32); + for hir_id in hir_ids { + for_each_consumable(hir_id, hir.find(hir_id), |hir_id| { + if !hir_id_map.contains_key(&hir_id) { + hir_id_map.insert(hir_id, next); + next = <_>::from(next.index() + 1); + } + }); + } + debug!("hir_id_map: {:?}", hir_id_map); + Self { hir_id_map, nodes: <_>::default() } + } + + fn hidx(&self, hir_id: HirId) -> HirIdIndex { + *self.hir_id_map.get(&hir_id).unwrap() + } + + pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool { + self.hir_id_map + .get(&hir_id) + .copied() + .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id)) + } + + /// Returns the number of values (hir_ids) that are tracked + fn num_values(&self) -> usize { + self.hir_id_map.len() + } + + fn node(&mut self, id: PostOrderId) -> &NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &self.nodes[id] + } + + fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &mut self.nodes[id] + } + + pub fn add_control_edge(&mut self, from: usize, to: usize) { + self.node_mut(from.into()).successors.push(to.into()); + } + + pub fn drop_at(&mut self, value: HirId, location: usize) { + let value = self.hidx(value); + self.node_mut(location.into()).drops.push(value); + } + + pub fn reinit_at(&mut self, value: HirId, location: usize) { + let value = match self.hir_id_map.get(&value) { + Some(value) => *value, + // If there's no value, this is never consumed and therefore is never dropped. We can + // ignore this. + None => return, + }; + self.node_mut(location.into()).reinits.push(value); + } + + pub fn propagate_to_fixpoint(&mut self) { + while self.propagate() {} + } + + fn propagate(&mut self) -> bool { + let mut visited = BitSet::new_empty(self.nodes.len()); + + self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false) + } + + fn visit( + &mut self, + visited: &mut BitSet<PostOrderId>, + id: PostOrderId, + pred_id: PostOrderId, + mut changed: bool, + ) -> bool { + if visited.contains(id) { + return changed; + } + visited.insert(id); + + changed &= self.nodes[id].merge_with(&self.nodes[pred_id]); + + if self.nodes[id].successors.len() == 0 { + self.visit(visited, PostOrderId::from(id.index() + 1), id, changed) + } else { + self.nodes[id] + .successors + .iter() + .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed)) + } + } +} + +struct NodeInfo { + /// IDs of nodes that can follow this one in the control flow + /// + /// If the vec is empty, then control proceeds to the next node. + successors: Vec<PostOrderId>, + + /// List of hir_ids that are dropped by this node. + drops: Vec<HirIdIndex>, + + /// List of hir_ids that are reinitialized by this node. + reinits: Vec<HirIdIndex>, + + /// Set of values that are definitely dropped at this point. + drop_state: BitSet<HirIdIndex>, +} + +impl NodeInfo { + fn new(num_values: usize) -> Self { + Self { + successors: vec![], + drops: vec![], + reinits: vec![], + drop_state: BitSet::new_empty(num_values), + } + } + + fn merge_with(&mut self, other: &NodeInfo) -> bool { + let mut changed = false; + for place in &self.drops { + if !self.drop_state.contains(place) && !self.reinits.contains(&place) { + changed = true; + self.drop_state.insert(place); + } + } + + for place in &self.reinits { + if self.drop_state.contains(place) { + changed = true; + self.drop_state.remove(place); + } + } + + changed + } +}