#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AnalysisId(u8);
pub const MAX_ANALYSES: u8 = 64;
const _: () = assert!(MAX_ANALYSES as u32 <= u64::BITS);
impl AnalysisId {
#[inline]
#[must_use]
pub const fn new(index: u8) -> Self {
assert!(
index < MAX_ANALYSES,
"analysis id is out of range: a Preserved set tracks MAX_ANALYSES analyses"
);
Self(index)
}
#[inline]
#[must_use]
pub const fn index(self) -> u8 {
self.0
}
#[inline]
#[must_use]
const fn bit(self) -> u64 {
1u64 << self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Preserved(u64);
impl Preserved {
pub const NONE: Preserved = Preserved(0);
pub const ALL: Preserved = Preserved(u64::MAX);
#[inline]
#[must_use]
pub const fn with(self, analysis: AnalysisId) -> Preserved {
Preserved(self.0 | analysis.bit())
}
#[inline]
#[must_use]
pub const fn without(self, analysis: AnalysisId) -> Preserved {
Preserved(self.0 & !analysis.bit())
}
#[inline]
#[must_use]
pub const fn preserves(self, analysis: AnalysisId) -> bool {
self.0 & analysis.bit() != 0
}
#[inline]
#[must_use]
pub const fn intersect(self, other: Preserved) -> Preserved {
Preserved(self.0 & other.0)
}
#[inline]
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[inline]
#[must_use]
pub const fn to_bits(self) -> u64 {
self.0
}
#[inline]
#[must_use]
pub const fn from_bits(bits: u64) -> Preserved {
Preserved(bits)
}
}
impl Default for Preserved {
fn default() -> Self {
Preserved::NONE
}
}
#[cfg(test)]
mod tests {
use super::{AnalysisId, Preserved};
const SCOPES: AnalysisId = AnalysisId::new(0);
const CONSTNESS: AnalysisId = AnalysisId::new(1);
const SLOTS: AnalysisId = AnalysisId::new(63);
#[test]
fn the_empty_set_preserves_nothing() {
assert!(Preserved::NONE.is_empty());
assert!(!Preserved::NONE.preserves(SCOPES));
}
#[test]
fn the_full_set_preserves_every_declared_analysis() {
assert!(Preserved::ALL.preserves(SCOPES));
assert!(Preserved::ALL.preserves(CONSTNESS));
assert!(Preserved::ALL.preserves(SLOTS));
assert!(!Preserved::ALL.is_empty());
}
#[test]
fn with_and_without_move_exactly_one_analysis() {
let set = Preserved::NONE.with(SCOPES).with(SLOTS);
assert!(set.preserves(SCOPES));
assert!(!set.preserves(CONSTNESS));
assert!(set.preserves(SLOTS));
let narrowed = set.without(SLOTS);
assert!(narrowed.preserves(SCOPES));
assert!(!narrowed.preserves(SLOTS));
}
#[test]
fn intersect_keeps_only_what_both_sides_preserve() {
let left = Preserved::NONE.with(SCOPES).with(CONSTNESS);
let right = Preserved::NONE.with(CONSTNESS).with(SLOTS);
let both = left.intersect(right);
assert!(!both.preserves(SCOPES));
assert!(both.preserves(CONSTNESS));
assert!(!both.preserves(SLOTS));
}
#[test]
fn intersecting_with_nothing_preserves_nothing() {
let set = Preserved::ALL.intersect(Preserved::NONE);
assert!(set.is_empty());
}
#[test]
fn bits_round_trip() {
let set = Preserved::NONE.with(SCOPES).with(SLOTS);
assert_eq!(Preserved::from_bits(set.to_bits()), set);
}
#[test]
fn the_default_set_is_the_conservative_one() {
assert_eq!(Preserved::default(), Preserved::NONE);
}
#[test]
fn intersection_is_const_evaluable() {
const LEFT: Preserved = Preserved::NONE.with(SCOPES).with(CONSTNESS);
const RIGHT: Preserved = Preserved::NONE.with(CONSTNESS);
const BOTH: Preserved = LEFT.intersect(RIGHT);
const _: () = assert!(BOTH.preserves(CONSTNESS));
const _: () = assert!(!BOTH.preserves(SCOPES));
assert_eq!(BOTH, RIGHT);
}
}