diff --git a/lattices/src/collections.rs b/lattices/src/collections.rs index 6e6ed98a46dc..4b4c7e73250a 100644 --- a/lattices/src/collections.rs +++ b/lattices/src/collections.rs @@ -278,12 +278,23 @@ impl MapMapValues for VecMap { } /// A type that will always be an empty set. -#[derive(Default)] +#[derive(Default, Debug, Clone, Copy, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EmptySet { _x: PhantomData, } +impl PartialEq for EmptySet +where + Rhs: Len, +{ + fn eq(&self, other: &Rhs) -> bool { + other.is_empty() + } +} + +impl Eq for EmptySet {} + impl Collection for EmptySet { type Item = T; } @@ -400,6 +411,129 @@ impl IterMut for SingletonSet { } } +/// A key-value entry wrapper representing a singleton map. +#[derive(Debug, Clone, Copy, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EmptyMap(pub PhantomData, pub PhantomData); +impl IntoIterator for EmptyMap { + type Item = (K, V); + type IntoIter = std::iter::Empty<(K, V)>; + + fn into_iter(self) -> Self::IntoIter { + std::iter::empty() + } +} + +impl PartialEq for EmptyMap +where + Rhs: Len, +{ + fn eq(&self, other: &Rhs) -> bool { + other.is_empty() + } +} + +impl Eq for EmptyMap {} + +impl Collection for EmptyMap { + type Item = V; +} +impl Len for EmptyMap { + fn len(&self) -> usize { + 0 + } +} +impl CollectionRef for EmptyMap { + type ItemRef<'a> = &'a Self::Item + where + Self: 'a; + + covariant_item_ref!(); +} +impl<'a, Q, K, V> Get<&'a Q> for EmptyMap +where + K: Borrow, + Q: Eq + ?Sized, +{ + fn get(&self, _key: &'a Q) -> Option> { + None + } +} +impl CollectionMut for EmptyMap { + type ItemMut<'a> = &'a mut Self::Item + where + Self: 'a; + + covariant_item_mut!(); +} +impl<'a, Q, K, V> GetMut<&'a Q> for EmptyMap +where + K: Borrow, + Q: Eq + ?Sized, +{ + fn get_mut(&mut self, _key: &'a Q) -> Option> { + None + } +} +impl Keyed for EmptyMap { + type Key = K; +} +impl KeyedRef for EmptyMap { + type KeyRef<'a> = &'a Self::Key + where + Self: 'a; + + covariant_key_ref!(); +} +impl<'a, Q, K, V> GetKeyValue<&'a Q> for EmptyMap +where + K: Borrow, + Q: Eq + ?Sized, +{ + fn get_key_value(&self, _key: &'a Q) -> Option<(Self::KeyRef<'_>, Self::ItemRef<'_>)> { + None + } +} +impl<'a, Q, K, V> GetKeyValueMut<&'a Q> for EmptyMap +where + K: Borrow, + Q: Eq + ?Sized, +{ + fn get_key_value_mut(&mut self, _key: &'a Q) -> Option<(Self::KeyRef<'_>, Self::ItemMut<'_>)> { + None + } +} +impl Iter for EmptyMap { + type Iter<'a> = std::iter::Empty<&'a V> + where + Self: 'a; + + fn iter(&self) -> Self::Iter<'_> { + std::iter::empty() + } +} +impl SimpleKeyedRef for EmptyMap { + simple_keyed_ref!(); +} +impl MapIter for EmptyMap { + type Iter<'a> = std::iter::Empty<(&'a K, &'a V)> + where + Self: 'a; + + fn iter(&self) -> Self::Iter<'_> { + std::iter::empty() + } +} +impl MapIterMut for EmptyMap { + type IterMut<'a> = std::iter::Empty<(&'a K, &'a mut V)> + where + Self: 'a; + + fn iter_mut(&mut self) -> Self::IterMut<'_> { + std::iter::empty() + } +} + /// A key-value entry wrapper representing a singleton map. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/lattices/src/lib.rs b/lattices/src/lib.rs index 953e41d81e87..66188402773c 100644 --- a/lattices/src/lib.rs +++ b/lattices/src/lib.rs @@ -10,6 +10,7 @@ pub mod collections; mod conflict; mod dom_pair; pub mod map_union; +pub mod map_union_with_tombstones; mod ord; mod pair; mod point; diff --git a/lattices/src/map_union_with_tombstones.rs b/lattices/src/map_union_with_tombstones.rs new file mode 100644 index 000000000000..1a216282e8cd --- /dev/null +++ b/lattices/src/map_union_with_tombstones.rs @@ -0,0 +1,443 @@ +//! Module containing the [`MapUnionWithTombstones`] lattice and aliases for different datastructures. + +use std::cmp::Ordering::{self, *}; +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; + +use cc_traits::{Get, Iter, Len, Remove}; + +use crate::cc_traits::{GetMut, Keyed, Map, MapIter, SimpleKeyedRef}; +use crate::collections::{EmptyMap, EmptySet, SingletonMap, SingletonSet}; +use crate::{IsBot, IsTop, LatticeFrom, LatticeOrd, Merge}; + +/// Map-union-with-tombstones compound lattice. +/// +/// When a key is deleted from the map-union-with-tombstones lattice, it is removed from the underlying `map` and placed into +/// the `tombstones` set. +/// +/// This forms the first invariant for this data structure. A key should appear either nowhere, in `map` or in `tombstones`. +/// but never in `map` and `tombstones` at the same time. +/// +/// merging is done by merging the underlying `map` and then merging the `tombstones` set, then doing `map` = `map` - `tombstones`. +/// +/// The implementation of `tombstones` can be any set-like thing. This allows a user to plug in their own set-like implementation. +/// For example, if the user knows that keys will be created and deleted strictly sequentially, then they could create a highly optimized set implementation +/// which would just be a single integer, correpsonding to the current key value that the set is up to. Queries for keys below that integer would return true, +/// queries for keys above it would return false. +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MapUnionWithTombstones { + map: Map, + tombstones: TombstoneSet, +} + +impl MapUnionWithTombstones { + /// Create a new `MapUnionWithTombstones` from a `Map` and a `TombstoneSet`. + pub fn new(map: Map, tombstones: TombstoneSet) -> Self { + Self { map, tombstones } + } + + /// Create a new `MapUnionWithTombstones` from an `Into` and an `Into`. + pub fn new_from(map: impl Into, tombstones: impl Into) -> Self { + Self::new(map.into(), tombstones.into()) + } + + /// Reveal the inner value as a shared reference. + pub fn as_reveal_ref(&self) -> (&Map, &TombstoneSet) { + (&self.map, &self.tombstones) + } + + /// Reveal the inner value as an exclusive reference. + pub fn as_reveal_mut(&mut self) -> (&mut Map, &mut TombstoneSet) { + (&mut self.map, &mut self.tombstones) + } + + /// Gets the inner by value, consuming self. + pub fn into_reveal(self) -> (Map, TombstoneSet) { + (self.map, self.tombstones) + } +} + +impl + Merge> + for MapUnionWithTombstones +where + MapSelf: Keyed + + Extend<(K, ValSelf)> + + for<'a> GetMut<&'a K, Item = ValSelf> + + for<'b> Remove<&'b K>, + MapOther: IntoIterator, + ValSelf: Merge + LatticeFrom, + ValOther: IsBot, + TombstoneSetSelf: Extend + Len + for<'a> Get<&'a K> + Iter, + TombstoneSetOther: IntoIterator, +{ + fn merge(&mut self, other: MapUnionWithTombstones) -> bool { + let mut changed = false; + // This vec collect is needed to prevent simultaneous mut references `self.0.extend` and + // `self.0.get_mut`. + // TODO(mingwei): This could be fixed with a different structure, maybe some sort of + // `Collection` entry API. + let iter: Vec<_> = other + .map + .into_iter() + .filter(|(k_other, val_other)| { + !val_other.is_bot() && !self.tombstones.contains(k_other) + }) + .filter_map(|(k_other, val_other)| { + match self.map.get_mut(&k_other) { + // Key collision, merge into `self`. + Some(mut val_self) => { + changed |= val_self.merge(val_other); + None + } + // New value, convert for extending. + None => { + changed = true; + Some((k_other, ValSelf::lattice_from(val_other))) + } + } + }) + .collect(); + self.map.extend(iter); + + let old_self_tombstones_len = self.tombstones.len(); + + self.tombstones + .extend(other.tombstones.into_iter().inspect(|k| { + self.map.remove(k); + })); + + if old_self_tombstones_len != self.tombstones.len() { + changed = true; + } + + changed + } +} + +impl + LatticeFrom> + for MapUnionWithTombstones +where + MapSelf: Keyed + FromIterator<(K, ValSelf)>, + MapOther: IntoIterator, + ValSelf: LatticeFrom, + TombstoneSetSelf: FromIterator, + TombstoneSetOther: IntoIterator, +{ + fn lattice_from(other: MapUnionWithTombstones) -> Self { + Self { + map: other + .map + .into_iter() + .map(|(k_other, val_other)| (k_other, LatticeFrom::lattice_from(val_other))) + .collect(), + tombstones: other.tombstones.into_iter().collect(), + } + } +} + +impl + PartialOrd> + for MapUnionWithTombstones +where + MapSelf: Map + MapIter + SimpleKeyedRef, + MapOther: Map + MapIter + SimpleKeyedRef, + ValSelf: PartialOrd + IsBot, + ValOther: IsBot, + TombstoneSetSelf: Len + Iter + for<'a> Get<&'a K>, + TombstoneSetOther: Len + Iter + for<'a> Get<&'a K>, +{ + fn partial_cmp( + &self, + other: &MapUnionWithTombstones, + ) -> Option { + let self_tombstones_greater = self + .tombstones + .iter() + .any(|k| !other.tombstones.contains(&*k)); + + let other_tombstones_greater = other + .tombstones + .iter() + .any(|k| !self.tombstones.contains(&*k)); + + if self_tombstones_greater && other_tombstones_greater { + return None; + } + + let mut self_any_greater = false; + let mut other_any_greater = false; + let self_keys = self + .map + .iter() + .filter(|(k, v)| { + !v.is_bot() && !self.tombstones.contains(k) && !other.tombstones.contains(k) + }) + .map(|(k, _v)| ::into_ref(k)); + let other_keys = other + .map + .iter() + .filter(|(k, v)| { + !v.is_bot() && !self.tombstones.contains(k) && !other.tombstones.contains(k) + }) + .map(|(k, _v)| ::into_ref(k)); + + for k in self_keys.chain(other_keys) { + match (self.map.get(k), other.map.get(k)) { + (Some(self_value), Some(other_value)) => { + match self_value.partial_cmp(&*other_value) { + None => { + return None; + } + Some(Less) => { + other_any_greater = true; + } + Some(Greater) => { + self_any_greater = true; + } + Some(Equal) => {} + } + } + (Some(_), None) => { + self_any_greater = true; + } + (None, Some(_)) => { + other_any_greater = true; + } + (None, None) => unreachable!(), + } + if self_any_greater && other_any_greater { + return None; + } + } + match ( + self_any_greater, + other_any_greater, + self_tombstones_greater, + other_tombstones_greater, + ) { + (false, false, false, false) => Some(Equal), + + (false, false, true, false) => Some(Greater), + (false, false, false, true) => Some(Less), + + (true, false, false, false) => Some(Greater), + (false, true, false, false) => Some(Less), + + (true, false, true, false) => Some(Greater), + (false, true, false, true) => Some(Less), + + (true, false, false, true) => None, + (false, true, true, false) => None, + + (true, true, _, _) => unreachable!(), + (_, _, true, true) => unreachable!(), + } + } +} +impl + LatticeOrd> + for MapUnionWithTombstones +where + Self: PartialOrd>, +{ +} + +impl + PartialEq> + for MapUnionWithTombstones +where + MapSelf: Map + MapIter + SimpleKeyedRef, + MapOther: Map + MapIter + SimpleKeyedRef, + ValSelf: PartialEq + IsBot, + ValOther: IsBot, + TombstoneSetSelf: Len + Iter + for<'a> Get<&'a K>, + TombstoneSetOther: Len + Iter + for<'b> Get<&'b K>, +{ + fn eq(&self, other: &MapUnionWithTombstones) -> bool { + if self.tombstones.len() != other.tombstones.len() { + return false; + } + + if self + .tombstones + .iter() + .any(|k| !other.tombstones.contains(&*k)) + { + return false; + } + + if other + .tombstones + .iter() + .any(|k| !self.tombstones.contains(&*k)) + { + return false; + } + + let self_keys = self + .map + .iter() + .filter(|(_k, v)| !v.is_bot()) + .map(|(k, _v)| ::into_ref(k)); + let other_keys = other + .map + .iter() + .filter(|(_k, v)| !v.is_bot()) + .map(|(k, _v)| ::into_ref(k)); + for k in self_keys.chain(other_keys) { + match (self.map.get(k), other.map.get(k)) { + (Some(self_value), Some(other_value)) => { + if *self_value != *other_value { + return false; + } + } + (None, None) => unreachable!(), + _ => { + return false; + } + } + } + + true + } +} +impl Eq for MapUnionWithTombstones where + Self: PartialEq +{ +} + +impl IsBot for MapUnionWithTombstones +where + Map: Iter, + Map::Item: IsBot, + TombstoneSet: Len, +{ + fn is_bot(&self) -> bool { + self.map.iter().all(|v| v.is_bot()) && self.tombstones.is_empty() + } +} + +impl IsTop for MapUnionWithTombstones { + fn is_top(&self) -> bool { + false + } +} + +/// [`std::collections::HashMap`]-backed [`MapUnionWithTombstones`] lattice. +pub type MapUnionHashMapWithTombstoneHashSet = + MapUnionWithTombstones, HashSet>; + +/// [`crate::collections::SingletonMap`]-backed [`MapUnionWithTombstones`] lattice. +pub type MapUnionWithTombstonesSingletonMapOnly = + MapUnionWithTombstones, EmptySet>; + +/// [`crate::collections::SingletonSet`]-backed [`MapUnionWithTombstones`] lattice. +pub type MapUnionWithTombstonesTombstoneSingletonSetOnly = + MapUnionWithTombstones, SingletonSet>; + +#[cfg(test)] +mod test { + use std::collections::HashSet; + + use super::*; + use crate::collections::{SingletonMap, SingletonSet}; + use crate::set_union::{SetUnion, SetUnionHashSet, SetUnionSingletonSet}; + use crate::test::check_all; + use crate::NaiveLatticeOrd; + + #[test] + fn test_map_union() { + type K = &'static str; + type V = usize; + + type M = MapUnionWithTombstones>, HashSet>; + type S = MapUnionWithTombstones>, EmptySet>; + type T = MapUnionWithTombstones>>, SingletonSet>; + + let mut my_map_a = M::default(); + let my_map_b = S::new( + SingletonMap("hello", SetUnion::new(SingletonSet(100))), + Default::default(), + ); + + let my_map_c = T::new(Default::default(), SingletonSet("hello")); + + my_map_a.merge(my_map_b); + my_map_a.merge(my_map_c); + + assert!(!my_map_a.as_reveal_ref().0.contains_key("hello")); + } + + #[test] + fn contrain1() { + type T = MapUnionWithTombstones>>, HashSet>; + + let a = T::new_from([], HashSet::from_iter([0])); + let b = T::new_from( + [(0, SetUnionHashSet::new_from([0]))], + HashSet::from_iter([]), + ); + + assert_eq!(a.naive_cmp(&b), Some(Greater)); + assert_eq!(a.partial_cmp(&b), Some(Greater)); + + let a = T::new_from([], HashSet::from_iter([1])); + let b = T::new_from([(0, SetUnionHashSet::new_from([0]))], HashSet::default()); + + assert_eq!(a.naive_cmp(&b), None); + assert_eq!(a.partial_cmp(&b), None); + } + + #[test] + fn consistency() { + type K = &'static str; + type V = SetUnion>; + + type M = MapUnionWithTombstones, HashSet>; + + let mut test_vec = Vec::new(); + + #[rustfmt::skip] + { + test_vec.push(M::new_from([], HashSet::from_iter([]))); + + test_vec.push(M::new_from([], HashSet::from_iter(["a"]))); + test_vec.push(M::new_from([], HashSet::from_iter(["b"]))); + test_vec.push(M::new_from([], HashSet::from_iter(["a", "b"]))); + + test_vec.push(M::new_from([("a", SetUnionHashSet::new_from([]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("a", SetUnionHashSet::new_from([0]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("a", SetUnionHashSet::new_from([1]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("a", SetUnionHashSet::new_from([0, 1]))], HashSet::from_iter([]))); + + test_vec.push(M::new_from([("b", SetUnionHashSet::new_from([]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("b", SetUnionHashSet::new_from([0]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("b", SetUnionHashSet::new_from([1]))], HashSet::from_iter([]))); + test_vec.push(M::new_from([("b", SetUnionHashSet::new_from([0, 1]))], HashSet::from_iter([]))); + }; + + check_all(&test_vec); + } + + /// Check that a key with a value of bottom is the same as an empty map, etc. + #[test] + fn test_collapses_bot() { + type K = &'static str; + type V = SetUnion>; + + type A = MapUnionWithTombstones, HashSet>; + type B = MapUnionWithTombstones, HashSet>; + + let map_empty = A::default(); + + let map_a_bot = B::new(SingletonMap("a", Default::default()), Default::default()); + let map_b_bot = B::new(SingletonMap("b", Default::default()), Default::default()); + + assert_eq!(map_empty, map_a_bot); + assert_eq!(map_empty, map_b_bot); + assert_eq!(map_a_bot, map_b_bot); + } +}