Skip to main content

adam_rs/
requirement.rs

1//! Boolean checks attached to a cell, identified by stable handles.
2//!
3//! Each [`Requirement`] is a pure predicate over some set of cells whose result after
4//! `Sheet::propagate` describes whether a cell's preconditions currently hold. A
5//! requirement's inputs may be any cells in the sheet, not only the cell it's attached to.
6//! See [`crate::sheet::Sheet::add_requirement`].
7//!
8//! ```
9//! use adam_rs::{Requirement, Sheet};
10//! use std::any::TypeId;
11//! let mut sheet = Sheet::new();
12//! let value = sheet.add_cell("ok!".to_string());
13//! let suffix = sheet.add_cell('!');
14//! sheet.add_requirement(value, Requirement::new(vec![value],
15//!     vec![TypeId::of::<String>()], |v| Ok(!v[0].downcast_ref::<String>().unwrap().is_empty())))?;
16//! sheet.add_requirement(value, Requirement::from_fn_1(value, |v: &String| Ok(!v.is_empty())))?;
17//! sheet.add_requirement(value, Requirement::from_fn_2([value, suffix],
18//!     |v: &String, suffix: &char| Ok(v.ends_with(*suffix))))?;
19//! sheet.propagate()?;
20//! assert!(sheet.cell_requirements_valid(value));
21//! assert_eq!(sheet.cell_requirements(value).unwrap().len(), 3);
22//! # Ok::<(), adam_rs::Error>(())
23//! ```
24
25use std::any::{Any, TypeId};
26
27use slotmap::new_key_type;
28
29use crate::cell::CellId;
30
31new_key_type! {
32    /// A stable handle to a requirement in a [`crate::sheet::Sheet`].
33    ///
34    /// # Examples
35    /// See the [predicate example](crate::requirement).
36    pub struct RequirementId;
37}
38
39/// Type-erased predicate stored inside a [`Requirement`].
40type RequirementFn = Box<dyn Fn(&[&dyn Any]) -> Result<bool, anyhow::Error>>;
41
42/// A single boolean check over some set of cells, attached to a cell.
43///
44/// Its predicate must be a pure, deterministic function of its input values and
45/// immutable captured constants, with no externally observable side effects.
46/// Equal inputs produce equivalent results or errors. There is no guarantee that
47/// the predicate executes, or when or how often it executes.
48///
49/// # Examples
50/// See the [predicate example](crate::requirement).
51pub struct Requirement {
52    pub(crate) inputs: Vec<CellId>,
53    pub(crate) input_types: Vec<TypeId>,
54    pub(crate) function: RequirementFn,
55}
56
57impl Requirement {
58    /// Creates a requirement from explicit TypeIds and a type-erased predicate.
59    ///
60    /// - Precondition: `inputs.len() == input_types.len()`.
61    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
62    ///
63    /// # Examples
64    /// See the [predicate example](crate::requirement).
65    pub fn new<F>(inputs: Vec<CellId>, input_types: Vec<TypeId>, f: F) -> Self
66    where
67        F: Fn(&[&dyn Any]) -> Result<bool, anyhow::Error> + 'static,
68    {
69        debug_assert_eq!(inputs.len(), input_types.len());
70        Requirement {
71            inputs,
72            input_types,
73            function: Box::new(f),
74        }
75    }
76
77    /// Creates a 1-input requirement from a typed closure.
78    ///
79    /// The TypeId for `A` is captured automatically. The requirement is validated against
80    /// its cell registration when passed to [`crate::sheet::Sheet::add_requirement`].
81    ///
82    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
83    ///
84    /// # Examples
85    /// See the [predicate example](crate::requirement).
86    pub fn from_fn_1<A, F>(input: CellId, f: F) -> Self
87    where
88        A: Any + 'static,
89        F: Fn(&A) -> Result<bool, anyhow::Error> + 'static,
90    {
91        Requirement {
92            inputs: vec![input],
93            input_types: vec![TypeId::of::<A>()],
94            function: Box::new(move |args| {
95                let a = args[0]
96                    .downcast_ref::<A>()
97                    .expect("type checked at add_requirement");
98                f(a)
99            }),
100        }
101    }
102
103    /// Creates a 2-input requirement from a typed closure.
104    ///
105    /// `inputs[0]` maps to `A` and `inputs[1]` maps to `B`. TypeIds are captured
106    /// automatically. The requirement is validated when passed to
107    /// [`crate::sheet::Sheet::add_requirement`].
108    ///
109    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
110    ///
111    /// # Examples
112    /// See the [predicate example](crate::requirement).
113    pub fn from_fn_2<A, B, F>(inputs: [CellId; 2], f: F) -> Self
114    where
115        A: Any + 'static,
116        B: Any + 'static,
117        F: Fn(&A, &B) -> Result<bool, anyhow::Error> + 'static,
118    {
119        Requirement {
120            inputs: inputs.to_vec(),
121            input_types: vec![TypeId::of::<A>(), TypeId::of::<B>()],
122            function: Box::new(move |args| {
123                let a = args[0]
124                    .downcast_ref::<A>()
125                    .expect("type checked at add_requirement");
126                let b = args[1]
127                    .downcast_ref::<B>()
128                    .expect("type checked at add_requirement");
129                f(a, b)
130            }),
131        }
132    }
133}
134
135/// Internal storage for a single requirement.
136pub(crate) struct RequirementData {
137    pub(crate) cell: CellId,
138    pub(crate) inputs: Vec<CellId>,
139    pub(crate) function: RequirementFn,
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    /// Preserves heterogeneous binding order, false results and typed predicate failures.
147    #[test]
148    fn typed_adapters_preserve_order_and_errors() {
149        let mut sheet = crate::Sheet::new();
150        let a = sheet.add_cell(String::new());
151        let b = sheet.add_cell('!');
152        let predicate = Requirement::from_fn_2([a, b], |a: &String, b: &char| Ok(a.ends_with(*b)));
153        assert!((predicate.function)(&[&"yes!".to_string(), &'!']).unwrap());
154        assert!(!(predicate.function)(&[&"no?".to_string(), &'!']).unwrap());
155        let one = Requirement::from_fn_1(a, |_: &String| Err(std::io::Error::other("one").into()));
156        let two = Requirement::from_fn_2([a, b], |_: &String, _: &char| {
157            Err(std::io::Error::other("two").into())
158        });
159        assert!(
160            (one.function)(&[&String::new()])
161                .unwrap_err()
162                .downcast_ref::<std::io::Error>()
163                .is_some()
164        );
165        assert!(
166            (two.function)(&[&String::new(), &'!'])
167                .unwrap_err()
168                .downcast_ref::<std::io::Error>()
169                .is_some()
170        );
171    }
172
173    #[test]
174    /// Checks that requirement identities satisfy the Copy bound.
175    fn requirement_id_is_copy() {
176        /// Accepts only carriers that implement Copy.
177        fn takes_copy<T: Copy>(_: T) {}
178        takes_copy(RequirementId::default());
179    }
180
181    #[test]
182    /// Checks preservation of an erased requirement's input identities and types.
183    fn requirement_new_stores_types_and_cell_ids() {
184        use slotmap::SlotMap;
185
186        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
187        let a = map.insert(());
188        let b = map.insert(());
189
190        let requirement = Requirement::new(
191            vec![a, b],
192            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
193            |args| {
194                let x = args[0].downcast_ref::<i32>().unwrap();
195                let y = args[1].downcast_ref::<i32>().unwrap();
196                Ok(x + y <= 10)
197            },
198        );
199
200        assert_eq!(requirement.inputs, vec![a, b]);
201        assert_eq!(
202            requirement.input_types,
203            vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
204        );
205
206        let x: i32 = 3;
207        let y: i32 = 4;
208        assert!((requirement.function)(&[&x, &y]).unwrap());
209        let x: i32 = 8;
210        let y: i32 = 8;
211        assert!(!(requirement.function)(&[&x, &y]).unwrap());
212    }
213
214    #[test]
215    /// Checks unary requirement signature types and predicate evaluation.
216    fn from_fn_1_stores_correct_type_ids() {
217        use slotmap::SlotMap;
218
219        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
220        let a = map.insert(());
221
222        let requirement = Requirement::from_fn_1(a, |x: &i32| Ok(*x <= 5));
223
224        assert_eq!(requirement.inputs, vec![a]);
225        assert_eq!(requirement.input_types, vec![TypeId::of::<i32>()]);
226
227        let x: i32 = 3;
228        assert!((requirement.function)(&[&x]).unwrap());
229        let x: i32 = 9;
230        assert!(!(requirement.function)(&[&x]).unwrap());
231    }
232
233    #[test]
234    /// Checks binary requirement signature types and predicate evaluation.
235    fn from_fn_2_stores_correct_type_ids() {
236        use slotmap::SlotMap;
237
238        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
239        let a = map.insert(());
240        let b = map.insert(());
241
242        let requirement = Requirement::from_fn_2([a, b], |x: &i32, y: &i32| Ok(x * y <= 20));
243
244        assert_eq!(requirement.inputs, vec![a, b]);
245        assert_eq!(
246            requirement.input_types,
247            vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
248        );
249
250        let x: i32 = 4;
251        let y: i32 = 5;
252        assert!((requirement.function)(&[&x, &y]).unwrap());
253        let x: i32 = 5;
254        let y: i32 = 5;
255        assert!(!(requirement.function)(&[&x, &y]).unwrap());
256    }
257}