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
8use std::any::{Any, TypeId};
9
10use slotmap::new_key_type;
11
12use crate::cell::CellId;
13
14new_key_type! {
15    /// A stable handle to a requirement in a [`crate::sheet::Sheet`].
16    pub struct RequirementId;
17}
18
19/// Type-erased predicate stored inside a [`Requirement`].
20type RequirementFn = Box<dyn Fn(&[&dyn Any]) -> Result<bool, anyhow::Error>>;
21
22/// A single boolean check over some set of cells, attached to a cell.
23///
24/// Its predicate must be a pure, deterministic function of its input values and
25/// immutable captured constants, with no externally observable side effects.
26/// Equal inputs produce equivalent results or errors. There is no guarantee that
27/// the predicate executes, or when or how often it executes.
28pub struct Requirement {
29    pub(crate) inputs: Vec<CellId>,
30    pub(crate) input_types: Vec<TypeId>,
31    pub(crate) function: RequirementFn,
32}
33
34impl Requirement {
35    /// Creates a requirement from explicit TypeIds and a type-erased predicate.
36    ///
37    /// - Precondition: `inputs.len() == input_types.len()`.
38    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
39    pub fn new<F>(inputs: Vec<CellId>, input_types: Vec<TypeId>, f: F) -> Self
40    where
41        F: Fn(&[&dyn Any]) -> Result<bool, anyhow::Error> + 'static,
42    {
43        debug_assert_eq!(inputs.len(), input_types.len());
44        Requirement {
45            inputs,
46            input_types,
47            function: Box::new(f),
48        }
49    }
50
51    /// Creates a 1-input requirement from a typed closure.
52    ///
53    /// The TypeId for `A` is captured automatically. The requirement is validated against
54    /// its cell registration when passed to [`crate::sheet::Sheet::add_requirement`].
55    ///
56    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
57    pub fn from_fn_1<A, F>(input: CellId, f: F) -> Self
58    where
59        A: Any + 'static,
60        F: Fn(&A) -> Result<bool, anyhow::Error> + 'static,
61    {
62        Requirement {
63            inputs: vec![input],
64            input_types: vec![TypeId::of::<A>()],
65            function: Box::new(move |args| {
66                let a = args[0]
67                    .downcast_ref::<A>()
68                    .expect("type checked at add_requirement");
69                f(a)
70            }),
71        }
72    }
73
74    /// Creates a 2-input requirement from a typed closure.
75    ///
76    /// `inputs[0]` maps to `A` and `inputs[1]` maps to `B`. TypeIds are captured
77    /// automatically. The requirement is validated when passed to
78    /// [`crate::sheet::Sheet::add_requirement`].
79    ///
80    /// - Precondition: `f` satisfies [`Requirement`]'s purity contract.
81    pub fn from_fn_2<A, B, F>(inputs: [CellId; 2], f: F) -> Self
82    where
83        A: Any + 'static,
84        B: Any + 'static,
85        F: Fn(&A, &B) -> Result<bool, anyhow::Error> + 'static,
86    {
87        Requirement {
88            inputs: inputs.to_vec(),
89            input_types: vec![TypeId::of::<A>(), TypeId::of::<B>()],
90            function: Box::new(move |args| {
91                let a = args[0]
92                    .downcast_ref::<A>()
93                    .expect("type checked at add_requirement");
94                let b = args[1]
95                    .downcast_ref::<B>()
96                    .expect("type checked at add_requirement");
97                f(a, b)
98            }),
99        }
100    }
101}
102
103/// Internal storage for a single requirement.
104pub(crate) struct RequirementData {
105    pub(crate) cell: CellId,
106    pub(crate) inputs: Vec<CellId>,
107    pub(crate) function: RequirementFn,
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn requirement_id_is_copy() {
116        fn takes_copy<T: Copy>(_: T) {}
117        takes_copy(RequirementId::default());
118    }
119
120    #[test]
121    fn requirement_new_stores_types_and_cell_ids() {
122        use slotmap::SlotMap;
123
124        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
125        let a = map.insert(());
126        let b = map.insert(());
127
128        let requirement = Requirement::new(
129            vec![a, b],
130            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
131            |args| {
132                let x = args[0].downcast_ref::<i32>().unwrap();
133                let y = args[1].downcast_ref::<i32>().unwrap();
134                Ok(x + y <= 10)
135            },
136        );
137
138        assert_eq!(requirement.inputs, vec![a, b]);
139        assert_eq!(
140            requirement.input_types,
141            vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
142        );
143
144        let x: i32 = 3;
145        let y: i32 = 4;
146        assert!((requirement.function)(&[&x, &y]).unwrap());
147        let x: i32 = 8;
148        let y: i32 = 8;
149        assert!(!(requirement.function)(&[&x, &y]).unwrap());
150    }
151
152    #[test]
153    fn from_fn_1_stores_correct_type_ids() {
154        use slotmap::SlotMap;
155
156        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
157        let a = map.insert(());
158
159        let requirement = Requirement::from_fn_1(a, |x: &i32| Ok(*x <= 5));
160
161        assert_eq!(requirement.inputs, vec![a]);
162        assert_eq!(requirement.input_types, vec![TypeId::of::<i32>()]);
163
164        let x: i32 = 3;
165        assert!((requirement.function)(&[&x]).unwrap());
166        let x: i32 = 9;
167        assert!(!(requirement.function)(&[&x]).unwrap());
168    }
169
170    #[test]
171    fn from_fn_2_stores_correct_type_ids() {
172        use slotmap::SlotMap;
173
174        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
175        let a = map.insert(());
176        let b = map.insert(());
177
178        let requirement = Requirement::from_fn_2([a, b], |x: &i32, y: &i32| Ok(x * y <= 20));
179
180        assert_eq!(requirement.inputs, vec![a, b]);
181        assert_eq!(
182            requirement.input_types,
183            vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
184        );
185
186        let x: i32 = 4;
187        let y: i32 = 5;
188        assert!((requirement.function)(&[&x, &y]).unwrap());
189        let x: i32 = 5;
190        let y: i32 = 5;
191        assert!(!(requirement.function)(&[&x, &y]).unwrap());
192    }
193}