#![allow(clippy::unwrap_used, clippy::expect_used)]
mod law_support;
use std::mem::discriminant;
use std::sync::Arc;
use brink_format::{ClosureEnvEntry, ClosureValue, ListValue, OrderedMap, ProjSegment, Value};
use law_support::arb_value_full;
use proptest::prelude::*;
fn contains_nan(v: &Value) -> bool {
match v {
Value::Float(f) => f.is_nan(),
Value::Array(items) => items.iter().any(contains_nan),
Value::Map(map) => map.iter().any(|(_, val)| contains_nan(val)),
Value::Record { fields, .. } => fields.iter().any(contains_nan),
Value::Closure(c) => c.env.iter().any(|e| contains_nan(&e.payload)),
Value::Projection(p) => p.segments.iter().any(|seg| match seg {
ProjSegment::Index(_) => false,
ProjSegment::Key(k) => contains_nan(k),
}),
Value::OptionVal(inner) => inner.as_deref().is_some_and(contains_nan),
Value::Weighted(w) => w.entries.iter().any(|(_, val)| contains_nan(val)),
Value::Vec2(v) => v.is_nan(),
Value::Vec3(v) => v.is_nan(),
Value::Vec4(v) => v.is_nan(),
Value::Quat(q) => q.is_nan(),
Value::Mat2(m) => m.is_nan(),
Value::Mat3(m) => m.is_nan(),
Value::Mat4(m) => m.is_nan(),
Value::Range { .. }
| Value::Int(_)
| Value::Bool(_)
| Value::String(_)
| Value::List(_)
| Value::DivertTarget(_)
| Value::VariablePointer(_)
| Value::TempPointer { .. }
| Value::Null
| Value::FragmentRef(_)
| Value::FnRef(_)
| Value::Handle { .. } => false,
}
}
fn deep_copy(v: &Value) -> Value {
match v {
Value::Array(items) => Value::array(items.iter().map(deep_copy).collect()),
Value::Map(map) => {
let mut out = OrderedMap::with_capacity(map.len());
for (key, val) in map.iter() {
out.insert(key.clone(), deep_copy(val));
}
Value::map(out)
}
Value::Record { shape, fields } => {
Value::record(*shape, fields.iter().map(deep_copy).collect())
}
Value::Closure(c) => {
let ClosureValue { target, env } = c.as_ref();
let env = env
.iter()
.map(|e| ClosureEnvEntry {
name: e.name,
is_ref: e.is_ref,
payload: deep_copy(&e.payload),
})
.collect();
Value::closure(*target, env)
}
Value::Projection(p) => {
let segments = p
.segments
.iter()
.map(|seg| match seg {
ProjSegment::Index(n) => ProjSegment::Index(*n),
ProjSegment::Key(k) => ProjSegment::Key(deep_copy(k)),
})
.collect();
Value::projection(p.cell, segments)
}
Value::List(lv) => Value::List(Arc::new(ListValue {
items: lv.items.clone(),
origins: lv.origins.clone(),
})),
other => other.clone(),
}
}
fn shares_backing(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Array(x), Value::Array(y))
| (Value::Record { fields: x, .. }, Value::Record { fields: y, .. }) => Arc::ptr_eq(x, y),
(Value::Map(x), Value::Map(y)) => Arc::ptr_eq(x, y),
(Value::Closure(x), Value::Closure(y)) => Arc::ptr_eq(x, y),
(Value::Projection(x), Value::Projection(y)) => Arc::ptr_eq(x, y),
(Value::List(x), Value::List(y)) => Arc::ptr_eq(x, y),
_ => false,
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn eq_is_reflexive(v in arb_value_full()) {
prop_assume!(!contains_nan(&v));
prop_assert!(v == v, "value not equal to itself: {v:?}");
}
#[test]
fn eq_is_symmetric(a in arb_value_full(), b in arb_value_full()) {
prop_assert_eq!(a == b, b == a);
}
#[test]
fn different_variant_never_equal(a in arb_value_full(), b in arb_value_full()) {
prop_assume!(discriminant(&a) != discriminant(&b));
prop_assert_ne!(a, b);
}
#[test]
fn deep_copy_is_structurally_equal(v in arb_value_full()) {
prop_assume!(!contains_nan(&v));
let copy = deep_copy(&v);
prop_assert!(!shares_backing(&v, ©));
prop_assert_eq!(&v, ©);
}
}