adam_rs/sheet.rs
1//! The [`Sheet`] owns and manages a property model constraint graph.
2//!
3//! All cells and relationships are created through the sheet and are
4//! destroyed when the sheet is dropped.
5//!
6//! Queries borrow declarations or describe a completed propagation; they do not
7//! propagate implicitly. This graph combines a conditional relationship, a fixed
8//! writer, a live source filter and a requirement.
9//!
10//! ```
11//! use adam_rs::{CellKind, Filter, MatchExpr, Method, Requirement, Sheet};
12//! use std::any::TypeId;
13//! let mut sheet = Sheet::default();
14//! let mode = sheet.add_source(true);
15//! let limit = sheet.add_source(100_i32);
16//! let a = sheet.add_cell(7_i32);
17//! let b = sheet.add_cell(0_i32);
18//! let out = sheet.add_cell(0_i32);
19//! sheet.add_filter(a, Filter::range(TypeId::of::<i32>(), vec![limit],
20//! vec![TypeId::of::<i32>()],
21//! |v, args| Ok(Box::new((*v.downcast_ref::<i32>().unwrap())
22//! .clamp(0, *args[0].downcast_ref::<i32>().unwrap()))),
23//! |args| Some((Box::new(0_i32), Box::new(*args[0].downcast_ref::<i32>()?)))))?;
24//! let rel = sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])?;
25//! let conditional = sheet.add_conditional(MatchExpr::cell(mode),
26//! vec![(vec![true], vec![rel])], vec![])?;
27//! sheet.add_out(Method::from_fn_1_1(b, out, |v: &i32| Ok(*v)), vec![])?;
28//! let check = sheet.add_requirement(b, Requirement::from_fn_1(b, |v: &i32| Ok(*v >= 0)))?;
29//! sheet.validate()?;
30//! sheet.write(a, 9_i32)?;
31//! sheet.propagate()?;
32//! assert_eq!(*sheet.read::<i32>(out)?, 9);
33//! assert_eq!(*sheet.source::<i32>(a)?, 9);
34//! assert_eq!(sheet.cell_type_id(a), Some(TypeId::of::<i32>()));
35//! assert_eq!(sheet.cell_kind(out), Some(CellKind::Out));
36//! assert_eq!(sheet.cells().count(), 5);
37//! assert_eq!(sheet.relationships().count(), 2);
38//! assert_eq!(sheet.cell_adj(b).unwrap().len(), 2);
39//! assert_eq!(sheet.relationship_adj(rel), Some([a, b].as_slice()));
40//! assert_eq!(sheet.method_inputs(rel, 0), Some([a].as_slice()));
41//! assert_eq!(sheet.method_outputs(rel, 0), Some([b].as_slice()));
42//! assert_eq!(sheet.selected_method(rel), Some(0));
43//! assert!(sheet.is_source(a));
44//! assert!(sheet.is_forced(b));
45//! assert!(sheet.forced_cells().any(|id| id == b));
46//! assert!(sheet.is_relationship_forced(rel));
47//! assert!(sheet.forced_relationships().any(|id| id == rel));
48//! assert_eq!(sheet.conditionals().collect::<Vec<_>>(), [conditional]);
49//! assert_eq!(sheet.out_cells().collect::<Vec<_>>(), [out]);
50//! assert_eq!(sheet.conditional_match_cells(conditional), Some([mode].as_slice()));
51//! assert_eq!(sheet.conditional_branch_count(conditional), Some(1));
52//! assert_eq!(sheet.conditional_branch_relationships(conditional, 0), Some([rel].as_slice()));
53//! assert_eq!(sheet.conditional_default_relationships(conditional), Some([].as_slice()));
54//! assert_eq!(sheet.conditional_active_branch(conditional)?, Some(0));
55//! assert_eq!(sheet.filter_args(a), Some([limit].as_slice()));
56//! assert_eq!(sheet.filter_dependents(limit), [a]);
57//! assert!(matches!(sheet.filter_kind(a), Some(adam_rs::FilterKind::Range { .. })));
58//! assert_eq!(sheet.filter_range::<i32>(a), Some((0, 100)));
59//! assert!(sheet.filter_violation(a).is_none());
60//! assert_eq!(sheet.filter_violated_cells().count(), 0);
61//! assert!(sheet.filter_violation_cells().is_empty());
62//! assert_eq!(sheet.cell_requirements(b), Some([check].as_slice()));
63//! assert_eq!(sheet.requirement_cell(check), Some(b));
64//! assert_eq!(sheet.requirement_inputs(check), Some([b].as_slice()));
65//! assert!(sheet.cell_requirements_valid(b));
66//! assert_eq!(sheet.violated_requirements(b).count(), 0);
67//! assert!(sheet.contributing_cells(b).contains(&limit));
68//! assert!(sheet.requirement_contributing_cells(check).contains(&a));
69//! assert!(sheet.requirement_relevant_cells().contains(&a));
70//! assert!(sheet.requirement_violation_cells().is_empty());
71//! assert!(sheet.changed().any(|id| id == out));
72//! sheet.clear_changed();
73//! assert_eq!(sheet.changed().count(), 0);
74//! # Ok::<(), adam_rs::Error>(())
75//! ```
76//!
77//! # Forced, self-referencing, and reversible outcomes
78//!
79//! A cell has three distinct outcomes when a relationship determines it:
80//!
81//! - **Conditionally forced.** The forcing relationship supplies the effective
82//! value, [`Sheet::read`] reports it, and the cell's [`Sheet::source`]
83//! aspiration is unchanged. The cell keeps the priority it had when forcing
84//! began, and an explicit write while forced replaces that priority with the
85//! write's recency. When the last forcing cause ends, the retained aspiration
86//! and priority compete in the first plan that can use the cell, so no
87//! additional edit is needed.
88//! - **Self-referencing.** A method that reads and writes the same cell seeds
89//! from its aspiration and honors that edit's priority. Forcing and release
90//! do not erase either.
91//! - **Ordinary reversible.** A losing cell receives the winner's value as its
92//! new aspiration with a derived priority. It does not revive an earlier
93//! value when the relationship later deactivates, and forcing a cell that
94//! was already derived retains that derived value and priority.
95//!
96//! ```
97//! use adam_rs::{MatchExpr, Method, Sheet};
98//! let mut sheet = Sheet::default();
99//! let mode = sheet.add_cell(0_i32);
100//! let input = sheet.add_cell(30_i32);
101//! let a = sheet.add_cell(1_i32);
102//! let b = sheet.add_cell(2_i32);
103//! let force = sheet.add_relationship(vec![Method::from_fn_1_1(input, a, |v: &i32| Ok(*v))])?;
104//! let reversible = sheet.add_relationship(vec![
105//! Method::from_fn_1_1(a, b, |v: &i32| Ok(*v)),
106//! Method::from_fn_1_1(b, a, |v: &i32| Ok(*v)),
107//! ])?;
108//! sheet.add_conditional(
109//! MatchExpr::cell(mode),
110//! vec![(vec![1_i32], vec![force]), (vec![2_i32], vec![reversible])],
111//! vec![],
112//! )?;
113//! sheet.write(b, 22_i32)?;
114//! sheet.write(a, 11_i32)?;
115//! sheet.write(mode, 1_i32)?;
116//! sheet.propagate()?;
117//! assert!(sheet.is_forced(a));
118//! assert_eq!((*sheet.read::<i32>(a)?, *sheet.source::<i32>(a)?), (30, 11));
119//!
120//! // Releasing `a` keeps its newer edit, so `a` wins the reversible relationship.
121//! sheet.write(mode, 2_i32)?;
122//! sheet.propagate()?;
123//! assert!(sheet.is_source(a));
124//! assert_eq!(*sheet.read::<i32>(b)?, 11);
125//! # Ok::<(), adam_rs::Error>(())
126//! ```
127
128use std::any::{Any, TypeId};
129use std::collections::{HashMap, HashSet};
130use std::rc::Rc;
131
132use slotmap::SlotMap;
133
134use crate::{
135 cell::{CellData, CellId, CellKind},
136 conditional::{Branch, ConditionalData, ConditionalId, MatchExpr, MatchSource},
137 error::{Error, ErrorSite},
138 filter::{Filter, FilterKind, FilterViolation},
139 planner::{Plan, PlanStep, SeedSignatures, SeedSource, Seeds},
140 relationship::{Method, RelationshipData, RelationshipId},
141 requirement::{Requirement, RequirementData, RequirementId},
142};
143
144mod cached;
145mod dependency;
146mod prerequisites;
147
148#[cfg(test)]
149use cached::ReuseStats;
150use cached::{CachedPlan, PreparedPlan};
151
152/// Owns a complete property model constraint graph.
153///
154/// Create cells with [`Sheet::add_cell`], define multi-way constraints with
155/// [`Sheet::add_relationship`], write input values with [`Sheet::write`],
156/// then call [`Sheet::propagate`] to update derived cells. Propagation automatically
157/// reuses eligible prepared plans or plans again when necessary.
158///
159/// After a mutating operation returns an error, the sheet's state is unspecified.
160/// Do not inspect or reuse it; discard it and rebuild from host-owned inputs.
161///
162/// # Example
163///
164/// ```rust
165/// use adam_rs::{Sheet, Method};
166///
167/// let mut sheet = Sheet::new();
168/// let a = sheet.add_cell(0_i32);
169/// let b = sheet.add_cell(0_i32);
170/// sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))]).unwrap();
171/// sheet.write(a, 3_i32).unwrap();
172/// assert_eq!(*sheet.read::<i32>(a).unwrap(), 3);
173/// ```
174pub struct Sheet {
175 pub(crate) cells: SlotMap<CellId, CellData>,
176 pub(crate) relationships: SlotMap<RelationshipId, RelationshipData>,
177 pub(crate) changed_cells: Vec<CellId>,
178 /// Monotonic counter incremented by both `add_cell` and `write`; cells added
179 /// later and cells written later have strictly higher strength, making the
180 /// default method-selection direction deterministic.
181 next_strength: u64,
182 /// The prepared snapshot from the last successful `propagate()` call, retained so the
183 /// display-only accessors ([`Sheet::is_source`], [`Sheet::selected_method`],
184 /// [`Sheet::is_forced`]) can report which method the planner picked for each
185 /// relationship without re-planning.
186 last_plan: Option<Rc<PreparedPlan>>,
187 /// Number of cells with a priority retained through conditional forcing.
188 remembered_strength_count: usize,
189 /// Eligible prepared unconditional plan, independent of the display snapshot.
190 pre_plan_cache: Option<CachedPlan>,
191 /// Eligible prepared final active plan, independent of the unconditional plan.
192 main_plan_cache: Option<CachedPlan>,
193 /// Ordered method signatures retained until the graph structure changes.
194 seed_signatures: Option<Rc<SeedSignatures>>,
195 /// Counts actual preparation and reuse-validation work for this sheet only.
196 #[cfg(test)]
197 reuse_stats: ReuseStats,
198 /// All conditionals registered on this sheet.
199 pub(crate) conditionals: SlotMap<ConditionalId, ConditionalData>,
200 /// Union of all RelationshipIds assigned to any conditional branch or default.
201 /// Used to exclude them from the unconditional active set.
202 pub(crate) conditional_relationships: HashSet<RelationshipId>,
203 /// All requirements registered on this sheet, across all cells.
204 requirements: SlotMap<RequirementId, RequirementData>,
205 /// Requirements that evaluated `false` as of the last `propagate()` call, grouped
206 /// by cell. Sparse: a cell with no entry had all its requirements hold.
207 last_requirement_violations: HashMap<CellId, Vec<RequirementId>>,
208 /// Filter violations recorded against a derived value as of the last `propagate()`
209 /// call.
210 last_filter_violations: HashMap<CellId, FilterViolation>,
211 /// Reverse index of `filter_args`: for each cell, the live cells whose filter
212 /// references it as one of its dynamic arguments. Built incrementally in
213 /// `add_filter`; cells and filters are never removed once added, so this needs no
214 /// invalidation, matching every other per-cell set/map `Sheet` already maintains
215 /// for its own lifetime.
216 filter_dependents: HashMap<CellId, Vec<CellId>>,
217 /// Whether static guard independence has been validated for the current structure.
218 guard_independent: bool,
219}
220
221/// A conditional's evaluated match value: borrowed (existing cell, no allocation) or owned
222/// (freshly computed by a [`MatchExpr`] function).
223enum MatchValue<'a> {
224 Ref(&'a dyn Any),
225 Owned(Box<dyn Any>),
226}
227
228impl MatchValue<'_> {
229 /// Returns the contained value as a type-erased reference, regardless of variant.
230 fn as_dyn(&self) -> &dyn Any {
231 match self {
232 MatchValue::Ref(r) => *r,
233 MatchValue::Owned(b) => b.as_ref(),
234 }
235 }
236}
237
238/// Provenance needed to decide whether a staged guard-cone step can be reused.
239///
240/// Reuse is intentionally conservative: a step is reusable only when the final plan selects the
241/// same method, producer path, source/derived classification, and seed inputs. A mismatch returns
242/// `Error::Conflict` rather than guessing a compatible assignment or reusing stale values.
243#[derive(Clone, PartialEq, Eq)]
244struct StepProvenance {
245 input_producers: Vec<Option<PlanStep>>,
246 seed_inputs: Vec<CellId>,
247 source_producer: Option<PlanStep>,
248 output_classes: Vec<(CellId, OutputClassification)>,
249}
250
251/// Whether a selected step writes a cell's staged value as source or derived.
252#[derive(Clone, Copy, PartialEq, Eq)]
253enum OutputClassification {
254 /// The write replaces the staged source value.
255 Source,
256 /// The write shadows the staged source value.
257 Derived,
258}
259
260/// Selected producer paths for every step and output in one execution plan.
261#[derive(Default)]
262struct PlanProvenance {
263 steps: HashMap<PlanStep, StepProvenance>,
264 output_producers: HashMap<CellId, PlanStep>,
265}
266
267/// Transactional propagation state whose writes remain private until commit.
268///
269/// Staged derived values override staged sources, which override live sources.
270/// Each replaced source receives a fresh version; absent sources have version zero.
271/// `changed` contains each written cell exactly once and has the same members as
272/// `changed_set`. Dropping an uncommitted stage discards every owned result.
273#[derive(Default)]
274struct PropagationStage {
275 sources: HashMap<CellId, Box<dyn Any>>,
276 /// Version of each staged source value; an absent cell reads its live source (version 0).
277 source_versions: HashMap<CellId, u64>,
278 last_source_version: u64,
279 derived: HashMap<CellId, Box<dyn Any>>,
280 executed_methods: HashSet<(RelationshipId, usize)>,
281 executed_filters: HashSet<CellId>,
282 step_provenance: HashMap<PlanStep, StepProvenance>,
283 changed: Vec<CellId>,
284 changed_set: HashSet<CellId>,
285}
286
287impl PropagationStage {
288 /// Returns the staged effective value for `id`, ignoring any live sheet `derived`.
289 fn effective<'a>(&'a self, cells: &'a SlotMap<CellId, CellData>, id: CellId) -> &'a dyn Any {
290 self.derived
291 .get(&id)
292 .map(|value| value.as_ref())
293 .or_else(|| self.sources.get(&id).map(|value| value.as_ref()))
294 .unwrap_or_else(|| cells[id].source.as_ref())
295 }
296
297 /// Returns the staged source value for `id`, ignoring any live sheet `derived`.
298 fn source<'a>(&'a self, cells: &'a SlotMap<CellId, CellData>, id: CellId) -> &'a dyn Any {
299 self.sources
300 .get(&id)
301 .map(|value| value.as_ref())
302 .unwrap_or_else(|| cells[id].source.as_ref())
303 }
304
305 /// Returns the staged source value for `id` with a version identifying that value.
306 ///
307 /// - Postcondition: two calls return equal versions only if no staged source write or
308 /// reclassification changed `id`'s source value between them.
309 fn seed_source<'a>(
310 &'a self,
311 cells: &'a SlotMap<CellId, CellData>,
312 id: CellId,
313 ) -> SeedSource<'a> {
314 SeedSource {
315 value: self.source(cells, id),
316 version: self.source_versions.get(&id).copied().unwrap_or(0),
317 }
318 }
319
320 /// Records a new staged source value for `id` under a fresh version.
321 fn insert_source(&mut self, id: CellId, value: Box<dyn Any>) {
322 self.last_source_version += 1;
323 self.source_versions.insert(id, self.last_source_version);
324 self.sources.insert(id, value);
325 }
326
327 /// Shadows the source for derived writes or replaces it for ordinary writes.
328 fn write(&mut self, id: CellId, value: Box<dyn Any>, shadow: bool) {
329 if shadow {
330 self.derived.insert(id, value);
331 } else {
332 self.insert_source(id, value);
333 self.derived.remove(&id);
334 }
335 if self.changed_set.insert(id) {
336 self.changed.push(id);
337 }
338 }
339
340 /// Reclassifies an existing staged output for the general plan's shadowing rule.
341 ///
342 /// - Precondition: `id` has a staged source or derived value.
343 fn reclassify(&mut self, id: CellId, shadow: bool) {
344 if shadow {
345 if let Some(value) = self.sources.remove(&id) {
346 self.source_versions.remove(&id);
347 self.derived.insert(id, value);
348 }
349 } else if let Some(value) = self.derived.remove(&id) {
350 self.insert_source(id, value);
351 }
352 }
353}
354
355impl Sheet {
356 /// Creates an empty sheet with no cells or relationships.
357 ///
358 /// # Examples
359 /// See [`Sheet`]'s example.
360 pub fn new() -> Self {
361 Sheet {
362 cells: SlotMap::with_key(),
363 relationships: SlotMap::with_key(),
364 changed_cells: Vec::new(),
365 next_strength: 0,
366 last_plan: None,
367 remembered_strength_count: 0,
368 pre_plan_cache: None,
369 main_plan_cache: None,
370 seed_signatures: None,
371 #[cfg(test)]
372 reuse_stats: ReuseStats::default(),
373 conditionals: SlotMap::with_key(),
374 conditional_relationships: HashSet::new(),
375 requirements: SlotMap::with_key(),
376 last_requirement_violations: HashMap::new(),
377 last_filter_violations: HashMap::new(),
378 filter_dependents: HashMap::new(),
379 guard_independent: true,
380 }
381 }
382
383 /// Registers a cell with an initial value and returns a stable handle.
384 ///
385 /// The cell's `TypeId` is fixed at creation time; subsequent `write` and
386 /// `read` calls that use a different type will return `Error::TypeMismatch`.
387 ///
388 /// Each call increments the sheet's internal strength counter and sets bit 63
389 /// of the result. This partitions the strength space: written/added cells always
390 /// have higher strength than derived cells, ensuring stability across conditional
391 /// branch switches.
392 ///
393 /// - Complexity: Amortized O(1), plus O(A) when invalidation releases the final
394 /// owners of prepared artifacts of total size A.
395 ///
396 /// # Examples
397 /// See the [graph example](crate::sheet).
398 pub fn add_cell<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId {
399 self.invalidate_prepared_plans();
400 self.next_strength += 1;
401 let strength = self.next_strength | (1u64 << 63);
402 self.cells.insert(CellData {
403 source: Box::new(value),
404 derived: None,
405 type_id: TypeId::of::<T>(),
406 strength,
407 remembered_strength: None,
408 changed: false,
409 adj: Vec::new(),
410 eq_fn: |a, b| a.downcast_ref::<T>() == b.downcast_ref::<T>(),
411 filter: None,
412 kind: CellKind::Cell,
413 requirements: Vec::new(),
414 })
415 }
416
417 /// Registers a cell that can never be claimed as any method's output — always a
418 /// planner source, forever.
419 ///
420 /// - Complexity: Amortized O(1), plus O(A) when invalidation releases the final
421 /// owners of prepared artifacts of total size A.
422 ///
423 /// # Examples
424 /// See the [graph example](crate::sheet).
425 pub fn add_source<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId {
426 let id = self.add_cell(value);
427 self.cells[id].kind = CellKind::Source;
428 id
429 }
430
431 /// Returns the fixed concrete type of a live cell, or `None` for an invalid ID.
432 ///
433 /// # Examples
434 ///
435 /// ```
436 /// use std::any::TypeId;
437 /// use adam_rs::{CellId, Sheet};
438 /// let mut sheet = Sheet::new();
439 /// let cell = sheet.add_cell(7i32);
440 /// assert_eq!(sheet.cell_type_id(cell), Some(TypeId::of::<i32>()));
441 /// assert_eq!(sheet.cell_type_id(CellId::default()), None);
442 /// ```
443 pub fn cell_type_id(&self, id: CellId) -> Option<TypeId> {
444 self.cells.get(id).map(|cell| cell.type_id)
445 }
446
447 /// Returns `id`'s fixed cell kind.
448 ///
449 /// Returns `None` if `id` is not a live cell in this sheet.
450 ///
451 /// # Examples
452 /// See the [graph example](crate::sheet).
453 pub fn cell_kind(&self, id: CellId) -> Option<CellKind> {
454 self.cells.get(id).map(|c| c.kind)
455 }
456
457 /// Registers a relationship defined by a non-empty list of methods.
458 ///
459 /// All methods are validated: their declared `TypeId`s must match the
460 /// registered cells, and each method must have at least one output. A method
461 /// with no inputs is explicitly allowed: it defines a fixed point (a constant,
462 /// independent of every other cell) rather than a derivation.
463 /// On success the `RelationshipId` is added to each adjacent cell's adjacency list.
464 ///
465 /// A cell that appears in both a method's inputs and its outputs is a self-referencing
466 /// cell and is explicitly allowed.
467 ///
468 /// # Errors
469 ///
470 /// - `Error::InvalidMethod` — `methods` is empty, or a method has no outputs.
471 /// - `Error::MismatchedMethodCells` — some method's `inputs ∪ outputs` differs
472 /// from another method's in the same relationship.
473 /// - `Error::InvalidMethodOutputs` — a method's own `outputs` list names a cell
474 /// more than once, or two methods in the same relationship have identical or
475 /// nested `outputs` sets. Overlapping non-nested output sets are allowed.
476 /// - `Error::InvalidId` — a `CellId` in any method is not found in this sheet.
477 /// - `Error::TypeMismatch` — a method's declared `TypeId` does not match the
478 /// cell's registered `TypeId`.
479 /// - `Error::InvalidCellKind` — a method's output cell is `Source`- or `Out`-kind.
480 ///
481 /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
482 /// not by this mutator.
483 ///
484 /// - Complexity: O(m² × c) where m is the total number of methods and c is the
485 /// maximum number of cells per method, due to pairwise output-set comparison,
486 /// plus O(A) when invalidation releases the final owners of prepared artifacts
487 /// of total size A.
488 ///
489 /// # Examples
490 /// See the [graph example](crate::sheet).
491 pub fn add_relationship(&mut self, methods: Vec<Method>) -> Result<RelationshipId, Error> {
492 if methods.is_empty() {
493 return Err(Error::InvalidMethod { sites: vec![] });
494 }
495
496 for (idx, method) in methods.iter().enumerate() {
497 if method.outputs.is_empty() {
498 return Err(Error::InvalidMethod {
499 sites: vec![ErrorSite::MethodIndex(idx)],
500 });
501 }
502
503 // declared type counts must match cell-id counts
504 if method.inputs.len() != method.input_types.len()
505 || method.outputs.len() != method.output_types.len()
506 {
507 return Err(Error::InvalidMethod {
508 sites: vec![ErrorSite::MethodIndex(idx)],
509 });
510 }
511
512 for (&cell_id, &declared) in method.inputs.iter().zip(method.input_types.iter()) {
513 let cell = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
514 if cell.type_id != declared {
515 return Err(Error::TypeMismatch {
516 expected: cell.type_id,
517 found: declared,
518 sites: vec![ErrorSite::MethodIndex(idx)],
519 });
520 }
521 }
522
523 for (&cell_id, &declared) in method.outputs.iter().zip(method.output_types.iter()) {
524 let cell = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
525 if cell.kind != CellKind::Cell {
526 return Err(Error::InvalidCellKind {
527 sites: vec![ErrorSite::MethodIndex(idx), ErrorSite::Cell(cell_id)],
528 });
529 }
530 if cell.type_id != declared {
531 return Err(Error::TypeMismatch {
532 expected: cell.type_id,
533 found: declared,
534 sites: vec![ErrorSite::MethodIndex(idx)],
535 });
536 }
537 }
538 }
539
540 // Every method in a relationship must reference the same set of cells: the
541 // union of a method's inputs and outputs (as a set) must be identical across
542 // all methods. A relationship models a fixed set of related cells; methods
543 // differ only in which subset of that set they treat as outputs (using the
544 // "ignore an input" pattern), not in which cells they reference at all.
545 let cell_sets: Vec<HashSet<CellId>> = methods
546 .iter()
547 .map(|m| m.inputs.iter().chain(m.outputs.iter()).copied().collect())
548 .collect();
549 if let Some(rel_idx) = cell_sets[1..].iter().position(|set| set != &cell_sets[0]) {
550 let diverging = rel_idx + 1;
551 let mut sites = vec![ErrorSite::MethodIndex(diverging), ErrorSite::MethodIndex(0)];
552 // Symmetric difference of the two cell sets, in a stable order.
553 for &c in cell_sets[diverging].symmetric_difference(&cell_sets[0]) {
554 sites.push(ErrorSite::Cell(c));
555 }
556 return Err(Error::MismatchedMethodCells { sites });
557 }
558
559 // A method's own outputs must be duplicate-free, and no two methods in a
560 // relationship may claim identical or nested output sets: the planner's
561 // matching stage treats a method's pure-output set as an indivisible claim,
562 // so nested claims would make that claim ambiguous while still allowing
563 // overlapping non-nested sets.
564 let mut seen_output_sets: Vec<(usize, HashSet<CellId>, &[CellId])> =
565 Vec::with_capacity(methods.len());
566 for (idx, method) in methods.iter().enumerate() {
567 let output_set: HashSet<CellId> = method.outputs.iter().copied().collect();
568 if output_set.len() != method.outputs.len() {
569 // A cell repeated within this method's own outputs. Report each
570 // distinct repeated cell once, no matter how many times it repeats.
571 let mut sites = vec![ErrorSite::MethodIndex(idx)];
572 let mut seen = HashSet::new();
573 let mut reported = HashSet::new();
574 for &o in &method.outputs {
575 if !seen.insert(o) && reported.insert(o) {
576 sites.push(ErrorSite::Cell(o));
577 }
578 }
579 return Err(Error::InvalidMethodOutputs { sites });
580 }
581 for (earlier, earlier_set, earlier_outputs) in &seen_output_sets {
582 if output_set == *earlier_set
583 || (output_set.len() < earlier_set.len() && output_set.is_subset(earlier_set))
584 || (earlier_set.len() < output_set.len() && earlier_set.is_subset(&output_set))
585 {
586 let mut sites = vec![
587 ErrorSite::MethodIndex(idx),
588 ErrorSite::MethodIndex(*earlier),
589 ];
590 let offending_outputs: &[CellId] = if output_set.len() <= earlier_set.len() {
591 &method.outputs
592 } else {
593 earlier_outputs
594 };
595 for &o in offending_outputs {
596 sites.push(ErrorSite::Cell(o));
597 }
598 return Err(Error::InvalidMethodOutputs { sites });
599 }
600 }
601 seen_output_sets.push((idx, output_set, &method.outputs));
602 }
603
604 // Collect the union of all adjacent cells in insertion order, deduplicated.
605 let mut adj: Vec<CellId> = Vec::new();
606 let mut seen: std::collections::HashSet<CellId> = std::collections::HashSet::new();
607 for method in &methods {
608 for &cell_id in method.inputs.iter().chain(method.outputs.iter()) {
609 if seen.insert(cell_id) {
610 adj.push(cell_id);
611 }
612 }
613 }
614
615 self.invalidate_prepared_plans();
616 let rel_id = self.relationships.insert(RelationshipData { methods, adj });
617
618 for &cell_id in &self.relationships[rel_id].adj {
619 if let Some(cell) = self.cells.get_mut(cell_id)
620 && !cell.adj.contains(&rel_id)
621 {
622 cell.adj.push(rel_id);
623 }
624 }
625
626 self.guard_independent = false;
627 Ok(rel_id)
628 }
629
630 /// Registers a conditional that activates relationships based on the value of a match
631 /// subject: either a single existing cell, or a [`MatchExpr`] computed from multiple
632 /// input cells.
633 ///
634 /// Each element of `branches` is `(keys, relationships)`: when the match subject's
635 /// value equals any key in `keys`, the branch's `relationships` are added to the active
636 /// set for `propagate`. Branches are evaluated in definition order; first match wins.
637 /// `default` holds relationships activated when no branch matches; pass an empty `Vec`
638 /// for no default.
639 ///
640 /// # Errors
641 ///
642 /// - `Error::InvalidId` — the match subject references a cell not in this sheet.
643 /// - `Error::TypeMismatch` — (expression match subject only) an input cell's registered
644 /// type doesn't match the expression's declared type for that input.
645 /// - `Error::InvalidConditional` — the match subject's output type does not match `T`;
646 /// a referenced relationship does not exist; a relationship already appears in
647 /// another conditional branch; or a branch has no keys.
648 /// - `Error::InvalidCellKind` — a referenced relationship is a fixed `Out` writer.
649 ///
650 /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
651 /// not by this mutator.
652 ///
653 /// - Complexity: O(I + K + E) time and O(K + R) space for match inputs I,
654 /// branch keys K, referenced relationships R and their total method-output
655 /// edges E, plus O(A) when invalidation releases final prepared artifacts of size A.
656 ///
657 /// # Examples
658 /// See the [graph example](crate::sheet).
659 pub fn add_conditional<T: Any + PartialEq + 'static>(
660 &mut self,
661 source: MatchExpr,
662 branches: Vec<(Vec<T>, Vec<RelationshipId>)>,
663 default: Vec<RelationshipId>,
664 ) -> Result<ConditionalId, Error> {
665 match &source.0 {
666 MatchSource::Cell(cell) => {
667 let cell_data = self.cells.get(*cell).ok_or(Error::InvalidId)?;
668 if cell_data.type_id != TypeId::of::<T>() {
669 return Err(Error::InvalidConditional {
670 sites: vec![ErrorSite::Cell(*cell)],
671 });
672 }
673 }
674 MatchSource::Expr(expr) => {
675 if expr.output_type != TypeId::of::<T>() {
676 return Err(Error::InvalidConditional { sites: vec![] });
677 }
678 for (&cell_id, &declared) in expr.inputs.iter().zip(expr.input_types.iter()) {
679 let cell_data = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
680 if cell_data.type_id != declared {
681 return Err(Error::TypeMismatch {
682 expected: cell_data.type_id,
683 found: declared,
684 sites: vec![],
685 });
686 }
687 }
688 }
689 };
690
691 // Collect and validate all relationship IDs (branches + default).
692 let all_rels: Vec<RelationshipId> = branches
693 .iter()
694 .flat_map(|(_, rels)| rels.iter().copied())
695 .chain(default.iter().copied())
696 .collect();
697
698 for &rel_id in &all_rels {
699 let relationship = self
700 .relationships
701 .get(rel_id)
702 .ok_or(Error::InvalidConditional { sites: vec![] })?;
703 if let Some(output) = relationship
704 .methods
705 .iter()
706 .flat_map(|method| &method.outputs)
707 .find(|&&id| self.cells[id].kind == CellKind::Out)
708 {
709 return Err(Error::InvalidCellKind {
710 sites: vec![ErrorSite::Relationship(rel_id), ErrorSite::Cell(*output)],
711 });
712 }
713 if self.conditional_relationships.contains(&rel_id) {
714 return Err(Error::InvalidConditional {
715 sites: vec![ErrorSite::Relationship(rel_id)],
716 });
717 }
718 }
719
720 // Validate branch keys are non-empty.
721 for (keys, _) in &branches {
722 if keys.is_empty() {
723 return Err(Error::InvalidConditional { sites: vec![] });
724 }
725 }
726
727 // Check for duplicate relationship IDs within this call.
728 let mut seen: HashSet<RelationshipId> = HashSet::new();
729 for &rel_id in &all_rels {
730 if !seen.insert(rel_id) {
731 return Err(Error::InvalidConditional {
732 sites: vec![ErrorSite::Relationship(rel_id)],
733 });
734 }
735 }
736
737 // Type-erase branch keys.
738 let typed_branches: Vec<Branch> = branches
739 .into_iter()
740 .map(|(keys, relationships)| Branch {
741 keys: keys
742 .into_iter()
743 .map(|k| Box::new(k) as Box<dyn Any>)
744 .collect(),
745 relationships,
746 })
747 .collect();
748
749 // Record all relationships as conditional so they are excluded from the
750 // unconditional active set in propagate().
751 self.invalidate_prepared_plans();
752 for &rel_id in &all_rels {
753 self.conditional_relationships.insert(rel_id);
754 }
755
756 let id = self.conditionals.insert(ConditionalData {
757 source: source.0,
758 branches: typed_branches,
759 default,
760 });
761
762 self.guard_independent = false;
763 Ok(id)
764 }
765
766 /// Returns `true` if `id` is already claimed as some existing method's output —
767 /// i.e. it cannot legally become an `out` cell's writer target, since that would
768 /// leave two producers claiming the same cell.
769 ///
770 /// - Complexity: O(E) for all registered method-output edges E.
771 fn cell_has_prior_use(&self, id: CellId) -> bool {
772 self.relationships
773 .values()
774 .any(|rel| rel.methods.iter().any(|m| m.outputs.contains(&id)))
775 }
776
777 /// Attaches a requirement to `cell` and returns its stable ID.
778 ///
779 /// `requirement.inputs` may be any cells in the sheet, not only `cell` itself.
780 /// The predicate runs during propagation, so callers can report false results
781 /// separately from propagation failures.
782 ///
783 /// # Errors
784 ///
785 /// - `Error::InvalidId` — `cell`, or one of `requirement`'s input cells, is not a
786 /// live cell in this sheet.
787 /// - `Error::TypeMismatch` — an input's declared type does not match its cell's
788 /// registered type.
789 /// - `Error::InvalidRequirement` — the input signature is invalid.
790 ///
791 /// - Complexity: O(k) where k is `requirement`'s input count, plus O(A) when
792 /// invalidation releases the final owners of prepared artifacts of total size A.
793 ///
794 /// # Examples
795 ///
796 /// ```
797 /// use adam_rs::{Requirement, Sheet};
798 /// let mut sheet = Sheet::new();
799 /// let cell = sheet.add_cell(1_i32);
800 /// let id = sheet.add_requirement(cell, Requirement::from_fn_1(cell, |x: &i32| Ok(*x > 0)))?;
801 /// assert_eq!(sheet.cell_requirements(cell), Some([id].as_slice()));
802 /// # Ok::<(), adam_rs::Error>(())
803 /// ```
804 pub fn add_requirement(
805 &mut self,
806 cell: CellId,
807 requirement: Requirement,
808 ) -> Result<RequirementId, Error> {
809 self.cells.get(cell).ok_or(Error::InvalidId)?;
810 if requirement.inputs.len() != requirement.input_types.len() {
811 return Err(Error::InvalidRequirement);
812 }
813 for (&input_id, &declared) in requirement
814 .inputs
815 .iter()
816 .zip(requirement.input_types.iter())
817 {
818 let input_cell = self.cells.get(input_id).ok_or(Error::InvalidId)?;
819 if input_cell.type_id != declared {
820 return Err(Error::TypeMismatch {
821 expected: input_cell.type_id,
822 found: declared,
823 sites: vec![],
824 });
825 }
826 }
827
828 self.invalidate_prepared_plans();
829 let rid = self.requirements.insert(RequirementData {
830 cell,
831 inputs: requirement.inputs,
832 function: requirement.function,
833 });
834 self.cells[cell].requirements.push(rid);
835 Ok(rid)
836 }
837
838 /// Registers `writer` as the sole producer of its one output cell, which becomes
839 /// an `out` cell: always derived by `writer`, never `write()`-able, but otherwise
840 /// an ordinary, freely-referenceable cell. `requirements` are attached to that
841 /// cell one at a time, in order, via [`Sheet::add_requirement`].
842 ///
843 /// # Errors
844 ///
845 /// - `Error::InvalidOutput` — `writer` does not have exactly one output cell.
846 /// - `Error::InvalidCellKind` — the writer's output cell is already `Source` or
847 /// `Out` kind, or already claimed as some existing method's output.
848 /// - Any error [`Sheet::add_relationship`] or [`Sheet::add_requirement`] can
849 /// return.
850 ///
851 /// - Postcondition: not atomic — if `add_requirement` fails partway through
852 /// `requirements`, the output cell is left `Out`-kind, `writer` is already
853 /// registered as its producer, and every requirement before the failing one is
854 /// already attached; none of this is rolled back on error.
855 ///
856 /// - Complexity: O(k + i + m²×c) where k is the number of requirements and i is
857 /// their total input count, plus the
858 /// cost of `add_relationship` for `writer` alone (m = 1 method, c = cells in
859 /// that method), plus O(A) when invalidation releases the final owners of
860 /// prepared artifacts of total size A.
861 ///
862 /// # Examples
863 ///
864 /// ```
865 /// use adam_rs::{Method, Requirement, Sheet};
866 /// let mut sheet = Sheet::new();
867 /// let input = sheet.add_cell(1_i32);
868 /// let output = sheet.add_cell(0_i32);
869 /// let id = sheet.add_out(
870 /// Method::from_fn_1_1(input, output, |x: &i32| Ok(*x)),
871 /// vec![Requirement::from_fn_1(output, |x: &i32| Ok(*x > 0))],
872 /// )?;
873 /// assert_eq!(id, output);
874 /// sheet.propagate()?;
875 /// assert!(sheet.cell_requirements_valid(output));
876 /// # Ok::<(), adam_rs::Error>(())
877 /// ```
878 pub fn add_out(
879 &mut self,
880 writer: Method,
881 requirements: Vec<Requirement>,
882 ) -> Result<CellId, Error> {
883 let out_cell = writer
884 .outputs
885 .first()
886 .copied()
887 .ok_or(Error::InvalidOutput)?;
888 self.add_out_writer(writer)?;
889 for requirement in requirements {
890 self.add_requirement(out_cell, requirement)?;
891 }
892 Ok(out_cell)
893 }
894
895 /// Registers the sole output writer and returns its native relationship identity.
896 ///
897 /// Reports the same writer validation errors as [`Self::add_out`].
898 /// - Complexity: O(r × e + i) for prior relationships r, edges e and writer inputs i.
899 pub(crate) fn add_out_writer(&mut self, writer: Method) -> Result<RelationshipId, Error> {
900 if writer.outputs.len() != 1 {
901 return Err(Error::InvalidOutput);
902 }
903 let out_cell = writer.outputs[0];
904
905 let kind = self.cells.get(out_cell).ok_or(Error::InvalidId)?.kind;
906 if kind != CellKind::Cell || self.cell_has_prior_use(out_cell) {
907 return Err(Error::InvalidCellKind { sites: vec![] });
908 }
909
910 let relationship = self.add_relationship(vec![writer])?;
911 self.cells[out_cell].kind = CellKind::Out;
912 Ok(relationship)
913 }
914
915 /// Attaches `filter` to `cell`.
916 ///
917 /// Never evaluates `filter`'s function — attaching a filter is not a fresh
918 /// external input, so it never changes `cell`'s current effective value. The next
919 /// full [`Sheet::propagate`] call conforms `cell` via the planner's
920 /// `PlanStep::FilterReclamp` step; until then, `read()` reflects whatever `cell`
921 /// held before this call.
922 ///
923 /// # Errors
924 ///
925 /// - `Error::InvalidId` — `cell`, or one of `filter`'s argument cells, is not a
926 /// live cell in this sheet.
927 /// - `Error::InvalidFilter` — `cell` already has a filter, `filter`'s own value
928 /// type does not match `cell`'s registered type, or `filter`'s argument list
929 /// names `cell` itself.
930 /// - `Error::TypeMismatch` — an argument cell's registered type does not match the
931 /// type `filter` declared for it.
932 ///
933 /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
934 /// not by this mutator.
935 ///
936 /// - Complexity: O(a) where a is the number of filter argument cells, plus O(A)
937 /// when invalidation releases the final owners of prepared artifacts of total
938 /// size A.
939 ///
940 /// # Examples
941 /// See the [graph example](crate::sheet).
942 pub fn add_filter(&mut self, cell: CellId, filter: Filter) -> Result<(), Error> {
943 let cell_type = self.cells.get(cell).ok_or(Error::InvalidId)?.type_id;
944 if self.cells[cell].filter.is_some() {
945 return Err(Error::InvalidFilter);
946 }
947 if filter.0.value_type != cell_type {
948 return Err(Error::InvalidFilter);
949 }
950 if filter.0.args.contains(&cell) {
951 return Err(Error::InvalidFilter);
952 }
953 for (&arg_id, &declared) in filter.0.args.iter().zip(filter.0.arg_types.iter()) {
954 let arg_cell = self.cells.get(arg_id).ok_or(Error::InvalidId)?;
955 if arg_cell.type_id != declared {
956 return Err(Error::TypeMismatch {
957 expected: arg_cell.type_id,
958 found: declared,
959 sites: vec![],
960 });
961 }
962 }
963
964 self.invalidate_prepared_plans();
965 for &arg in &filter.0.args {
966 self.filter_dependents.entry(arg).or_default().push(cell);
967 }
968 self.cells[cell].filter = Some(filter.0);
969 self.guard_independent = false;
970 Ok(())
971 }
972
973 /// Returns the argument cells of `id`'s filter, in declaration order.
974 ///
975 /// Returns `None` if `id` is not a live cell in this sheet, or has no filter.
976 ///
977 /// # Examples
978 /// See the [graph example](crate::sheet).
979 pub fn filter_args(&self, id: CellId) -> Option<&[CellId]> {
980 self.cells
981 .get(id)?
982 .filter
983 .as_ref()
984 .map(|f| f.args.as_slice())
985 }
986
987 /// Returns the live cells whose filter references `id` as one of its dynamic
988 /// arguments — the reverse of a filter's own argument list ([`Sheet::filter_args`]).
989 ///
990 /// - Postcondition: empty if no live cell's filter references `id`.
991 ///
992 /// # Examples
993 /// See the [graph example](crate::sheet).
994 pub fn filter_dependents(&self, id: CellId) -> &[CellId] {
995 self.filter_dependents
996 .get(&id)
997 .map(Vec::as_slice)
998 .unwrap_or(&[])
999 }
1000
1001 /// Returns the kind of validation/derivation `id`'s filter performs, if it has one.
1002 ///
1003 /// Returns `None` if `id` is not a live cell in this sheet, or has no filter.
1004 ///
1005 /// # Examples
1006 /// See the [graph example](crate::sheet).
1007 pub fn filter_kind(&self, id: CellId) -> Option<&FilterKind> {
1008 self.cells.get(id)?.filter.as_ref().map(|f| &f.kind)
1009 }
1010
1011 /// Returns `id`'s filter's current `(lo, hi)` bounds, if it has a [`FilterKind::Range`]
1012 /// filter.
1013 ///
1014 /// Resolves the filter's argument cells' current effective values via the same path
1015 /// [`Sheet::add_filter`] already uses, then calls the filter's `bounds` function.
1016 ///
1017 /// Returns `None` if `id` is not a live cell in this sheet, has no filter, its filter's
1018 /// kind isn't [`FilterKind::Range`], its bounds have a different type from `T`,
1019 /// or the range expression fails to evaluate against
1020 /// the filter's current argument values (e.g. a fallible arithmetic op in a range
1021 /// endpoint) — the same degraded-to-`None` outcome as any other reason no live bounds
1022 /// are available right now.
1023 ///
1024 /// - Complexity: O(a) where a is the number of the filter's argument cells.
1025 ///
1026 /// # Examples
1027 /// See the [graph example](crate::sheet).
1028 pub fn filter_range<T: Any>(&self, id: CellId) -> Option<(T, T)> {
1029 let filter = self.cells.get(id)?.filter.as_ref()?;
1030 let FilterKind::Range { bounds } = &filter.kind else {
1031 return None;
1032 };
1033 let args: Vec<&dyn Any> = filter
1034 .args
1035 .iter()
1036 .map(|&a| self.cells[a].effective())
1037 .collect();
1038 let (lo, hi) = bounds(&args)?;
1039 Some((*lo.downcast::<T>().ok()?, *hi.downcast::<T>().ok()?))
1040 }
1041
1042 /// Returns the filter violation recorded for `id` as of the last full
1043 /// `propagate()` call, if any.
1044 ///
1045 /// - Postcondition: `None` if `id` has no filter, `id`'s filter's last-checked
1046 /// value held, or no full `propagate()` has run since `id` was last a plain
1047 /// external write.
1048 ///
1049 /// # Examples
1050 /// See the [graph example](crate::sheet).
1051 pub fn filter_violation(&self, id: CellId) -> Option<&FilterViolation> {
1052 self.last_filter_violations.get(&id)
1053 }
1054
1055 /// Iterates cells whose filter is currently violated, as of the last full
1056 /// `propagate()` call.
1057 ///
1058 /// - Complexity: O(n) where n is the number of currently-violated filters.
1059 ///
1060 /// # Examples
1061 /// See the [graph example](crate::sheet).
1062 pub fn filter_violated_cells(&self) -> impl Iterator<Item = CellId> + '_ {
1063 self.last_filter_violations.keys().copied()
1064 }
1065
1066 /// Returns the set of root cells currently determining a violated filter's own
1067 /// value or any of its argument values, as of the last full `propagate()` call —
1068 /// the same "which upstream cells caused this" query
1069 /// `requirement_contributing_cells`/`requirement_violation_cells` already provide
1070 /// for `Requirement`.
1071 ///
1072 /// - Postcondition: empty if no filter is currently violated.
1073 /// - Complexity: O(sum of `contributing_cells` cost over every violated filter and
1074 /// its argument cells).
1075 ///
1076 /// # Examples
1077 /// See the [graph example](crate::sheet).
1078 pub fn filter_violation_cells(&self) -> HashSet<CellId> {
1079 let mut result = HashSet::new();
1080 for cell_id in self.filter_violated_cells() {
1081 result.extend(self.contributing_cells(cell_id));
1082 if let Some(args) = self.filter_args(cell_id) {
1083 for &arg in args {
1084 result.extend(self.contributing_cells(arg));
1085 }
1086 }
1087 }
1088 result
1089 }
1090
1091 /// Returns the requirements attached to `id`, in attachment order.
1092 ///
1093 /// Returns `None` if `id` is not a live cell in this sheet.
1094 ///
1095 /// # Examples
1096 /// See the [graph example](crate::sheet).
1097 pub fn cell_requirements(&self, id: CellId) -> Option<&[RequirementId]> {
1098 self.cells.get(id).map(|c| c.requirements.as_slice())
1099 }
1100
1101 /// Returns the cell requirement `id` is attached to.
1102 ///
1103 /// Returns `None` if `id` is not a live requirement in this sheet.
1104 ///
1105 /// # Examples
1106 /// See the [graph example](crate::sheet).
1107 pub fn requirement_cell(&self, id: RequirementId) -> Option<CellId> {
1108 self.requirements.get(id).map(|c| c.cell)
1109 }
1110
1111 /// Returns the cells requirement `id` reads.
1112 ///
1113 /// Returns `None` if `id` is not a live requirement in this sheet.
1114 ///
1115 /// # Examples
1116 /// See the [graph example](crate::sheet).
1117 pub fn requirement_inputs(&self, id: RequirementId) -> Option<&[CellId]> {
1118 self.requirements.get(id).map(|c| c.inputs.as_slice())
1119 }
1120
1121 /// Returns `true` if every requirement on `id` held as of the last `propagate()`
1122 /// call.
1123 ///
1124 /// Returns `false` if no propagation has run yet. Also returns `true` for an
1125 /// `id` that is not a live cell in this sheet, since no requirement can have
1126 /// failed for a cell that doesn't exist.
1127 ///
1128 /// # Examples
1129 /// See the [graph example](crate::sheet).
1130 pub fn cell_requirements_valid(&self, id: CellId) -> bool {
1131 if self.last_plan.is_none() {
1132 return false;
1133 }
1134 !self.last_requirement_violations.contains_key(&id)
1135 }
1136
1137 /// Iterates the requirements on `id` that evaluated to `false` as of the last
1138 /// `propagate()` call.
1139 ///
1140 /// - Postcondition: empty if `id`'s requirements all held, `id` is not a live
1141 /// cell in this sheet, or no propagation has run yet.
1142 ///
1143 /// # Examples
1144 /// See the [graph example](crate::sheet).
1145 pub fn violated_requirements(&self, id: CellId) -> impl Iterator<Item = RequirementId> + '_ {
1146 self.last_requirement_violations
1147 .get(&id)
1148 .into_iter()
1149 .flatten()
1150 .copied()
1151 }
1152
1153 /// Returns the set of root cells that could determine `id`'s value for *some* choice of
1154 /// cell strengths, as of the last `propagate()` call.
1155 ///
1156 /// A cell is a root candidate for `current` whenever some *active* relationship
1157 /// adjacent to `current` has a method producing it — not just the one method the
1158 /// current strengths happen to have selected: a different strength ordering could pick
1159 /// a different method of that same relationship, making any of its other cells the
1160 /// source instead. So every method (of every active relationship touching `current`)
1161 /// with `current` among its outputs contributes its inputs to the walk, not only the
1162 /// currently-selected one. A cell outside [`Sheet::is_forced`]'s pure-output
1163 /// classification contributes its own stored aspiration. This includes a
1164 /// self-referencing input even when a mandatory writer prevents the cell from
1165 /// remaining unclaimed: its seed can still depend on that aspiration.
1166 ///
1167 /// Every visited cell is also checked against every conditional in the sheet: if any of
1168 /// that conditional's branches (or its default) has a method whose outputs include the
1169 /// visited cell, the conditional's match cell is added as a contributor too, and is
1170 /// itself traced recursively. This covers both an active producer that happens to
1171 /// belong to a conditional branch, and a cell with *no* active producer specifically
1172 /// because the branch that would define it isn't the one currently selected (that
1173 /// absence is itself a fact controlled by the match cell, not an indication that the
1174 /// match cell is irrelevant).
1175 ///
1176 /// A cell that can remain a source also contributes its dynamic filter arguments:
1177 /// the filter determines its effective value. Filters on outputs claimed in every
1178 /// acyclic assignment, including self-referencing outputs, only diagnose values
1179 /// and do not add value dependencies.
1180 ///
1181 /// Eligibility searches use the candidate's complete input/output relationship
1182 /// component within the last successful active set. That completed assignment
1183 /// witnesses feasibility of the independent components. Registered methods are
1184 /// never replaced or removed, so writes, appended structure and failed
1185 /// propagation preserve this witness; newly registered relationships stay outside
1186 /// these assignment searches until successful propagation publishes another
1187 /// active set. Filter arguments and potential conditional contributors still
1188 /// come from the current registered graph.
1189 ///
1190 /// - Postcondition: returns `{id}` if no propagation has run yet, or if `id` is not
1191 /// forced and no active or conditional relationship or dynamic filter could produce it.
1192 ///
1193 /// - Complexity: O(N · (R·M·K² + B·M·K)) where N is the number of cells reachable from `id`
1194 /// (including conditional match cells), R is the number of relationships adjacent to
1195 /// a visited cell, M is the maximum number of methods per relationship, K is
1196 /// the maximum input/output count per method, and B is the
1197 /// total number of branch/default relationships across all conditionals, plus the
1198 /// number of traversed filter argument edges. Each visited non-forced filtered
1199 /// cell with dynamic arguments that is not currently a source adds at most one
1200 /// exact eligibility search after expected linear traversal of that candidate's
1201 /// component adjacency (including inactive adjacency entries). Each search takes
1202 /// expected O(T + U · (1 + A · K²)) time, where T is relationship storage slots
1203 /// scanned, A is active relationships in that component, and U counts that
1204 /// search's recursive calls and examined method candidates. U is bounded by
1205 /// O(1 + M + … + M^A), including partial checks when M = 1; disconnected
1206 /// components do not multiply its search tree. Debug builds add O(G) validation
1207 /// during each component discovery, where G is prepared active relationships.
1208 /// Traversal storage is O(N + E) for visited cells and queued dependency edges E,
1209 /// plus visited component cells/relationships and O(1 + A · (1 + K)) auxiliary
1210 /// storage for one eligibility search. Repeated queries do not cache searches.
1211 ///
1212 /// # Examples
1213 /// See the [graph example](crate::sheet).
1214 pub fn contributing_cells(&self, id: CellId) -> HashSet<CellId> {
1215 if self.last_plan.is_none() {
1216 return HashSet::from([id]);
1217 }
1218 let mut result = HashSet::new();
1219 let mut visited: HashSet<CellId> = HashSet::new();
1220 let mut stack = vec![id];
1221 while let Some(current) = stack.pop() {
1222 if !visited.insert(current) {
1223 continue;
1224 }
1225
1226 for match_cell in self.conditionals_potentially_producing(current) {
1227 stack.push(match_cell);
1228 }
1229
1230 if !self.is_forced(current) {
1231 result.insert(current);
1232 if let Some(prepared) = &self.last_plan
1233 && let Some(args) = self.filter_args(current)
1234 && !args.is_empty()
1235 && (self.is_source(current) || {
1236 let component = crate::planner::active_relationship_component(
1237 &self.cells,
1238 &self.relationships,
1239 &prepared.active,
1240 current,
1241 );
1242 crate::planner::can_be_source(&self.relationships, &component, current)
1243 })
1244 {
1245 stack.extend(args.iter().copied());
1246 }
1247 }
1248
1249 let Some(cell) = self.cells.get(current) else {
1250 continue;
1251 };
1252 for &rel_id in &cell.adj {
1253 if !self.is_relationship_active(rel_id) {
1254 continue;
1255 }
1256 for method in &self.relationships[rel_id].methods {
1257 if !method.outputs.contains(¤t) {
1258 continue;
1259 }
1260 for &input in &method.inputs {
1261 if method.outputs.contains(&input) {
1262 result.insert(input);
1263 } else {
1264 stack.push(input);
1265 }
1266 }
1267 }
1268 }
1269 }
1270 result
1271 }
1272
1273 /// Returns `true` if `rel_id` was part of the active relationship set for the last
1274 /// successful `propagate()` call — an unconditional relationship is always active; a conditional
1275 /// branch/default relationship is active only while its conditional currently selects
1276 /// it.
1277 ///
1278 /// Returns `false` if no propagation has run yet.
1279 fn is_relationship_active(&self, rel_id: RelationshipId) -> bool {
1280 self.last_plan
1281 .as_ref()
1282 .is_some_and(|plan| plan.active.contains(&rel_id))
1283 }
1284
1285 /// Returns the match cells of every conditional with at least one branch (or default)
1286 /// relationship that touches `cell` (as an input or output of any of its methods) —
1287 /// every conditional whose branch choice currently determines, or could determine,
1288 /// `cell`'s value or whether it has an active producer at all.
1289 ///
1290 /// - Complexity: O(B) where B is the total number of branch/default relationships
1291 /// across all conditionals.
1292 fn conditionals_potentially_producing(&self, cell: CellId) -> Vec<CellId> {
1293 self.conditionals
1294 .values()
1295 .filter(|cond| {
1296 cond.branches
1297 .iter()
1298 .flat_map(|branch| branch.relationships.iter())
1299 .chain(cond.default.iter())
1300 .any(|&rel_id| self.relationships[rel_id].adj.contains(&cell))
1301 })
1302 .flat_map(|cond| cond.match_cells().iter().copied())
1303 .collect()
1304 }
1305
1306 /// Returns the union of [`Sheet::contributing_cells`] over requirement `id`'s own
1307 /// declared inputs.
1308 ///
1309 /// Returns an empty set if `id` is not a live requirement in this sheet.
1310 ///
1311 /// - Complexity: The sum of [`Self::contributing_cells`] costs over the requirement's
1312 /// inputs, with O(N) space for their union of N root cells.
1313 ///
1314 /// # Examples
1315 /// See the [graph example](crate::sheet).
1316 pub fn requirement_contributing_cells(&self, id: RequirementId) -> HashSet<CellId> {
1317 let Some(requirement) = self.requirements.get(id) else {
1318 return HashSet::new();
1319 };
1320 requirement
1321 .inputs
1322 .iter()
1323 .flat_map(|&input| self.contributing_cells(input))
1324 .collect()
1325 }
1326
1327 /// Returns the union of `contributing_cells` over every cell with at least one
1328 /// requirement — the set of cells currently determining at least one
1329 /// requirement-checked value, as of the last `propagate()` call.
1330 ///
1331 /// - Postcondition: empty if no cell in the sheet has any requirements.
1332 /// - Complexity: O(sum of `contributing_cells` cost over every cell with
1333 /// requirements).
1334 ///
1335 /// # Examples
1336 /// See the [graph example](crate::sheet).
1337 pub fn requirement_relevant_cells(&self) -> HashSet<CellId> {
1338 self.cells
1339 .iter()
1340 .filter(|(_, c)| !c.requirements.is_empty())
1341 .flat_map(|(id, _)| self.contributing_cells(id))
1342 .collect()
1343 }
1344
1345 /// Returns the union of `requirement_contributing_cells` over every requirement
1346 /// that evaluated `false` as of the last `propagate()` call, across every cell
1347 /// in the sheet.
1348 ///
1349 /// - Postcondition: empty if no requirement anywhere in the sheet currently
1350 /// fails.
1351 /// - Complexity: O(sum of `requirement_contributing_cells` cost over every
1352 /// violated requirement).
1353 ///
1354 /// # Examples
1355 /// See the [graph example](crate::sheet).
1356 pub fn requirement_violation_cells(&self) -> HashSet<CellId> {
1357 self.cells
1358 .keys()
1359 .flat_map(|id| self.violated_requirements(id))
1360 .flat_map(|rid| self.requirement_contributing_cells(rid))
1361 .collect()
1362 }
1363
1364 /// Writes a value to a cell, incrementing the cell's write-recency strength.
1365 ///
1366 /// Each successful `write` increments a global monotonic counter and assigns
1367 /// the new value to `cell.strength`, so the most-recently-written cell always
1368 /// has the highest strength.
1369 ///
1370 /// - Postcondition: any pending derived override is cleared, so the written value is immediately visible via `read()`.
1371 /// - Complexity: Expected O(1) validation and phase-membership work, plus O(A)
1372 /// when a claimed-cell write releases the final owners of prepared artifacts
1373 /// of total size A, excluding destruction of replaced values.
1374 ///
1375 /// # Errors
1376 ///
1377 /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1378 /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1379 /// - `Error::InvalidCellKind` — `id` is `Out`-kind.
1380 ///
1381 /// # Examples
1382 /// See the [graph example](crate::sheet).
1383 pub fn write<T: Any + 'static>(&mut self, id: CellId, value: T) -> Result<(), Error> {
1384 if self.cells.get(id).is_some_and(|c| c.kind == CellKind::Out) {
1385 return Err(Error::InvalidCellKind { sites: vec![] });
1386 }
1387 let cell_type = self.cells.get(id).ok_or(Error::InvalidId)?.type_id;
1388 if cell_type != TypeId::of::<T>() {
1389 return Err(Error::TypeMismatch {
1390 expected: cell_type,
1391 found: TypeId::of::<T>(),
1392 sites: vec![],
1393 });
1394 }
1395
1396 self.invalidate_after_write(id);
1397 self.last_filter_violations.remove(&id);
1398 self.next_strength += 1;
1399 let cell = &mut self.cells[id];
1400 cell.strength = self.next_strength | (1u64 << 63);
1401 if cell.remembered_strength.is_some() {
1402 cell.remembered_strength = Some(cell.strength);
1403 }
1404 cell.source = Box::new(value);
1405 cell.derived = None;
1406 Ok(())
1407 }
1408
1409 /// Returns a shared reference to the cell's effective current value: its derived
1410 /// override if one exists, otherwise its source (last written) value.
1411 ///
1412 /// # Errors
1413 ///
1414 /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1415 /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1416 ///
1417 /// # Examples
1418 /// See the [graph example](crate::sheet).
1419 pub fn read<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error> {
1420 let cell = self.cells.get(id).ok_or(Error::InvalidId)?;
1421 if cell.type_id != TypeId::of::<T>() {
1422 return Err(Error::TypeMismatch {
1423 expected: cell.type_id,
1424 found: TypeId::of::<T>(),
1425 sites: vec![],
1426 });
1427 }
1428 Ok(cell
1429 .effective()
1430 .downcast_ref::<T>()
1431 .expect("type checked above"))
1432 }
1433
1434 /// Returns the raw `source` slot: the last value written via `write()`/`add_cell`,
1435 /// ignoring any `derived` override produced by a self-referencing method or a
1436 /// conditionally forced relationship. For an ordinary (unshadowed) derived cell,
1437 /// `propagate()` writes straight into this same slot, so `source()` agrees with
1438 /// `read()`; the two diverge only for cells currently shadowed.
1439 ///
1440 /// # Errors
1441 ///
1442 /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1443 /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1444 ///
1445 /// # Examples
1446 ///
1447 /// ```rust
1448 /// use adam_rs::Sheet;
1449 ///
1450 /// let mut sheet = Sheet::new();
1451 /// let a = sheet.add_cell(3_i32);
1452 /// sheet.write(a, 8_i32).unwrap();
1453 /// assert_eq!(*sheet.source::<i32>(a).unwrap(), 8);
1454 /// ```
1455 pub fn source<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error> {
1456 let cell = self.cells.get(id).ok_or(Error::InvalidId)?;
1457 if cell.type_id != TypeId::of::<T>() {
1458 return Err(Error::TypeMismatch {
1459 expected: cell.type_id,
1460 found: TypeId::of::<T>(),
1461 sites: vec![],
1462 });
1463 }
1464 Ok(cell.source.downcast_ref::<T>().expect("type checked above"))
1465 }
1466
1467 /// Iterates over cells updated during the most recent successful `propagate()` call.
1468 ///
1469 /// This includes cells written by selected methods and cells that reverted to their
1470 /// source values because the relationship that had been shadowing them (self-referencing
1471 /// or conditionally forced) is no longer producing them this round (Phase 5), even though
1472 /// no method wrote to them this round. It does not attempt to compare old/new values for
1473 /// equality.
1474 ///
1475 /// - Complexity: O(n) where n is the number of changed cells.
1476 ///
1477 /// # Examples
1478 /// See the [graph example](crate::sheet).
1479 pub fn changed(&self) -> impl Iterator<Item = CellId> + '_ {
1480 self.changed_cells.iter().copied()
1481 }
1482
1483 /// Clears the changed-cell set and resets each cell's `changed` flag.
1484 ///
1485 /// Call after processing the results of `propagate()`.
1486 ///
1487 /// - Complexity: O(n) where n is the number of changed cells.
1488 ///
1489 /// # Examples
1490 /// See the [graph example](crate::sheet).
1491 pub fn clear_changed(&mut self) {
1492 for id in std::mem::take(&mut self.changed_cells) {
1493 if let Some(cell) = self.cells.get_mut(id) {
1494 cell.changed = false;
1495 }
1496 }
1497 }
1498
1499 /// Validates static guard independence for the current sheet structure.
1500 ///
1501 /// A filter's argument cells and a conditional's match cells must not depend on any
1502 /// cell governed by that filter or conditional in the static dependency graph. A
1503 /// successful call records the current structure as validated.
1504 ///
1505 /// # Errors
1506 ///
1507 /// - `Error::DependencyCycle` — a guard edge lies on a cycle in the static
1508 /// dependency graph.
1509 ///
1510 /// # Examples
1511 ///
1512 /// ```rust
1513 /// use adam_rs::{Filter, Method, Sheet};
1514 ///
1515 /// let mut sheet = Sheet::new();
1516 /// let limit = sheet.add_cell(10_i32);
1517 /// let value = sheet.add_cell(5_i32);
1518 /// let copy = sheet.add_cell(0_i32);
1519 /// sheet
1520 /// .add_relationship(vec![Method::from_fn_1_1(value, copy, |x: &i32| Ok(*x))])
1521 /// .unwrap();
1522 /// sheet
1523 /// .add_filter(value, Filter::from_fn_1(limit, |v: &i32, hi: &i32| Ok((*v).min(*hi))))
1524 /// .unwrap();
1525 /// sheet.validate().unwrap();
1526 /// ```
1527 ///
1528 /// - Complexity: O(1) when no structural mutation has happened since the last
1529 /// successful validation; otherwise O(V + E), where V is the number of cells and
1530 /// E is the number of static method and guard edges.
1531 pub fn validate(&mut self) -> Result<(), Error> {
1532 if self.guard_independent {
1533 return Ok(());
1534 }
1535 if let Some(path) = self.guard_violation() {
1536 return Err(Error::DependencyCycle {
1537 sites: path.into_sites(),
1538 });
1539 }
1540 self.guard_independent = true;
1541 Ok(())
1542 }
1543
1544 /// Iterates all live cell IDs in the sheet.
1545 ///
1546 /// - Complexity: O(n) where n is the number of cells.
1547 ///
1548 /// # Examples
1549 /// See the [graph example](crate::sheet).
1550 pub fn cells(&self) -> impl Iterator<Item = CellId> + '_ {
1551 self.cells.keys()
1552 }
1553
1554 /// Iterates all live relationship IDs in the sheet.
1555 ///
1556 /// - Complexity: O(n) where n is the number of relationships.
1557 ///
1558 /// # Examples
1559 /// See the [graph example](crate::sheet).
1560 pub fn relationships(&self) -> impl Iterator<Item = RelationshipId> + '_ {
1561 self.relationships.keys()
1562 }
1563
1564 /// Returns the relationships adjacent to `id`.
1565 ///
1566 /// Returns `None` if `id` is not a live cell in this sheet.
1567 ///
1568 /// - Complexity: O(1).
1569 ///
1570 /// # Examples
1571 /// See the [graph example](crate::sheet).
1572 pub fn cell_adj(&self, id: CellId) -> Option<&[RelationshipId]> {
1573 self.cells.get(id).map(|c| c.adj.as_slice())
1574 }
1575
1576 /// Returns the cells adjacent to `id` (union across all methods).
1577 ///
1578 /// Returns `None` if `id` is not a live relationship in this sheet.
1579 ///
1580 /// - Complexity: O(1).
1581 ///
1582 /// # Examples
1583 /// See the [graph example](crate::sheet).
1584 pub fn relationship_adj(&self, id: RelationshipId) -> Option<&[CellId]> {
1585 self.relationships.get(id).map(|r| r.adj.as_slice())
1586 }
1587
1588 /// Evaluates conditional `cond`'s current live match value.
1589 ///
1590 /// # Errors
1591 ///
1592 /// - `Error::MethodFailed` — the match subject is a [`MatchExpr`] whose function
1593 /// returned an error; its site identifies the executing conditional.
1594 ///
1595 /// - Complexity: O(I) time and space for expression inputs I, plus callback cost.
1596 fn evaluate_match_source(
1597 &self,
1598 id: ConditionalId,
1599 cond: &ConditionalData,
1600 ) -> Result<MatchValue<'_>, Error> {
1601 match &cond.source {
1602 MatchSource::Cell(id) => Ok(MatchValue::Ref(self.cells[*id].effective())),
1603 MatchSource::Expr(expr) => {
1604 let args: Vec<&dyn Any> = expr
1605 .inputs
1606 .iter()
1607 .map(|&id| self.cells[id].effective())
1608 .collect();
1609 let value = (expr.function)(&args).map_err(|error| Error::MethodFailed {
1610 error,
1611 sites: vec![ErrorSite::Conditional(id)],
1612 })?;
1613 Ok(MatchValue::Owned(value))
1614 }
1615 }
1616 }
1617
1618 /// Evaluates conditional `cond` against staged propagation state.
1619 ///
1620 /// Uses `stage`'s writes first, then falls back to each cell's `source` value,
1621 /// matching the state visible after Phase 0 excludes old derived overrides and any
1622 /// pre-plan steps already staged.
1623 ///
1624 /// # Errors
1625 ///
1626 /// - `Error::MethodFailed` — the match subject is a [`MatchExpr`] whose function
1627 /// returned an error; its site identifies the executing conditional.
1628 ///
1629 /// - Complexity: O(I) time and space for expression inputs I, plus callback cost.
1630 fn evaluate_match_source_staged<'a>(
1631 &'a self,
1632 id: ConditionalId,
1633 cond: &ConditionalData,
1634 stage: &'a PropagationStage,
1635 ) -> Result<MatchValue<'a>, Error> {
1636 match &cond.source {
1637 MatchSource::Cell(id) => Ok(MatchValue::Ref(stage.effective(&self.cells, *id))),
1638 MatchSource::Expr(expr) => {
1639 let args: Vec<&dyn Any> = expr
1640 .inputs
1641 .iter()
1642 .map(|&id| stage.effective(&self.cells, id))
1643 .collect();
1644 let value = (expr.function)(&args).map_err(|error| Error::MethodFailed {
1645 error,
1646 sites: vec![ErrorSite::Conditional(id)],
1647 })?;
1648 Ok(MatchValue::Owned(value))
1649 }
1650 }
1651 }
1652
1653 /// Returns the equality function used to compare `cond`'s match value against branch
1654 /// keys: the match cell's own `eq_fn` for a plain match subject, or the expression's
1655 /// captured `eq_fn` for a computed one.
1656 fn match_eq_fn(&self, cond: &ConditionalData) -> fn(&dyn Any, &dyn Any) -> bool {
1657 match &cond.source {
1658 MatchSource::Cell(id) => self.cells[*id].eq_fn,
1659 MatchSource::Expr(expr) => expr.eq_fn,
1660 }
1661 }
1662
1663 /// Builds the active relationship set against staged propagation state.
1664 ///
1665 /// Every conditional reads `stage`'s writes first and otherwise falls back to cell
1666 /// `source` values rather than any live
1667 /// `derived` override already present on the sheet.
1668 ///
1669 /// # Errors
1670 ///
1671 /// - `Error::MethodFailed` — an expression-sourced conditional's function returned an
1672 /// error.
1673 fn build_active_set_staged(
1674 &self,
1675 stage: &PropagationStage,
1676 ) -> Result<HashSet<RelationshipId>, Error> {
1677 let mut active: HashSet<RelationshipId> = self
1678 .relationships
1679 .keys()
1680 .filter(|id| !self.conditional_relationships.contains(id))
1681 .collect();
1682
1683 for (id, cond) in &self.conditionals {
1684 let value = self.evaluate_match_source_staged(id, cond, stage)?;
1685 let value_ref = value.as_dyn();
1686 let eq_fn = self.match_eq_fn(cond);
1687
1688 let mut matched = false;
1689 for branch in &cond.branches {
1690 if branch.keys.iter().any(|key| eq_fn(value_ref, key.as_ref())) {
1691 for &rel_id in &branch.relationships {
1692 active.insert(rel_id);
1693 }
1694 matched = true;
1695 break;
1696 }
1697 }
1698 if !matched {
1699 for &rel_id in &cond.default {
1700 active.insert(rel_id);
1701 }
1702 }
1703 }
1704
1705 Ok(active)
1706 }
1707
1708 /// Records the actual dependency producers and write classifications for `plan`.
1709 ///
1710 /// - Complexity: expected O(V + E), where V is the number of selected steps and E is
1711 /// the number of method inputs, outputs, and filter arguments.
1712 fn plan_provenance(&self, plan: &Plan) -> PlanProvenance {
1713 let mut provenance = PlanProvenance::default();
1714 let mut effective_producers = HashMap::new();
1715 let mut source_producers = HashMap::new();
1716
1717 for &step in &plan.execution_order {
1718 let step_provenance = match step {
1719 PlanStep::Method(rel_id, method_index) => {
1720 let method = &self.relationships[rel_id].methods[method_index];
1721 let method_inputs: HashSet<CellId> = method.inputs.iter().copied().collect();
1722 let method_outputs: HashSet<CellId> = method.outputs.iter().copied().collect();
1723 let mut input_producers = Vec::with_capacity(method.inputs.len());
1724 let mut seed_inputs = Vec::new();
1725 for &input in &method.inputs {
1726 if method_outputs.contains(&input) {
1727 seed_inputs.push(input);
1728 input_producers.push(None);
1729 } else {
1730 input_producers.push(effective_producers.get(&input).copied());
1731 }
1732 }
1733 let output_classes: Vec<_> = method
1734 .outputs
1735 .iter()
1736 .map(|&output| {
1737 (
1738 output,
1739 if method_inputs.contains(&output)
1740 || plan.forced_outputs.contains(&output)
1741 {
1742 OutputClassification::Derived
1743 } else {
1744 OutputClassification::Source
1745 },
1746 )
1747 })
1748 .collect();
1749 for &(output, classification) in &output_classes {
1750 effective_producers.insert(output, step);
1751 if classification == OutputClassification::Source {
1752 source_producers.insert(output, step);
1753 }
1754 }
1755 StepProvenance {
1756 input_producers,
1757 seed_inputs,
1758 source_producer: None,
1759 output_classes,
1760 }
1761 }
1762 PlanStep::FilterReclamp(cell) => {
1763 let filter = self.cells[cell]
1764 .filter
1765 .as_ref()
1766 .expect("plan() only emits FilterReclamp for a filtered cell");
1767 let input_producers = filter
1768 .args
1769 .iter()
1770 .map(|input| effective_producers.get(input).copied())
1771 .collect();
1772 let step_provenance = StepProvenance {
1773 input_producers,
1774 seed_inputs: Vec::new(),
1775 source_producer: source_producers.get(&cell).copied(),
1776 output_classes: vec![(cell, OutputClassification::Derived)],
1777 };
1778 effective_producers.insert(cell, step);
1779 step_provenance
1780 }
1781 };
1782 provenance.steps.insert(step, step_provenance);
1783 }
1784 provenance.output_producers = effective_producers;
1785 provenance
1786 }
1787
1788 /// Checks that a staged prerequisite step has identical selected producer paths in both plans.
1789 ///
1790 /// - Postcondition: `true` implies the selected method/filter, its input producers,
1791 /// seed inputs, source producer, and output classifications all match.
1792 /// - Complexity: O(I + O) for the step's input and output provenance lengths.
1793 fn prerequisite_step_is_compatible(
1794 &self,
1795 pre_plan: &PlanProvenance,
1796 final_plan: &PlanProvenance,
1797 step: PlanStep,
1798 stage: &PropagationStage,
1799 ) -> bool {
1800 let Some(staged) = stage.step_provenance.get(&step) else {
1801 return false;
1802 };
1803 pre_plan.steps.get(&step) == Some(staged) && final_plan.steps.get(&step) == Some(staged)
1804 }
1805
1806 /// Returns concrete cells and relationships implicated by a staged-step mismatch.
1807 ///
1808 /// - Complexity: O(K) sites for the step's cells and producer edges.
1809 fn conflict_sites_for_step(
1810 &self,
1811 step: PlanStep,
1812 pre_plan: &PlanProvenance,
1813 final_plan: &PlanProvenance,
1814 ) -> Vec<ErrorSite> {
1815 let mut sites = Vec::new();
1816 let mut seen_sites = HashSet::new();
1817 let mut add_site = |site| {
1818 if seen_sites.insert(site) {
1819 sites.push(site);
1820 }
1821 };
1822 let mut cells = Vec::new();
1823
1824 match step {
1825 PlanStep::Method(rel_id, method_index) => {
1826 add_site(ErrorSite::Relationship(rel_id));
1827 add_site(ErrorSite::Method(rel_id, method_index));
1828 let method = &self.relationships[rel_id].methods[method_index];
1829 cells.extend(method.inputs.iter().copied());
1830 cells.extend(method.outputs.iter().copied());
1831 }
1832 PlanStep::FilterReclamp(cell) => {
1833 cells.push(cell);
1834 if let Some(filter) = &self.cells[cell].filter {
1835 cells.extend(filter.args.iter().copied());
1836 }
1837 }
1838 }
1839
1840 for provenance in [pre_plan.steps.get(&step), final_plan.steps.get(&step)]
1841 .into_iter()
1842 .flatten()
1843 {
1844 for producer in provenance
1845 .input_producers
1846 .iter()
1847 .copied()
1848 .flatten()
1849 .chain(provenance.source_producer)
1850 {
1851 Self::add_step_relationship_site(producer, &mut add_site);
1852 }
1853 }
1854 for cell in cells {
1855 add_site(ErrorSite::Cell(cell));
1856 for plan in [pre_plan, final_plan] {
1857 if let Some(&producer) = plan.output_producers.get(&cell) {
1858 Self::add_step_relationship_site(producer, &mut add_site);
1859 }
1860 }
1861 }
1862 sites
1863 }
1864
1865 /// Adds the relationship site associated with a selected method producer.
1866 fn add_step_relationship_site(step: PlanStep, add_site: &mut impl FnMut(ErrorSite)) {
1867 match step {
1868 PlanStep::Method(rel_id, _) => add_site(ErrorSite::Relationship(rel_id)),
1869 PlanStep::FilterReclamp(cell) => add_site(ErrorSite::Cell(cell)),
1870 }
1871 }
1872
1873 /// Executes `execution_order` once into transactional staged state.
1874 ///
1875 /// A method or filter step already evaluated by the conditional pre-plan reuses
1876 /// its staged result when the general plan contains the same compatible step.
1877 /// This is an internal optimization, not a callback invocation guarantee.
1878 /// Filter failures remain non-fatal and leave the staged cell untouched.
1879 ///
1880 /// - Precondition: each step belongs to this graph and has matching plan provenance.
1881 /// - Complexity: O(s × k²) structural work for s steps with at most k input/output
1882 /// cells, plus callback costs and storage for staged output values.
1883 ///
1884 /// # Errors
1885 ///
1886 /// - `Error::MethodFailed` — a `PlanStep::Method` step's function returned an error,
1887 /// or the method produced a different number of outputs than declared.
1888 /// - `Error::TypeMismatch` — a `PlanStep::Method` step's output runtime type does
1889 /// not match the cell's registered type.
1890 fn execute_plan_staged(
1891 &self,
1892 execution_order: &[PlanStep],
1893 seeds: &Seeds,
1894 forced_outputs: &HashSet<CellId>,
1895 plan_provenance: &PlanProvenance,
1896 stage: &mut PropagationStage,
1897 filter_violations: &mut Vec<(CellId, FilterViolation)>,
1898 ) -> Result<(), Error> {
1899 for step in execution_order {
1900 match *step {
1901 PlanStep::Method(rel_id, method_idx) => {
1902 if stage.executed_methods.contains(&(rel_id, method_idx)) {
1903 let method = &self.relationships[rel_id].methods[method_idx];
1904 for &output in &method.outputs {
1905 stage.reclassify(
1906 output,
1907 method.inputs.contains(&output) || forced_outputs.contains(&output),
1908 );
1909 }
1910 continue;
1911 }
1912 let method = &self.relationships[rel_id].methods[method_idx];
1913 let outputs = {
1914 let inputs: Vec<&dyn Any> = method
1915 .inputs
1916 .iter()
1917 .map(|&id| {
1918 if method.outputs.contains(&id) {
1919 seeds
1920 .get(&id)
1921 .map(|value| value.as_ref())
1922 .unwrap_or_else(|| stage.source(&self.cells, id))
1923 } else {
1924 stage.effective(&self.cells, id)
1925 }
1926 })
1927 .collect();
1928 (method.function)(&inputs).map_err(|error| Error::MethodFailed {
1929 error,
1930 sites: vec![ErrorSite::Method(rel_id, method_idx)],
1931 })?
1932 };
1933
1934 if outputs.len() != method.outputs.len() {
1935 return Err(Error::MethodFailed {
1936 error: anyhow::anyhow!(
1937 "method produced {} outputs but relationship expects {}",
1938 outputs.len(),
1939 method.outputs.len()
1940 ),
1941 sites: vec![ErrorSite::Method(rel_id, method_idx)],
1942 });
1943 }
1944
1945 for (&cell_id, new_value) in method.outputs.iter().zip(outputs) {
1946 let found = new_value.as_ref().type_id();
1947 let cell = &self.cells[cell_id];
1948 if found != cell.type_id {
1949 return Err(Error::TypeMismatch {
1950 expected: cell.type_id,
1951 found,
1952 sites: vec![ErrorSite::Method(rel_id, method_idx)],
1953 });
1954 }
1955 stage.write(
1956 cell_id,
1957 new_value,
1958 method.inputs.contains(&cell_id) || forced_outputs.contains(&cell_id),
1959 );
1960 }
1961 stage.executed_methods.insert((rel_id, method_idx));
1962 stage
1963 .step_provenance
1964 .insert(*step, plan_provenance.steps[step].clone());
1965 }
1966 PlanStep::FilterReclamp(id) => {
1967 if stage.executed_filters.contains(&id) {
1968 continue;
1969 }
1970 let filter = self.cells[id]
1971 .filter
1972 .as_ref()
1973 .expect("plan() only emits FilterReclamp for a filtered cell");
1974 let args: Vec<&dyn Any> = filter
1975 .args
1976 .iter()
1977 .map(|&a| stage.effective(&self.cells, a))
1978 .collect();
1979 let current = stage.source(&self.cells, id);
1980 match (filter.function)(current, &args) {
1981 Ok(v) if v.as_ref().type_id() == self.cells[id].type_id => {
1982 stage.write(id, v, true);
1983 }
1984 Ok(_) => filter_violations.push((
1985 id,
1986 FilterViolation::Failed(anyhow::anyhow!(
1987 "filter returned a value of a different type than the cell"
1988 )),
1989 )),
1990 Err(error) => {
1991 filter_violations.push((id, FilterViolation::Failed(error)));
1992 }
1993 }
1994 stage.executed_filters.insert(id);
1995 stage
1996 .step_provenance
1997 .insert(*step, plan_provenance.steps[step].clone());
1998 }
1999 }
2000 }
2001 Ok(())
2002 }
2003
2004 /// Assigns derived-cell strengths after executing a selected plan.
2005 ///
2006 /// Walks `execution_order` and assigns a decrementing counter (starting at
2007 /// `0x7FFF_FFFF_FFFF_FFFF`) to each output cell of each selected method, in
2008 /// execution order. Cells evaluated first receive the highest derived strength.
2009 /// Source cells (not the output of any selected method) are not modified.
2010 ///
2011 /// A cell claimed *self-referencingly* (its claiming method reads the cell as one of
2012 /// its own inputs) keeps a live explicit strength rather than being demoted: such a
2013 /// method computes the cell from its own `source` aspiration (see
2014 /// [`crate::planner::evaluate_seeds`]), so an explicit `write()` to that cell is still
2015 /// the authority behind the value and must keep outranking never-written cells in
2016 /// later rounds. Demoting it would discard the edit the next time the cell is
2017 /// re-seeded.
2018 ///
2019 /// - Complexity: O(R·K²) where R is the number of entries and K is the maximum
2020 /// inputs or outputs per method.
2021 /// - Postcondition: returns whether any cell's strength actually changed.
2022 fn post_process_strengths(&mut self, execution_order: &[PlanStep]) -> bool {
2023 let mut changed = false;
2024 let mut derived_strength = u64::MAX >> 1; // 0x7FFF_FFFF_FFFF_FFFF
2025 let mut seen: std::collections::HashSet<CellId> = std::collections::HashSet::new();
2026 for step in execution_order {
2027 let PlanStep::Method(rel_id, method_idx) = step else {
2028 continue;
2029 };
2030 if let Some(rel) = self.relationships.get(*rel_id)
2031 && let Some(method) = rel.methods.get(*method_idx)
2032 {
2033 for &output in &method.outputs {
2034 let self_referencing = method.inputs.contains(&output);
2035 if seen.insert(output)
2036 && let Some(cell) = self.cells.get_mut(output)
2037 {
2038 if self_referencing && cell.has_explicit_strength() {
2039 continue;
2040 }
2041 changed |= cell.strength != derived_strength;
2042 cell.strength = derived_strength;
2043 derived_strength = derived_strength.saturating_sub(1);
2044 }
2045 }
2046 }
2047 }
2048 changed
2049 }
2050
2051 /// Publishes a fully validated propagation stage and returns previously derived cells.
2052 ///
2053 /// Clears every old derived override, applies staged source and derived writes, and
2054 /// records each staged output in [`Sheet::changed`]. No callback is evaluated here.
2055 ///
2056 /// - Complexity: O(cells).
2057 fn commit_stage(&mut self, stage: PropagationStage) -> Vec<CellId> {
2058 let previously_derived: Vec<CellId> = self
2059 .cells
2060 .iter()
2061 .filter(|(_, cell)| cell.derived.is_some())
2062 .map(|(id, _)| id)
2063 .collect();
2064 for (_, cell) in self.cells.iter_mut() {
2065 cell.derived = None;
2066 }
2067 for (id, value) in stage.sources {
2068 self.cells[id].source = value;
2069 }
2070 for (id, value) in stage.derived {
2071 self.cells[id].derived = Some(value);
2072 }
2073 for id in stage.changed {
2074 let cell = &mut self.cells[id];
2075 if !cell.changed {
2076 cell.changed = true;
2077 self.changed_cells.push(id);
2078 }
2079 }
2080 previously_derived
2081 }
2082
2083 /// Updates the sheet using eligible prepared plans or newly selected methods.
2084 ///
2085 /// Validates static guard independence before mutating state, then clears the
2086 /// changed-cell set from the previous `propagate()` call before staged planning.
2087 /// After propagation, call [`Sheet::changed`] to inspect which cells were updated,
2088 /// and [`Sheet::clear_changed`] when done.
2089 ///
2090 /// Method, filter, conditional-expression, and requirement callbacks must be
2091 /// purely functional. There is no guarantee that any callback executes, or when
2092 /// or how often it executes; results may be reused or recomputed while preserving
2093 /// cell values and diagnostics. The phases below describe the current algorithm,
2094 /// not a callback invocation contract.
2095 ///
2096 /// Retains at most one prepared unconditional plan and one prepared main plan,
2097 /// each with its own released sources and active relationships. Successful writes
2098 /// to released sources preserve that phase's eligibility; writes to claimed cells
2099 /// (including self-referencing claimants) discard it. Filter reclamps do not claim
2100 /// released sources. Actual structural mutations discard both entries.
2101 /// Prepared plans retain structural seed recipes, not values from earlier calls.
2102 ///
2103 /// **Phase 0 — Staging:** propagation starts from each cell's source value, excluding
2104 /// every derived override from the previous round, but does not mutate live cells.
2105 ///
2106 /// **Phase 1 — Pre-plan:** when conditionals exist, an eligible prepared unconditional
2107 /// plan is obtained or a new one is prepared. Its selected guard-prerequisite cone
2108 /// executes so derived guard values are current before branch evaluation.
2109 /// Unrelated filters remain deferred until the final active plan, after the relationships
2110 /// that produce their arguments.
2111 ///
2112 /// **Phase 2 — Conditional evaluation:** each conditional's match cell value is
2113 /// read and compared against branch keys; the active relationship set is built.
2114 ///
2115 /// **Phase 3 — General plan:** an eligible prepared main plan is obtained for the active
2116 /// set, or the Adam algorithm selects and prepares a new one. Seed-cycle
2117 /// validation and method execution consume staged Phase 1 values. A callback already
2118 /// evaluated in Phase 1 is reused only when the final plan preserves its selected
2119 /// method, input producers, and output classification. A mismatched plan returns a
2120 /// conservative conflict rather than committing stale values. This
2121 /// boundary may reject a sheet even when another assignment could have avoided the mismatch;
2122 /// propagation reports the implicated sites instead of attempting that alternate assignment.
2123 /// A Phase 1 seed callback is likewise reused only when every input reads the same staged
2124 /// source version, recursive seed result, or accumulated value; otherwise propagation
2125 /// returns a conflict instead of reusing a stale seed.
2126 ///
2127 /// **Phase 4 — Commit and strength post-processing:** after planning, seed
2128 /// validation, and staged method execution all succeed, staged writes are published
2129 /// atomically. Derived cells then receive low-order strengths in evaluation order,
2130 /// enforcing the stability invariant. A cell claimed self-referencingly keeps any
2131 /// live explicit strength instead, since its own written value is still the authority
2132 /// behind the result.
2133 ///
2134 /// **Phase 5 — Reversion change-tracking:** a cell whose derived override existed
2135 /// before this round but wasn't reclaimed by any method this round has effectively
2136 /// reverted to its source value (e.g. its forcing conditional went inactive); it is
2137 /// marked changed even though no method wrote to it this round.
2138 ///
2139 /// **Phase 6 — Requirement evaluation:** every registered requirement is evaluated
2140 /// against current cell values, rebuilding `last_requirement_violations` from
2141 /// scratch, so [`Sheet::cell_requirements_valid`] and [`Sheet::violated_requirements`]
2142 /// reflect this round.
2143 ///
2144 /// Filter failures remain diagnostics rather than propagation errors, and diagnostic
2145 /// results use current values.
2146 ///
2147 /// - Complexity: on a cache miss, prerequisite-cone indexing, traversal, and producer
2148 /// preparation take O(V + E) per selected plan, distinct from assignment matching.
2149 /// Cache hits omit this preparation, assignment matching, global strength sorting,
2150 /// and complete seed-elimination replay. Active-set comparison takes expected O(R)
2151 /// for R active relationships; a pending strength certificate takes O(C) for C cells.
2152 /// Current strength gates and local sibling sorts still run (O(S log S) comparisons
2153 /// for S siblings, with structural signature comparison costs). Fresh staged
2154 /// execution, seed evaluation, commit, and diagnostics retain their costs.
2155 /// Discarding final owners of prepared artifacts of total size A can take O(A).
2156 ///
2157 /// # Errors
2158 ///
2159 /// - `Error::DependencyCycle` — a filter or conditional guard edge lies on a cycle in
2160 /// the static dependency graph.
2161 /// - `Error::Conflict` — no valid method assignment exists, or a pre-executed guard
2162 /// prerequisite is incompatible with the final selected plan.
2163 /// - `Error::SeedCycle` — sibling seed dependencies form a non-self cycle.
2164 /// - `Error::MethodFailed` — a method's function returned an error, a method
2165 /// produced the wrong number of outputs, or a requirement's function returned
2166 /// an error. A requirement error includes its governed cell as an error site.
2167 /// - `Error::TypeMismatch` — a method output's runtime type does not match the
2168 /// cell's registered type.
2169 pub fn propagate(&mut self) -> Result<(), Error> {
2170 let mut pre = self.pre_plan_cache.take();
2171 let mut main = self.main_plan_cache.take();
2172 let result = self.propagate_with_caches(&mut pre, &mut main);
2173 if result.is_ok() {
2174 self.pre_plan_cache = pre;
2175 self.main_plan_cache = main;
2176 }
2177 result
2178 }
2179
2180 /// Returns the index of the method selected for `rel` in the last successful propagation.
2181 ///
2182 /// Returns `None` if no propagation has run yet, `rel` is not in the cached plan,
2183 /// or `rel` was added after the last `propagate()` call.
2184 ///
2185 /// - Complexity: O(S) worst-case time for S completed execution steps; O(1) space.
2186 ///
2187 /// # Examples
2188 /// See the [graph example](crate::sheet).
2189 pub fn selected_method(&self, rel: RelationshipId) -> Option<usize> {
2190 self.last_plan
2191 .as_ref()?
2192 .plan
2193 .execution_order
2194 .iter()
2195 .find_map(|step| match step {
2196 PlanStep::Method(r, idx) if *r == rel => Some(*idx),
2197 _ => None,
2198 })
2199 }
2200
2201 /// Returns the input cells of method `idx` in relationship `rel`.
2202 ///
2203 /// Returns `None` if `rel` is not a live relationship or `idx` is out of bounds.
2204 ///
2205 /// # Examples
2206 /// See the [graph example](crate::sheet).
2207 pub fn method_inputs(&self, rel: RelationshipId, idx: usize) -> Option<&[CellId]> {
2208 self.relationships
2209 .get(rel)?
2210 .methods
2211 .get(idx)
2212 .map(|m| m.inputs.as_slice())
2213 }
2214
2215 /// Returns the output cells of method `idx` in relationship `rel`.
2216 ///
2217 /// Returns `None` if `rel` is not a live relationship or `idx` is out of bounds.
2218 ///
2219 /// # Examples
2220 /// See the [graph example](crate::sheet).
2221 pub fn method_outputs(&self, rel: RelationshipId, idx: usize) -> Option<&[CellId]> {
2222 self.relationships
2223 .get(rel)?
2224 .methods
2225 .get(idx)
2226 .map(|m| m.outputs.as_slice())
2227 }
2228
2229 /// Returns `true` if `id` was not written by any selected method in the last successful propagation.
2230 ///
2231 /// Returns `false` if no propagation has succeeded yet. This display query does not
2232 /// determine prepared-plan eligibility.
2233 ///
2234 /// Cells absent from the completed plan, including newly added cells, return `true`.
2235 ///
2236 /// - Complexity: Expected O(1).
2237 ///
2238 /// # Examples
2239 /// See the [graph example](crate::sheet).
2240 pub fn is_source(&self, id: CellId) -> bool {
2241 let Some(plan) = &self.last_plan else {
2242 return false;
2243 };
2244 !plan.certificate.contains_cell(id) || plan.certificate.is_released(id)
2245 }
2246
2247 /// Iterates snapshot sources contributing to `results` in the last successful plan.
2248 ///
2249 /// An empty `results` slice selects the entire sheet: unclaimed cells and outputs
2250 /// read by their own selected method. Otherwise, selects only those sources in
2251 /// the requested cells' selected producer paths, including conforming filter
2252 /// arguments and conditional selectors governing those paths. Unselected methods'
2253 /// inputs and diagnostic-only filter arguments do not contribute.
2254 ///
2255 /// Each source appears once, in unspecified order, even when results repeat or
2256 /// share dependencies. Pure derived outputs and `Out` cells are excluded.
2257 /// Returns an empty iterator before any successful propagation. Like
2258 /// [`Self::is_source`], classification describes the last successful plan;
2259 /// newly added non-`Out` cells count as unclaimed.
2260 ///
2261 /// To capture a settled result, iterate after successful propagation and before
2262 /// further edits, reading effective values with [`Self::read`], not the
2263 /// aspirations returned by [`Self::source`]. Clients own baseline comparison,
2264 /// relevance filtering, stable cell identity, and typed serialization.
2265 ///
2266 /// Replay writes all captured values in any order as one batch before
2267 /// propagation. For a settled solution satisfying its relationships and filters,
2268 /// a compatible graph with matching uncaptured inputs reproduces the requested
2269 /// effective values (or all values when `results` is empty). Relationship methods
2270 /// must preserve an already-satisfied assignment, and self-referencing methods
2271 /// and filters must be idempotent;
2272 /// these caller contracts make the solution a fixed point even if replay
2273 /// selects different methods. No strengths or write history need to be captured.
2274 /// A snapshot does not preserve original aspirations, priority history,
2275 /// diagnostics, or behavior under subsequent edits, including spring-back.
2276 ///
2277 /// - Precondition: every result is a live cell belonging to this sheet.
2278 /// - Complexity: empty results take O(C + S + Σ(I × O)) traversal time and O(1)
2279 /// auxiliary space, for C cells, S selected steps, and each selected method's
2280 /// I inputs and O outputs. Nonempty results add expected O(N + V + E + V × G)
2281 /// time and O(N + V + E) auxiliary space, for N requested results, V reachable
2282 /// value/producer pairs, E their dependency edges, and G the total size of the
2283 /// registered conditional graph. Does not copy cell values.
2284 ///
2285 /// # Examples
2286 ///
2287 /// ```
2288 /// use adam_rs::Sheet;
2289 /// let mut sheet = Sheet::new();
2290 /// let width = sheet.add_source(10_i32);
2291 /// sheet.add_source(false);
2292 /// sheet.propagate()?;
2293 /// assert_eq!(sheet.source_cells(&[]).count(), 2);
2294 /// let capture = sheet.source_cells(&[width])
2295 /// .map(|id| Ok((id, *sheet.read::<i32>(id)?)))
2296 /// .collect::<Result<Vec<_>, adam_rs::Error>>()?;
2297 /// assert_eq!(capture, [(width, 10)]);
2298 /// // Use host-defined names or schema keys, not live CellIds, for persistence.
2299 /// # Ok::<(), adam_rs::Error>(())
2300 /// ```
2301 pub fn source_cells(&self, results: &[CellId]) -> impl Iterator<Item = CellId> + use<'_> {
2302 debug_assert!(results.iter().all(|id| self.cells.contains_key(*id)));
2303 let contributors = (!results.is_empty()).then(|| self.capture_contributors(results));
2304 let unclaimed = self.cells.keys().filter(|&id| self.is_source(id));
2305 let self_referencing = self
2306 .last_plan
2307 .iter()
2308 .flat_map(|prepared| &prepared.plan.execution_order)
2309 .filter_map(|step| match *step {
2310 PlanStep::Method(id, index) => Some(&self.relationships[id].methods[index]),
2311 PlanStep::FilterReclamp(_) => None,
2312 })
2313 .flat_map(|method| {
2314 method
2315 .outputs
2316 .iter()
2317 .copied()
2318 .filter(|id| method.inputs.contains(id))
2319 });
2320 unclaimed.chain(self_referencing).filter(move |&id| {
2321 self.cells[id].kind != CellKind::Out
2322 && contributors
2323 .as_ref()
2324 .is_none_or(|cells| cells.contains(&id))
2325 })
2326 }
2327
2328 /// Returns the source leaves of `results`' selected value and control dependencies.
2329 ///
2330 /// Producer identities distinguish a filter's incoming source from its effective
2331 /// output. Self-referencing inputs terminate at their captured effective value,
2332 /// rather than introducing a cycle or following alternative seed methods.
2333 ///
2334 /// - Precondition: every result is a live cell belonging to this sheet.
2335 /// - Complexity: expected O(N + V + E + V × G) time and O(N + V + E) space,
2336 /// for N results, V reachable value/producer pairs, E dependency edges, and
2337 /// G the total size of the registered conditional graph.
2338 fn capture_contributors(&self, results: &[CellId]) -> HashSet<CellId> {
2339 let Some(prepared) = &self.last_plan else {
2340 return HashSet::new();
2341 };
2342 let provenance = &prepared.provenance;
2343 let current_value = |cell| (cell, provenance.output_producers.get(&cell).copied());
2344 let mut pending: Vec<_> = results.iter().copied().map(current_value).collect();
2345 let mut visited = HashSet::new();
2346 let mut contributors = HashSet::new();
2347
2348 while let Some((cell, producer)) = pending.pop() {
2349 if !visited.insert((cell, producer)) {
2350 continue;
2351 }
2352 pending.extend(
2353 self.conditionals
2354 .values()
2355 .filter(|conditional| {
2356 conditional
2357 .branches
2358 .iter()
2359 .flat_map(|branch| branch.relationships.iter())
2360 .chain(conditional.default.iter())
2361 .any(|&relationship| {
2362 matches!(
2363 producer,
2364 Some(PlanStep::Method(selected, _)) if selected == relationship
2365 ) || (!prepared.active.contains(&relationship)
2366 && self.relationships[relationship]
2367 .methods
2368 .iter()
2369 .any(|method| method.outputs.contains(&cell)))
2370 })
2371 })
2372 .flat_map(|conditional| conditional.match_cells().iter().copied())
2373 .map(current_value),
2374 );
2375 let Some(step) = producer else {
2376 contributors.insert(cell);
2377 continue;
2378 };
2379 let step_provenance = &provenance.steps[&step];
2380 let inputs = match step {
2381 PlanStep::Method(relationship, index) => {
2382 &self.relationships[relationship].methods[index].inputs
2383 }
2384 PlanStep::FilterReclamp(filtered) => {
2385 pending.push((filtered, step_provenance.source_producer));
2386 &self.cells[filtered]
2387 .filter
2388 .as_ref()
2389 .expect("planned filter reclamps have an attached filter")
2390 .args
2391 }
2392 };
2393 pending.extend(
2394 inputs
2395 .iter()
2396 .copied()
2397 .zip(step_provenance.input_producers.iter().copied()),
2398 );
2399 }
2400 contributors
2401 }
2402
2403 /// Returns whether `id` belongs to the last successful plan's forced pure-output set.
2404 ///
2405 /// The planner intersects pure outputs across viable methods and eliminates
2406 /// alternatives that conflict with another relationship's forced outputs until
2407 /// the classification stabilizes. A forced pure output's producer does not read
2408 /// that cell as a self-referencing input.
2409 ///
2410 /// A conditionally forced cell retains its pre-forcing priority and source
2411 /// aspiration for release; see [the forcing outcomes](crate::sheet#forced-self-referencing-and-reversible-outcomes).
2412 ///
2413 /// This does not decide whether some assignment can leave the cell unclaimed.
2414 /// A mandatory self-referencing writer claims its output exclusively but remains
2415 /// outside this classification; its editable source aspiration can affect its seed.
2416 ///
2417 /// Returns `false` if no propagation has run yet.
2418 ///
2419 /// # Examples
2420 /// See the [graph example](crate::sheet).
2421 pub fn is_forced(&self, id: CellId) -> bool {
2422 self.last_plan
2423 .as_ref()
2424 .is_some_and(|prepared| prepared.plan.forced_outputs.contains(&id))
2425 }
2426
2427 /// Iterates cells that are forced (see [`Sheet::is_forced`]) as of the last
2428 /// successful `propagate()` call.
2429 ///
2430 /// - Complexity: O(n) where n is the number of forced cells.
2431 ///
2432 /// # Examples
2433 /// See the [graph example](crate::sheet).
2434 pub fn forced_cells(&self) -> impl Iterator<Item = CellId> + '_ {
2435 self.last_plan
2436 .iter()
2437 .flat_map(|prepared| prepared.plan.forced_outputs.iter().copied())
2438 }
2439
2440 /// Returns `true` if `id` had exactly one viable method as of the last successful
2441 /// `propagate()` call — the planner has no alternative method to choose for this
2442 /// relationship, regardless of cell strength.
2443 ///
2444 /// Returns `false` if no propagation has run yet.
2445 ///
2446 /// # Examples
2447 /// See the [graph example](crate::sheet).
2448 pub fn is_relationship_forced(&self, id: RelationshipId) -> bool {
2449 self.last_plan
2450 .as_ref()
2451 .is_some_and(|prepared| prepared.plan.forced_relationships.contains(&id))
2452 }
2453
2454 /// Iterates relationships that are forced (see [`Sheet::is_relationship_forced`])
2455 /// as of the last successful `propagate()` call.
2456 ///
2457 /// - Complexity: O(n) where n is the number of forced relationships.
2458 ///
2459 /// # Examples
2460 /// See the [graph example](crate::sheet).
2461 pub fn forced_relationships(&self) -> impl Iterator<Item = RelationshipId> + '_ {
2462 self.last_plan
2463 .iter()
2464 .flat_map(|prepared| prepared.plan.forced_relationships.iter().copied())
2465 }
2466
2467 /// Iterates all live conditional IDs in the sheet.
2468 ///
2469 /// - Complexity: O(n) where n is the number of conditionals.
2470 ///
2471 /// # Examples
2472 /// See the [graph example](crate::sheet).
2473 pub fn conditionals(&self) -> impl Iterator<Item = ConditionalId> + '_ {
2474 self.conditionals.keys()
2475 }
2476
2477 /// Iterates all live `Out`-kind cells in the sheet.
2478 ///
2479 /// - Complexity: O(n) where n is the number of cells.
2480 ///
2481 /// # Examples
2482 /// See the [graph example](crate::sheet).
2483 pub fn out_cells(&self) -> impl Iterator<Item = CellId> + '_ {
2484 self.cells
2485 .iter()
2486 .filter(|(_, c)| c.kind == CellKind::Out)
2487 .map(|(id, _)| id)
2488 }
2489
2490 /// Returns the match cells for conditional `id`: a single cell for a plain match
2491 /// subject, or every input of a [`MatchExpr`] match subject.
2492 ///
2493 /// Returns `None` if `id` is not a live conditional in this sheet.
2494 ///
2495 /// # Examples
2496 /// See the [graph example](crate::sheet).
2497 pub fn conditional_match_cells(&self, id: ConditionalId) -> Option<&[CellId]> {
2498 self.conditionals.get(id).map(|c| c.match_cells())
2499 }
2500
2501 /// Returns the number of named branches in conditional `id`.
2502 ///
2503 /// Returns `None` if `id` is not a live conditional in this sheet.
2504 ///
2505 /// # Examples
2506 /// See the [graph example](crate::sheet).
2507 pub fn conditional_branch_count(&self, id: ConditionalId) -> Option<usize> {
2508 self.conditionals.get(id).map(|c| c.branches.len())
2509 }
2510
2511 /// Returns the relationship IDs for branch `branch` of conditional `id`.
2512 ///
2513 /// Returns `None` if `id` is not a live conditional, or `branch` is out of bounds.
2514 ///
2515 /// # Examples
2516 /// See the [graph example](crate::sheet).
2517 pub fn conditional_branch_relationships(
2518 &self,
2519 id: ConditionalId,
2520 branch: usize,
2521 ) -> Option<&[RelationshipId]> {
2522 self.conditionals
2523 .get(id)?
2524 .branches
2525 .get(branch)
2526 .map(|b| b.relationships.as_slice())
2527 }
2528
2529 /// Returns the default relationship IDs for conditional `id`.
2530 ///
2531 /// These relationships are active when no named branch key matches the match cell.
2532 /// Returns `None` if `id` is not a live conditional in this sheet.
2533 ///
2534 /// # Examples
2535 /// See the [graph example](crate::sheet).
2536 pub fn conditional_default_relationships(
2537 &self,
2538 id: ConditionalId,
2539 ) -> Option<&[RelationshipId]> {
2540 self.conditionals.get(id).map(|c| c.default.as_slice())
2541 }
2542
2543 /// Returns the index of the currently matching branch for conditional `id`.
2544 ///
2545 /// Evaluates branch keys against the match subject's current value in definition
2546 /// order; returns the index of the first matching branch. Returns `Ok(None)` if no
2547 /// branch key matches (the default branch is active) or if `id` is not a live
2548 /// conditional.
2549 ///
2550 /// # Errors
2551 ///
2552 /// - `Error::MethodFailed` — `id` is a live, expression-sourced conditional whose
2553 /// function returned an error. The original cause and `ErrorSite::Conditional(id)`
2554 /// identify the failing execution.
2555 ///
2556 /// - Complexity: O(B·K) where B = branches, K = keys per branch.
2557 ///
2558 /// # Examples
2559 /// See the [graph example](crate::sheet).
2560 pub fn conditional_active_branch(&self, id: ConditionalId) -> Result<Option<usize>, Error> {
2561 let Some(cond) = self.conditionals.get(id) else {
2562 return Ok(None);
2563 };
2564 let value = self.evaluate_match_source(id, cond)?;
2565 let value_ref = value.as_dyn();
2566 let eq_fn = self.match_eq_fn(cond);
2567 Ok(cond
2568 .branches
2569 .iter()
2570 .enumerate()
2571 .find(|(_, branch)| branch.keys.iter().any(|key| eq_fn(value_ref, key.as_ref())))
2572 .map(|(i, _)| i))
2573 }
2574}
2575
2576impl Default for Sheet {
2577 /// Returns `Sheet::new()`.
2578 fn default() -> Self {
2579 Self::new()
2580 }
2581}
2582
2583#[cfg(test)]
2584mod tests {
2585 use crate::{
2586 CellKind, ConditionalId, Error, MatchExpr, Method, Requirement, Sheet,
2587 cell::CellId,
2588 error::ErrorSite,
2589 filter::{Filter, FilterKind, FilterViolation},
2590 planner::{PlanStep, Seeds},
2591 relationship::RelationshipId,
2592 };
2593 use std::any::{Any, TypeId};
2594 use std::collections::{HashMap, HashSet};
2595
2596 /// Starts an empty default sheet and reports registered concrete identities.
2597 #[test]
2598 fn default_and_type_id_obey_empty_and_registration_contracts() {
2599 let mut sheet = Sheet::default();
2600 assert_eq!(sheet.cells().count(), 0);
2601 assert_eq!(sheet.relationships().count(), 0);
2602 assert_eq!(sheet.conditionals().count(), 0);
2603 assert_eq!(sheet.changed().count(), 0);
2604 assert_eq!(sheet.cell_type_id(CellId::default()), None);
2605 let cell = sheet.add_cell(String::new());
2606 assert_eq!(sheet.cell_type_id(cell), Some(TypeId::of::<String>()));
2607 }
2608
2609 /// Rebuilds a fresh sheet after a staged type error.
2610 #[test]
2611 fn late_output_type_failure_requires_rebuilding_the_sheet() {
2612 let make_sheet = |source_value| {
2613 let mut sheet = Sheet::new();
2614 let source = sheet.add_cell(source_value);
2615 let a = sheet.add_cell(0_i32);
2616 let b = sheet.add_cell(0_i32);
2617 sheet
2618 .add_relationship(vec![Method::new(
2619 vec![source],
2620 vec![a, b],
2621 vec![TypeId::of::<i32>()],
2622 vec![TypeId::of::<i32>(); 2],
2623 |args| {
2624 let value = *args[0].downcast_ref::<i32>().unwrap();
2625 let last: Box<dyn Any> = if value < 0 {
2626 Box::new(false)
2627 } else {
2628 Box::new(value)
2629 };
2630 Ok(vec![Box::new(value), last])
2631 },
2632 )])
2633 .unwrap();
2634 (sheet, source, a, b)
2635 };
2636
2637 let (mut sheet, source, _, _) = make_sheet(5_i32);
2638 sheet.propagate().unwrap();
2639 sheet.write(source, -9_i32).unwrap();
2640 assert!(matches!(sheet.propagate(), Err(Error::TypeMismatch { .. })));
2641 drop(sheet);
2642
2643 let (mut rebuilt, _, a, b) = make_sheet(12_i32);
2644 rebuilt.propagate().unwrap();
2645 assert_eq!(*rebuilt.read::<i32>(a).unwrap(), 12);
2646 assert_eq!(*rebuilt.read::<i32>(b).unwrap(), 12);
2647 }
2648
2649 #[test]
2650 /// Checks the ordinary kind assigned to a new editable cell.
2651 fn add_cell_has_cell_kind() {
2652 let mut sheet = Sheet::new();
2653 let a = sheet.add_cell(0_i32);
2654 assert_eq!(sheet.cell_kind(a), Some(CellKind::Cell));
2655 }
2656
2657 #[test]
2658 /// Checks the source-only kind assigned to a new source.
2659 fn add_source_has_source_kind() {
2660 let mut sheet = Sheet::new();
2661 let a = sheet.add_source(0_i32);
2662 assert_eq!(sheet.cell_kind(a), Some(CellKind::Source));
2663 }
2664
2665 #[test]
2666 /// Checks absence of kind metadata for an unknown cell.
2667 fn cell_kind_returns_none_for_invalid_id() {
2668 let mut sheet = Sheet::new();
2669 sheet.add_cell(0_i32); // occupies slotmap index 0 in `sheet`
2670 let mut other = Sheet::new();
2671 other.add_cell(0_i32); // index 0 in `other`
2672 let bogus = other.add_cell(0_i32); // index 1 in `other` -- out of range for `sheet`,
2673 // which only ever allocated index 0, so this is
2674 // guaranteed invalid regardless of generation
2675 // (a same-index key from a second fresh SlotMap
2676 // would otherwise collide with `sheet`'s own key)
2677 assert_eq!(sheet.cell_kind(bogus), None);
2678 }
2679
2680 #[test]
2681 /// Checks that a later plain staged write replaces an earlier derived override.
2682 fn execute_plan_staged_clears_a_stale_override_on_a_later_plain_write() {
2683 let mut sheet = Sheet::new();
2684 let x = sheet.add_cell(8_i32);
2685 let y = sheet.add_cell(2_i32);
2686 let z = sheet.add_cell(10_i32);
2687 let self_ref = sheet
2688 .add_relationship(vec![
2689 Method::from_fn_2_1([x, y], x, |a: &i32, b: &i32| Ok((*a).min(*b))),
2690 Method::from_fn_2_1([x, y], y, |a: &i32, b: &i32| Ok((*a).max(*b))),
2691 ])
2692 .unwrap();
2693 let plain = sheet
2694 .add_relationship(vec![Method::from_fn_1_1(z, x, |v: &i32| {
2695 v.checked_add(1).ok_or_else(|| anyhow::anyhow!("overflow"))
2696 })])
2697 .unwrap();
2698
2699 let no_seeds: Seeds = HashMap::new();
2700 let no_forced: HashSet<CellId> = HashSet::new();
2701 let mut stage = super::PropagationStage::default();
2702 let mut plan = crate::planner::plan(
2703 &sheet.cells,
2704 &sheet.relationships,
2705 &HashSet::from([self_ref]),
2706 )
2707 .unwrap();
2708 plan.execution_order = vec![PlanStep::Method(self_ref, 0)];
2709 plan.forced_outputs.clear();
2710 let provenance = sheet.plan_provenance(&plan);
2711 sheet
2712 .execute_plan_staged(
2713 &[PlanStep::Method(self_ref, 0)],
2714 &no_seeds,
2715 &no_forced,
2716 &provenance,
2717 &mut stage,
2718 &mut Vec::new(),
2719 )
2720 .unwrap();
2721 assert_eq!(
2722 stage.effective(&sheet.cells, x).downcast_ref::<i32>(),
2723 Some(&2)
2724 );
2725 assert_eq!(*sheet.read::<i32>(x).unwrap(), 8);
2726
2727 sheet.write(z, 41_i32).unwrap();
2728 let mut plan =
2729 crate::planner::plan(&sheet.cells, &sheet.relationships, &HashSet::from([plain]))
2730 .unwrap();
2731 plan.forced_outputs.clear();
2732 let provenance = sheet.plan_provenance(&plan);
2733 sheet
2734 .execute_plan_staged(
2735 &[PlanStep::Method(plain, 0)],
2736 &no_seeds,
2737 &no_forced,
2738 &provenance,
2739 &mut stage,
2740 &mut Vec::new(),
2741 )
2742 .unwrap();
2743 assert_eq!(
2744 stage.effective(&sheet.cells, x).downcast_ref::<i32>(),
2745 Some(&42)
2746 );
2747 sheet.commit_stage(stage);
2748 assert_eq!(*sheet.read::<i32>(x).unwrap(), 42);
2749 }
2750
2751 #[test]
2752 /// Checks rejection of an unknown conditional match cell.
2753 fn add_conditional_returns_error_for_invalid_cell() {
2754 let mut sheet = Sheet::new();
2755 let result = sheet.add_conditional(
2756 MatchExpr::cell(CellId::default()),
2757 vec![(vec![0_i32], vec![])],
2758 vec![],
2759 );
2760 assert!(matches!(result, Err(Error::InvalidId)));
2761 }
2762
2763 #[test]
2764 /// Checks rejection of branch keys with the wrong match type.
2765 fn add_conditional_returns_invalid_conditional_for_type_mismatch() {
2766 let mut sheet = Sheet::new();
2767 let a = sheet.add_cell(0_i32);
2768 // Branch keys are f64 but cell holds i32.
2769 let result =
2770 sheet.add_conditional(MatchExpr::cell(a), vec![(vec![0.0_f64], vec![])], vec![]);
2771 assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2772 }
2773
2774 #[test]
2775 /// Checks rejection of an unknown conditional relationship.
2776 fn add_conditional_returns_invalid_conditional_for_missing_relationship() {
2777 let mut sheet = Sheet::new();
2778 let a = sheet.add_cell(0_i32);
2779 let result = sheet.add_conditional(
2780 MatchExpr::cell(a),
2781 vec![(vec![0_i32], vec![RelationshipId::default()])],
2782 vec![],
2783 );
2784 assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2785 }
2786
2787 #[test]
2788 /// Checks the match-cell site in a conditional dependency-cycle error.
2789 fn dependency_cycle_from_a_conditional_names_the_match_cell() {
2790 let mut sheet = Sheet::new();
2791 let a = sheet.add_cell(0_i32);
2792 let b = sheet.add_cell(0_i32);
2793 // Relationship has two methods and involves `a` (the match cell).
2794 let rel = sheet
2795 .add_relationship(vec![
2796 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2797 Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2798 ])
2799 .unwrap();
2800 sheet
2801 .add_conditional(MatchExpr::cell(a), vec![(vec![0_i32], vec![rel])], vec![])
2802 .unwrap();
2803 let err = sheet.validate().unwrap_err();
2804 assert!(matches!(err, Error::DependencyCycle { .. }));
2805 assert!(err.sites().contains(&ErrorSite::Cell(a)));
2806 }
2807
2808 #[test]
2809 /// Checks rejection of a branch that can derive an upstream match dependency.
2810 fn add_conditional_returns_error_when_branch_rel_writes_a_cell_upstream_of_match_cell() {
2811 let mut sheet = Sheet::new();
2812 let a = sheet.add_cell(0_i32);
2813 let b = sheet.add_cell(0_i32);
2814 let p = sheet.add_cell(0_i32);
2815 // Unconditional: a → p (a contributes to match cell p).
2816 sheet
2817 .add_relationship(vec![Method::from_fn_1_1(a, p, |x: &i32| Ok(*x))])
2818 .unwrap();
2819 // Branch relationship has two methods and involves `a`, which feeds p.
2820 let rel = sheet
2821 .add_relationship(vec![
2822 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2823 Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2824 ])
2825 .unwrap();
2826 sheet
2827 .add_conditional(MatchExpr::cell(p), vec![(vec![0_i32], vec![rel])], vec![])
2828 .unwrap();
2829 assert!(matches!(
2830 sheet.validate(),
2831 Err(Error::DependencyCycle { .. })
2832 ));
2833 }
2834
2835 #[test]
2836 /// Checks guard independence for every computed-match input.
2837 fn add_conditional_returns_error_when_branch_rel_writes_a_cell_upstream_of_either_expr_input() {
2838 let mut sheet = Sheet::new();
2839 let a = sheet.add_cell(0_i32);
2840 let b = sheet.add_cell(0_i32);
2841 let p = sheet.add_cell(0_i32);
2842 let q = sheet.add_cell(0_i32);
2843 // Unconditional: a → q (a contributes to expr input q).
2844 sheet
2845 .add_relationship(vec![Method::from_fn_1_1(a, q, |x: &i32| Ok(*x))])
2846 .unwrap();
2847 // Branch relationship has two methods and involves `a`, which feeds q, one of the
2848 // match expression's two inputs (p, q).
2849 let rel = sheet
2850 .add_relationship(vec![
2851 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2852 Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2853 ])
2854 .unwrap();
2855 let expr = MatchExpr::from_fn_2([p, q], |x: &i32, y: &i32| Ok(*x + *y));
2856 sheet
2857 .add_conditional(expr, vec![(vec![0_i32], vec![rel])], vec![])
2858 .unwrap();
2859 assert!(matches!(
2860 sheet.validate(),
2861 Err(Error::DependencyCycle { .. })
2862 ));
2863 }
2864
2865 #[test]
2866 /// Checks branch selection from a computed two-input subject.
2867 fn add_conditional_activates_branch_from_two_cell_expression() {
2868 let mut sheet = Sheet::new();
2869 let a = sheet.add_cell(false);
2870 let b = sheet.add_cell(false);
2871 let x = sheet.add_cell(0_i32);
2872 let y = sheet.add_cell(0_i32);
2873 let rel_true = sheet
2874 .add_relationship(vec![Method::from_fn_1_1(x, y, |v: &i32| Ok(*v))])
2875 .unwrap();
2876 let expr = MatchExpr::from_fn_2([a, b], |p: &bool, q: &bool| Ok(*p && *q));
2877 let cid = sheet
2878 .add_conditional(expr, vec![(vec![true], vec![rel_true])], vec![])
2879 .unwrap();
2880
2881 sheet.write(a, true).unwrap();
2882 sheet.write(b, false).unwrap();
2883 assert_eq!(sheet.conditional_active_branch(cid).unwrap(), None);
2884
2885 sheet.write(b, true).unwrap();
2886 assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(0));
2887 assert_eq!(sheet.conditional_match_cells(cid).unwrap(), &[a, b]);
2888 }
2889
2890 #[test]
2891 /// Checks rejection of keys incompatible with the computed subject's output type.
2892 fn add_conditional_returns_invalid_conditional_for_expr_output_type_mismatch() {
2893 let mut sheet = Sheet::new();
2894 let a = sheet.add_cell(0_i32);
2895 let b = sheet.add_cell(0_i32);
2896 // Expression computes an i32, but branch keys below are f64.
2897 let expr = MatchExpr::from_fn_2([a, b], |x: &i32, y: &i32| Ok(x + y));
2898 let result = sheet.add_conditional::<f64>(expr, vec![(vec![0.0], vec![])], vec![]);
2899 assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2900 }
2901
2902 #[test]
2903 /// Checks rejection of an unknown computed-subject input.
2904 fn add_conditional_returns_invalid_id_for_bad_expr_input_cell() {
2905 let mut sheet = Sheet::new();
2906 let a = sheet.add_cell(0_i32);
2907 let expr = MatchExpr::from_fn_2([a, CellId::default()], |x: &i32, y: &i32| Ok(x + y));
2908 let result = sheet.add_conditional::<i32>(expr, vec![], vec![]);
2909 assert!(matches!(result, Err(Error::InvalidId)));
2910 }
2911
2912 #[test]
2913 /// Checks propagation of a computed subject's callback failure.
2914 fn propagate_surfaces_method_failed_from_a_failing_match_expression() {
2915 let mut sheet = Sheet::new();
2916 let a = sheet.add_cell(0_i32);
2917 let expr = MatchExpr::from_fn_1(a, |_x: &i32| -> Result<i32, anyhow::Error> {
2918 Err(anyhow::anyhow!("boom"))
2919 });
2920 sheet
2921 .add_conditional::<i32>(expr, vec![(vec![0], vec![])], vec![])
2922 .unwrap();
2923 let result = sheet.propagate();
2924 assert!(matches!(result, Err(Error::MethodFailed { .. })));
2925 }
2926
2927 #[test]
2928 /// Checks acceptance of multi-method branches independent of the match cell.
2929 fn add_conditional_allows_multi_method_rel_not_involving_match_cell() {
2930 let mut sheet = Sheet::new();
2931 let mode = sheet.add_cell(0_i32);
2932 let a = sheet.add_cell(0_i32);
2933 let b = sheet.add_cell(0_i32);
2934 // Relationship has two methods but does not involve `mode` (the match cell).
2935 // Branch relationships whose outputs do not reach the match cell may have any
2936 // number of methods.
2937 let rel = sheet
2938 .add_relationship(vec![
2939 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2940 Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2941 ])
2942 .unwrap();
2943 let result = sheet.add_conditional(
2944 MatchExpr::cell(mode),
2945 vec![(vec![0_i32], vec![rel])],
2946 vec![],
2947 );
2948 assert!(result.is_ok());
2949 }
2950
2951 #[test]
2952 /// Checks rejection of an explicit branch without keys.
2953 fn add_conditional_returns_invalid_conditional_for_empty_branch_keys() {
2954 let mut sheet = Sheet::new();
2955 let a = sheet.add_cell(0_i32);
2956 let b = sheet.add_cell(0_i32);
2957 let rel = sheet
2958 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
2959 .unwrap();
2960 // Empty key list is invalid.
2961 let result =
2962 sheet.add_conditional::<i32>(MatchExpr::cell(a), vec![(vec![], vec![rel])], vec![]);
2963 assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2964 }
2965
2966 #[test]
2967 /// Checks duplicate conditional ownership rejection and its relationship site.
2968 fn invalid_conditional_duplicate_relationship_names_that_relationship() {
2969 let mut sheet = Sheet::new();
2970 let a = sheet.add_cell(0_i32);
2971 let b = sheet.add_cell(0_i32);
2972 let dup_rel = sheet
2973 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
2974 .unwrap();
2975 // Add dup_rel to the first conditional.
2976 sheet
2977 .add_conditional(
2978 MatchExpr::cell(a),
2979 vec![(vec![0_i32], vec![dup_rel])],
2980 vec![],
2981 )
2982 .unwrap();
2983 // Try to add the same relationship to a second conditional.
2984 let result = sheet.add_conditional(
2985 MatchExpr::cell(a),
2986 vec![(vec![1_i32], vec![dup_rel])],
2987 vec![],
2988 );
2989 let err = result.unwrap_err();
2990 assert!(matches!(err, Error::InvalidConditional { .. }));
2991 assert!(err.sites().contains(&ErrorSite::Relationship(dup_rel)));
2992 }
2993
2994 #[test]
2995 /// Checks registration and lookup of a valid conditional identity.
2996 fn add_conditional_returns_id_for_valid_input() {
2997 let mut sheet = Sheet::new();
2998 let a = sheet.add_cell(0_i32);
2999 let b = sheet.add_cell(0_i32);
3000 let rel = sheet
3001 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3002 .unwrap();
3003 let cid = sheet
3004 .add_conditional(MatchExpr::cell(a), vec![(vec![0_i32], vec![rel])], vec![])
3005 .unwrap();
3006 assert_eq!(sheet.conditionals().collect::<Vec<_>>(), vec![cid]);
3007 assert_eq!(sheet.conditional_match_cells(cid), Some([a].as_slice()));
3008 }
3009
3010 #[test]
3011 /// Checks distinct identities for separate cell declarations.
3012 fn add_cell_returns_distinct_ids() {
3013 let mut sheet = Sheet::new();
3014 let a = sheet.add_cell(1_i32);
3015 let b = sheet.add_cell(2_i32);
3016 assert_ne!(a, b);
3017 }
3018
3019 #[test]
3020 /// Checks rejection of external writes to fixed Out cells.
3021 fn write_returns_invalid_cell_kind_for_an_output_cell() {
3022 let mut sheet = Sheet::new();
3023 let writer_input = sheet.add_cell(1_i32);
3024 let out_cell = sheet.add_cell(0_i32);
3025 let out = sheet
3026 .add_out(
3027 Method::from_fn_1_1(writer_input, out_cell, |x: &i32| Ok(*x)),
3028 vec![],
3029 )
3030 .unwrap();
3031 assert!(matches!(
3032 sheet.write(out, 5_i32),
3033 Err(Error::InvalidCellKind { .. })
3034 ));
3035 }
3036
3037 #[test]
3038 /// Checks rejection of method outputs targeting source-only cells.
3039 fn add_relationship_returns_invalid_cell_kind_when_a_source_cell_is_an_output() {
3040 let mut sheet = Sheet::new();
3041 let a = sheet.add_source(0_i32);
3042 let b = sheet.add_cell(0_i32);
3043 let result = sheet.add_relationship(vec![Method::from_fn_1_1(b, a, |x: &i32| Ok(*x))]);
3044 assert!(matches!(result, Err(Error::InvalidCellKind { .. })));
3045 assert_eq!(
3046 result.unwrap_err().sites().first().copied(),
3047 Some(ErrorSite::MethodIndex(0))
3048 );
3049 }
3050
3051 #[test]
3052 /// Checks method and cell sites for a rejected source-only output.
3053 fn invalid_cell_kind_names_the_method_and_the_source_output_cell() {
3054 let mut sheet = Sheet::new();
3055 let s = sheet.add_source(0_i32); // Source-kind cell
3056 // A Source-kind output is rejected.
3057 let err = sheet
3058 .add_relationship(vec![Method::from_fn_1_1(s, s, |x: &i32| Ok(*x))])
3059 .unwrap_err();
3060 let sites = err.sites();
3061 assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3062 assert!(sites[1..].contains(&ErrorSite::Cell(s)));
3063 }
3064
3065 #[test]
3066 /// Checks acceptance of source-only cells as method inputs.
3067 fn add_relationship_allows_a_source_cell_as_an_input() {
3068 let mut sheet = Sheet::new();
3069 let a = sheet.add_source(5_i32);
3070 let b = sheet.add_cell(0_i32);
3071 let result = sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))]);
3072 assert!(result.is_ok());
3073 }
3074
3075 #[test]
3076 /// Checks external editing of source-only cells.
3077 fn write_succeeds_on_a_source_cell() {
3078 let mut sheet = Sheet::new();
3079 let a = sheet.add_source(0_i32);
3080 assert!(sheet.write(a, 5_i32).is_ok());
3081 }
3082
3083 #[test]
3084 /// Checks immediate reads of a successfully written value.
3085 fn write_read_roundtrip() {
3086 let mut sheet = Sheet::new();
3087 let id = sheet.add_cell(42_i32);
3088 sheet.write(id, 99_i32).unwrap();
3089 assert_eq!(*sheet.read::<i32>(id).unwrap(), 99);
3090 }
3091
3092 #[test]
3093 /// Checks typed-write rejection for an incompatible value.
3094 fn write_wrong_type_returns_type_mismatch() {
3095 let mut sheet = Sheet::new();
3096 let id = sheet.add_cell(0_i32);
3097 assert!(matches!(
3098 sheet.write(id, 1.0_f64),
3099 Err(Error::TypeMismatch { .. })
3100 ));
3101 }
3102
3103 #[test]
3104 /// Checks typed-read rejection for an incompatible requested type.
3105 fn read_wrong_type_returns_type_mismatch() {
3106 let mut sheet = Sheet::new();
3107 let id = sheet.add_cell(0_i32);
3108 assert!(matches!(
3109 sheet.read::<f64>(id),
3110 Err(Error::TypeMismatch { .. })
3111 ));
3112 }
3113
3114 #[test]
3115 /// Checks source/effective agreement without a derived override.
3116 fn source_matches_read_for_a_plain_unshadowed_cell() {
3117 let mut sheet = Sheet::new();
3118 let a = sheet.add_cell(3_i32);
3119 assert_eq!(*sheet.source::<i32>(a).unwrap(), 3);
3120 assert_eq!(*sheet.read::<i32>(a).unwrap(), 3);
3121
3122 sheet.write(a, 8_i32).unwrap();
3123 assert_eq!(*sheet.source::<i32>(a).unwrap(), 8);
3124 assert_eq!(*sheet.read::<i32>(a).unwrap(), 8);
3125 }
3126
3127 #[test]
3128 /// Checks source lookup rejection for an unknown cell.
3129 fn source_returns_invalid_id_for_unknown_cell() {
3130 let sheet = Sheet::new();
3131 assert!(matches!(
3132 sheet.source::<i32>(CellId::default()),
3133 Err(Error::InvalidId)
3134 ));
3135 }
3136
3137 #[test]
3138 /// Checks source lookup rejection for an incompatible requested type.
3139 fn source_wrong_type_returns_type_mismatch() {
3140 let mut sheet = Sheet::new();
3141 let id = sheet.add_cell(0_i32);
3142 assert!(matches!(
3143 sheet.source::<f64>(id),
3144 Err(Error::TypeMismatch { .. })
3145 ));
3146 }
3147
3148 #[test]
3149 /// Checks rejection of a relationship without methods.
3150 fn add_relationship_empty_methods_returns_invalid_method() {
3151 let mut sheet = Sheet::new();
3152 let result = sheet.add_relationship(vec![]);
3153 assert!(matches!(result, Err(Error::InvalidMethod { .. })));
3154 assert!(result.unwrap_err().sites().is_empty());
3155 }
3156
3157 #[test]
3158 /// Checks rejection of a method signature inconsistent with registered cell types.
3159 fn add_relationship_type_mismatch_returns_error() {
3160 let mut sheet = Sheet::new();
3161 let a = sheet.add_cell(0_i32);
3162 let b = sheet.add_cell(0_i32);
3163 // Method declares f64 input but cell holds i32.
3164 let method = Method::from_fn_1_1(a, b, |x: &f64| Ok(*x * 2.0));
3165 let result = sheet.add_relationship(vec![method]);
3166 assert!(matches!(result, Err(Error::TypeMismatch { .. })));
3167 assert_eq!(
3168 result.unwrap_err().sites().first().copied(),
3169 Some(ErrorSite::MethodIndex(0))
3170 );
3171 }
3172
3173 #[test]
3174 /// Checks the producing method's site for a runtime output-type mismatch.
3175 fn execute_plan_type_mismatch_reports_the_method_that_produced_it() {
3176 let mut sheet = Sheet::new();
3177 let a = sheet.add_cell(0_i32);
3178 let b = sheet.add_cell(false); // bool cell
3179 // Declares an i32 output (matching nothing about `b`'s actual bool type at
3180 // add_relationship time -- add_relationship validates output_types against the cell,
3181 // so declare bool here to pass that check, then lie about it at runtime below).
3182 let method = Method::new(
3183 vec![a],
3184 vec![b],
3185 vec![TypeId::of::<i32>()],
3186 vec![TypeId::of::<bool>()],
3187 |args| {
3188 let x = *args[0].downcast_ref::<i32>().unwrap();
3189 // Lies: returns an i32 though output_types declared bool, reproducing the
3190 // execute_plan runtime type-mismatch path (add_relationship can't catch this --
3191 // it only checks the declared TypeId, not what the closure actually returns).
3192 Ok(vec![Box::new(x) as Box<dyn std::any::Any>])
3193 },
3194 );
3195 let rel_id = sheet.add_relationship(vec![method]).unwrap();
3196 let result = sheet.propagate();
3197 assert!(matches!(result, Err(Error::TypeMismatch { .. })));
3198 assert_eq!(
3199 result.unwrap_err().sites().first().copied(),
3200 Some(ErrorSite::Method(rel_id, 0))
3201 );
3202 }
3203
3204 #[test]
3205 /// Checks the producing method's site for a callback failure.
3206 fn execute_plan_method_failed_reports_the_method_that_produced_it() {
3207 let mut sheet = Sheet::new();
3208 let a = sheet.add_cell(0_i32);
3209 let b = sheet.add_cell(0_i32);
3210 let method = Method::from_fn_1_1(a, b, |_: &i32| -> Result<i32, anyhow::Error> {
3211 Err(anyhow::anyhow!("boom"))
3212 });
3213 let rel_id = sheet.add_relationship(vec![method]).unwrap();
3214 let result = sheet.propagate();
3215 assert!(matches!(result, Err(Error::MethodFailed { .. })));
3216 assert_eq!(
3217 result.unwrap_err().sites().first().copied(),
3218 Some(ErrorSite::Method(rel_id, 0))
3219 );
3220 }
3221
3222 #[test]
3223 /// Checks evaluation of a constant-producing zero-input method.
3224 fn add_relationship_zero_input_method_defines_a_fixed_point() {
3225 // A method with no inputs is a valid degenerate case: it always produces the
3226 // same value, independent of every other cell in the sheet.
3227 let mut sheet = Sheet::new();
3228 let b = sheet.add_cell(0_i32);
3229 let method = Method::new(vec![], vec![b], vec![], vec![TypeId::of::<i32>()], |_| {
3230 Ok(vec![Box::new(42_i32)])
3231 });
3232 let rel = sheet.add_relationship(vec![method]).unwrap();
3233 sheet.propagate().unwrap();
3234 assert_eq!(*sheet.read::<i32>(b).unwrap(), 42);
3235 assert!(sheet.is_forced(b));
3236 assert!(sheet.is_relationship_forced(rel));
3237 }
3238
3239 #[test]
3240 /// Checks rejection of a method without outputs.
3241 fn add_relationship_empty_outputs_returns_invalid_method() {
3242 let mut sheet = Sheet::new();
3243 let a = sheet.add_cell(0_i32);
3244 let method = Method::new(
3245 vec![a],
3246 vec![], // no outputs
3247 vec![TypeId::of::<i32>()],
3248 vec![],
3249 |_| Ok(vec![]),
3250 );
3251 let result = sheet.add_relationship(vec![method]);
3252 assert!(matches!(result, Err(Error::InvalidMethod { .. })));
3253 assert_eq!(
3254 result.unwrap_err().sites().first().copied(),
3255 Some(ErrorSite::MethodIndex(0))
3256 );
3257 }
3258
3259 #[test]
3260 /// Checks acceptance of distinct output alternatives sharing one complete cell set.
3261 fn add_relationship_consistent_cell_sets_across_methods_succeeds() {
3262 let mut sheet = Sheet::new();
3263 let a = sheet.add_cell(0_i32);
3264 let b = sheet.add_cell(0_i32);
3265 let c = sheet.add_cell(0_i32);
3266 // Triangle relationship: every method references {a, b, c}.
3267 let result = sheet.add_relationship(vec![
3268 Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(x + y)),
3269 Method::from_fn_2_1([a, c], b, |x: &i32, y: &i32| Ok(y - x)),
3270 Method::from_fn_2_1([b, c], a, |x: &i32, y: &i32| Ok(y - x)),
3271 ]);
3272 assert!(result.is_ok());
3273 }
3274
3275 #[test]
3276 /// Checks rejection of alternatives with different complete cell sets.
3277 fn add_relationship_inconsistent_cell_sets_across_methods_returns_mismatched_method_cells() {
3278 let mut sheet = Sheet::new();
3279 let a = sheet.add_cell(0_i32);
3280 let b = sheet.add_cell(0_i32);
3281 let c = sheet.add_cell(0_i32);
3282 // Method 0 references {a, b}; method 1 references {b, c} -- inconsistent.
3283 let result = sheet.add_relationship(vec![
3284 Method::from_fn_1_1(a, b, |v: &i32| Ok(*v)),
3285 Method::from_fn_1_1(b, c, |v: &i32| Ok(*v)),
3286 ]);
3287 assert!(matches!(result, Err(Error::MismatchedMethodCells { .. })));
3288 assert_eq!(
3289 result.unwrap_err().sites().first().copied(),
3290 Some(ErrorSite::MethodIndex(1))
3291 );
3292 }
3293
3294 #[test]
3295 /// Checks rejection of alternatives with identical output sets.
3296 fn add_relationship_duplicate_output_set_across_methods_returns_invalid_method_outputs() {
3297 let mut sheet = Sheet::new();
3298 let a = sheet.add_cell(0_i32);
3299 let b = sheet.add_cell(0_i32);
3300 // Both methods reference {a, b} (so the cell-set-consistency check passes) but
3301 // both output {b} from different inputs -- their output sets are identical,
3302 // which must be rejected.
3303 let result = sheet.add_relationship(vec![
3304 Method::from_fn_2_1([a, b], b, |x: &i32, _y: &i32| Ok(*x)),
3305 Method::from_fn_2_1([a, b], b, |_x: &i32, y: &i32| Ok(*y)),
3306 ]);
3307 assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3308 assert_eq!(
3309 result.unwrap_err().sites().first().copied(),
3310 Some(ErrorSite::MethodIndex(1))
3311 );
3312 }
3313
3314 #[test]
3315 /// Checks rejection of strictly nested alternative output sets.
3316 fn add_relationship_rejects_strictly_nested_method_outputs() {
3317 let mut sheet = Sheet::new();
3318 let a = sheet.add_cell(0_i32);
3319 let b = sheet.add_cell(0_i32);
3320 let c = sheet.add_cell(0_i32);
3321 // Both methods span {a, b, c}; one outputs {b}, the other outputs {b, c}.
3322 let result = sheet.add_relationship(vec![
3323 Method::from_fn_2_1([a, c], b, |x: &i32, y: &i32| Ok(x + y)),
3324 Method::new(
3325 vec![a],
3326 vec![b, c],
3327 vec![std::any::TypeId::of::<i32>()],
3328 vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3329 |args| {
3330 let x = *args[0].downcast_ref::<i32>().unwrap();
3331 Ok(vec![Box::new(x), Box::new(x)])
3332 },
3333 ),
3334 ]);
3335 let err = result.unwrap_err();
3336 assert!(matches!(err, Error::InvalidMethodOutputs { .. }));
3337 let sites = err.sites();
3338 assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3339 assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3340 assert_eq!(sites[2..], [ErrorSite::Cell(b)]);
3341 }
3342
3343 #[test]
3344 /// Checks acceptance of overlapping output alternatives without nesting.
3345 fn add_relationship_accepts_overlapping_non_nested_method_outputs() {
3346 let mut sheet = Sheet::new();
3347 let a = sheet.add_cell(0_i32);
3348 let b = sheet.add_cell(0_i32);
3349 let c = sheet.add_cell(0_i32);
3350 // Both methods span {a, b, c}; outputs overlap at b but neither set nests the other.
3351 let result = sheet.add_relationship(vec![
3352 Method::new(
3353 vec![a, c],
3354 vec![a, b],
3355 vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3356 vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3357 |args| {
3358 let x = *args[0].downcast_ref::<i32>().unwrap();
3359 let y = *args[1].downcast_ref::<i32>().unwrap();
3360 Ok(vec![Box::new(x), Box::new(y)])
3361 },
3362 ),
3363 Method::new(
3364 vec![a, b],
3365 vec![b, c],
3366 vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3367 vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3368 |args| {
3369 let x = *args[0].downcast_ref::<i32>().unwrap();
3370 let y = *args[1].downcast_ref::<i32>().unwrap();
3371 Ok(vec![Box::new(y), Box::new(x)])
3372 },
3373 ),
3374 ]);
3375 assert!(result.is_ok());
3376 }
3377
3378 #[test]
3379 /// Checks distinct identities for separate relationship declarations.
3380 fn add_relationship_returns_distinct_ids() {
3381 let mut sheet = Sheet::new();
3382 let a = sheet.add_cell(0_i32);
3383 let b = sheet.add_cell(0_i32);
3384 let r1 = sheet
3385 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3386 .unwrap();
3387 let c = sheet.add_cell(0_i32);
3388 let r2 = sheet
3389 .add_relationship(vec![Method::from_fn_1_1(b, c, |x: &i32| Ok(*x))])
3390 .unwrap();
3391 assert_ne!(r1, r2);
3392 }
3393
3394 #[test]
3395 /// Checks rejection of inconsistent positional cell sets.
3396 fn add_relationship_mismatched_cells_returns_error() {
3397 let mut sheet = Sheet::new();
3398 let a = sheet.add_cell(0_i32);
3399 let b = sheet.add_cell(0_i32);
3400 let c = sheet.add_cell(0_i32);
3401 let d = sheet.add_cell(0_i32);
3402 // Method 0 spans {a, b}; Method 1 spans {c, d} — mismatched cell sets.
3403 let result = sheet.add_relationship(vec![
3404 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3405 Method::from_fn_1_1(c, d, |x: &i32| Ok(*x)),
3406 ]);
3407 assert!(matches!(result, Err(Error::MismatchedMethodCells { .. })));
3408 assert_eq!(
3409 result.unwrap_err().sites().first().copied(),
3410 Some(ErrorSite::MethodIndex(1))
3411 );
3412 }
3413
3414 #[test]
3415 /// Checks rejection of duplicate alternative output sets.
3416 fn add_relationship_duplicate_output_sets_across_methods_returns_error() {
3417 let mut sheet = Sheet::new();
3418 let a = sheet.add_cell(0_i32);
3419 let b = sheet.add_cell(0_i32);
3420 let c = sheet.add_cell(0_i32);
3421 // Both methods span {a, b, c} and both output {c} — identical output sets.
3422 let result = sheet.add_relationship(vec![
3423 Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(*x + *y)),
3424 Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(*x - *y)),
3425 ]);
3426 assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3427 assert_eq!(
3428 result.unwrap_err().sites().first().copied(),
3429 Some(ErrorSite::MethodIndex(1))
3430 );
3431 }
3432
3433 #[test]
3434 /// Checks rejection of a repeated output identity within one method.
3435 fn add_relationship_duplicate_cell_within_own_outputs_returns_error() {
3436 let mut sheet = Sheet::new();
3437 let a = sheet.add_cell(0_i32);
3438 let b = sheet.add_cell(0_i32);
3439 // The method's own outputs list names `b` twice.
3440 let method = Method::new(
3441 vec![a],
3442 vec![b, b],
3443 vec![TypeId::of::<i32>()],
3444 vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
3445 |args| {
3446 let x = args[0].downcast_ref::<i32>().unwrap();
3447 Ok(vec![Box::new(*x), Box::new(*x)])
3448 },
3449 );
3450 let result = sheet.add_relationship(vec![method]);
3451 assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3452 assert_eq!(
3453 result.unwrap_err().sites().first().copied(),
3454 Some(ErrorSite::MethodIndex(0))
3455 );
3456 }
3457
3458 #[test]
3459 /// Checks both method sites and the differing cell in a cell-set mismatch.
3460 fn mismatched_method_cells_names_both_methods_and_the_differing_cell() {
3461 let mut sheet = Sheet::new();
3462 let a = sheet.add_cell(0_i32);
3463 let b = sheet.add_cell(0_i32);
3464 let c = sheet.add_cell(0_i32);
3465 // method 0 references {a,b}; method 1 references {a,c}: c (and b) diverge.
3466 let err = sheet
3467 .add_relationship(vec![
3468 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3469 Method::from_fn_1_1(a, c, |x: &i32| Ok(*x)),
3470 ])
3471 .unwrap_err();
3472 let sites = err.sites();
3473 assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3474 assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3475 assert!(sites[2..].contains(&ErrorSite::Cell(c)));
3476 }
3477
3478 #[test]
3479 /// Checks both method sites and the shared cell in a duplicate-output error.
3480 fn duplicate_output_set_across_methods_names_both_methods_and_the_shared_cell() {
3481 let mut sheet = Sheet::new();
3482 let a = sheet.add_cell(0_i32);
3483 let b = sheet.add_cell(0_i32);
3484 // both methods reference {a,b} and both output b.
3485 let err = sheet
3486 .add_relationship(vec![
3487 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3488 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x + 1)),
3489 ])
3490 .unwrap_err();
3491 let sites = err.sites();
3492 assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3493 assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3494 assert!(sites[2..].contains(&ErrorSite::Cell(b)));
3495 }
3496
3497 #[test]
3498 /// Checks the method and repeated-cell sites for duplicate positional outputs.
3499 fn duplicate_cell_within_own_outputs_names_the_method_and_the_repeated_cell() {
3500 let mut sheet = Sheet::new();
3501 let a = sheet.add_cell(0_i32);
3502 let b = sheet.add_cell(0_i32);
3503 let i32_ty = std::any::TypeId::of::<i32>();
3504 // one method whose outputs name b twice.
3505 let err = sheet
3506 .add_relationship(vec![Method::new(
3507 vec![a],
3508 vec![b, b],
3509 vec![i32_ty],
3510 vec![i32_ty, i32_ty],
3511 |args| {
3512 let v = *args[0].downcast_ref::<i32>().unwrap();
3513 Ok(vec![Box::new(v), Box::new(v)])
3514 },
3515 )])
3516 .unwrap_err();
3517 let sites = err.sites();
3518 assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3519 assert!(sites[1..].contains(&ErrorSite::Cell(b)));
3520 }
3521
3522 #[test]
3523 /// Checks deduplication of diagnostic cell sites for repeated outputs.
3524 fn duplicate_cell_repeated_three_times_reports_cell_once() {
3525 let mut sheet = Sheet::new();
3526 let a = sheet.add_cell(0_i32);
3527 let b = sheet.add_cell(0_i32);
3528 let i32_ty = std::any::TypeId::of::<i32>();
3529 // one method whose outputs name b three times.
3530 let err = sheet
3531 .add_relationship(vec![Method::new(
3532 vec![a],
3533 vec![b, b, b],
3534 vec![i32_ty],
3535 vec![i32_ty, i32_ty, i32_ty],
3536 |args| {
3537 let v = *args[0].downcast_ref::<i32>().unwrap();
3538 Ok(vec![Box::new(v), Box::new(v), Box::new(v)])
3539 },
3540 )])
3541 .unwrap_err();
3542 let sites = err.sites();
3543 assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3544 let cell_sites: Vec<_> = sites[1..]
3545 .iter()
3546 .filter(|&&s| s == ErrorSite::Cell(b))
3547 .collect();
3548 assert_eq!(
3549 cell_sites.len(),
3550 1,
3551 "expected exactly one Cell(b) site for a triple repeat, got {sites:?}"
3552 );
3553 }
3554
3555 #[test]
3556 /// Checks an empty changed set before propagation.
3557 fn changed_is_empty_before_propagate() {
3558 let sheet = Sheet::new();
3559 assert_eq!(sheet.changed().count(), 0);
3560 }
3561
3562 #[test]
3563 /// Checks changed membership for committed method outputs.
3564 fn changed_after_propagate_contains_method_outputs() {
3565 let mut sheet = Sheet::new();
3566 let a = sheet.add_cell(0_i32);
3567 let b = sheet.add_cell(0_i32);
3568 sheet
3569 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
3570 .unwrap();
3571 sheet.write(a, 3_i32).unwrap();
3572 sheet.propagate().unwrap();
3573 let changed: Vec<_> = sheet.changed().collect();
3574 assert_eq!(changed, vec![b]);
3575 }
3576
3577 #[test]
3578 /// Checks explicit clearing of changed membership.
3579 fn clear_changed_empties_changed_set() {
3580 let mut sheet = Sheet::new();
3581 let a = sheet.add_cell(0_i32);
3582 let b = sheet.add_cell(0_i32);
3583 sheet
3584 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
3585 .unwrap();
3586 sheet.write(a, 3_i32).unwrap();
3587 sheet.propagate().unwrap();
3588 sheet.clear_changed();
3589 assert_eq!(sheet.changed().count(), 0);
3590 }
3591
3592 #[test]
3593 /// Checks replacement of previous changed membership after reversing method direction.
3594 fn propagate_clears_previous_changed_set() {
3595 let mut sheet = Sheet::new();
3596 let a = sheet.add_cell(0_i32);
3597 let b = sheet.add_cell(0_i32);
3598 sheet
3599 .add_relationship(vec![
3600 Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3601 Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
3602 ])
3603 .unwrap();
3604 sheet.write(a, 3_i32).unwrap();
3605 sheet.propagate().unwrap();
3606 assert_eq!(sheet.changed().collect::<Vec<_>>(), vec![b]);
3607 sheet.write(b, 5_i32).unwrap();
3608 sheet.propagate().unwrap();
3609 let changed: Vec<_> = sheet.changed().collect();
3610 assert_eq!(changed, vec![a]);
3611 }
3612
3613 #[test]
3614 /// Checks enumeration of every live cell identity.
3615 fn cells_returns_all_cell_ids() {
3616 let mut sheet = Sheet::new();
3617 let a = sheet.add_cell(0_i32);
3618 let b = sheet.add_cell(0_i32);
3619 let ids: Vec<_> = sheet.cells().collect();
3620 assert!(ids.contains(&a));
3621 assert!(ids.contains(&b));
3622 assert_eq!(ids.len(), 2);
3623 }
3624
3625 #[test]
3626 /// Checks empty cell enumeration on a new sheet.
3627 fn cells_returns_empty_for_empty_sheet() {
3628 let sheet = Sheet::new();
3629 assert_eq!(sheet.cells().count(), 0);
3630 }
3631
3632 #[test]
3633 /// Checks enumeration of every live relationship identity.
3634 fn relationships_returns_all_relationship_ids() {
3635 let mut sheet = Sheet::new();
3636 let a = sheet.add_cell(0_i32);
3637 let b = sheet.add_cell(0_i32);
3638 let r = sheet
3639 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3640 .unwrap();
3641 let ids: Vec<_> = sheet.relationships().collect();
3642 assert_eq!(ids, vec![r]);
3643 }
3644
3645 #[test]
3646 /// Checks empty relationship enumeration on a new sheet.
3647 fn relationships_returns_empty_for_empty_sheet() {
3648 let sheet = Sheet::new();
3649 assert_eq!(sheet.relationships().count(), 0);
3650 }
3651
3652 #[test]
3653 /// Checks a cell's adjacent relationship identities.
3654 fn cell_adj_returns_adjacent_relationships() {
3655 let mut sheet = Sheet::new();
3656 let a = sheet.add_cell(0_i32);
3657 let b = sheet.add_cell(0_i32);
3658 let r = sheet
3659 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3660 .unwrap();
3661 assert!(sheet.cell_adj(a).unwrap().contains(&r));
3662 assert!(sheet.cell_adj(b).unwrap().contains(&r));
3663 }
3664
3665 #[test]
3666 /// Checks absent adjacency for an unknown cell.
3667 fn cell_adj_returns_none_for_invalid_id() {
3668 let sheet = Sheet::new();
3669 assert!(sheet.cell_adj(CellId::default()).is_none());
3670 }
3671
3672 #[test]
3673 /// Checks a relationship's complete adjacent cell set.
3674 fn relationship_adj_returns_adjacent_cells() {
3675 let mut sheet = Sheet::new();
3676 let a = sheet.add_cell(0_i32);
3677 let b = sheet.add_cell(0_i32);
3678 let r = sheet
3679 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3680 .unwrap();
3681 let adj = sheet.relationship_adj(r).unwrap();
3682 assert!(adj.contains(&a));
3683 assert!(adj.contains(&b));
3684 }
3685
3686 #[test]
3687 /// Checks absent adjacency for an unknown relationship.
3688 fn relationship_adj_returns_none_for_invalid_id() {
3689 let sheet = Sheet::new();
3690 assert!(sheet.relationship_adj(RelationshipId::default()).is_none());
3691 }
3692
3693 #[test]
3694 /// Checks absence of a selected method before propagation.
3695 fn selected_method_returns_none_before_propagate() {
3696 let mut sheet = Sheet::new();
3697 let a = sheet.add_cell(0_i32);
3698 let b = sheet.add_cell(0_i32);
3699 let rel = sheet
3700 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3701 .unwrap();
3702 assert!(sheet.selected_method(rel).is_none());
3703 }
3704
3705 #[test]
3706 /// Checks the completed plan's selected method index.
3707 fn selected_method_returns_index_after_propagate() {
3708 let mut sheet = Sheet::new();
3709 let a = sheet.add_cell(0_i32);
3710 let b = sheet.add_cell(0_i32);
3711 let rel = sheet
3712 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3713 .unwrap();
3714 // Write to `a` so it has the highest strength and becomes the source,
3715 // making the a → b method eligible.
3716 sheet.write(a, 0_i32).unwrap();
3717 sheet.propagate().unwrap();
3718 assert_eq!(sheet.selected_method(rel), Some(0));
3719 }
3720
3721 #[test]
3722 /// Checks positional input lookup for a live method.
3723 fn method_inputs_returns_inputs_for_valid_method() {
3724 let mut sheet = Sheet::new();
3725 let a = sheet.add_cell(0_i32);
3726 let b = sheet.add_cell(0_i32);
3727 let rel = sheet
3728 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3729 .unwrap();
3730 assert_eq!(sheet.method_inputs(rel, 0), Some([a].as_slice()));
3731 }
3732
3733 #[test]
3734 /// Checks positional output lookup for a live method.
3735 fn method_outputs_returns_outputs_for_valid_method() {
3736 let mut sheet = Sheet::new();
3737 let a = sheet.add_cell(0_i32);
3738 let b = sheet.add_cell(0_i32);
3739 let rel = sheet
3740 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3741 .unwrap();
3742 assert_eq!(sheet.method_outputs(rel, 0), Some([b].as_slice()));
3743 }
3744
3745 #[test]
3746 /// Checks absent input metadata for an invalid method index.
3747 fn method_inputs_returns_none_for_out_of_bounds_idx() {
3748 let mut sheet = Sheet::new();
3749 let a = sheet.add_cell(0_i32);
3750 let b = sheet.add_cell(0_i32);
3751 let rel = sheet
3752 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3753 .unwrap();
3754 assert!(sheet.method_inputs(rel, 99).is_none());
3755 }
3756
3757 #[test]
3758 /// Checks absent output metadata for an invalid method index.
3759 fn method_outputs_returns_none_for_out_of_bounds_idx() {
3760 let mut sheet = Sheet::new();
3761 let a = sheet.add_cell(0_i32);
3762 let b = sheet.add_cell(0_i32);
3763 let rel = sheet
3764 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3765 .unwrap();
3766 assert!(sheet.method_outputs(rel, 99).is_none());
3767 }
3768
3769 #[test]
3770 /// Checks absence of completed-plan source classification before propagation.
3771 fn is_source_returns_false_before_propagate() {
3772 let mut sheet = Sheet::new();
3773 let a = sheet.add_cell(0_i32);
3774 let b = sheet.add_cell(0_i32);
3775 sheet
3776 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3777 .unwrap();
3778 assert!(!sheet.is_source(a));
3779 }
3780
3781 #[test]
3782 /// Checks source classification of an unclaimed input after propagation.
3783 fn is_source_returns_true_for_input_cell_after_propagate() {
3784 let mut sheet = Sheet::new();
3785 let a = sheet.add_cell(0_i32);
3786 let b = sheet.add_cell(0_i32);
3787 sheet
3788 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3789 .unwrap();
3790 // Write to `a` so it has the highest strength and becomes the source.
3791 sheet.write(a, 0_i32).unwrap();
3792 sheet.propagate().unwrap();
3793 assert!(sheet.is_source(a));
3794 }
3795
3796 #[test]
3797 /// Checks non-source classification of a selected method's output.
3798 fn is_source_returns_false_for_output_cell_after_propagate() {
3799 let mut sheet = Sheet::new();
3800 let a = sheet.add_cell(0_i32);
3801 let b = sheet.add_cell(0_i32);
3802 sheet
3803 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3804 .unwrap();
3805 // Write to `a` so it has the highest strength and becomes the source.
3806 sheet.write(a, 0_i32).unwrap();
3807 sheet.propagate().unwrap();
3808 assert!(!sheet.is_source(b));
3809 }
3810
3811 // ── Conditional accessor tests ─────────────────────────────────────────
3812
3813 /// Builds a sheet with two keyed branches and returns its conditional identity.
3814 fn sheet_with_two_branch_conditional() -> (Sheet, ConditionalId) {
3815 let mut sheet = Sheet::new();
3816 let a = sheet.add_cell(0_i32);
3817 let b = sheet.add_cell(0_i32);
3818 let p = sheet.add_cell(0_i32);
3819
3820 let rel0 = sheet
3821 .add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])
3822 .unwrap();
3823 let rel1 = sheet
3824 .add_relationship(vec![Method::from_fn_1_1(b, a, |v: &i32| Ok(*v))])
3825 .unwrap();
3826
3827 let cid = sheet
3828 .add_conditional(
3829 MatchExpr::cell(p),
3830 vec![(vec![0_i32], vec![rel0]), (vec![1_i32], vec![rel1])],
3831 vec![],
3832 )
3833 .unwrap();
3834 (sheet, cid)
3835 }
3836
3837 /// Builds a conditional fixture and identifies its default relationship.
3838 fn sheet_with_default_conditional() -> (Sheet, ConditionalId, RelationshipId) {
3839 let mut sheet = Sheet::new();
3840 let a = sheet.add_cell(0_i32);
3841 let b = sheet.add_cell(0_i32);
3842 let p = sheet.add_cell(99_i32); // no branch matches → default
3843
3844 let rel_default = sheet
3845 .add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])
3846 .unwrap();
3847
3848 let cid = sheet
3849 .add_conditional::<i32>(MatchExpr::cell(p), vec![], vec![rel_default])
3850 .unwrap();
3851 (sheet, cid, rel_default)
3852 }
3853
3854 #[test]
3855 /// Checks enumeration of a registered conditional identity.
3856 fn conditionals_returns_registered_id() {
3857 let (sheet, cid) = sheet_with_two_branch_conditional();
3858 assert!(sheet.conditionals().any(|id| id == cid));
3859 }
3860
3861 #[test]
3862 /// Checks empty conditional enumeration on a new sheet.
3863 fn conditionals_empty_on_new_sheet() {
3864 let sheet = Sheet::new();
3865 assert_eq!(sheet.conditionals().count(), 0);
3866 }
3867
3868 #[test]
3869 /// Checks the registered conditional's match-cell identity.
3870 fn conditional_match_cells_returns_correct_cell() {
3871 let mut sheet = Sheet::new();
3872 let p = sheet.add_cell(0_i32);
3873 let cid = sheet
3874 .add_conditional::<i32>(MatchExpr::cell(p), vec![], vec![])
3875 .unwrap();
3876 assert_eq!(sheet.conditional_match_cells(cid), Some([p].as_slice()));
3877 }
3878
3879 #[test]
3880 /// Checks absent match inputs for an unknown conditional.
3881 fn conditional_match_cells_returns_none_for_invalid_id() {
3882 let sheet = Sheet::new();
3883 assert_eq!(
3884 sheet.conditional_match_cells(ConditionalId::default()),
3885 None
3886 );
3887 }
3888
3889 #[test]
3890 /// Checks the authored explicit-branch count.
3891 fn conditional_branch_count_returns_correct_count() {
3892 let (sheet, cid) = sheet_with_two_branch_conditional();
3893 assert_eq!(sheet.conditional_branch_count(cid), Some(2));
3894 }
3895
3896 #[test]
3897 /// Checks absent branch counts for an unknown conditional.
3898 fn conditional_branch_count_returns_none_for_invalid_id() {
3899 let sheet = Sheet::new();
3900 assert_eq!(
3901 sheet.conditional_branch_count(ConditionalId::default()),
3902 None
3903 );
3904 }
3905
3906 #[test]
3907 /// Checks the exact authored relationship identities of both explicit branches.
3908 fn conditional_branch_relationships_returns_correct_rels() {
3909 let (sheet, cid) = sheet_with_two_branch_conditional();
3910 let rels0 = sheet.conditional_branch_relationships(cid, 0).unwrap();
3911 let rels1 = sheet.conditional_branch_relationships(cid, 1).unwrap();
3912 let authored: Vec<_> = sheet.relationships().collect();
3913 assert_eq!(rels0, [authored[0]]);
3914 assert_eq!(rels1, [authored[1]]);
3915 }
3916
3917 #[test]
3918 /// Checks absent relationship metadata for an invalid branch index.
3919 fn conditional_branch_relationships_returns_none_for_out_of_bounds() {
3920 let (sheet, cid) = sheet_with_two_branch_conditional();
3921 assert!(sheet.conditional_branch_relationships(cid, 2).is_none());
3922 }
3923
3924 #[test]
3925 /// Checks absent branch relationships for an unknown conditional.
3926 fn conditional_branch_relationships_returns_none_for_invalid_id() {
3927 let sheet = Sheet::new();
3928 assert!(
3929 sheet
3930 .conditional_branch_relationships(ConditionalId::default(), 0)
3931 .is_none()
3932 );
3933 }
3934
3935 #[test]
3936 /// Checks the exact authored default-relationship identity.
3937 fn conditional_default_relationships_returns_correct_rels() {
3938 let (sheet, cid, rel_default) = sheet_with_default_conditional();
3939 let rels = sheet.conditional_default_relationships(cid).unwrap();
3940 assert_eq!(rels, [rel_default]);
3941 }
3942
3943 #[test]
3944 /// Checks empty default membership when no default is declared.
3945 fn conditional_default_relationships_empty_when_no_default() {
3946 let (sheet, cid) = sheet_with_two_branch_conditional();
3947 assert_eq!(sheet.conditional_default_relationships(cid).unwrap(), &[]);
3948 }
3949
3950 #[test]
3951 /// Checks absent default metadata for an unknown conditional.
3952 fn conditional_default_relationships_returns_none_for_invalid_id() {
3953 let sheet = Sheet::new();
3954 assert!(
3955 sheet
3956 .conditional_default_relationships(ConditionalId::default())
3957 .is_none()
3958 );
3959 }
3960
3961 #[test]
3962 /// Checks branch-index lookup for a matching subject.
3963 fn conditional_active_branch_returns_matching_branch_index() {
3964 let (mut sheet, cid) = sheet_with_two_branch_conditional();
3965 let p = sheet.conditional_match_cells(cid).unwrap()[0];
3966 sheet.write(p, 0_i32).unwrap();
3967 assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(0));
3968 sheet.write(p, 1_i32).unwrap();
3969 assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(1));
3970 }
3971
3972 #[test]
3973 /// Checks absent explicit-branch selection for an unmatched subject.
3974 fn conditional_active_branch_returns_none_when_no_branch_matches() {
3975 let (mut sheet, cid) = sheet_with_two_branch_conditional();
3976 let p = sheet.conditional_match_cells(cid).unwrap()[0];
3977 sheet.write(p, 99_i32).unwrap();
3978 assert_eq!(sheet.conditional_active_branch(cid).unwrap(), None);
3979 }
3980
3981 #[test]
3982 /// Checks absent branch selection for an unknown conditional.
3983 fn conditional_active_branch_returns_none_for_invalid_id() {
3984 let sheet = Sheet::new();
3985 assert_eq!(
3986 sheet
3987 .conditional_active_branch(ConditionalId::default())
3988 .unwrap(),
3989 None
3990 );
3991 }
3992
3993 #[test]
3994 /// Checks filter attachment rejection for an unknown governed cell.
3995 fn add_filter_returns_invalid_id_for_missing_cell() {
3996 let mut sheet = Sheet::new();
3997 let result = sheet.add_filter(CellId::default(), Filter::from_fn_0(|x: &i32| Ok(*x)));
3998 assert!(matches!(result, Err(Error::InvalidId)));
3999 }
4000
4001 #[test]
4002 /// Checks that attachment alone preserves the cell's current value.
4003 fn add_filter_does_not_change_the_cells_current_value() {
4004 let mut sheet = Sheet::new();
4005 let a = sheet.add_cell(500_i32);
4006 sheet
4007 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4008 .unwrap();
4009 // add_filter never evaluates the function against the current value: the raw
4010 // out-of-range value survives until the next propagate().
4011 assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4012 }
4013
4014 #[test]
4015 /// Checks conformance of the initial value at propagation rather than attachment.
4016 fn propagate_after_add_filter_conforms_the_initial_value() {
4017 let mut sheet = Sheet::new();
4018 let a = sheet.add_cell(500_i32);
4019 sheet
4020 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4021 .unwrap();
4022 assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4023 sheet.propagate().unwrap();
4024 // `a` has no filter args and belongs to no relationship — this is the ordinary
4025 // first-round case of Task 1's fix, not a special "cold start" path.
4026 assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4027 }
4028
4029 #[test]
4030 /// Checks rejection of a second filter on the same cell.
4031 fn add_filter_returns_invalid_filter_when_cell_already_has_a_filter() {
4032 let mut sheet = Sheet::new();
4033 let a = sheet.add_cell(5_i32);
4034 sheet
4035 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4036 .unwrap();
4037 let result = sheet.add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)));
4038 assert!(matches!(result, Err(Error::InvalidFilter)));
4039 }
4040
4041 #[test]
4042 /// Checks rejection of a filter declaring the wrong governed-value type.
4043 fn add_filter_returns_invalid_filter_for_mismatched_value_type() {
4044 let mut sheet = Sheet::new();
4045 let a = sheet.add_cell(5_i32);
4046 let result = sheet.add_filter(a, Filter::from_fn_0(|x: &f64| Ok(*x)));
4047 assert!(matches!(result, Err(Error::InvalidFilter)));
4048 }
4049
4050 #[test]
4051 /// Checks rejection of a filter's direct dynamic self-dependency.
4052 fn add_filter_returns_invalid_filter_when_args_name_the_filtered_cell_itself() {
4053 let mut sheet = Sheet::new();
4054 let a = sheet.add_cell(5_i32);
4055 let result = sheet.add_filter(
4056 a,
4057 Filter::from_fn_1(a, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4058 );
4059 assert!(matches!(result, Err(Error::InvalidFilter)));
4060 }
4061
4062 #[test]
4063 /// Checks filter attachment rejection for an unknown argument.
4064 fn add_filter_returns_invalid_id_for_missing_arg_cell() {
4065 let mut sheet = Sheet::new();
4066 let a = sheet.add_cell(5_i32);
4067 let result = sheet.add_filter(
4068 a,
4069 Filter::from_fn_1(CellId::default(), |x: &i32, bound: &i32| {
4070 Ok((*x).min(*bound))
4071 }),
4072 );
4073 assert!(matches!(result, Err(Error::InvalidId)));
4074 }
4075
4076 #[test]
4077 /// Checks filter attachment rejection for an incompatible argument type.
4078 fn add_filter_returns_type_mismatch_for_wrong_arg_cell_type() {
4079 let mut sheet = Sheet::new();
4080 let a = sheet.add_cell(5_i32);
4081 let bound = sheet.add_cell(1.0_f64); // wrong type: filter declares i32
4082 let result = sheet.add_filter(
4083 a,
4084 Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4085 );
4086 assert!(matches!(result, Err(Error::TypeMismatch { .. })));
4087 }
4088
4089 #[test]
4090 /// Checks filter attachment to a source-only cell.
4091 fn add_filter_succeeds_on_a_source_kind_cell() {
4092 let mut sheet = Sheet::new();
4093 let a = sheet.add_source(5_i32);
4094 assert!(
4095 sheet
4096 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 10))))
4097 .is_ok()
4098 );
4099 }
4100
4101 #[test]
4102 /// Checks that both dynamic bounds affect a binary filter's conformed value.
4103 fn from_fn_2_conforms_values_through_sheet_using_both_dynamic_arguments() {
4104 let mut sheet = Sheet::new();
4105 let a = sheet.add_cell(500_i32);
4106 let lo = sheet.add_cell(0_i32);
4107 let hi = sheet.add_cell(100_i32);
4108 sheet
4109 .add_filter(
4110 a,
4111 Filter::from_fn_2([lo, hi], |x: &i32, lo: &i32, hi: &i32| {
4112 Ok((*x).clamp(*lo, *hi))
4113 }),
4114 )
4115 .unwrap();
4116 sheet.propagate().unwrap();
4117 assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4118
4119 sheet.write(a, -10_i32).unwrap();
4120 sheet.propagate().unwrap();
4121 assert_eq!(*sheet.read::<i32>(a).unwrap(), 0);
4122 }
4123
4124 #[test]
4125 /// Checks direct source/effective publication by an unfiltered write.
4126 fn write_without_a_filter_behaves_exactly_as_before() {
4127 let mut sheet = Sheet::new();
4128 let a = sheet.add_cell(0_i32);
4129 sheet.write(a, 42_i32).unwrap();
4130 assert_eq!(*sheet.read::<i32>(a).unwrap(), 42);
4131 }
4132
4133 #[test]
4134 /// Checks preservation of the raw aspiration across deferred filtering.
4135 fn write_leaves_the_raw_value_in_source_until_propagate_conforms_it() {
4136 let mut sheet = Sheet::new();
4137 let a = sheet.add_cell(0_i32);
4138 sheet
4139 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4140 .unwrap();
4141 sheet.write(a, 500_i32).unwrap();
4142 // write() no longer runs the filter: the raw value stands until propagate().
4143 assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4144 sheet.propagate().unwrap();
4145 assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4146 }
4147
4148 #[test]
4149 /// Checks absence of diagnostics for a conforming derived value.
4150 fn propagate_reports_no_violation_when_a_derived_value_conforms() {
4151 let mut sheet = Sheet::new();
4152 let a = sheet.add_cell(10_i32);
4153 let b = sheet.add_cell(0_i32);
4154 sheet
4155 .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4156 .unwrap();
4157 sheet
4158 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4159 .unwrap();
4160 sheet.propagate().unwrap();
4161 assert_eq!(*sheet.read::<i32>(b).unwrap(), 10);
4162 assert!(sheet.last_filter_violations.is_empty());
4163 }
4164
4165 #[test]
4166 /// Checks a soft nonconformance diagnostic without rewriting a derived value.
4167 fn propagate_reports_not_conformed_when_a_derived_value_violates_its_filter() {
4168 let mut sheet = Sheet::new();
4169 let a = sheet.add_cell(60_i32);
4170 let b = sheet.add_cell(0_i32);
4171 sheet
4172 .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4173 .unwrap();
4174 sheet
4175 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4176 .unwrap();
4177 sheet.propagate().unwrap();
4178 // 60 * 2 = 120, clamp(0, 100) => 100 != 120.
4179 assert_eq!(*sheet.read::<i32>(b).unwrap(), 120);
4180 assert!(matches!(
4181 sheet.last_filter_violations.get(&b),
4182 Some(FilterViolation::NotConformed)
4183 ));
4184 }
4185
4186 #[test]
4187 /// Checks a soft callback-failure diagnostic for a derived value.
4188 fn propagate_reports_failed_when_the_filter_errors_on_a_derived_value() {
4189 let mut sheet = Sheet::new();
4190 let a = sheet.add_cell(1_i32);
4191 let b = sheet.add_cell(0_i32);
4192 // Accept exactly 0 (b's initial value) so this filter's shape is exercised
4193 // only by the relationship's derived value (1, copied from `a`), not by
4194 // anything add_filter itself does — add_filter no longer evaluates a filter's
4195 // function at all.
4196 sheet
4197 .add_filter(
4198 b,
4199 Filter::from_fn_0(|x: &i32| {
4200 if *x == 0 {
4201 Ok(*x)
4202 } else {
4203 Err(anyhow::anyhow!("cannot conform"))
4204 }
4205 }),
4206 )
4207 .unwrap();
4208 sheet
4209 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4210 .unwrap();
4211 // propagate() must not abort even though the filter errors.
4212 sheet.propagate().unwrap();
4213 assert!(matches!(
4214 sheet.last_filter_violations.get(&b),
4215 Some(FilterViolation::Failed(_))
4216 ));
4217 }
4218
4219 #[test]
4220 /// Checks a soft type-failure diagnostic for a malformed derived-value filter result.
4221 fn propagate_reports_failed_when_the_filter_returns_the_wrong_type_on_a_derived_value() {
4222 let mut sheet = Sheet::new();
4223 let a = sheet.add_cell(1_i32);
4224 let b = sheet.add_cell(0_i32);
4225 // Returns a `f64` for a nonzero derived value, tripping
4226 // propagate()'s diagnostic-phase defensive check once `a`'s value (1) is
4227 // copied into `b` this round.
4228 let filter = Filter::new(TypeId::of::<i32>(), vec![], vec![], |value, _args| {
4229 let v = *value.downcast_ref::<i32>().unwrap();
4230 if v == 0 {
4231 Ok(Box::new(v) as Box<dyn Any>)
4232 } else {
4233 Ok(Box::new(1.5_f64) as Box<dyn Any>)
4234 }
4235 });
4236 sheet.add_filter(b, filter).unwrap();
4237 sheet
4238 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4239 .unwrap();
4240 // propagate() must not abort even though the filter's function returns the
4241 // wrong type.
4242 sheet.propagate().unwrap();
4243 assert!(matches!(
4244 sheet.filter_violation(b),
4245 Some(FilterViolation::Failed(_))
4246 ));
4247 }
4248
4249 #[test]
4250 /// Checks absence of nonconformance diagnostics for a successfully conformed source.
4251 fn propagate_never_flags_a_filtered_cell_that_stayed_a_plain_source() {
4252 let mut sheet = Sheet::new();
4253 let a = sheet.add_cell(60_i32);
4254 sheet
4255 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4256 .unwrap();
4257 sheet.propagate().unwrap();
4258 assert!(sheet.last_filter_violations.is_empty());
4259 }
4260
4261 #[test]
4262 /// Checks reclamping of a source after its dynamic argument changes.
4263 fn propagate_reclamps_a_filtered_source_cell_when_its_argument_changes() {
4264 // Issue #132's exact repro.
4265 let mut sheet = Sheet::new();
4266 let a = sheet.add_cell(50_i32);
4267 let bound = sheet.add_cell(100_i32);
4268 sheet
4269 .add_filter(
4270 a,
4271 Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4272 )
4273 .unwrap();
4274 sheet.write(bound, 10_i32).unwrap();
4275 sheet.propagate().unwrap();
4276 assert_eq!(*sheet.read::<i32>(a).unwrap(), 10);
4277 }
4278
4279 #[test]
4280 /// Checks that downstream methods consume the settled conformed source.
4281 fn propagate_reclamps_before_a_relationship_consumes_the_reclamped_value() {
4282 // The inequality.adm2-shaped case: a and b are linked by a two-method mutual
4283 // relationship (b := min(a, b); a := max(a, b)); a is the currently-source cell
4284 // of the pair and is filtered against a bound that just shrank. b (derived) must
4285 // reflect the corrected a, not the pre-reclamp one, within a single propagate().
4286 //
4287 // b is created before a so that a (created later) outranks b in strength —
4288 // release::resolve keeps the higher-strength cell a source (see
4289 // release::tests::strength_prefers_the_higher_strength_cell_as_source) — making a
4290 // the source and b the derived cell of the pair, as this test needs.
4291 let mut sheet = Sheet::new();
4292 let b = sheet.add_cell(20_i32);
4293 let a = sheet.add_cell(50_i32);
4294 let bound = sheet.add_cell(100_i32);
4295 sheet
4296 .add_filter(
4297 a,
4298 Filter::from_fn_1(bound, |v: &i32, bnd: &i32| Ok((*v).min(*bnd))),
4299 )
4300 .unwrap();
4301 sheet
4302 .add_relationship(vec![
4303 Method::from_fn_2_1([a, b], b, |x: &i32, y: &i32| Ok((*x).min(*y))),
4304 Method::from_fn_2_1([a, b], a, |x: &i32, y: &i32| Ok((*x).max(*y))),
4305 ])
4306 .unwrap();
4307 sheet.propagate().unwrap();
4308
4309 sheet.write(bound, 5_i32).unwrap();
4310 sheet.propagate().unwrap();
4311
4312 // a reclamps to min(50, 5) = 5; b's method (a.min(b)) then reads the reclamped
4313 // a, not the stale 50.
4314 assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4315 assert_eq!(*sheet.read::<i32>(b).unwrap(), 5);
4316 }
4317
4318 #[test]
4319 /// Checks restoration of the stored aspiration when a dynamic bound loosens.
4320 fn filtered_source_cell_springs_back_to_its_original_value_when_a_bound_loosens() {
4321 let mut sheet = Sheet::new();
4322 let a = sheet.add_cell(50_i32);
4323 let bound = sheet.add_cell(100_i32);
4324 sheet
4325 .add_filter(
4326 a,
4327 Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4328 )
4329 .unwrap();
4330
4331 sheet.write(bound, 10_i32).unwrap();
4332 sheet.propagate().unwrap();
4333 assert_eq!(*sheet.read::<i32>(a).unwrap(), 10);
4334
4335 sheet.write(bound, 100_i32).unwrap();
4336 sheet.propagate().unwrap();
4337 // a's original 50 must survive in `source` across the whole round-trip: it
4338 // springs back once the bound loosens again, rather than staying stuck at the
4339 // intermediate clamp.
4340 assert_eq!(*sheet.read::<i32>(a).unwrap(), 50);
4341 }
4342
4343 #[test]
4344 /// Checks a soft diagnostic for a malformed successful source-filter result.
4345 fn filter_reclamp_records_failed_violation_when_the_filters_function_returns_the_wrong_type() {
4346 let mut sheet = Sheet::new();
4347 let a = sheet.add_cell(5_i32);
4348 let trigger = sheet.add_cell(0_i32);
4349 // Returns an f64 when trigger becomes non-zero,
4350 // tripping FilterReclamp's check once `trigger` is updated.
4351 let filter = Filter::new(
4352 TypeId::of::<i32>(),
4353 vec![trigger],
4354 vec![TypeId::of::<i32>()],
4355 |value, args| {
4356 let v = *value.downcast_ref::<i32>().unwrap();
4357 let t = *args[0].downcast_ref::<i32>().unwrap();
4358 if t == 0 {
4359 Ok(Box::new(v) as Box<dyn Any>)
4360 } else {
4361 Ok(Box::new(1.5_f64) as Box<dyn Any>)
4362 }
4363 },
4364 );
4365 sheet.add_filter(a, filter).unwrap();
4366
4367 // trigger changes, causing reclamp where the filter returns wrong type
4368 sheet.write(trigger, 1_i32).unwrap();
4369 sheet.propagate().unwrap();
4370
4371 assert!(matches!(
4372 sheet.filter_violation(a),
4373 Some(FilterViolation::Failed(_))
4374 ));
4375 // The wrong-type result is discarded: the cell's stored value is unchanged.
4376 assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4377 }
4378
4379 #[test]
4380 /// Checks that a failed reclamp preserves the cell and does not abort propagation.
4381 fn filter_reclamp_failure_is_recorded_without_aborting_propagate_or_changing_the_cell() {
4382 let mut sheet = Sheet::new();
4383 let a = sheet.add_cell(5_i32);
4384 let bound = sheet.add_cell(100_i32);
4385 // Accept anything up to `bound` (a's initial 5 is within bound's initial 100);
4386 // the write to `bound` below is what trips the filter's next live reclamp.
4387 sheet
4388 .add_filter(
4389 a,
4390 Filter::from_fn_1(bound, |v: &i32, b: &i32| {
4391 if *v <= *b {
4392 Ok(*v)
4393 } else {
4394 Err(anyhow::anyhow!("cannot conform"))
4395 }
4396 }),
4397 )
4398 .unwrap();
4399 sheet.write(bound, 0_i32).unwrap();
4400
4401 sheet.propagate().unwrap();
4402
4403 assert!(matches!(
4404 sheet.filter_violation(a),
4405 Some(FilterViolation::Failed(_))
4406 ));
4407 // Rejected reclamp: the cell's stored value is left completely unchanged.
4408 assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4409 }
4410
4411 #[test]
4412 /// Checks positional lookup of a filter's dynamic argument identities.
4413 fn filter_args_returns_the_filters_argument_cells() {
4414 let mut sheet = Sheet::new();
4415 let a = sheet.add_cell(5_i32);
4416 let bound = sheet.add_cell(10_i32);
4417 sheet
4418 .add_filter(
4419 a,
4420 Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4421 )
4422 .unwrap();
4423 assert_eq!(sheet.filter_args(a), Some(&[bound][..]));
4424 }
4425
4426 #[test]
4427 /// Checks absent argument metadata on an unfiltered cell.
4428 fn filter_args_returns_none_for_a_cell_with_no_filter() {
4429 let mut sheet = Sheet::new();
4430 let a = sheet.add_cell(5_i32);
4431 assert_eq!(sheet.filter_args(a), None);
4432 }
4433
4434 #[test]
4435 /// Checks absent filter arguments for an unknown cell.
4436 fn filter_args_returns_none_for_an_invalid_cell() {
4437 let sheet = Sheet::new();
4438 assert_eq!(sheet.filter_args(CellId::default()), None);
4439 }
4440
4441 #[test]
4442 /// Checks absence of filter diagnostics before propagation.
4443 fn filter_violation_returns_none_before_any_propagate() {
4444 let mut sheet = Sheet::new();
4445 let cell = sheet.add_cell(100_i32);
4446 sheet
4447 .add_filter(
4448 cell,
4449 Filter::from_fn_0(|value: &i32| Ok((*value).clamp(0, 10))),
4450 )
4451 .unwrap();
4452 assert!(sheet.filter_violation(cell).is_none());
4453 assert!(sheet.filter_violation(CellId::default()).is_none());
4454 }
4455
4456 #[test]
4457 /// Checks enumeration of a currently nonconforming filtered cell.
4458 fn filter_violated_cells_reports_a_currently_violated_filter() {
4459 let mut sheet = Sheet::new();
4460 let a = sheet.add_cell(60_i32);
4461 let b = sheet.add_cell(0_i32);
4462 sheet
4463 .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4464 .unwrap();
4465 sheet
4466 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4467 .unwrap();
4468 sheet.propagate().unwrap();
4469 assert!(sheet.filter_violated_cells().any(|id| id == b));
4470 assert!(matches!(
4471 sheet.filter_violation(b),
4472 Some(FilterViolation::NotConformed)
4473 ));
4474 }
4475
4476 #[test]
4477 /// Checks an empty diagnostic-contributor set when all filters conform.
4478 fn filter_violation_cells_is_empty_when_nothing_is_violated() {
4479 let mut sheet = Sheet::new();
4480 let a = sheet.add_cell(10_i32);
4481 sheet
4482 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4483 .unwrap();
4484 sheet.propagate().unwrap();
4485 assert!(sheet.filter_violation_cells().is_empty());
4486 }
4487
4488 #[test]
4489 /// Checks root contributors of a nonconforming derived value.
4490 fn filter_violation_cells_includes_root_causes_of_a_violation() {
4491 let mut sheet = Sheet::new();
4492 let a = sheet.add_cell(60_i32);
4493 let bound = sheet.add_cell(100_i32);
4494 let b = sheet.add_cell(0_i32);
4495 sheet
4496 .add_filter(
4497 b,
4498 Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4499 )
4500 .unwrap();
4501 sheet
4502 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4503 .unwrap();
4504 sheet.propagate().unwrap();
4505 let violation_cells = sheet.filter_violation_cells();
4506 // `b` is forced (its relationship has only one method), so — mirroring
4507 // `contributing_cells`'s existing semantics — it is `a` and `bound` that
4508 // appear as the upstream root causes, not `b` itself. `b`'s own membership
4509 // is already answered by `filter_violated_cells()`, tested separately above.
4510 assert!(violation_cells.contains(&a));
4511 assert!(violation_cells.contains(&bound));
4512 }
4513
4514 #[test]
4515 /// Checks root contributors of a failed filter diagnostic.
4516 fn filter_violation_cells_includes_root_causes_of_a_failed_violation() {
4517 let mut sheet = Sheet::new();
4518 let a = sheet.add_cell(1_i32);
4519 let bound = sheet.add_cell(100_i32);
4520 let b = sheet.add_cell(0_i32);
4521 // Errors on a nonzero input so the relationship's derived value (copied from `a`) trips a
4522 // `Failed` violation instead of `NotConformed`.
4523 sheet
4524 .add_filter(
4525 b,
4526 Filter::from_fn_1(bound, |x: &i32, _bound: &i32| {
4527 if *x == 0 {
4528 Ok(*x)
4529 } else {
4530 Err(anyhow::anyhow!("cannot conform"))
4531 }
4532 }),
4533 )
4534 .unwrap();
4535 sheet
4536 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4537 .unwrap();
4538 sheet.propagate().unwrap();
4539 assert!(matches!(
4540 sheet.filter_violation(b),
4541 Some(FilterViolation::Failed(_))
4542 ));
4543 let violation_cells = sheet.filter_violation_cells();
4544 // Mirroring the `NotConformed` case above: `b` is forced, so `a` and `bound`
4545 // are the upstream root causes, not `b` itself.
4546 assert!(violation_cells.contains(&a));
4547 assert!(violation_cells.contains(&bound));
4548 }
4549
4550 #[test]
4551 /// Checks reverse lookup of filter-argument dependents.
4552 fn filter_dependents_returns_the_cells_whose_filter_references_this_one() {
4553 let mut sheet = Sheet::new();
4554 let bound = sheet.add_cell(10_i32);
4555 let a = sheet.add_cell(5_i32);
4556 sheet
4557 .add_filter(
4558 a,
4559 Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4560 )
4561 .unwrap();
4562 assert_eq!(sheet.filter_dependents(bound), &[a]);
4563 }
4564
4565 #[test]
4566 /// Checks empty reverse-filter membership for an unreferenced cell.
4567 fn filter_dependents_is_empty_for_a_cell_no_filter_references() {
4568 let mut sheet = Sheet::new();
4569 let bound = sheet.add_cell(10_i32);
4570 let a = sheet.add_cell(5_i32);
4571 sheet
4572 .add_filter(a, Filter::from_fn_0(|v: &i32| Ok((*v).clamp(0, 100))))
4573 .unwrap();
4574 assert!(sheet.filter_dependents(bound).is_empty());
4575 }
4576
4577 #[test]
4578 /// Checks empty reverse-filter membership for an unknown cell.
4579 fn filter_dependents_is_empty_for_an_invalid_cell() {
4580 let sheet = Sheet::new();
4581 assert!(sheet.filter_dependents(CellId::default()).is_empty());
4582 }
4583
4584 #[test]
4585 /// Checks complete reverse membership when several filters share an argument.
4586 fn filter_dependents_aggregates_multiple_dependents_of_the_same_argument() {
4587 let mut sheet = Sheet::new();
4588 let bound = sheet.add_cell(10_i32);
4589 let a = sheet.add_cell(5_i32);
4590 let b = sheet.add_cell(5_i32);
4591 sheet
4592 .add_filter(
4593 a,
4594 Filter::from_fn_1(bound, |v: &i32, bd: &i32| Ok((*v).min(*bd))),
4595 )
4596 .unwrap();
4597 sheet
4598 .add_filter(
4599 b,
4600 Filter::from_fn_1(bound, |v: &i32, bd: &i32| Ok((*v).min(*bd))),
4601 )
4602 .unwrap();
4603 let dependents = sheet.filter_dependents(bound);
4604 assert_eq!(dependents.len(), 2);
4605 assert!(dependents.contains(&a));
4606 assert!(dependents.contains(&b));
4607 }
4608
4609 #[test]
4610 /// Checks absent presentation metadata on an unfiltered cell.
4611 fn filter_kind_returns_none_for_a_cell_with_no_filter() {
4612 let mut sheet = Sheet::new();
4613 let a = sheet.add_cell(0_i32);
4614 assert!(sheet.filter_kind(a).is_none());
4615 }
4616
4617 #[test]
4618 /// Checks opaque metadata for an ordinary filter.
4619 fn filter_kind_returns_opaque_for_a_plain_filter() {
4620 let mut sheet = Sheet::new();
4621 let a = sheet.add_cell(0_i32);
4622 sheet
4623 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4624 .unwrap();
4625 assert!(matches!(sheet.filter_kind(a), Some(FilterKind::Opaque)));
4626 }
4627
4628 #[test]
4629 /// Checks range metadata for a range filter.
4630 fn filter_kind_returns_range_for_a_range_filter() {
4631 let mut sheet = Sheet::new();
4632 let a = sheet.add_cell(0_i32);
4633 let filter = Filter::range(
4634 TypeId::of::<i32>(),
4635 vec![],
4636 vec![],
4637 |value, _args| Ok(Box::new(*value.downcast_ref::<i32>().unwrap()) as Box<dyn Any>),
4638 |_args| {
4639 Some((
4640 Box::new(0i32) as Box<dyn Any>,
4641 Box::new(100i32) as Box<dyn Any>,
4642 ))
4643 },
4644 );
4645 sheet.add_filter(a, filter).unwrap();
4646 assert!(matches!(
4647 sheet.filter_kind(a),
4648 Some(FilterKind::Range { .. })
4649 ));
4650 }
4651
4652 #[test]
4653 /// Checks live range-bound values read from dynamic argument cells.
4654 fn filter_range_returns_live_bounds_from_argument_cells() {
4655 let mut sheet = Sheet::new();
4656 let a = sheet.add_cell(0_i32);
4657 let lo = sheet.add_cell(0_i32);
4658 let hi = sheet.add_cell(100_i32);
4659 let filter = Filter::range(
4660 TypeId::of::<i32>(),
4661 vec![lo, hi],
4662 vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
4663 |value, args| {
4664 let v = *value.downcast_ref::<i32>().unwrap();
4665 let lo = *args[0].downcast_ref::<i32>().unwrap();
4666 let hi = *args[1].downcast_ref::<i32>().unwrap();
4667 Ok(Box::new(v.clamp(lo, hi)) as Box<dyn Any>)
4668 },
4669 |args| {
4670 Some((
4671 Box::new(*args[0].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4672 Box::new(*args[1].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4673 ))
4674 },
4675 );
4676 sheet.add_filter(a, filter).unwrap();
4677 assert_eq!(sheet.filter_range::<i32>(a), Some((0, 100)));
4678 sheet.write(hi, 10_i32).unwrap();
4679 assert_eq!(sheet.filter_range::<i32>(a), Some((0, 10)));
4680 }
4681
4682 #[test]
4683 /// Checks range introspection of a relationship-derived bound.
4684 fn filter_range_reflects_a_bound_derived_by_a_relationship_not_just_a_direct_write() {
4685 // `hi` isn't itself written — its value is derived from `hi_source` via a relationship —
4686 // exercising `filter_range`'s use of `effective()` (which sees a relationship's derived
4687 // override), not just `source`.
4688 let mut sheet = Sheet::new();
4689 let a = sheet.add_cell(0_i32);
4690 let lo = sheet.add_cell(0_i32);
4691 let hi = sheet.add_cell(100_i32);
4692 let hi_source = sheet.add_cell(100_i32);
4693 sheet
4694 .add_relationship(vec![Method::from_fn_1_1(hi_source, hi, |v: &i32| Ok(*v))])
4695 .unwrap();
4696 let filter = Filter::range(
4697 TypeId::of::<i32>(),
4698 vec![lo, hi],
4699 vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
4700 |value, args| {
4701 let v = *value.downcast_ref::<i32>().unwrap();
4702 let lo = *args[0].downcast_ref::<i32>().unwrap();
4703 let hi = *args[1].downcast_ref::<i32>().unwrap();
4704 Ok(Box::new(v.clamp(lo, hi)) as Box<dyn Any>)
4705 },
4706 |args| {
4707 Some((
4708 Box::new(*args[0].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4709 Box::new(*args[1].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4710 ))
4711 },
4712 );
4713 sheet.add_filter(a, filter).unwrap();
4714 sheet.write(hi_source, 20_i32).unwrap();
4715 sheet.propagate().unwrap();
4716 assert_eq!(sheet.filter_range::<i32>(a), Some((0, 20)));
4717 }
4718
4719 #[test]
4720 /// Checks absence of range bounds for an opaque filter.
4721 fn filter_range_returns_none_for_an_opaque_filter() {
4722 let mut sheet = Sheet::new();
4723 let a = sheet.add_cell(0_i32);
4724 sheet
4725 .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4726 .unwrap();
4727 assert!(sheet.filter_range::<i32>(a).is_none());
4728 }
4729
4730 #[test]
4731 /// Attaches each check independently and returns IDs in registration order.
4732 fn add_requirement_returns_distinct_ids_on_same_cell() {
4733 let mut sheet = Sheet::new();
4734 let a = sheet.add_cell(5_i32);
4735 let first = sheet
4736 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4737 .unwrap();
4738 let result = sheet.add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x < 100)));
4739 let second = result.unwrap();
4740 assert_ne!(first, second);
4741 assert_eq!(sheet.cell_requirements(a), Some([first, second].as_slice()));
4742 }
4743
4744 #[test]
4745 /// Reports an ordinary cell's false requirement after propagation.
4746 fn add_requirement_defers_false_cell_requirement_until_propagation() {
4747 let mut sheet = Sheet::new();
4748 let a = sheet.add_cell(-5_i32);
4749 let requirement = sheet
4750 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4751 .unwrap();
4752 sheet.propagate().unwrap();
4753 assert!(!sheet.cell_requirements_valid(a));
4754 assert_eq!(
4755 sheet.violated_requirements(a).collect::<Vec<_>>(),
4756 [requirement]
4757 );
4758 }
4759
4760 #[test]
4761 /// Reports a source cell's false requirement after propagation.
4762 fn add_requirement_defers_false_source_requirement_until_propagation() {
4763 let mut sheet = Sheet::new();
4764 let a = sheet.add_source(-5_i32);
4765 let requirement = sheet
4766 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4767 .unwrap();
4768 sheet.propagate().unwrap();
4769 assert!(!sheet.cell_requirements_valid(a));
4770 assert_eq!(
4771 sheet.violated_requirements(a).collect::<Vec<_>>(),
4772 [requirement]
4773 );
4774 }
4775
4776 #[test]
4777 /// Propagates requirement-evaluation failure during propagation.
4778 fn add_requirement_defers_evaluation_error_until_propagation() {
4779 let mut sheet = Sheet::new();
4780 let a = sheet.add_cell(5_i32);
4781 sheet
4782 .add_requirement(
4783 a,
4784 Requirement::from_fn_1(a, |_: &i32| Err(anyhow::anyhow!("boom"))),
4785 )
4786 .unwrap();
4787 assert!(matches!(
4788 sheet.propagate(),
4789 Err(Error::MethodFailed { sites, .. }) if sites == [ErrorSite::Cell(a)]
4790 ));
4791 }
4792
4793 #[test]
4794 /// Checks lookup of the requirement attached to a cell.
4795 fn cell_has_the_requirement_it_was_given() {
4796 let mut sheet = Sheet::new();
4797 let a = sheet.add_cell(5_i32);
4798 let rid = sheet
4799 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4800 .unwrap();
4801 assert_eq!(sheet.cells[a].requirements, vec![rid]);
4802 }
4803
4804 #[test]
4805 /// Checks that Out declaration returns its governed cell identity.
4806 fn add_out_returns_the_cell_id_directly() {
4807 let mut sheet = Sheet::new();
4808 let width = sheet.add_cell(4_i32);
4809 let height = sheet.add_cell(5_i32);
4810 let area = sheet.add_cell(0_i32);
4811 let cell = sheet
4812 .add_out(
4813 Method::from_fn_2_1([width, height], area, |w: &i32, h: &i32| Ok(w * h)),
4814 vec![],
4815 )
4816 .unwrap();
4817 assert_eq!(cell, area);
4818 assert_eq!(sheet.cell_kind(area), Some(CellKind::Out));
4819 }
4820
4821 #[test]
4822 /// Checks downstream use of a fixed Out cell as an ordinary input.
4823 fn out_cell_is_referenceable_as_another_relationships_input() {
4824 let mut sheet = Sheet::new();
4825 let width = sheet.add_cell(4_i32);
4826 let height = sheet.add_cell(5_i32);
4827 let area = sheet.add_cell(0_i32);
4828 let doubled = sheet.add_cell(0_i32);
4829 sheet
4830 .add_out(
4831 Method::from_fn_2_1([width, height], area, |w: &i32, h: &i32| Ok(w * h)),
4832 vec![],
4833 )
4834 .unwrap();
4835 let result =
4836 sheet.add_relationship(vec![Method::from_fn_1_1(
4837 area,
4838 doubled,
4839 |a: &i32| Ok(a * 2),
4840 )]);
4841 assert!(result.is_ok());
4842 }
4843
4844 #[test]
4845 /// Checks use of a fixed Out cell as a conditional subject.
4846 fn out_cell_is_referenceable_as_a_conditional_match_subject() {
4847 let mut sheet = Sheet::new();
4848 let flag = sheet.add_cell(0_i32);
4849 let derived_flag = sheet.add_cell(0_i32);
4850 sheet
4851 .add_out(
4852 Method::from_fn_1_1(flag, derived_flag, |f: &i32| Ok(*f)),
4853 vec![],
4854 )
4855 .unwrap();
4856 let result = sheet.add_conditional(
4857 MatchExpr::cell(derived_flag),
4858 Vec::<(Vec<i32>, Vec<RelationshipId>)>::new(),
4859 vec![],
4860 );
4861 assert!(result.is_ok());
4862 }
4863
4864 #[test]
4865 /// Checks rejection of a second fixed writer for an Out cell.
4866 fn add_out_returns_invalid_cell_kind_for_a_second_writer() {
4867 let mut sheet = Sheet::new();
4868 let width = sheet.add_cell(4_i32);
4869 let height = sheet.add_cell(5_i32);
4870 let area = sheet.add_cell(0_i32);
4871 sheet
4872 .add_out(Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)), vec![])
4873 .unwrap();
4874 let result = sheet.add_out(Method::from_fn_1_1(height, area, |h: &i32| Ok(*h)), vec![]);
4875 assert!(matches!(result, Err(Error::InvalidCellKind { .. })));
4876 }
4877
4878 #[test]
4879 /// Checks legal Out promotion of a previously input-only cell.
4880 fn add_out_succeeds_when_target_cell_was_previously_used_only_as_an_input() {
4881 let mut sheet = Sheet::new();
4882 let a = sheet.add_cell(1_i32);
4883 let b = sheet.add_cell(2_i32);
4884 sheet
4885 .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4886 .unwrap();
4887 let c = sheet.add_cell(0_i32);
4888 sheet
4889 .add_out(
4890 Method::from_fn_1_1(c, a, |x: &i32| {
4891 x.checked_mul(2).ok_or_else(|| anyhow::anyhow!("overflow"))
4892 }),
4893 vec![],
4894 )
4895 .unwrap();
4896 assert_eq!(sheet.cell_kind(a), Some(CellKind::Out));
4897 sheet.write(c, 9_i32).unwrap();
4898 sheet.propagate().unwrap();
4899 assert_eq!(*sheet.read::<i32>(a).unwrap(), 18);
4900 assert_eq!(*sheet.read::<i32>(b).unwrap(), 18);
4901 }
4902
4903 #[test]
4904 /// Checks deferred requirement evaluation on a newly declared Out cell.
4905 fn add_out_with_a_requirement_that_would_fail_still_succeeds_and_propagate_reports_it() {
4906 let mut sheet = Sheet::new();
4907 let width = sheet.add_cell(4_i32);
4908 let area = sheet.add_cell(0_i32);
4909 let area_cell = sheet
4910 .add_out(
4911 Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)),
4912 vec![Requirement::from_fn_1(area, |a: &i32| Ok(*a > 100))],
4913 )
4914 .unwrap();
4915 sheet.propagate().unwrap();
4916 assert!(!sheet.cell_requirements_valid(area_cell));
4917 assert_eq!(sheet.violated_requirements(area_cell).count(), 1);
4918 }
4919
4920 #[test]
4921 /// Checks exclusion of ordinary cells from Out enumeration.
4922 fn out_cells_iterates_only_out_kind_cells() {
4923 let mut sheet = Sheet::new();
4924 let a = sheet.add_cell(1_i32);
4925 let b = sheet.add_cell(0_i32);
4926 let out_id = sheet
4927 .add_out(Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)), vec![])
4928 .unwrap();
4929 let ids: Vec<CellId> = sheet.out_cells().collect();
4930 assert_eq!(ids, vec![out_id]);
4931 }
4932
4933 #[test]
4934 /// Checks legal diagnostic-filter attachment to a fixed Out cell.
4935 fn add_filter_succeeds_on_a_real_out_kind_cell() {
4936 let mut sheet = Sheet::new();
4937 let width = sheet.add_cell(4_i32);
4938 let area = sheet.add_cell(0_i32);
4939 let out_cell = sheet
4940 .add_out(Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)), vec![])
4941 .unwrap();
4942 let result = sheet.add_filter(out_cell, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 10))));
4943 assert!(result.is_ok());
4944 }
4945
4946 #[test]
4947 /// Checks relevance contributors for a requirement on an ordinary cell.
4948 fn requirement_relevant_cells_covers_a_plain_cells_requirement_too() {
4949 let mut sheet = Sheet::new();
4950 let a = sheet.add_cell(5_i32);
4951 sheet
4952 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4953 .unwrap();
4954 sheet.propagate().unwrap();
4955 assert!(sheet.requirement_relevant_cells().contains(&a));
4956 }
4957
4958 #[test]
4959 /// Checks violation contributors for a failed requirement on an ordinary cell.
4960 fn requirement_violation_cells_covers_a_plain_cells_violation_too() {
4961 let mut sheet = Sheet::new();
4962 let a = sheet.add_cell(200_i32);
4963 sheet
4964 .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 100)))
4965 .unwrap();
4966 sheet.write(a, 5_i32).unwrap();
4967 sheet.propagate().unwrap();
4968 assert!(sheet.requirement_violation_cells().contains(&a));
4969 }
4970}