Files
ghostty-rs/crates/ghostty-datastruct/src/comparison.rs
T
ddidderr 5b8c92b98c feat(unicode,datastruct): port Unicode tables and data structures
ghostty-unicode: Ghostty computes codepoint properties with the uucode
Zig package (v0.2.0, Unicode 17.0.0). There is no Rust equivalent, so
a trimmed UCD set is vendored in ucd/ and build.rs re-implements
uucode's derivations: cell width rules incl. Ghostty's overrides for
emoji modifiers and Prepend, grapheme break classes with the
compile-time grapheme_break state table, the is_symbol set, and emoji
presentation. Lookups are three array indexes per codepoint. Validated
against the official GraphemeBreakTest.txt.

ghostty-datastruct: split_tree (used for GTK splits; f16 ratios so
rounding and debug diagrams match Zig), circ_buf, lru, cache_table,
blocking_queue, segmented_pool, message_data, comparison,
array_list_collection. intrusive_linked_list is not needed (lru is
index based). lru now reports the evicted (old) key, Zig reported the
new key, which appears to be a bug.

Not yet covered: case_folding_full (needed by input bindings).

Test Plan:
- cargo test -p ghostty-unicode (16) -p ghostty-datastruct (88)
- cargo clippy for both crates with -D warnings
2026-10-11 21:54:57 +02:00

535 lines
15 KiB
Rust

//! Deep/approximate equality helpers (`comparison.zig`).
//!
//! The Zig version uses comptime reflection to compare arbitrary types
//! field-by-field. In Rust, `PartialEq` already covers structural equality;
//! [`DeepEqual`] exists to allow a type to customise its "deep equal"
//! behaviour independent of `PartialEq` (the Zig `equal` decl), and
//! [`ApproxEq`] provides the float-tolerant comparison used by
//! `expectApproxEqual`.
/// A deep equality comparison that works for most types. Types may implement
/// this to customise comparison (the Zig `equal` declaration). Blanket
/// implementations exist for primitives, strings, and containers of
/// `DeepEqual` types.
pub trait DeepEqual {
fn deep_equal(&self, other: &Self) -> bool;
}
/// Compare two values with [`DeepEqual`].
pub fn deep_equal<T: DeepEqual + ?Sized>(old: &T, new: &T) -> bool {
old.deep_equal(new)
}
macro_rules! impl_deep_equal_prim {
($($t:ty),* $(,)?) => {
$(impl DeepEqual for $t {
#[allow(clippy::float_cmp)]
fn deep_equal(&self, other: &Self) -> bool { self == other }
})*
};
}
impl_deep_equal_prim!(
(),
bool,
char,
i8,
i16,
i32,
i64,
i128,
isize,
u8,
u16,
u32,
u64,
u128,
usize,
f32,
f64,
str,
String,
);
impl<T: DeepEqual> DeepEqual for Option<T> {
fn deep_equal(&self, other: &Self) -> bool {
match (self, other) {
(None, None) => true,
(Some(a), Some(b)) => a.deep_equal(b),
_ => false,
}
}
}
impl<T: DeepEqual> DeepEqual for [T] {
fn deep_equal(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a.deep_equal(b))
}
}
impl<T: DeepEqual, const N: usize> DeepEqual for [T; N] {
fn deep_equal(&self, other: &Self) -> bool {
self.as_slice().deep_equal(other.as_slice())
}
}
impl<T: DeepEqual> DeepEqual for Vec<T> {
fn deep_equal(&self, other: &Self) -> bool {
self.as_slice().deep_equal(other.as_slice())
}
}
impl<T: DeepEqual + ?Sized> DeepEqual for &T {
fn deep_equal(&self, other: &Self) -> bool {
(**self).deep_equal(*other)
}
}
/// Approximate equality: relative tolerance of `sqrt(eps)` for floats and
/// plain equality for everything else.
pub trait ApproxEq {
fn approx_eq(&self, other: &Self) -> bool;
}
macro_rules! impl_approx_float {
($($t:ty),*) => {
$(impl ApproxEq for $t {
fn approx_eq(&self, other: &Self) -> bool {
// std.math.approxEqRel with tolerance sqrt(eps)
let tolerance = <$t>::EPSILON.sqrt();
let (a, b) = (*self, *other);
if a == b { return true; }
if a.is_nan() || b.is_nan() { return false; }
if a.is_infinite() || b.is_infinite() { return false; }
(a - b).abs() <= a.abs().max(b.abs()) * tolerance
}
})*
};
}
impl_approx_float!(f32, f64);
macro_rules! impl_approx_eq_prim {
($($t:ty),* $(,)?) => {
$(impl ApproxEq for $t {
fn approx_eq(&self, other: &Self) -> bool { self == other }
})*
};
}
impl_approx_eq_prim!(
(),
bool,
char,
i8,
i16,
i32,
i64,
isize,
u8,
u16,
u32,
u64,
usize,
str,
String
);
impl<T: ApproxEq> ApproxEq for Option<T> {
fn approx_eq(&self, other: &Self) -> bool {
match (self, other) {
(None, None) => true,
(Some(a), Some(b)) => a.approx_eq(b),
_ => false,
}
}
}
impl<T: ApproxEq> ApproxEq for [T] {
fn approx_eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().zip(other).all(|(a, b)| a.approx_eq(b))
}
}
impl<T: ApproxEq, const N: usize> ApproxEq for [T; N] {
fn approx_eq(&self, other: &Self) -> bool {
self.as_slice().approx_eq(other.as_slice())
}
}
impl<T: ApproxEq> ApproxEq for Vec<T> {
fn approx_eq(&self, other: &Self) -> bool {
self.as_slice().approx_eq(other.as_slice())
}
}
macro_rules! impl_approx_tuple {
($(($($n:ident : $i:tt),+)),+ $(,)?) => {
$(impl<$($n: ApproxEq),+> ApproxEq for ($($n,)+) {
fn approx_eq(&self, other: &Self) -> bool {
$(self.$i.approx_eq(&other.$i))&&+
}
})+
};
}
impl_approx_tuple!((A: 0), (A: 0, B: 1), (A: 0, B: 1, C: 2), (A: 0, B: 1, C: 2, D: 3));
/// Asserts that two values are (approximately) equal, panicking otherwise.
/// Counterpart of `expectApproxEqual`.
#[track_caller]
pub fn expect_approx_equal<T: ApproxEq + std::fmt::Debug + ?Sized>(expected: &T, actual: &T) {
assert!(
expected.approx_eq(actual),
"expected approximately {expected:?}, found {actual:?}"
);
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq)]
enum Value {
Int(i32),
Float(f32),
}
impl ApproxEq for Value {
fn approx_eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Int(a), Value::Int(b)) => a.approx_eq(b),
(Value::Float(a), Value::Float(b)) => a.approx_eq(b),
_ => false,
}
}
}
#[test]
fn expect_approx_equal_union_enum() {
let b10 = Value::Float(10.0);
let b10plus = Value::Float(10.000001);
expect_approx_equal(&b10, &b10plus);
assert!(!Value::Int(1).approx_eq(&Value::Float(1.0)));
assert!(!Value::Float(10.0).approx_eq(&Value::Float(10.1)));
}
#[test]
fn expect_approx_equal_nested_array() {
let a = [[1.0f32, 0.0], [0.0, 1.0]];
let b = [[1.000001f32, 0.0], [0.0, 0.999999]];
expect_approx_equal(&a, &b);
let c = [[1.1f32, 0.0], [0.0, 1.0]];
assert!(!a.approx_eq(&c));
}
#[test]
fn expect_approx_equal_vector() {
// Zig @Vector(4, f32) maps to a fixed array.
let a = [4.0f32; 4];
let b = [4.000001f32; 4];
expect_approx_equal(&a, &b);
}
#[test]
fn expect_approx_equal_struct() {
// Zig anonymous struct maps to a tuple.
let a = (1, 1.0f32);
let b = (1, 0.999999f32);
expect_approx_equal(&a, &b);
}
#[test]
fn deep_equal_void() {
assert!(deep_equal(&(), &()));
}
#[test]
fn deep_equal_bool() {
assert!(deep_equal(&true, &true));
assert!(deep_equal(&false, &false));
assert!(!deep_equal(&true, &false));
assert!(!deep_equal(&false, &true));
}
#[test]
fn deep_equal_int() {
assert!(deep_equal(&42i32, &42));
assert!(deep_equal(&-100i32, &-100));
assert!(!deep_equal(&42i32, &43));
assert!(deep_equal(&0u64, &0));
assert!(!deep_equal(&0u64, &1));
}
#[test]
fn deep_equal_float() {
assert!(deep_equal(&1.0f32, &1.0));
assert!(!deep_equal(&1.0f32, &1.1));
assert!(deep_equal(&1.23456f64, &1.23456));
assert!(!deep_equal(&1.23456f64, &1.23455));
}
#[test]
fn deep_equal_enum() {
#[derive(PartialEq)]
enum Color {
Red,
Green,
Blue,
}
impl DeepEqual for Color {
fn deep_equal(&self, other: &Self) -> bool {
self == other
}
}
assert!(deep_equal(&Color::Red, &Color::Red));
assert!(deep_equal(&Color::Blue, &Color::Blue));
assert!(!deep_equal(&Color::Red, &Color::Green));
assert!(!deep_equal(&Color::Green, &Color::Blue));
}
#[test]
fn deep_equal_slice_u8() {
assert!(deep_equal("hello", "hello"));
assert!(deep_equal("", ""));
assert!(!deep_equal("hello", "world"));
assert!(!deep_equal("hello", "hell"));
assert!(!deep_equal("hello", "hello!"));
assert!(deep_equal(b"hello".as_slice(), b"hello".as_slice()));
}
#[test]
fn deep_equal_sentinel_slice_u8() {
// [:0]const u8 maps to &str / CString-like; plain strings here.
assert!(deep_equal("foo", "foo"));
assert!(!deep_equal("foo", "bar"));
assert!(!deep_equal("foo", "fo"));
}
#[test]
fn deep_equal_slice_of_strings() {
let a: &[&str] = &["one", "two", "three"];
let b: &[&str] = &["one", "two", "three"];
let c: &[&str] = &["one", "two"];
let d: &[&str] = &["one", "two", "four"];
let e: &[&str] = &[];
assert!(deep_equal(a, b));
assert!(!deep_equal(a, c));
assert!(!deep_equal(a, d));
assert!(deep_equal(e, e));
assert!(!deep_equal(a, e));
}
#[test]
fn deep_equal_optional() {
assert!(deep_equal::<Option<i32>>(&None, &None));
assert!(deep_equal(&Some(42), &Some(42)));
assert!(!deep_equal(&None, &Some(42)));
assert!(!deep_equal(&Some(42), &None));
assert!(!deep_equal(&Some(42), &Some(43)));
}
#[derive(Debug, PartialEq)]
struct Nested {
x: i32,
y: i32,
}
impl DeepEqual for Nested {
fn deep_equal(&self, other: &Self) -> bool {
self.x.deep_equal(&other.x) && self.y.deep_equal(&other.y)
}
}
#[test]
fn deep_equal_optional_nested() {
assert!(deep_equal::<Option<Nested>>(&None, &None));
assert!(deep_equal(
&Some(Nested { x: 1, y: 2 }),
&Some(Nested { x: 1, y: 2 })
));
assert!(!deep_equal(
&Some(Nested { x: 1, y: 2 }),
&Some(Nested { x: 1, y: 3 })
));
assert!(!deep_equal(&Some(Nested { x: 1, y: 2 }), &None));
}
#[test]
fn deep_equal_array() {
assert!(deep_equal(&[1, 2, 3], &[1, 2, 3]));
assert!(!deep_equal(&[1, 2, 3], &[1, 2, 4]));
assert!(!deep_equal(&[1, 2, 3], &[0, 2, 3]));
assert!(deep_equal::<[i32; 0]>(&[], &[]));
}
#[test]
fn deep_equal_nested_array() {
let a = [[1, 2], [3, 4]];
let b = [[1, 2], [3, 4]];
let c = [[1, 2], [3, 5]];
assert!(deep_equal(&a, &b));
assert!(!deep_equal(&a, &c));
}
struct Outer {
a: Nested,
b: Nested,
}
impl DeepEqual for Outer {
fn deep_equal(&self, o: &Self) -> bool {
self.a.deep_equal(&o.a) && self.b.deep_equal(&o.b)
}
}
#[test]
fn deep_equal_struct() {
assert!(deep_equal(
&Nested { x: 10, y: 20 },
&Nested { x: 10, y: 20 }
));
assert!(!deep_equal(
&Nested { x: 10, y: 20 },
&Nested { x: 10, y: 21 }
));
assert!(!deep_equal(
&Nested { x: 10, y: 20 },
&Nested { x: 11, y: 20 }
));
}
#[test]
fn deep_equal_struct_nested() {
let mk = |a, b| Outer {
a: Nested { x: a, y: 0 },
b: Nested { x: b, y: 0 },
};
assert!(deep_equal(&mk(1, 2), &mk(1, 2)));
assert!(!deep_equal(&mk(1, 2), &mk(1, 3)));
}
struct Custom {
value: i32,
}
impl DeepEqual for Custom {
fn deep_equal(&self, other: &Self) -> bool {
self.value.rem_euclid(10) == other.value.rem_euclid(10)
}
}
#[test]
fn deep_equal_struct_with_equal_decl() {
assert!(deep_equal(&Custom { value: 5 }, &Custom { value: 15 }));
assert!(deep_equal(&Custom { value: 100 }, &Custom { value: 0 }));
assert!(!deep_equal(&Custom { value: 5 }, &Custom { value: 6 }));
}
#[derive(Debug)]
enum U {
Int(i32),
Float(f32),
None,
}
impl DeepEqual for U {
fn deep_equal(&self, other: &Self) -> bool {
match (self, other) {
(U::Int(a), U::Int(b)) => a == b,
(U::Float(a), U::Float(b)) => a == b,
(U::None, U::None) => true,
_ => false,
}
}
}
#[test]
fn deep_equal_union() {
assert!(deep_equal(&U::Int(42), &U::Int(42)));
assert!(!deep_equal(&U::Int(42), &U::Int(43)));
assert!(!deep_equal(&U::Int(42), &U::Float(42.0)));
assert!(deep_equal(&U::None, &U::None));
assert!(!deep_equal(&U::None, &U::Int(0)));
}
enum UCustom {
Num(i32),
Str(&'static str),
}
impl DeepEqual for UCustom {
fn deep_equal(&self, other: &Self) -> bool {
match (self, other) {
(UCustom::Num(n), UCustom::Num(m)) => n.rem_euclid(10) == m.rem_euclid(10),
(UCustom::Str(s), UCustom::Str(t)) => s.len() == t.len(),
_ => false,
}
}
}
#[test]
fn deep_equal_union_with_equal_decl() {
assert!(deep_equal(&UCustom::Num(5), &UCustom::Num(25)));
assert!(!deep_equal(&UCustom::Num(5), &UCustom::Num(6)));
assert!(deep_equal(&UCustom::Str("abc"), &UCustom::Str("xyz")));
assert!(!deep_equal(&UCustom::Str("abc"), &UCustom::Str("ab")));
}
struct Item {
id: i32,
name: &'static str,
}
impl DeepEqual for Item {
fn deep_equal(&self, o: &Self) -> bool {
self.id.deep_equal(&o.id) && self.name.deep_equal(o.name)
}
}
#[test]
fn deep_equal_array_of_structs() {
let mk = |n: &'static str| [Item { id: 1, name: "one" }, Item { id: 2, name: n }];
assert!(deep_equal(&mk("two"), &mk("two")));
assert!(!deep_equal(&mk("two"), &mk("TWO")));
}
struct Config {
name: &'static str,
port: Option<u16>,
}
impl DeepEqual for Config {
fn deep_equal(&self, o: &Self) -> bool {
self.name.deep_equal(o.name) && self.port.deep_equal(&o.port)
}
}
#[test]
fn deep_equal_struct_with_optional_field() {
let c = |port| Config { name: "app", port };
assert!(deep_equal(&c(Some(8080)), &c(Some(8080))));
assert!(deep_equal(&c(None), &c(None)));
assert!(!deep_equal(&c(Some(8080)), &c(None)));
assert!(!deep_equal(&c(Some(8080)), &c(Some(8081))));
}
struct Data {
values: [i32; 3],
}
impl DeepEqual for Data {
fn deep_equal(&self, o: &Self) -> bool {
self.values.deep_equal(&o.values)
}
}
#[test]
fn deep_equal_struct_with_array_field() {
assert!(deep_equal(
&Data { values: [1, 2, 3] },
&Data { values: [1, 2, 3] }
));
assert!(!deep_equal(
&Data { values: [1, 2, 3] },
&Data { values: [1, 2, 4] }
));
}
#[test]
fn deep_equal_vec() {
assert!(deep_equal(&vec![1, 2], &vec![1, 2]));
assert!(!deep_equal(&vec![1, 2], &vec![1]));
}
}