Skip to content

Commit 7d43a52

Browse files
committed
introduce Join and JoinMut traits
Join: infallible binary merge (semilattice operation). Implementors must satisfy idempotent, commutative, and associative laws. JoinMut: in-place variant; blanket impl provides Join automatically. assert_join_laws! is a crate-internal macro (gated behind prop-tests feature, scoped via #[macro_use]). Given an arbitrary strategy, it generates three proptests verifying the semilattice laws for any Join + Clone + PartialEq + Debug type. Used by downstream commits to validate collection and domain-type Join implementations.
1 parent 8e3b6e3 commit 7d43a52

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/// Trait for infallible join operation.
2+
pub trait Join: Sized {
3+
/// Merge two values of a given type into a new value of the same type
4+
/// incorporating the information of both inputs.
5+
///
6+
/// This operation should be associative, commutative and idempotent.
7+
fn join(self, other: Self) -> Self;
8+
}
9+
10+
/// In-place variant of [`Join`].
11+
///
12+
/// Implementing `JoinMut` provides `Join` automatically via blanket impl.
13+
pub trait JoinMut: Sized {
14+
/// Merge `other` into `self` in place.
15+
fn join_mut(&mut self, other: Self);
16+
}
17+
18+
impl<T: JoinMut> Join for T {
19+
fn join(mut self, other: Self) -> Self {
20+
self.join_mut(other);
21+
self
22+
}
23+
}
24+
25+
/// Proptest macros for verifying lattice laws. Available when `prop-tests`
26+
/// feature is enabled.
27+
///
28+
/// # `assert_join_laws!($strategy)`
29+
///
30+
/// Generates idempotent, commutative, and associative property tests for
31+
/// any type implementing [`Join`] + `Clone` + `PartialEq` + `Debug`.
32+
#[cfg(feature = "prop-tests")]
33+
#[allow(unused_macros)] // used by downstream commits (partial.rs, collection modules)
34+
macro_rules! assert_join_laws {
35+
($strategy:expr) => {
36+
proptest! {
37+
#[test]
38+
fn idempotent(a in $strategy) {
39+
prop_assert_eq!(a.clone().join(a.clone()), a);
40+
}
41+
42+
#[test]
43+
fn commutative(a in $strategy, b in $strategy) {
44+
prop_assert_eq!(a.clone().join(b.clone()), b.join(a));
45+
}
46+
47+
#[test]
48+
fn associative(a in $strategy, b in $strategy, c in $strategy) {
49+
prop_assert_eq!(
50+
a.clone().join(b.clone()).join(c.clone()),
51+
a.join(b.join(c)),
52+
);
53+
}
54+
}
55+
};
56+
}
57+
58+
#[cfg(test)]
59+
#[cfg_attr(coverage_nightly, coverage(off))]
60+
mod tests {
61+
use super::*;
62+
63+
impl JoinMut for u32 {
64+
fn join_mut(&mut self, other: Self) {
65+
*self = (*self).max(other);
66+
}
67+
}
68+
69+
#[cfg(feature = "unit-tests")]
70+
mod unit {
71+
use super::*;
72+
73+
impl Join for () {
74+
fn join(self, _other: Self) -> Self {
75+
self
76+
}
77+
}
78+
79+
#[test]
80+
fn unit_type_join() {
81+
assert_eq!(Join::join((), ()), ());
82+
}
83+
84+
#[test]
85+
fn join_mut_in_place() {
86+
let mut a = 1u32;
87+
a.join_mut(2);
88+
assert_eq!(a, 2);
89+
}
90+
91+
#[test]
92+
fn blanket_join_from_join_mut() {
93+
assert_eq!(Join::join(1u32, 2), 2);
94+
}
95+
}
96+
97+
#[cfg(feature = "prop-tests")]
98+
mod prop {
99+
use super::*;
100+
use proptest::prelude::*;
101+
102+
assert_join_laws!(any::<u32>());
103+
}
104+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[macro_use]
2+
pub mod join;

crates/concurrent-psbt/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
#![forbid(unsafe_code)]
22
#![allow(unused_features)]
33
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
4+
5+
#[macro_use]
6+
mod lattice;
7+
8+
pub use lattice::join::{Join, JoinMut};

0 commit comments

Comments
 (0)