Skip to main content

macroonz_compiler/bounded/
type_guard.rs

1//! The bounded home's invariant nucleus: every road that reaches a private field.
2//!
3//! Declared inside `types.rs` as its own child, which is what makes this home's claims structural.
4//! A list longer than its ceiling and a non-empty list with nothing in it are values nobody can build, rather than shapes something downstream has to check for.
5
6use super::{Bounded, Capped, Capping, Empty, NonEmpty, NonEmptyError, Overflow};
7
8impl<T, const N: usize> Bounded<T, N> {
9    /// An empty collection under this ceiling.
10    #[must_use]
11    pub const fn empty() -> Self {
12        Self(Vec::new())
13    }
14
15    /// Admits one complete ordered offering under this ceiling.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`Overflow`] when more than `N` items are offered.
20    pub fn new(items: Vec<T>) -> Result<Self, Overflow> {
21        if items.len() <= N {
22            Ok(Self(items))
23        } else {
24            Err(Overflow {
25                capacity: N,
26                offered: items.len(),
27            })
28        }
29    }
30
31    /// Admits a fixed-arity offering whose fit is settled at compile time.
32    #[must_use]
33    pub fn from_array<const M: usize>(items: [T; M]) -> Self {
34        const {
35            assert!(
36                M <= N,
37                "a fixed list longer than the ceiling it is declared under"
38            );
39        }
40        Self(Vec::from(items))
41    }
42
43    /// The held items.
44    #[must_use]
45    pub fn as_slice(&self) -> &[T] {
46        self.0.as_slice()
47    }
48
49    /// Reads the held items in order.
50    pub fn iter(&self) -> impl Iterator<Item = &T> {
51        self.0.iter()
52    }
53
54    /// How many items are held.
55    #[must_use]
56    pub fn len(&self) -> usize {
57        self.0.len()
58    }
59
60    /// Whether nothing is held.
61    #[must_use]
62    pub fn is_empty(&self) -> bool {
63        self.0.is_empty()
64    }
65
66    /// Appends one item where the resulting collection fits under this ceiling.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`Overflow`] without changing the list where the appended item would exceed `N`.
71    pub fn try_push(&mut self, item: T) -> Result<(), Overflow> {
72        let offered = self.0.len().saturating_add(1);
73        if offered > N {
74            return Err(Overflow {
75                capacity: N,
76                offered,
77            });
78        }
79        self.0.push(item);
80        Ok(())
81    }
82}
83
84impl<T, const N: usize> NonEmpty<T, N> {
85    /// A non-empty collection holding exactly one item.
86    #[must_use]
87    pub const fn one(value: T) -> Self {
88        const {
89            assert!(
90                N >= 1,
91                "a non-empty list under a ceiling that admits no item"
92            );
93        }
94        Self {
95            head: value,
96            tail: Vec::new(),
97        }
98    }
99
100    /// Admits one complete ordered offering that is non-empty and under this ceiling.
101    ///
102    /// # Errors
103    ///
104    /// Returns [`Empty`] when nothing is offered, and [`Overflow`] when more than `N` items are.
105    pub fn new(items: Vec<T>) -> Result<Self, NonEmptyError> {
106        let offered = items.len();
107        let mut rest = items.into_iter();
108        let Some(head) = rest.next() else {
109            return Err(NonEmptyError::Empty(Empty));
110        };
111        if offered <= N {
112            Ok(Self {
113                head,
114                tail: rest.collect(),
115            })
116        } else {
117            Err(NonEmptyError::Overflow(Overflow {
118                capacity: N,
119                offered,
120            }))
121        }
122    }
123
124    /// The first item, which this list always has.
125    #[must_use]
126    pub const fn first(&self) -> &T {
127        &self.head
128    }
129
130    /// The first item and the rest, in order.
131    #[must_use]
132    pub fn split(&self) -> (&T, &[T]) {
133        (&self.head, self.tail.as_slice())
134    }
135
136    /// Reads the held items in order.
137    pub fn iter(&self) -> impl Iterator<Item = &T> {
138        self.into_iter()
139    }
140
141    /// How many items are held, which is never zero.
142    #[must_use]
143    pub fn count(&self) -> usize {
144        self.tail.len().saturating_add(1)
145    }
146}
147
148impl<'held, T, const N: usize> IntoIterator for &'held NonEmpty<T, N> {
149    type Item = &'held T;
150    type IntoIter = core::iter::Chain<core::iter::Once<&'held T>, core::slice::Iter<'held, T>>;
151
152    fn into_iter(self) -> Self::IntoIter {
153        core::iter::once(&self.head).chain(self.tail.iter())
154    }
155}
156
157impl<T, const N: usize> Capped<T, N> {
158    /// A capped collection that kept its complete lawful offering.
159    #[must_use]
160    pub const fn all(items: NonEmpty<T, N>) -> Self {
161        Self {
162            items,
163            capping: Capping::Complete,
164        }
165    }
166
167    /// Keeps the first item and the ordered prefix of the rest that fits, then records the exact omitted count.
168    #[must_use]
169    pub fn first_n(first: T, rest: impl Iterator<Item = T>) -> Self {
170        const {
171            assert!(N >= 1, "a capped list under a ceiling that admits no item");
172        }
173        let mut tail = Vec::new();
174        let mut omitted = 0_usize;
175        for item in rest {
176            if tail.len() < N.saturating_sub(1) {
177                tail.push(item);
178            } else {
179                omitted = omitted.saturating_add(1);
180            }
181        }
182        Self {
183            items: NonEmpty { head: first, tail },
184            capping: capping_over(omitted),
185        }
186    }
187
188    /// The items the list kept.
189    #[must_use]
190    pub const fn items(&self) -> &NonEmpty<T, N> {
191        &self.items
192    }
193
194    /// Whether the list kept everything offered to it.
195    #[must_use]
196    pub const fn capping(&self) -> Capping {
197        self.capping
198    }
199}
200
201/// Reads the capping off the exact count of what was dropped.
202const fn capping_over(omitted: usize) -> Capping {
203    if omitted == 0 {
204        Capping::Complete
205    } else {
206        Capping::Truncated { omitted }
207    }
208}