Skip to main content

pgevolve_core/ir/
eq.rs

1//! `Equiv` trait — produces structured differences between two IR values.
2
3use super::difference::Difference;
4
5/// Compute the structured difference between two IR values.
6///
7/// Equivalence is the inverse of `differences(...).is_empty()`. Implementors
8/// derive equivalence from `Equiv` rather than from `PartialEq` so that
9/// equivalence rules can diverge from structural equality (e.g., field
10/// reordering inside a `Vec<Constraint>` doesn't matter, but `PartialEq` would
11/// say it does).
12pub trait Equiv {
13    /// List the differences between `self` and `other`. Empty list = equivalent.
14    fn differences(&self, other: &Self) -> Vec<Difference>;
15
16    /// Convenience: `true` iff `self.differences(other).is_empty()`.
17    fn canonical_eq(&self, other: &Self) -> bool {
18        self.differences(other).is_empty()
19    }
20}
21
22/// Helper: produces a single-element `Vec<Difference>` if `from != to`, else empty.
23pub fn field_difference<T: PartialEq + std::fmt::Display>(
24    path: &str,
25    from: &T,
26    to: &T,
27) -> Vec<Difference> {
28    if from == to {
29        Vec::new()
30    } else {
31        vec![Difference::new(path, from, to)]
32    }
33}
34
35/// Helper: prefix every element's path.
36#[must_use]
37pub fn prefix_differences(prefix: &str, diffs: Vec<Difference>) -> Vec<Difference> {
38    diffs.into_iter().map(|d| d.prefix_path(prefix)).collect()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn field_difference_matches() {
47        let r = field_difference("name", &1, &1);
48        assert!(r.is_empty());
49    }
50
51    #[test]
52    fn field_difference_reports() {
53        let r = field_difference("name", &1, &2);
54        assert_eq!(r.len(), 1);
55        assert_eq!(r[0].path, "name");
56    }
57
58    #[test]
59    fn prefix_differences_simple() {
60        let d = vec![Difference::new("len", "5", "10")];
61        let p = prefix_differences("ty", d);
62        assert_eq!(p[0].path, "ty.len");
63    }
64
65    #[test]
66    fn prefix_differences_empty_path() {
67        let d = vec![Difference::new("", "a", "b")];
68        let p = prefix_differences("ty", d);
69        assert_eq!(p[0].path, "ty");
70    }
71}