Skip to main content

adam_rs/
relationship.rs

1//! Relationships and methods in the property model bipartite graph.
2//!
3//! Each relationship holds a list of [`Method`]s; the planner selects one
4//! method per relationship at propagation time based on cell strength.
5
6use std::any::{Any, TypeId};
7
8use slotmap::new_key_type;
9
10use crate::cell::CellId;
11
12new_key_type! {
13    /// A stable handle to a relationship in a [`crate::sheet::Sheet`].
14    pub struct RelationshipId;
15}
16
17/// Type-erased function stored inside a [`Method`].
18///
19/// Takes a slice of type-erased input references and returns a `Vec` of
20/// type-erased boxed outputs, or an error.
21type MethodFn = Box<dyn Fn(&[&dyn Any]) -> Result<Vec<Box<dyn Any>>, anyhow::Error>>;
22
23/// A single method within a relationship.
24///
25/// Its callback must be a pure, deterministic function of its input values and
26/// immutable captured constants, with no externally observable side effects.
27/// Equal inputs produce equivalent outputs or errors. There is no guarantee that
28/// the callback executes, or when or how often it executes.
29/// A self-referencing method must also be idempotent.
30pub struct Method {
31    pub(crate) inputs: Vec<CellId>,
32    pub(crate) outputs: Vec<CellId>,
33    pub(crate) input_types: Vec<TypeId>,
34    pub(crate) output_types: Vec<TypeId>,
35    pub(crate) function: MethodFn,
36}
37
38impl Method {
39    /// Creates a method from explicit TypeIds and a type-erased function.
40    ///
41    /// - Precondition: `inputs.len() == input_types.len()` and `outputs.len() == output_types.len()`.
42    /// - Precondition: The function must return exactly `outputs.len()` values in the correct order.
43    /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
44    pub fn new<F>(
45        inputs: Vec<CellId>,
46        outputs: Vec<CellId>,
47        input_types: Vec<TypeId>,
48        output_types: Vec<TypeId>,
49        f: F,
50    ) -> Self
51    where
52        F: Fn(&[&dyn Any]) -> Result<Vec<Box<dyn Any>>, anyhow::Error> + 'static,
53    {
54        debug_assert_eq!(inputs.len(), input_types.len());
55        debug_assert_eq!(outputs.len(), output_types.len());
56        Method {
57            inputs,
58            outputs,
59            input_types,
60            output_types,
61            function: Box::new(f),
62        }
63    }
64
65    /// Creates a 1-input, 1-output method from a typed closure.
66    ///
67    /// TypeIds for `A` and `B` are captured automatically. The method is validated
68    /// against its cell registrations when passed to [`crate::sheet::Sheet::add_relationship`].
69    ///
70    /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
71    pub fn from_fn_1_1<A, B, F>(input: CellId, output: CellId, f: F) -> Self
72    where
73        A: Any + 'static,
74        B: Any + 'static,
75        F: Fn(&A) -> Result<B, anyhow::Error> + 'static,
76    {
77        Method {
78            inputs: vec![input],
79            outputs: vec![output],
80            input_types: vec![TypeId::of::<A>()],
81            output_types: vec![TypeId::of::<B>()],
82            function: Box::new(move |args| {
83                let a = args[0]
84                    .downcast_ref::<A>()
85                    .expect("type checked at add_relationship");
86                Ok(vec![Box::new(f(a)?)])
87            }),
88        }
89    }
90
91    /// Creates a 2-input, 1-output method from a typed closure.
92    ///
93    /// `inputs[0]` maps to `A` and `inputs[1]` maps to `B`. TypeIds are captured
94    /// automatically. The method is validated when passed to
95    /// [`crate::sheet::Sheet::add_relationship`].
96    ///
97    /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
98    pub fn from_fn_2_1<A, B, C, F>(inputs: [CellId; 2], output: CellId, f: F) -> Self
99    where
100        A: Any + 'static,
101        B: Any + 'static,
102        C: Any + 'static,
103        F: Fn(&A, &B) -> Result<C, anyhow::Error> + 'static,
104    {
105        Method {
106            inputs: inputs.to_vec(),
107            outputs: vec![output],
108            input_types: vec![TypeId::of::<A>(), TypeId::of::<B>()],
109            output_types: vec![TypeId::of::<C>()],
110            function: Box::new(move |args| {
111                let a = args[0]
112                    .downcast_ref::<A>()
113                    .expect("type checked at add_relationship");
114                let b = args[1]
115                    .downcast_ref::<B>()
116                    .expect("type checked at add_relationship");
117                Ok(vec![Box::new(f(a, b)?)])
118            }),
119        }
120    }
121}
122
123/// Internal storage for a relationship; fields are used by `Sheet` (added in a later task).
124pub(crate) struct RelationshipData {
125    pub(crate) methods: Vec<Method>,
126    /// The set of cells referenced by every method in this relationship — every method
127    /// references the same set (enforced by `Sheet::add_relationship`).
128    pub(crate) adj: Vec<CellId>,
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn relationship_id_is_copy() {
137        fn takes_copy<T: Copy>(_: T) {}
138        // RelationshipId must be Copy so it can be stored in adjacency Vecs cheaply.
139        // This test fails to compile if RelationshipId is not Copy.
140        takes_copy(RelationshipId::default());
141    }
142
143    #[test]
144    fn method_new_stores_types_and_cell_ids() {
145        use slotmap::SlotMap;
146
147        // Create real CellIds from a SlotMap to avoid using the default (null) key.
148        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
149        let a = map.insert(());
150        let b = map.insert(());
151        let c = map.insert(());
152
153        let method = Method::new(
154            vec![a, b],
155            vec![c],
156            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
157            vec![TypeId::of::<i32>()],
158            |args| {
159                let x = args[0].downcast_ref::<i32>().unwrap();
160                let y = args[1].downcast_ref::<i32>().unwrap();
161                Ok(vec![Box::new(x + y)])
162            },
163        );
164
165        assert_eq!(method.inputs, vec![a, b]);
166        assert_eq!(method.outputs, vec![c]);
167        assert_eq!(
168            method.input_types,
169            vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
170        );
171        assert_eq!(method.output_types, vec![TypeId::of::<i32>()]);
172
173        // Verify the stored function works.
174        let x: i32 = 3;
175        let y: i32 = 4;
176        let result = (method.function)(&[&x, &y]).unwrap();
177        assert_eq!(*result[0].downcast_ref::<i32>().unwrap(), 7);
178    }
179
180    #[test]
181    fn from_fn_1_1_stores_correct_type_ids() {
182        use slotmap::SlotMap;
183
184        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
185        let a = map.insert(());
186        let b = map.insert(());
187
188        let method = Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2));
189
190        assert_eq!(method.inputs, vec![a]);
191        assert_eq!(method.outputs, vec![b]);
192        assert_eq!(method.input_types, vec![TypeId::of::<i32>()]);
193        assert_eq!(method.output_types, vec![TypeId::of::<i32>()]);
194
195        let x: i32 = 5;
196        let result = (method.function)(&[&x]).unwrap();
197        assert_eq!(*result[0].downcast_ref::<i32>().unwrap(), 10);
198    }
199
200    #[test]
201    fn from_fn_2_1_stores_correct_type_ids() {
202        use slotmap::SlotMap;
203
204        let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
205        let a = map.insert(());
206        let b = map.insert(());
207        let c = map.insert(());
208
209        let method = Method::from_fn_2_1([a, b], c, |x: &f64, y: &f64| Ok((*x) * (*y)));
210
211        assert_eq!(method.inputs, vec![a, b]);
212        assert_eq!(method.outputs, vec![c]);
213        assert_eq!(
214            method.input_types,
215            vec![TypeId::of::<f64>(), TypeId::of::<f64>()]
216        );
217        assert_eq!(method.output_types, vec![TypeId::of::<f64>()]);
218
219        let x: f64 = 2.0;
220        let y: f64 = 3.0;
221        let result = (method.function)(&[&x, &y]).unwrap();
222        assert_eq!(*result[0].downcast_ref::<f64>().unwrap(), 6.0);
223    }
224}