extern crate core;
mod axis;
mod class;
mod effect;
pub use axis::{BindingOrigin, EscapeKind};
pub use class::ReactivityClass;
use effect::{EffectKind, EffectSet};
#[cfg(kani)]
mod classify_proofs;
#[cfg(kani)]
mod proofs {
use super::ReactivityClass::{PropsStable, Reactive, Static, Unstable};
use super::{EffectKind, EffectSet, ReactivityClass};
pub(super) const EFFECTS: [EffectKind; 8] = [
EffectKind::Freeze,
EffectKind::Capture,
EffectKind::ReadProp,
EffectKind::ReadReactive,
EffectKind::MutateLocal,
EffectKind::MutateGlobal,
EffectKind::CallUnknown,
EffectKind::Allocate,
];
fn class(rank: u8) -> ReactivityClass {
match rank {
0 => Static,
1 => PropsStable,
2 => Reactive,
_ => Unstable,
}
}
pub(super) fn any_class() -> ReactivityClass {
let rank: u8 = kani::any();
kani::assume(rank < 4);
class(rank)
}
pub(super) fn any_effect() -> EffectKind {
let index: usize = kani::any();
kani::assume(index < EFFECTS.len());
EFFECTS[index]
}
pub(super) fn effects(mask: u8) -> EffectSet {
let mut set = EffectSet::empty();
let mut index = 0;
while index < EFFECTS.len() {
if mask & (1 << index) != 0 {
set = set.with(EFFECTS[index]);
}
index += 1;
}
set
}
fn rank(class: ReactivityClass) -> u8 {
match class {
Static => 0,
PropsStable => 1,
Reactive => 2,
Unstable => 3,
}
}
pub(super) fn effect_demand(effect: EffectKind) -> ReactivityClass {
match effect {
EffectKind::Freeze | EffectKind::ReadProp | EffectKind::Allocate => PropsStable,
EffectKind::Capture | EffectKind::ReadReactive | EffectKind::MutateLocal => Reactive,
EffectKind::MutateGlobal | EffectKind::CallUnknown => Unstable,
}
}
#[kani::proof]
fn the_value_axis_is_a_strict_chain() {
let (a, b, c) = (any_class(), any_class(), any_class());
assert!(a <= a);
if a <= b && b <= c {
assert!(a <= c);
}
if a <= b && b <= a {
assert_eq!(a, b);
}
assert!(a <= b || b <= a);
assert!(Static <= a && a <= Unstable);
assert!(Static < PropsStable && PropsStable < Reactive && Reactive < Unstable);
assert_eq!(a <= b, rank(a) <= rank(b));
}
#[kani::proof]
fn join_is_a_least_upper_bound() {
let (a, b, c) = (any_class(), any_class(), any_class());
let joined = a.join(b);
assert!(joined >= a && joined >= b);
assert_eq!(joined, b.join(a));
assert_eq!(a.join(a), a);
assert_eq!(joined.join(c), a.join(b.join(c)));
if c >= a && c >= b {
assert!(c >= joined);
}
}
#[kani::proof]
fn join_has_its_units_and_is_monotone() {
let (a, b, c, d) = (any_class(), any_class(), any_class(), any_class());
assert_eq!(Static.join(a), a);
assert_eq!(a.join(Static), a);
assert_eq!(Unstable.join(a), Unstable);
assert_eq!(a.join(Unstable), Unstable);
assert_eq!(a.join(b) <= c, a <= c && b <= c);
assert_eq!(a.join(b) == b, a <= b);
assert_eq!(rank(a.join(b)), rank(a).max(rank(b)));
if a <= c && b <= d {
assert!(a.join(b) <= c.join(d));
}
}
#[kani::proof]
#[kani::unwind(10)]
fn the_effect_floor_is_the_join_of_the_rule_table() {
let mask: u8 = kani::any();
let mut joined = Static;
let mut index = 0;
while index < EFFECTS.len() {
if mask & (1 << index) != 0 {
assert!(effects(mask).contains(EFFECTS[index]));
joined = joined.join(effect_demand(EFFECTS[index]));
}
index += 1;
}
assert_eq!(effects(mask).class_floor(), joined);
assert_eq!(EffectSet::empty().class_floor(), Static);
assert_eq!(effects(mask).is_empty(), mask == 0);
}
#[kani::proof]
#[kani::unwind(10)]
fn adding_an_effect_joins_its_floor() {
let set = effects(kani::any());
let effect = any_effect();
let floor = EffectSet::one(effect).class_floor();
assert_eq!(floor, effect_demand(effect));
assert_eq!(
set.with(effect).class_floor(),
floor.join(set.class_floor())
);
if set.contains(effect) {
assert!(floor <= set.class_floor());
}
}
#[kani::proof]
#[kani::unwind(10)]
fn effect_union_preserves_the_class_floor() {
let (left_mask, right_mask): (u8, u8) = (kani::any(), kani::any());
let (left, right) = (effects(left_mask), effects(right_mask));
let union = left.union(right);
assert_eq!(
union.class_floor(),
left.class_floor().join(right.class_floor())
);
assert!(union.class_floor() >= left.class_floor());
assert!(union.class_floor() >= right.class_floor());
if left_mask & right_mask == left_mask {
assert!(left.class_floor() <= right.class_floor());
}
}
#[kani::proof]
#[kani::unwind(10)]
fn a_bound_on_every_effect_bounds_the_set() {
let mask: u8 = kani::any();
let bound = any_class();
let mut every_member_is_bounded = true;
let mut index = 0;
while index < EFFECTS.len() {
if mask & (1 << index) != 0 {
every_member_is_bounded &= effect_demand(EFFECTS[index]) <= bound;
}
index += 1;
}
assert_eq!(
effects(mask).class_floor() <= bound,
every_member_is_bounded
);
}
}