Skip to main content

pgevolve_core/ir/
eq.rs

1//! `Diff` trait — produces structured differences between two IR values.
2
3use super::difference::Difference;
4
5/// `#[derive(DiffMacro)]` proc-macro. Re-exported here so call sites only
6/// see the `ir::eq` module; the macro lives in `pgevolve-core-macros`.
7/// Distinct name from the trait to avoid shadowing. See
8/// `docs/superpowers/specs/2026-05-19-diff-derive-macro-design.md`.
9pub use pgevolve_core_macros::Diff as DiffMacro;
10
11/// Compute the structured difference between two IR values.
12///
13/// Equivalence is the inverse of `diff(...).is_empty()`. Implementors derive
14/// equivalence from `Diff` rather than from `PartialEq` so that equivalence
15/// rules can diverge from structural equality (e.g., field reordering inside
16/// a `Vec<Constraint>` doesn't matter, but `PartialEq` would say it does).
17pub trait Diff {
18    /// List the differences between `self` and `other`. Empty list = equivalent.
19    fn diff(&self, other: &Self) -> Vec<Difference>;
20
21    /// Convenience: `true` iff `self.diff(other).is_empty()`.
22    fn canonical_eq(&self, other: &Self) -> bool {
23        self.diff(other).is_empty()
24    }
25}
26
27/// Helper: produces a single-element `Vec<Difference>` if `from != to`, else empty.
28pub fn diff_field<T: PartialEq + std::fmt::Display>(
29    path: &str,
30    from: &T,
31    to: &T,
32) -> Vec<Difference> {
33    if from == to {
34        Vec::new()
35    } else {
36        vec![Difference::new(path, from, to)]
37    }
38}
39
40/// Helper: prefix every element's path.
41#[must_use]
42pub fn prefix_diffs(prefix: &str, diffs: Vec<Difference>) -> Vec<Difference> {
43    diffs.into_iter().map(|d| d.prefix_path(prefix)).collect()
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn diff_field_matches() {
52        let r = diff_field("name", &1, &1);
53        assert!(r.is_empty());
54    }
55
56    #[test]
57    fn diff_field_reports() {
58        let r = diff_field("name", &1, &2);
59        assert_eq!(r.len(), 1);
60        assert_eq!(r[0].path, "name");
61    }
62
63    #[test]
64    fn prefix_diffs_simple() {
65        let d = vec![Difference::new("len", "5", "10")];
66        let p = prefix_diffs("ty", d);
67        assert_eq!(p[0].path, "ty.len");
68    }
69
70    #[test]
71    fn prefix_diffs_empty_path() {
72        let d = vec![Difference::new("", "a", "b")];
73        let p = prefix_diffs("ty", d);
74        assert_eq!(p[0].path, "ty");
75    }
76}