Skip to content

Commit 1a91851

Browse files
authored
feat: impl Add for SizeHint's (#156)
implement perfect addition of two SizeHint's
1 parent 0d927f0 commit 1a91851

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

http-body/src/size_hint.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,126 @@ impl SizeHint {
8282
self.upper = Some(value);
8383
}
8484
}
85+
86+
/// Perfectly adds two `SizeHint'`s
87+
impl core::ops::Add for SizeHint {
88+
type Output = SizeHint;
89+
90+
fn add(self, rhs: Self) -> Self::Output {
91+
SizeHint {
92+
lower: self.lower() + rhs.lower(),
93+
upper: self
94+
.upper()
95+
.and_then(|this| rhs.upper().map(|rhs| this + rhs)),
96+
}
97+
}
98+
}
99+
100+
/// Asserts that SizeHint addition is perfect with a basic proof
101+
#[test]
102+
fn size_hint_addition_proof() {
103+
/// Converts a SizeHint to a tuple for equality checks and matching
104+
fn to_parts(s: SizeHint) -> (u64, Option<u64>) {
105+
(s.lower(), s.upper())
106+
}
107+
108+
// assuming addition itself is perfect, there are 3 distinct states:
109+
// (_, Some(_)) + (_, Some(_)) => (_ + _, Some(_ + _))
110+
// (_, Some(_)) + (_, None) => (_ + _, None)
111+
// (_, None) + (_, None) => (_ + _, None)
112+
//
113+
// we can assert this in the typesystem! (and name them for our tests)
114+
match (to_parts(SizeHint::new()), to_parts(SizeHint::new())) {
115+
((_, Some(_)), (_, Some(_))) => {} // 1
116+
((_, None), (_, None)) => {} // 2
117+
118+
// note that these cases are identical if we can prove lhs + rhs is equivalent to rhs + lhs
119+
// see below, we do prove that!
120+
((_, Some(_)), (_, None)) => {} // 3
121+
((_, None), (_, Some(_))) => {}
122+
}
123+
//
124+
// Additionally, we assert a with_exact remains intact if we add two with_exact's together
125+
//
126+
// Additionally, we assert that all operations are equivalent if we do a + b vs b + a
127+
128+
// asserts a + b == b + a == eq
129+
macro_rules! reciprocal_add_eq {
130+
($a:expr, $b:expr, $eq:expr) => {
131+
assert_eq!(to_parts(($a.clone() + $b.clone())), $eq);
132+
assert_eq!(to_parts(($b.clone() + $a.clone())), $eq);
133+
};
134+
}
135+
136+
// note that we use increasing powers of two every time we fetch a number, this ensures all
137+
// numbers will add uniquely
138+
139+
let exact_1 = SizeHint::with_exact(1);
140+
let exact_2 = SizeHint::with_exact(2);
141+
142+
// with_exact
143+
reciprocal_add_eq!(exact_1, exact_2, to_parts(SizeHint::with_exact(1 + 2)));
144+
145+
let some_lhs = SizeHint {
146+
lower: 4,
147+
upper: Some(8),
148+
};
149+
150+
let some_rhs = SizeHint {
151+
lower: 16,
152+
upper: Some(32),
153+
};
154+
155+
// case 1
156+
reciprocal_add_eq!(some_lhs, some_rhs, (4 + 16, Some(8 + 32)));
157+
158+
let none_lhs = SizeHint {
159+
lower: 64,
160+
upper: None,
161+
};
162+
163+
let none_rhs = SizeHint {
164+
lower: 128,
165+
upper: None,
166+
};
167+
168+
// case 2
169+
reciprocal_add_eq!(none_lhs, none_rhs, (64 + 128, None));
170+
171+
// case 3
172+
reciprocal_add_eq!(some_lhs, none_rhs, (4 + 128, None));
173+
}
174+
175+
/// Asserts that some "real data" gets passed through without issue
176+
#[test]
177+
fn size_hint_addition_basic() {
178+
let exact_l = SizeHint::with_exact(20);
179+
let exact_r = SizeHint::with_exact(5);
180+
181+
assert_eq!(Some(25), (exact_l.clone() + exact_r).exact());
182+
183+
let inexact_l = SizeHint {
184+
lower: 25,
185+
upper: None,
186+
};
187+
let inexact_r = SizeHint {
188+
lower: 10,
189+
upper: Some(50),
190+
};
191+
192+
let inexact = inexact_l + inexact_r.clone();
193+
194+
assert_eq!(inexact.lower(), 35);
195+
assert_eq!(inexact.upper(), None);
196+
197+
let exact_inexact = exact_l.clone() + inexact_r.clone();
198+
199+
assert_eq!(exact_inexact.lower(), 30);
200+
assert_eq!(exact_inexact.upper(), Some(70));
201+
202+
// same as previous but reversed operation order
203+
let inexact_exact = inexact_r + exact_l;
204+
205+
assert_eq!(inexact_exact.lower(), 30);
206+
assert_eq!(inexact_exact.upper(), Some(70));
207+
}

0 commit comments

Comments
 (0)