Skip to main content

libmagic_rs/evaluator/operators/
equality.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Equality and inequality operators for magic rule evaluation
5
6use std::cmp::Ordering;
7
8use crate::parser::ast::Value;
9
10use super::compare_values;
11
12/// Machine-epsilon threshold used when comparing `Value::Float` operands for
13/// equality.  Two floats are considered equal when `|a - b| <= FLOAT_EPSILON`.
14/// Special values (NaN, infinity) are handled explicitly before the epsilon
15/// check.
16const FLOAT_EPSILON: f64 = f64::EPSILON;
17
18/// Return `true` when two `f64` values are considered equal under
19/// epsilon-aware semantics.
20///
21/// * **NaN**: NaN is never equal to anything (including itself).
22/// * **Infinity**: positive/negative infinity are only equal to the same sign
23///   of infinity.
24/// * **Finite**: `|a - b| <= FLOAT_EPSILON`.
25fn floats_equal(a: f64, b: f64) -> bool {
26    if a.is_nan() || b.is_nan() {
27        return false;
28    }
29    // Infinities: equal only when same sign (inf - inf = NaN, so must check first).
30    // Exact comparison is correct here -- infinities have precise IEEE 754 bit patterns.
31    if a.is_infinite() || b.is_infinite() {
32        #[allow(clippy::float_cmp)]
33        return a == b;
34    }
35    (a - b).abs() <= FLOAT_EPSILON
36}
37
38/// Apply equality comparison between two values
39///
40/// Compares two `Value` instances for equality, handling proper type matching.
41/// Cross-type integer comparisons (`Uint` vs `Int`) are supported via `i128`
42/// coercion.  Float comparisons use epsilon-aware equality
43/// (`|a - b| <= f64::EPSILON`).  Incompatible types (e.g., string vs integer)
44/// are considered unequal.
45///
46/// # Arguments
47///
48/// * `left` - The left-hand side value (typically from file data)
49/// * `right` - The right-hand side value (typically from magic rule)
50///
51/// # Returns
52///
53/// `true` if the values are equal (including cross-type integer coercion and
54/// epsilon-aware float comparison), `false` otherwise
55///
56/// # Examples
57///
58/// ```
59/// use libmagic_rs::parser::ast::Value;
60/// use libmagic_rs::evaluator::operators::apply_equal;
61///
62/// // Same type, same value
63/// assert!(apply_equal(&Value::Uint(42), &Value::Uint(42)));
64///
65/// // Same type, different value
66/// assert!(!apply_equal(&Value::Uint(42), &Value::Uint(24)));
67///
68/// // Cross-type integer coercion
69/// assert!(apply_equal(&Value::Uint(42), &Value::Int(42)));
70///
71/// // String comparison
72/// assert!(apply_equal(
73///     &Value::String("hello".to_string()),
74///     &Value::String("hello".to_string())
75/// ));
76///
77/// // Float epsilon-aware equality
78/// assert!(apply_equal(&Value::Float(1.0), &Value::Float(1.0)));
79/// ```
80#[must_use]
81pub fn apply_equal(left: &Value, right: &Value) -> bool {
82    if let (Value::Float(a), Value::Float(b)) = (left, right) {
83        return floats_equal(*a, *b);
84    }
85    // String/Bytes cross-type equality: when the parser ingests a magic
86    // value like `\177ELF` it produces `Value::Bytes([0x7f, 'E', 'L',
87    // 'F'])`, but `read_string_exact` returns `Value::String("\x7fELF")`.
88    // The two represent the same byte sequence and must compare equal so
89    // that real-world rules with backslash-escaped values match. Compare
90    // by underlying byte sequence in both directions.
91    match (left, right) {
92        (Value::String(s), Value::Bytes(b)) | (Value::Bytes(b), Value::String(s)) => {
93            return s.as_bytes() == b.as_slice();
94        }
95        _ => {}
96    }
97    compare_values(left, right) == Some(Ordering::Equal)
98}
99
100/// Apply inequality comparison between two values
101///
102/// Compares two `Value` instances for inequality, implementing the negation
103/// of equality comparison logic. Returns `true` if the values are not equal
104/// or are of different types.
105///
106/// # Arguments
107///
108/// * `left` - The left-hand side value (typically from file data)
109/// * `right` - The right-hand side value (typically from magic rule)
110///
111/// # Returns
112///
113/// `true` if the values are not equal or of different types, `false` if they are equal
114///
115/// # Examples
116///
117/// ```
118/// use libmagic_rs::parser::ast::Value;
119/// use libmagic_rs::evaluator::operators::apply_not_equal;
120///
121/// // Same type, different value
122/// assert!(apply_not_equal(&Value::Uint(42), &Value::Uint(24)));
123///
124/// // Same type, same value
125/// assert!(!apply_not_equal(&Value::Uint(42), &Value::Uint(42)));
126///
127/// // Cross-type integers with same numeric value (equal via coercion)
128/// assert!(!apply_not_equal(&Value::Uint(42), &Value::Int(42)));
129///
130/// // String comparison
131/// assert!(apply_not_equal(
132///     &Value::String("hello".to_string()),
133///     &Value::String("world".to_string())
134/// ));
135/// ```
136#[must_use]
137pub fn apply_not_equal(left: &Value, right: &Value) -> bool {
138    !apply_equal(left, right)
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_apply_equal_uint_same_value() {
147        let left = Value::Uint(42);
148        let right = Value::Uint(42);
149        assert!(apply_equal(&left, &right));
150    }
151
152    #[test]
153    fn test_apply_equal_uint_different_value() {
154        let left = Value::Uint(42);
155        let right = Value::Uint(24);
156        assert!(!apply_equal(&left, &right));
157    }
158
159    #[test]
160    fn test_apply_equal_uint_zero() {
161        let left = Value::Uint(0);
162        let right = Value::Uint(0);
163        assert!(apply_equal(&left, &right));
164    }
165
166    #[test]
167    fn test_apply_equal_uint_max_value() {
168        let left = Value::Uint(u64::MAX);
169        let right = Value::Uint(u64::MAX);
170        assert!(apply_equal(&left, &right));
171    }
172
173    #[test]
174    fn test_apply_equal_int_same_value() {
175        let left = Value::Int(42);
176        let right = Value::Int(42);
177        assert!(apply_equal(&left, &right));
178    }
179
180    #[test]
181    fn test_apply_equal_int_different_value() {
182        let left = Value::Int(42);
183        let right = Value::Int(-42);
184        assert!(!apply_equal(&left, &right));
185    }
186
187    #[test]
188    fn test_apply_equal_int_negative() {
189        let left = Value::Int(-100);
190        let right = Value::Int(-100);
191        assert!(apply_equal(&left, &right));
192    }
193
194    #[test]
195    fn test_apply_equal_int_zero() {
196        let left = Value::Int(0);
197        let right = Value::Int(0);
198        assert!(apply_equal(&left, &right));
199    }
200
201    #[test]
202    fn test_apply_equal_int_extreme_values() {
203        let left = Value::Int(i64::MAX);
204        let right = Value::Int(i64::MAX);
205        assert!(apply_equal(&left, &right));
206
207        let left = Value::Int(i64::MIN);
208        let right = Value::Int(i64::MIN);
209        assert!(apply_equal(&left, &right));
210    }
211
212    #[test]
213    fn test_apply_equal_bytes_same_value() {
214        let left = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
215        let right = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
216        assert!(apply_equal(&left, &right));
217    }
218
219    #[test]
220    fn test_apply_equal_bytes_different_value() {
221        let left = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
222        let right = Value::Bytes(vec![0x50, 0x4b, 0x03, 0x04]);
223        assert!(!apply_equal(&left, &right));
224    }
225
226    #[test]
227    fn test_apply_equal_bytes_empty() {
228        let left = Value::Bytes(vec![]);
229        let right = Value::Bytes(vec![]);
230        assert!(apply_equal(&left, &right));
231    }
232
233    #[test]
234    fn test_apply_equal_bytes_different_length() {
235        let left = Value::Bytes(vec![0x7f, 0x45]);
236        let right = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
237        assert!(!apply_equal(&left, &right));
238    }
239
240    #[test]
241    fn test_apply_equal_bytes_single_byte() {
242        let left = Value::Bytes(vec![0x7f]);
243        let right = Value::Bytes(vec![0x7f]);
244        assert!(apply_equal(&left, &right));
245
246        let left = Value::Bytes(vec![0x7f]);
247        let right = Value::Bytes(vec![0x45]);
248        assert!(!apply_equal(&left, &right));
249    }
250
251    #[test]
252    fn test_apply_equal_string_same_value() {
253        let left = Value::String("hello".to_string());
254        let right = Value::String("hello".to_string());
255        assert!(apply_equal(&left, &right));
256    }
257
258    #[test]
259    fn test_apply_equal_string_different_value() {
260        let left = Value::String("hello".to_string());
261        let right = Value::String("world".to_string());
262        assert!(!apply_equal(&left, &right));
263    }
264
265    #[test]
266    fn test_apply_equal_string_empty() {
267        let left = Value::String(String::new());
268        let right = Value::String(String::new());
269        assert!(apply_equal(&left, &right));
270    }
271
272    #[test]
273    fn test_apply_equal_string_case_sensitive() {
274        let left = Value::String("Hello".to_string());
275        let right = Value::String("hello".to_string());
276        assert!(!apply_equal(&left, &right));
277    }
278
279    #[test]
280    fn test_apply_equal_string_unicode() {
281        let left = Value::String("\u{1f980} Rust".to_string());
282        let right = Value::String("\u{1f980} Rust".to_string());
283        assert!(apply_equal(&left, &right));
284
285        let left = Value::String("\u{1f980} Rust".to_string());
286        let right = Value::String("\u{1f40d} Python".to_string());
287        assert!(!apply_equal(&left, &right));
288    }
289
290    #[test]
291    fn test_apply_equal_string_whitespace() {
292        let left = Value::String("hello world".to_string());
293        let right = Value::String("hello world".to_string());
294        assert!(apply_equal(&left, &right));
295
296        let left = Value::String("hello world".to_string());
297        let right = Value::String("hello  world".to_string()); // Extra space
298        assert!(!apply_equal(&left, &right));
299    }
300
301    // Cross-type comparison tests (should all return false)
302    #[test]
303    fn test_apply_equal_uint_vs_int() {
304        // Same numeric value across types should match
305        let left = Value::Uint(42);
306        let right = Value::Int(42);
307        assert!(apply_equal(&left, &right));
308
309        let left = Value::Uint(0);
310        let right = Value::Int(0);
311        assert!(apply_equal(&left, &right));
312
313        // Negative Int cannot equal Uint
314        let left = Value::Uint(42);
315        let right = Value::Int(-42);
316        assert!(!apply_equal(&left, &right));
317
318        // Large Uint that doesn't fit in i64 cannot equal Int
319        let left = Value::Uint(u64::MAX);
320        let right = Value::Int(-1);
321        assert!(!apply_equal(&left, &right));
322    }
323
324    #[test]
325    fn test_apply_equal_uint_vs_bytes() {
326        let left = Value::Uint(42);
327        let right = Value::Bytes(vec![42]);
328        assert!(!apply_equal(&left, &right));
329    }
330
331    #[test]
332    fn test_apply_equal_uint_vs_string() {
333        let left = Value::Uint(42);
334        let right = Value::String("42".to_string());
335        assert!(!apply_equal(&left, &right));
336    }
337
338    #[test]
339    fn test_apply_equal_int_vs_bytes() {
340        let left = Value::Int(-42);
341        let right = Value::Bytes(vec![214]); // -42 as u8
342        assert!(!apply_equal(&left, &right));
343    }
344
345    #[test]
346    fn test_apply_equal_int_vs_string() {
347        let left = Value::Int(-42);
348        let right = Value::String("-42".to_string());
349        assert!(!apply_equal(&left, &right));
350    }
351
352    #[test]
353    fn test_apply_equal_bytes_vs_string() {
354        // libmagic-compatible policy (since the `\177ELF`-style escape fix):
355        // `Value::Bytes` and `Value::String` compare equal when their
356        // underlying byte sequences match. The parser produces
357        // `Value::Bytes` for backslash-escape patterns like `\177ELF`,
358        // while `read_string_exact` returns `Value::String` -- the
359        // comparison must succeed for the rule to match.
360        let left = Value::Bytes(vec![104, 101, 108, 108, 111]); // "hello" as bytes
361        let right = Value::String("hello".to_string());
362        assert!(apply_equal(&left, &right));
363        // Different byte sequences still don't compare equal.
364        let other = Value::String("world".to_string());
365        assert!(!apply_equal(&left, &other));
366    }
367
368    #[test]
369    fn test_apply_equal_all_cross_type_combinations() {
370        let values = [
371            Value::Uint(42),
372            Value::Int(42),
373            Value::Bytes(vec![42]),
374            Value::String("42".to_string()),
375        ];
376
377        // Test cross-type comparisons
378        for (i, left) in values.iter().enumerate() {
379            for (j, right) in values.iter().enumerate() {
380                if i != j {
381                    let result = apply_equal(left, right);
382                    // Uint(42) and Int(42) should be equal (cross-type coercion)
383                    if (i <= 1) && (j <= 1) {
384                        assert!(
385                            result,
386                            "Integer cross-type comparison should be true: {left:?} vs {right:?}"
387                        );
388                    } else {
389                        assert!(
390                            !result,
391                            "Non-integer cross-type comparison should be false: {left:?} vs {right:?}"
392                        );
393                    }
394                }
395            }
396        }
397    }
398
399    #[test]
400    fn test_apply_equal_reflexivity() {
401        let values = vec![
402            Value::Uint(42),
403            Value::Int(-42),
404            Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
405            Value::String("hello".to_string()),
406        ];
407
408        // Test that all values are equal to themselves
409        for value in values {
410            assert!(
411                apply_equal(&value, &value),
412                "Value should be equal to itself: {value:?}"
413            );
414        }
415    }
416
417    #[test]
418    fn test_apply_equal_symmetry() {
419        let test_cases = vec![
420            (Value::Uint(42), Value::Uint(42)),
421            (Value::Int(-100), Value::Int(-100)),
422            (Value::Bytes(vec![1, 2, 3]), Value::Bytes(vec![1, 2, 3])),
423            (
424                Value::String("test".to_string()),
425                Value::String("test".to_string()),
426            ),
427        ];
428
429        // Test that equality is symmetric: a == b implies b == a
430        for (left, right) in test_cases {
431            let left_to_right = apply_equal(&left, &right);
432            let right_to_left = apply_equal(&right, &left);
433            assert_eq!(
434                left_to_right, right_to_left,
435                "Equality should be symmetric: {left:?} vs {right:?}"
436            );
437        }
438    }
439
440    #[test]
441    fn test_apply_equal_transitivity() {
442        // Test transitivity: if a == b and b == c, then a == c
443        let a = Value::Uint(123);
444        let b = Value::Uint(123);
445        let c = Value::Uint(123);
446
447        assert!(apply_equal(&a, &b));
448        assert!(apply_equal(&b, &c));
449        assert!(apply_equal(&a, &c));
450    }
451
452    #[test]
453    fn test_apply_equal_edge_cases() {
454        // Test with maximum values
455        let max_unsigned = Value::Uint(u64::MAX);
456        let max_signed = Value::Int(i64::MAX);
457        let min_int = Value::Int(i64::MIN);
458
459        assert!(apply_equal(&max_unsigned, &max_unsigned));
460        assert!(apply_equal(&max_signed, &max_signed));
461        assert!(apply_equal(&min_int, &min_int));
462
463        // Cross-type edge cases
464        // u64::MAX != -1 in i64 (different mathematical values)
465        assert!(!apply_equal(&max_unsigned, &Value::Int(-1)));
466        // i64::MAX can be represented as u64, so should match
467        assert!(apply_equal(&Value::Uint(i64::MAX as u64), &max_signed));
468
469        // Test with empty collections
470        let empty_bytes = Value::Bytes(vec![]);
471        let empty_string = Value::String(String::new());
472
473        assert!(apply_equal(&empty_bytes, &empty_bytes));
474        assert!(apply_equal(&empty_string, &empty_string));
475        // Cross-type empty Bytes vs empty String: their underlying byte
476        // sequences are both zero-length, so the libmagic-compatible
477        // policy says they compare equal (see test_apply_equal_bytes_vs_string).
478        assert!(apply_equal(&empty_bytes, &empty_string));
479    }
480
481    // Tests for apply_not_equal function
482    //
483    // Per project guidelines, we do not maintain 1:1 mirrors of the apply_equal
484    // tests above. Instead, the single consistency test below proves the
485    // contract `apply_not_equal == !apply_equal` over a representative table of
486    // value pairs covering all variants and edge cases that the per-case
487    // not_equal tests previously exercised. Float-specific cases are covered
488    // separately because of the epsilon/NaN/infinity semantics.
489
490    #[test]
491    fn test_apply_not_equal_consistency_with_equal() {
492        let test_cases = vec![
493            // Uint variants: same/different/zero/max/cross-max
494            (Value::Uint(42), Value::Uint(42)),
495            (Value::Uint(42), Value::Uint(24)),
496            (Value::Uint(0), Value::Uint(0)),
497            (Value::Uint(u64::MAX), Value::Uint(u64::MAX)),
498            (Value::Uint(u64::MAX), Value::Uint(0)),
499            // Int variants: same/different/negative/zero/extremes
500            (Value::Int(42), Value::Int(42)),
501            (Value::Int(42), Value::Int(-42)),
502            (Value::Int(-100), Value::Int(-100)),
503            (Value::Int(-100), Value::Int(100)),
504            (Value::Int(0), Value::Int(0)),
505            (Value::Int(i64::MAX), Value::Int(i64::MAX)),
506            (Value::Int(i64::MIN), Value::Int(i64::MIN)),
507            (Value::Int(i64::MAX), Value::Int(i64::MIN)),
508            // Bytes variants: same/different/empty/different-length/single
509            (
510                Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
511                Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
512            ),
513            (
514                Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
515                Value::Bytes(vec![0x50, 0x4b, 0x03, 0x04]),
516            ),
517            (Value::Bytes(vec![]), Value::Bytes(vec![])),
518            (
519                Value::Bytes(vec![0x7f, 0x45]),
520                Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
521            ),
522            (Value::Bytes(vec![0x7f]), Value::Bytes(vec![0x7f])),
523            (Value::Bytes(vec![0x7f]), Value::Bytes(vec![0x45])),
524            // String variants: same/different/empty/case/unicode/whitespace
525            (
526                Value::String("hello".to_string()),
527                Value::String("hello".to_string()),
528            ),
529            (
530                Value::String("hello".to_string()),
531                Value::String("world".to_string()),
532            ),
533            (Value::String(String::new()), Value::String(String::new())),
534            (
535                Value::String("Hello".to_string()),
536                Value::String("hello".to_string()),
537            ),
538            (
539                Value::String("\u{1f980} Rust".to_string()),
540                Value::String("\u{1f980} Rust".to_string()),
541            ),
542            (
543                Value::String("\u{1f980} Rust".to_string()),
544                Value::String("\u{1f40d} Python".to_string()),
545            ),
546            (
547                Value::String("hello world".to_string()),
548                Value::String("hello world".to_string()),
549            ),
550            (
551                Value::String("hello world".to_string()),
552                Value::String("hello  world".to_string()),
553            ),
554            // Cross-type cases (Uint vs Int with coercion, plus incompatible)
555            (Value::Uint(42), Value::Int(42)),
556            (Value::Uint(0), Value::Int(0)),
557            (Value::Uint(42), Value::Int(-42)),
558            (Value::Uint(42), Value::Bytes(vec![42])),
559            (Value::Uint(42), Value::String("42".to_string())),
560            (Value::Int(-42), Value::Bytes(vec![214])),
561            (Value::Int(-42), Value::String("-42".to_string())),
562            (
563                Value::Bytes(vec![104, 101, 108, 108, 111]),
564                Value::String("hello".to_string()),
565            ),
566            (Value::Bytes(vec![42]), Value::Uint(42)),
567            // Edge: empty bytes vs empty string (cross-type, unequal)
568            (Value::Bytes(vec![]), Value::String(String::new())),
569        ];
570
571        // Test that apply_not_equal is always the negation of apply_equal
572        for (left, right) in test_cases {
573            let equal_result = apply_equal(&left, &right);
574            let not_equal_result = apply_not_equal(&left, &right);
575            assert_eq!(
576                equal_result, !not_equal_result,
577                "apply_not_equal should be negation of apply_equal: {left:?} vs {right:?}"
578            );
579        }
580    }
581
582    // ============================================================
583    // Float epsilon-aware equality / inequality tests
584    // ============================================================
585
586    #[test]
587    fn test_apply_equal_float_exact_same_value() {
588        assert!(apply_equal(&Value::Float(1.0), &Value::Float(1.0)));
589        assert!(apply_equal(&Value::Float(0.0), &Value::Float(0.0)));
590        assert!(apply_equal(&Value::Float(-3.125), &Value::Float(-3.125)));
591    }
592
593    #[test]
594    fn test_apply_equal_float_near_equal_within_epsilon() {
595        // Values that differ by exactly f64::EPSILON should be considered equal
596        let a = 1.0_f64;
597        let b = a + f64::EPSILON;
598        assert!(
599            apply_equal(&Value::Float(a), &Value::Float(b)),
600            "values differing by f64::EPSILON should be equal"
601        );
602    }
603
604    #[test]
605    fn test_apply_equal_float_clearly_unequal() {
606        assert!(!apply_equal(&Value::Float(1.0), &Value::Float(2.0)));
607        assert!(!apply_equal(&Value::Float(0.0), &Value::Float(1.0)));
608        assert!(!apply_equal(&Value::Float(-1.0), &Value::Float(1.0)));
609    }
610
611    #[test]
612    fn test_apply_equal_float_infinity() {
613        let pos_inf = f64::INFINITY;
614        let neg_inf = f64::NEG_INFINITY;
615
616        assert!(apply_equal(&Value::Float(pos_inf), &Value::Float(pos_inf)));
617        assert!(apply_equal(&Value::Float(neg_inf), &Value::Float(neg_inf)));
618        assert!(!apply_equal(&Value::Float(pos_inf), &Value::Float(neg_inf)));
619        assert!(!apply_equal(&Value::Float(pos_inf), &Value::Float(1.0)));
620    }
621
622    #[test]
623    fn test_apply_equal_float_nan() {
624        let nan = f64::NAN;
625        assert!(!apply_equal(&Value::Float(nan), &Value::Float(nan)));
626        assert!(!apply_equal(&Value::Float(nan), &Value::Float(0.0)));
627        assert!(!apply_equal(&Value::Float(0.0), &Value::Float(nan)));
628    }
629
630    #[test]
631    fn test_apply_not_equal_float_consistency_with_equal() {
632        // apply_not_equal must be the negation of apply_equal for all float
633        // edge cases: exact equality, epsilon-near, clearly unequal, NaN
634        // (NaN != NaN), and infinities (same sign equal, opposite sign unequal).
635        let nan = f64::NAN;
636        let near_a = 1.0_f64;
637        let near_b = near_a + f64::EPSILON;
638        let test_cases = vec![
639            (Value::Float(1.0), Value::Float(1.0)),
640            (Value::Float(0.0), Value::Float(0.0)),
641            (Value::Float(near_a), Value::Float(near_b)),
642            (Value::Float(1.0), Value::Float(2.0)),
643            (Value::Float(-1.0), Value::Float(1.0)),
644            (Value::Float(nan), Value::Float(nan)),
645            (Value::Float(nan), Value::Float(0.0)),
646            (Value::Float(0.0), Value::Float(nan)),
647            (Value::Float(f64::INFINITY), Value::Float(f64::INFINITY)),
648            (
649                Value::Float(f64::NEG_INFINITY),
650                Value::Float(f64::NEG_INFINITY),
651            ),
652            (Value::Float(f64::INFINITY), Value::Float(f64::NEG_INFINITY)),
653            (Value::Float(f64::INFINITY), Value::Float(1.0)),
654        ];
655        for (left, right) in test_cases {
656            let equal_result = apply_equal(&left, &right);
657            let not_equal_result = apply_not_equal(&left, &right);
658            assert_eq!(
659                equal_result, !not_equal_result,
660                "apply_not_equal should be negation of apply_equal: {left:?} vs {right:?}"
661            );
662        }
663    }
664}