1use std::collections::{HashMap, VecDeque};
15use std::error::Error;
16use std::fmt;
17
18use formualizer_common::{
19 CellAddress, ExcelError, ExcelErrorKind, LiteralValue, RangeAddress, RangeArea, SheetId,
20};
21use formualizer_parse::parser::{
22 ASTNode, ReferenceType, SpecialItem, TableReference, TableSpecifier,
23};
24use rustc_hash::FxHashMap;
25
26use crate::engine::named_range::NamedDefinition;
27use crate::engine::refs;
28use crate::engine::used_extent::{
29 ExtentPolicy, OpenRangeBounds, ResolvedExtent, resolve_used_extent,
30};
31use crate::engine::{Engine, VertexId, VertexKind};
32use crate::reference::{CellRef, Coord};
33use crate::traits::EvaluationContext;
34
35const DEFAULT_MAX_LINKS: u32 = 256;
36const DEFAULT_MAX_WORK: u64 = 100_000;
37
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
42pub struct StateStamp {
43 pub mutation_revision: u64,
44 pub recalc_epoch: u64,
45}
46
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
49#[non_exhaustive]
50pub enum Staleness {
51 Current,
52 Dirty,
53 NeverEvaluated,
54}
55
56#[cfg_attr(feature = "serde", derive(serde::Serialize))]
62#[derive(Clone, Debug, Eq, PartialEq, Hash)]
63#[non_exhaustive]
64pub enum SpillRole {
65 Anchor { extent: RangeAddress },
66 Member { anchor: CellAddress },
67}
68
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70#[derive(Clone, Debug, PartialEq)]
71#[non_exhaustive]
72pub struct CellSnapshot {
73 pub address: CellAddress,
74 pub formula: Option<String>,
75 pub value: Option<LiteralValue>,
76 pub value_included: bool,
77 pub staleness: Staleness,
78 pub volatile: bool,
79 pub spill: Option<SpillRole>,
82}
83
84#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92#[derive(Clone, Debug, PartialEq)]
93#[non_exhaustive]
94pub struct CellResultState {
95 pub address: CellAddress,
96 pub has_formula: bool,
97 pub value: Option<LiteralValue>,
98 pub staleness: Staleness,
99 pub spill: Option<SpillRole>,
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103#[derive(Clone, Debug, PartialEq)]
104#[non_exhaustive]
105pub struct CellSnapshotReport {
106 pub stamp: StateStamp,
107 pub cell: CellSnapshot,
108}
109
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111#[derive(Clone, Debug, PartialEq)]
112#[non_exhaustive]
113pub enum NameResolution {
114 Cell(CellAddress),
115 Range {
116 declared: RangeArea,
117 resolved: Option<RangeAddress>,
118 },
119 Literal(LiteralValue),
120 Formula {
121 formula: String,
122 value: Option<LiteralValue>,
123 },
124 Unresolved,
125}
126
127#[cfg_attr(feature = "serde", derive(serde::Serialize))]
128#[derive(Clone, Debug, PartialEq)]
129#[non_exhaustive]
130pub enum SemanticReference {
131 Cell(CellAddress),
132 Range {
133 declared: RangeArea,
134 resolved: Option<RangeAddress>,
135 cell_count: u64,
136 },
137 Name {
138 name: String,
139 resolution: NameResolution,
140 },
141 Table {
142 name: String,
143 specifier: String,
144 resolved: RangeAddress,
145 },
146 External {
147 raw: String,
148 },
149 Unsupported {
150 text: String,
151 reason: String,
152 },
153}
154
155#[cfg_attr(feature = "serde", derive(serde::Serialize))]
156#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
157#[non_exhaustive]
158pub enum Provenance {
159 Declared,
160 Observed,
161}
162
163#[cfg_attr(feature = "serde", derive(serde::Serialize))]
164#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
165#[non_exhaustive]
166pub enum TraceLinkKind {
167 Formula { provenance: Provenance },
168 SpillAnchor,
169 SpillReader,
170}
171
172#[cfg_attr(feature = "serde", derive(serde::Serialize))]
173#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
174#[non_exhaustive]
175pub enum LinkDisposition {
176 Expanded,
177 Convergent,
178 Cycle,
179 Elided,
180}
181
182#[cfg_attr(feature = "serde", derive(serde::Serialize))]
183#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
184#[non_exhaustive]
185pub enum OmittedCount {
186 Exact(u64),
187 AtLeast(u64),
188}
189
190#[cfg_attr(feature = "serde", derive(serde::Serialize))]
191#[derive(Clone, Debug, Default, Eq, PartialEq)]
192#[non_exhaustive]
193pub struct TruncationReport {
194 pub incomplete: bool,
195 pub omitted: Option<OmittedCount>,
199}
200
201#[cfg_attr(feature = "serde", derive(serde::Serialize))]
202#[derive(Clone, Debug, PartialEq)]
203#[non_exhaustive]
204pub struct Precedent {
205 pub reference: SemanticReference,
206 pub provenance: Provenance,
207}
208
209#[cfg_attr(feature = "serde", derive(serde::Serialize))]
210#[derive(Clone, Debug, PartialEq)]
211#[non_exhaustive]
212pub struct PrecedentReport {
213 pub stamp: StateStamp,
214 pub cell: CellAddress,
215 pub precedents: Vec<Precedent>,
216 pub truncation: TruncationReport,
217}
218
219#[cfg_attr(feature = "serde", derive(serde::Serialize))]
220#[derive(Clone, Debug, Eq, PartialEq)]
221#[non_exhaustive]
222pub struct Dependent {
223 pub cell: CellAddress,
224 pub via: Vec<CellAddress>,
228}
229
230#[cfg_attr(feature = "serde", derive(serde::Serialize))]
231#[derive(Clone, Debug, Eq, PartialEq)]
232#[non_exhaustive]
233pub struct DependentsReport {
234 pub stamp: StateStamp,
235 pub cell: CellAddress,
236 pub dependents: Vec<Dependent>,
237 pub truncation: TruncationReport,
238}
239
240#[cfg_attr(feature = "serde", derive(serde::Serialize))]
241#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
242pub struct TraceNodeId(pub u32);
243
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
246#[non_exhaustive]
247pub enum TraceDirection {
248 Precedents,
249 Dependents,
250}
251
252#[cfg_attr(feature = "serde", derive(serde::Serialize))]
253#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
254#[non_exhaustive]
255pub struct TraceLinkTarget {
256 pub node: TraceNodeId,
257 pub disposition: LinkDisposition,
258}
259
260#[cfg_attr(feature = "serde", derive(serde::Serialize))]
261#[derive(Clone, Debug, PartialEq)]
262#[non_exhaustive]
263pub struct TraceLink {
264 pub reference: SemanticReference,
265 pub kind: TraceLinkKind,
266 pub targets: Vec<TraceLinkTarget>,
267 pub omitted: Option<OmittedCount>,
268}
269
270#[cfg_attr(feature = "serde", derive(serde::Serialize))]
271#[derive(Clone, Debug, PartialEq)]
272#[non_exhaustive]
273pub struct TraceNode {
274 pub id: TraceNodeId,
275 pub cell: CellSnapshot,
276 pub links: Vec<TraceLink>,
277}
278
279#[cfg_attr(feature = "serde", derive(serde::Serialize))]
280#[derive(Clone, Debug, PartialEq)]
281#[non_exhaustive]
282pub struct TraceGraph {
283 pub stamp: StateStamp,
284 pub direction: TraceDirection,
285 pub roots: Vec<TraceNodeId>,
288 pub nodes: Vec<TraceNode>,
289 pub truncation: TruncationReport,
290}
291
292#[cfg_attr(feature = "serde", derive(serde::Serialize))]
293#[derive(Clone, Debug, PartialEq)]
294#[non_exhaustive]
295pub struct RangePage {
296 pub stamp: StateStamp,
297 pub declared: RangeArea,
298 pub resolved: Option<RangeAddress>,
299 pub total: u64,
300 pub offset: u64,
303 pub items: Vec<CellSnapshot>,
304 pub next_offset: Option<u64>,
305}
306
307#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
313#[derive(Clone, Copy, Debug, Eq, PartialEq)]
314pub struct SnapshotOptions {
315 pub include_values: bool,
316}
317
318impl Default for SnapshotOptions {
319 fn default() -> Self {
320 Self {
321 include_values: true,
322 }
323 }
324}
325
326impl SnapshotOptions {
327 pub fn with_include_values(mut self, include_values: bool) -> Self {
328 self.include_values = include_values;
329 self
330 }
331}
332
333#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
339#[derive(Clone, Copy, Debug, Eq, PartialEq)]
340pub struct PrecedentOptions {
341 pub max_links: u32,
342 pub max_work: u64,
343}
344
345impl Default for PrecedentOptions {
346 fn default() -> Self {
347 Self {
348 max_links: DEFAULT_MAX_LINKS,
349 max_work: DEFAULT_MAX_WORK,
350 }
351 }
352}
353
354impl PrecedentOptions {
355 pub fn with_max_links(mut self, max_links: u32) -> Self {
356 self.max_links = max_links;
357 self
358 }
359
360 pub fn with_max_work(mut self, max_work: u64) -> Self {
361 self.max_work = max_work;
362 self
363 }
364}
365
366#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
372#[derive(Clone, Copy, Debug, Eq, PartialEq)]
373pub struct DependentsOptions {
374 pub max_results: u32,
379 pub max_work: u64,
380}
381
382impl Default for DependentsOptions {
383 fn default() -> Self {
384 Self {
385 max_results: 256,
386 max_work: DEFAULT_MAX_WORK,
387 }
388 }
389}
390
391impl DependentsOptions {
392 pub fn with_max_results(mut self, max_results: u32) -> Self {
393 self.max_results = max_results;
394 self
395 }
396
397 pub fn with_max_work(mut self, max_work: u64) -> Self {
398 self.max_work = max_work;
399 self
400 }
401}
402
403#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
409#[derive(Clone, Copy, Debug, Eq, PartialEq)]
410pub struct TraceOptions {
411 pub direction: TraceDirection,
412 pub max_depth: u32,
415 pub max_nodes: u32,
418 pub max_links: u32,
419 pub max_work: u64,
420 pub range_member_budget: u32,
421 pub include_values: bool,
422}
423
424impl Default for TraceOptions {
425 fn default() -> Self {
426 Self {
427 direction: TraceDirection::Precedents,
428 max_depth: 6,
429 max_nodes: 512,
430 max_links: 1_024,
431 max_work: DEFAULT_MAX_WORK,
432 range_member_budget: 256,
433 include_values: true,
434 }
435 }
436}
437
438impl TraceOptions {
439 pub fn with_direction(mut self, direction: TraceDirection) -> Self {
440 self.direction = direction;
441 self
442 }
443
444 pub fn with_max_depth(mut self, max_depth: u32) -> Self {
445 self.max_depth = max_depth;
446 self
447 }
448
449 pub fn with_max_nodes(mut self, max_nodes: u32) -> Self {
450 self.max_nodes = max_nodes;
451 self
452 }
453
454 pub fn with_max_links(mut self, max_links: u32) -> Self {
455 self.max_links = max_links;
456 self
457 }
458
459 pub fn with_max_work(mut self, max_work: u64) -> Self {
460 self.max_work = max_work;
461 self
462 }
463
464 pub fn with_range_member_budget(mut self, range_member_budget: u32) -> Self {
465 self.range_member_budget = range_member_budget;
466 self
467 }
468
469 pub fn with_include_values(mut self, include_values: bool) -> Self {
470 self.include_values = include_values;
471 self
472 }
473}
474
475#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
481#[derive(Clone, Copy, Debug, Eq, PartialEq)]
482pub struct RangePageOptions {
483 pub offset: u64,
484 pub limit: u32,
485 pub include_values: bool,
486 pub expected_stamp: Option<StateStamp>,
487}
488
489impl Default for RangePageOptions {
490 fn default() -> Self {
491 Self {
492 offset: 0,
493 limit: 100,
494 include_values: true,
495 expected_stamp: None,
496 }
497 }
498}
499
500impl RangePageOptions {
501 pub fn with_offset(mut self, offset: u64) -> Self {
502 self.offset = offset;
503 self
504 }
505
506 pub fn with_limit(mut self, limit: u32) -> Self {
507 self.limit = limit;
508 self
509 }
510
511 pub fn with_include_values(mut self, include_values: bool) -> Self {
512 self.include_values = include_values;
513 self
514 }
515
516 pub fn with_expected_stamp(mut self, expected_stamp: StateStamp) -> Self {
517 self.expected_stamp = Some(expected_stamp);
518 self
519 }
520}
521
522#[cfg_attr(feature = "serde", derive(serde::Serialize))]
523#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
524#[non_exhaustive]
525pub enum InspectionUnavailableReason {
526 DeferredDependencyGraph,
527 DependencyAuthorityUnavailable,
530}
531
532#[cfg_attr(feature = "serde", derive(serde::Serialize))]
533#[derive(Clone, Debug, Eq, PartialEq)]
534#[non_exhaustive]
535pub enum InspectError {
536 SheetNotFound {
537 sheet: String,
538 },
539 InvalidAddress {
540 message: String,
541 },
542 InvalidOptions {
543 message: String,
544 },
545 DependencyStateUnavailable {
546 reason: InspectionUnavailableReason,
547 },
548 RevisionMismatch {
549 expected: StateStamp,
550 actual: StateStamp,
551 },
552 ResourceExhausted {
554 resource: &'static str,
555 },
556}
557
558impl fmt::Display for InspectError {
559 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560 match self {
561 Self::SheetNotFound { sheet } => write!(f, "sheet not found: {sheet}"),
562 Self::InvalidAddress { message } => write!(f, "invalid address: {message}"),
563 Self::InvalidOptions { message } => write!(f, "invalid inspection options: {message}"),
564 Self::DependencyStateUnavailable { reason } => {
565 write!(f, "dependency state unavailable: {reason:?}")
566 }
567 Self::RevisionMismatch { expected, actual } => write!(
568 f,
569 "inspection revision mismatch: expected {expected:?}, actual {actual:?}"
570 ),
571 Self::ResourceExhausted { resource } => {
572 write!(f, "inspection resource exhausted: {resource}")
573 }
574 }
575 }
576}
577
578impl Error for InspectError {}
579
580#[cfg_attr(feature = "serde", derive(serde::Serialize))]
581#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
582pub(crate) struct CellKey {
583 sheet_id: SheetId,
584 row0: u32,
585 col0: u32,
586}
587
588#[derive(Clone, Debug)]
589pub(crate) struct FormulaView {
590 ast: ASTNode,
591 volatile: bool,
592 dirty: bool,
593}
594
595#[derive(Clone, Debug)]
596pub(crate) enum InternalSpillRole {
597 Anchor { extent: RangeAddress },
598 Member { anchor: CellKey },
599}
600
601#[derive(Clone, Copy, Debug, Eq, PartialEq)]
602pub(crate) enum QueryCompleteness {
603 Complete,
604 Incomplete,
605}
606
607#[derive(Clone, Copy, Debug)]
608pub(crate) struct WorkBudget {
609 remaining: u64,
610}
611
612impl WorkBudget {
613 fn new(limit: u64) -> Self {
614 Self { remaining: limit }
615 }
616
617 fn charge(&mut self) -> bool {
618 if self.remaining == 0 {
619 false
620 } else {
621 self.remaining -= 1;
622 true
623 }
624 }
625}
626
627pub(crate) trait ReferenceVisitor {
628 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool;
630}
631
632pub(crate) trait DependentVisitor {
633 fn visit(&mut self, dependent: CellKey) -> bool;
635}
636
637pub(crate) trait InspectSource {
641 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError>;
642 fn visit_declared_references(
643 &self,
644 cell: CellKey,
645 visitor: &mut dyn ReferenceVisitor,
646 ) -> Result<(), InspectError>;
647 fn visit_dependents_covering(
648 &self,
649 cell: CellKey,
650 budget: &mut WorkBudget,
651 visitor: &mut dyn DependentVisitor,
652 ) -> Result<QueryCompleteness, InspectError>;
653 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole>;
654}
655
656struct LegacyInspectSource<'a, R> {
657 engine: &'a Engine<R>,
658}
659
660impl<R: EvaluationContext> LegacyInspectSource<'_, R> {
661 fn cell_ref(&self, key: CellKey) -> CellRef {
662 CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true))
663 }
664}
665
666fn visit_formula_ast_references(
667 ast: &ASTNode,
668 visitor: &mut dyn ReferenceVisitor,
669) -> Result<(), InspectError> {
670 struct Context<'a> {
671 visitor: &'a mut dyn ReferenceVisitor,
672 stopped: bool,
673 }
674 fn local_bindings(_: &Context<'_>, name: &str, _: usize) -> refs::LocalBindingStyle {
675 match name
676 .rsplit('.')
677 .next()
678 .unwrap_or(name)
679 .to_ascii_uppercase()
680 .as_str()
681 {
682 "LET" => refs::LocalBindingStyle::LocalBindingPairs,
683 "LAMBDA" => refs::LocalBindingStyle::LambdaParameters,
684 _ => refs::LocalBindingStyle::None,
685 }
686 }
687 fn consume(
688 context: &mut Context<'_>,
689 reference: refs::SemanticReference<'_>,
690 ) -> Result<(), ExcelError> {
691 if context.visitor.visit(reference) {
692 Ok(())
693 } else {
694 context.stopped = true;
695 Err(ExcelError::new(ExcelErrorKind::NImpl)
696 .with_message("inspection visitor requested stop"))
697 }
698 }
699
700 let mut context = Context {
701 visitor,
702 stopped: false,
703 };
704 let result = refs::visit_tree_references(ast, &mut context, local_bindings, consume);
705 if context.stopped {
706 Ok(())
707 } else {
708 result.map_err(|error| InspectError::InvalidAddress {
709 message: error.to_string(),
710 })
711 }
712}
713
714impl<R: EvaluationContext> InspectSource for LegacyInspectSource<'_, R> {
715 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError> {
716 let sheet = self.engine.graph.sheet_name(cell.sheet_id);
717 let row = cell.row0 + 1;
718 let col = cell.col0 + 1;
719 if let Some(text) = self.engine.get_staged_formula_text(sheet, row, col) {
720 let text = if text.starts_with('=') {
723 text
724 } else {
725 format!("={text}")
726 };
727 let ast =
728 formualizer_parse::parse(&text).map_err(|error| InspectError::InvalidAddress {
729 message: format!("staged formula at {sheet}!R{row}C{col} is invalid: {error}"),
730 })?;
731 let volatile = self.engine.graph.fp8_parity_is_ast_volatile(&ast);
732 return Ok(Some(FormulaView {
733 ast,
734 volatile,
735 dirty: true,
736 }));
737 }
738
739 let cell_ref = self.cell_ref(cell);
740 let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref) else {
741 return Ok(None);
742 };
743 let Some(ast) = self.engine.graph.get_formula(vertex) else {
744 return Ok(None);
745 };
746 Ok(Some(FormulaView {
747 ast,
748 volatile: self.engine.graph.is_volatile(vertex),
749 dirty: self.engine.graph.is_dirty(vertex),
750 }))
751 }
752
753 fn visit_declared_references(
754 &self,
755 cell: CellKey,
756 visitor: &mut dyn ReferenceVisitor,
757 ) -> Result<(), InspectError> {
758 let Some(formula) = self.formula_at(cell)? else {
759 return Ok(());
760 };
761
762 visit_formula_ast_references(&formula.ast, visitor)
763 }
764
765 fn visit_dependents_covering(
766 &self,
767 cell: CellKey,
768 budget: &mut WorkBudget,
769 visitor: &mut dyn DependentVisitor,
770 ) -> Result<QueryCompleteness, InspectError> {
771 #[cfg(not(any(test, feature = "legacy_oracle")))]
774 let complete = {
775 let _ = (cell, budget, visitor);
776 true
777 };
778 #[cfg(any(test, feature = "legacy_oracle"))]
779 let complete = self.engine.graph.visit_range_dependents_covering_bounded(
780 cell.sheet_id,
781 cell.row0,
782 cell.col0,
783 &mut budget.remaining,
784 &mut |vertex| {
785 self.engine
786 .graph
787 .get_cell_ref(vertex)
788 .is_none_or(|cell_ref| {
789 visitor.visit(CellKey {
790 sheet_id: cell_ref.sheet_id,
791 row0: cell_ref.coord.row(),
792 col0: cell_ref.coord.col(),
793 })
794 })
795 },
796 );
797 Ok(if complete {
798 QueryCompleteness::Complete
799 } else {
800 QueryCompleteness::Incomplete
801 })
802 }
803
804 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole> {
805 let cell_ref = self.cell_ref(cell);
806 if let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref)
807 && let Some(cells) = self.engine.graph.spill_cells_for_anchor(vertex)
808 {
809 let mut bounds: Option<(u32, u32, u32, u32)> = None;
810 for member in cells {
811 bounds = Some(match bounds {
812 None => (
813 member.coord.row(),
814 member.coord.col(),
815 member.coord.row(),
816 member.coord.col(),
817 ),
818 Some((sr, sc, er, ec)) => (
819 sr.min(member.coord.row()),
820 sc.min(member.coord.col()),
821 er.max(member.coord.row()),
822 ec.max(member.coord.col()),
823 ),
824 });
825 }
826 if let Some((sr, sc, er, ec)) = bounds {
827 let sheet = self.engine.graph.sheet_name(cell.sheet_id).to_string();
828 return Some(InternalSpillRole::Anchor {
829 extent: RangeAddress {
830 sheet,
831 start_row: sr + 1,
832 start_col: sc + 1,
833 end_row: er + 1,
834 end_col: ec + 1,
835 },
836 });
837 }
838 }
839 let anchor = self.engine.graph.spill_registry_anchor_for_cell(cell_ref)?;
840 let anchor_ref = self.engine.graph.get_cell_ref(anchor)?;
841 Some(InternalSpillRole::Member {
842 anchor: CellKey {
843 sheet_id: anchor_ref.sheet_id,
844 row0: anchor_ref.coord.row(),
845 col0: anchor_ref.coord.col(),
846 },
847 })
848 }
849}
850
851fn merge_omitted(target: &mut Option<OmittedCount>, addition: OmittedCount) {
852 *target = Some(match (target.take(), addition) {
853 (None, value) => value,
854 (Some(OmittedCount::Exact(a)), OmittedCount::Exact(b)) => {
855 OmittedCount::Exact(a.saturating_add(b))
856 }
857 (Some(OmittedCount::Exact(a)), OmittedCount::AtLeast(b))
858 | (Some(OmittedCount::AtLeast(a)), OmittedCount::Exact(b))
859 | (Some(OmittedCount::AtLeast(a)), OmittedCount::AtLeast(b)) => {
860 OmittedCount::AtLeast(a.saturating_add(b))
861 }
862 });
863}
864
865fn address_cmp(left: &CellAddress, right: &CellAddress) -> std::cmp::Ordering {
866 left.sheet
867 .cmp(&right.sheet)
868 .then_with(|| left.row.cmp(&right.row))
869 .then_with(|| left.column.cmp(&right.column))
870}
871
872impl<R: EvaluationContext> Engine<R> {
873 fn inspect_stamp(&self) -> StateStamp {
874 StateStamp {
875 mutation_revision: self.inspection_mutation_revision(),
876 recalc_epoch: self.recalc_epoch,
877 }
878 }
879
880 fn inspect_source(&self) -> LegacyInspectSource<'_, R> {
881 LegacyInspectSource { engine: self }
882 }
883
884 fn canonical_cell(
885 &self,
886 address: &CellAddress,
887 ) -> Result<(CellKey, CellAddress), InspectError> {
888 CellAddress::new(String::new(), address.row, address.column).map_err(|error| {
891 InspectError::InvalidAddress {
892 message: error.to_string(),
893 }
894 })?;
895 let Some(sheet_id) = self.graph.sheet_id(&address.sheet) else {
896 return Err(InspectError::SheetNotFound {
897 sheet: address.sheet.clone(),
898 });
899 };
900 let canonical = CellAddress {
901 sheet: self.graph.sheet_name(sheet_id).to_string(),
902 row: address.row,
903 column: address.column,
904 };
905 Ok((
906 CellKey {
907 sheet_id,
908 row0: address.row - 1,
909 col0: address.column - 1,
910 },
911 canonical,
912 ))
913 }
914
915 fn canonical_area(&self, area: &RangeArea) -> Result<(SheetId, RangeArea), InspectError> {
916 RangeArea::new(
917 area.sheet.clone(),
918 area.start_row,
919 area.start_column,
920 area.end_row,
921 area.end_column,
922 )
923 .map_err(|error| InspectError::InvalidAddress {
924 message: error.to_string(),
925 })?;
926 let Some(sheet_id) = self.graph.sheet_id(&area.sheet) else {
927 return Err(InspectError::SheetNotFound {
928 sheet: area.sheet.clone(),
929 });
930 };
931 Ok((
932 sheet_id,
933 RangeArea {
934 sheet: self.graph.sheet_name(sheet_id).to_string(),
935 start_row: area.start_row,
936 start_column: area.start_column,
937 end_row: area.end_row,
938 end_column: area.end_column,
939 },
940 ))
941 }
942
943 fn address_for_key(&self, key: CellKey) -> CellAddress {
944 CellAddress {
945 sheet: self.graph.sheet_name(key.sheet_id).to_string(),
946 row: key.row0 + 1,
947 column: key.col0 + 1,
948 }
949 }
950
951 fn key_for_vertex(&self, vertex: VertexId) -> Option<CellKey> {
952 if !matches!(
953 self.graph.get_vertex_kind(vertex),
954 VertexKind::FormulaScalar | VertexKind::FormulaArray
955 ) {
956 return None;
957 }
958 let cell = self.graph.get_cell_ref(vertex)?;
959 Some(CellKey {
960 sheet_id: cell.sheet_id,
961 row0: cell.coord.row(),
962 col0: cell.coord.col(),
963 })
964 }
965
966 fn resolve_semantic_area(&self, area: &RangeArea) -> Option<RangeAddress> {
967 let extent = resolve_used_extent(
968 OpenRangeBounds {
969 start_row: area.start_row,
970 start_column: area.start_column,
971 end_row: area.end_row,
972 end_column: area.end_column,
973 },
974 ExtentPolicy::Semantic,
975 |first, last| self.semantic_used_rows_for_columns(&area.sheet, first, last),
976 |first, last| self.semantic_used_cols_for_rows(&area.sheet, first, last),
977 )?;
978 Some(Self::range_from_extent(&area.sheet, extent))
979 }
980
981 fn range_from_extent(sheet: &str, extent: ResolvedExtent) -> RangeAddress {
982 RangeAddress {
983 sheet: sheet.to_string(),
984 start_row: extent.start_row,
985 start_col: extent.start_column,
986 end_row: extent.end_row,
987 end_col: extent.end_column,
988 }
989 }
990
991 fn snapshot_for_key(
992 &self,
993 key: CellKey,
994 include_value: bool,
995 ) -> Result<CellSnapshot, InspectError> {
996 let source = self.inspect_source();
997 let formula = source.formula_at(key)?;
998 let address = self.address_for_key(key);
999 let cached_value = self.read_cell_value(&address.sheet, address.row, address.column);
1000 let (canonical_formula, volatile, staleness) = match formula {
1001 Some(view) => {
1002 let staleness = if cached_value.is_none() {
1007 Staleness::NeverEvaluated
1008 } else if view.dirty {
1009 Staleness::Dirty
1010 } else {
1011 Staleness::Current
1012 };
1013 (
1014 Some(formualizer_parse::pretty::canonical_formula(&view.ast)),
1015 view.volatile,
1016 staleness,
1017 )
1018 }
1019 None => (None, false, Staleness::Current),
1020 };
1021 let spill = source.spill_role(key).map(|role| match role {
1022 InternalSpillRole::Anchor { extent } => SpillRole::Anchor { extent },
1023 InternalSpillRole::Member { anchor } => SpillRole::Member {
1024 anchor: self.address_for_key(anchor),
1025 },
1026 });
1027 Ok(CellSnapshot {
1028 address,
1029 formula: canonical_formula,
1030 value: include_value.then_some(cached_value).flatten(),
1031 value_included: include_value,
1032 staleness,
1033 volatile,
1034 spill,
1035 })
1036 }
1037
1038 #[doc(hidden)]
1043 pub fn formula_calls_visibility_reducer(&self, sheet: &str, row: u32, col: u32) -> bool {
1044 let Some(sheet_id) = self.graph.sheet_id(sheet) else {
1045 return false;
1046 };
1047 let (Some(row), Some(col)) = (row.checked_sub(1), col.checked_sub(1)) else {
1048 return false;
1049 };
1050 let cell = CellRef::new(sheet_id, Coord::new(row, col, true, true));
1051 self.graph
1052 .get_vertex_for_cell(&cell)
1053 .and_then(|vertex| self.graph.formula_view(vertex))
1054 .is_some_and(|view| {
1055 self.graph
1056 .data_store()
1057 .ast_calls_visibility_reducer(view.template)
1058 })
1059 }
1060
1061 pub fn inspect_cell_result(&self, cell: &CellAddress) -> Result<CellResultState, InspectError> {
1065 let (key, address) = self.canonical_cell(cell)?;
1066 let staged = self
1067 .get_staged_formula_text(&address.sheet, address.row, address.column)
1068 .is_some();
1069 let source = self.inspect_source();
1070 let vertex = self.graph.get_vertex_for_cell(&source.cell_ref(key));
1071 let has_formula = staged || vertex.is_some_and(|v| self.graph.has_formula(v));
1072 let value = self.read_cell_value(&address.sheet, address.row, address.column);
1073 let staleness = if !has_formula {
1074 Staleness::Current
1075 } else if value.is_none() {
1076 Staleness::NeverEvaluated
1077 } else if staged || vertex.is_some_and(|v| self.graph.is_dirty(v)) {
1078 Staleness::Dirty
1079 } else {
1080 Staleness::Current
1081 };
1082 let spill = source.spill_role(key).map(|role| match role {
1083 InternalSpillRole::Anchor { extent } => SpillRole::Anchor { extent },
1084 InternalSpillRole::Member { anchor } => SpillRole::Member {
1085 anchor: self.address_for_key(anchor),
1086 },
1087 });
1088 Ok(CellResultState {
1089 address,
1090 has_formula,
1091 value,
1092 staleness,
1093 spill,
1094 })
1095 }
1096
1097 pub fn inspect_cell(
1099 &self,
1100 cell: &CellAddress,
1101 options: &SnapshotOptions,
1102 ) -> Result<CellSnapshotReport, InspectError> {
1103 let (key, _) = self.canonical_cell(cell)?;
1104 Ok(CellSnapshotReport {
1105 stamp: self.inspect_stamp(),
1106 cell: self.snapshot_for_key(key, options.include_values)?,
1107 })
1108 }
1109
1110 fn resolve_name(&self, key: CellKey, name: &str) -> NameResolution {
1111 let Some(named) = self.graph.resolve_name_entry(name, key.sheet_id) else {
1112 return NameResolution::Unresolved;
1113 };
1114 match &named.definition {
1115 NamedDefinition::Cell(cell) => NameResolution::Cell(CellAddress {
1116 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
1117 row: cell.coord.row() + 1,
1118 column: cell.coord.col() + 1,
1119 }),
1120 NamedDefinition::Range(range) => {
1121 let resolved = RangeAddress {
1122 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
1123 start_row: range.start.coord.row() + 1,
1124 start_col: range.start.coord.col() + 1,
1125 end_row: range.end.coord.row() + 1,
1126 end_col: range.end.coord.col() + 1,
1127 };
1128 NameResolution::Range {
1129 declared: RangeArea::from_finite(&resolved),
1130 resolved: Some(resolved),
1131 }
1132 }
1133 NamedDefinition::Literal(value) => NameResolution::Literal(value.clone()),
1134 NamedDefinition::Formula { ast, .. } => NameResolution::Formula {
1135 formula: formualizer_parse::pretty::canonical_formula(ast),
1136 value: self.graph.get_value(named.vertex),
1137 },
1138 }
1139 }
1140
1141 fn resolve_table_area(
1142 &self,
1143 key: CellKey,
1144 table_ref: &TableReference,
1145 ) -> Option<(String, String, RangeAddress)> {
1146 let metadata = self.table_metadata(&table_ref.name)?;
1147 let canonical_name = metadata.name.clone();
1148 let specifier = table_ref
1149 .specifier
1150 .as_ref()
1151 .map(ToString::to_string)
1152 .unwrap_or_default();
1153 let mut start_row = metadata.start_row;
1154 let mut end_row = metadata.end_row;
1155 let mut start_col = metadata.start_col;
1156 let mut end_col = metadata.end_col;
1157 let data_start = start_row + u32::from(metadata.header_row);
1158 let data_end = end_row.saturating_sub(u32::from(metadata.totals_row));
1159
1160 fn col_index(headers: &[String], name: &str) -> Option<u32> {
1161 headers
1162 .iter()
1163 .position(|header| header.eq_ignore_ascii_case(name))
1164 .and_then(|index| u32::try_from(index).ok())
1165 }
1166
1167 match table_ref.specifier.as_ref()? {
1168 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1169 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1170 start_row = data_start;
1171 end_row = data_end;
1172 }
1173 TableSpecifier::Headers | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1174 if !metadata.header_row {
1175 return None;
1176 }
1177 end_row = start_row;
1178 }
1179 TableSpecifier::Totals | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1180 if !metadata.totals_row {
1181 return None;
1182 }
1183 start_row = end_row;
1184 }
1185 TableSpecifier::Column(name) => {
1186 let index = col_index(&metadata.headers, name)?;
1187 start_col += index;
1188 end_col = start_col;
1189 start_row = data_start;
1190 end_row = data_end;
1191 }
1192 TableSpecifier::ColumnRange(first, last) => {
1193 let mut first = col_index(&metadata.headers, first)?;
1194 let mut last = col_index(&metadata.headers, last)?;
1195 if first > last {
1196 std::mem::swap(&mut first, &mut last);
1197 }
1198 start_col += first;
1199 end_col = metadata.start_col + last;
1200 start_row = data_start;
1201 end_row = data_end;
1202 }
1203 TableSpecifier::SpecialItem(SpecialItem::ThisRow)
1204 | TableSpecifier::Row(formualizer_parse::parser::TableRowSpecifier::Current) => {
1205 let row = key.row0 + 1;
1206 if row < data_start || row > data_end {
1207 return None;
1208 }
1209 start_row = row;
1210 end_row = row;
1211 }
1212 TableSpecifier::Row(_) => return None,
1213 TableSpecifier::Combination(parts) => {
1214 let mut this_row = false;
1215 let mut selected_column: Option<(u32, u32)> = None;
1216 for part in parts {
1217 match part.as_ref() {
1218 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => this_row = true,
1219 TableSpecifier::Column(name) => {
1220 let column = col_index(&metadata.headers, name)?;
1221 selected_column = Some((column, column));
1222 }
1223 TableSpecifier::ColumnRange(first, last) => {
1224 let mut first = col_index(&metadata.headers, first)?;
1225 let mut last = col_index(&metadata.headers, last)?;
1226 if first > last {
1227 std::mem::swap(&mut first, &mut last);
1228 }
1229 selected_column = Some((first, last));
1230 }
1231 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1232 start_row = data_start;
1233 end_row = data_end;
1234 }
1235 TableSpecifier::Headers
1236 | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1237 if !metadata.header_row {
1238 return None;
1239 }
1240 end_row = start_row;
1241 }
1242 TableSpecifier::Totals
1243 | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1244 if !metadata.totals_row {
1245 return None;
1246 }
1247 start_row = end_row;
1248 }
1249 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1250 TableSpecifier::Row(_) | TableSpecifier::Combination(_) => return None,
1251 }
1252 }
1253 if this_row {
1254 let row = key.row0 + 1;
1255 if row < data_start || row > data_end {
1256 return None;
1257 }
1258 start_row = row;
1259 end_row = row;
1260 }
1261 if let Some((first, last)) = selected_column {
1262 start_col = metadata.start_col + first;
1263 end_col = metadata.start_col + last;
1264 if !this_row {
1265 start_row = data_start;
1266 end_row = data_end;
1267 }
1268 }
1269 }
1270 }
1271 if start_row > end_row || start_col > end_col {
1272 return None;
1273 }
1274 Some((
1275 canonical_name,
1276 specifier,
1277 RangeAddress {
1278 sheet: metadata.sheet,
1279 start_row,
1280 start_col,
1281 end_row,
1282 end_col,
1283 },
1284 ))
1285 }
1286
1287 fn own_reference(
1288 &self,
1289 key: CellKey,
1290 reference: refs::SemanticReference<'_>,
1291 ) -> SemanticReference {
1292 match reference {
1293 refs::SemanticReference::Cell(cell) => {
1294 let sheet_id = cell
1295 .sheet
1296 .name()
1297 .and_then(|name| self.graph.sheet_id(name))
1298 .unwrap_or(key.sheet_id);
1299 SemanticReference::Cell(CellAddress {
1300 sheet: self.graph.sheet_name(sheet_id).to_string(),
1301 row: cell.row,
1302 column: cell.col,
1303 })
1304 }
1305 refs::SemanticReference::FiniteRange(range)
1306 | refs::SemanticReference::OpenRange(range) => {
1307 let sheet_id = range
1308 .sheet
1309 .name()
1310 .and_then(|name| self.graph.sheet_id(name))
1311 .unwrap_or(key.sheet_id);
1312 let declared = RangeArea {
1313 sheet: self.graph.sheet_name(sheet_id).to_string(),
1314 start_row: range.start_row,
1315 start_column: range.start_col,
1316 end_row: range.end_row,
1317 end_column: range.end_col,
1318 };
1319 let resolved = self.resolve_semantic_area(&declared);
1320 let cell_count = resolved.as_ref().map_or(0, |range| {
1321 u64::from(range.width()) * u64::from(range.height())
1322 });
1323 SemanticReference::Range {
1324 declared,
1325 resolved,
1326 cell_count,
1327 }
1328 }
1329 refs::SemanticReference::Name(name) => SemanticReference::Name {
1330 name: name.to_string(),
1331 resolution: self.resolve_name(key, name),
1332 },
1333 refs::SemanticReference::Table(table) => {
1334 if let Some((name, specifier, resolved)) = self.resolve_table_area(key, table) {
1335 SemanticReference::Table {
1336 name,
1337 specifier,
1338 resolved,
1339 }
1340 } else {
1341 SemanticReference::Unsupported {
1342 text: ReferenceType::Table(table.clone()).to_string(),
1343 reason: "structured reference could not be resolved at this placement"
1344 .to_string(),
1345 }
1346 }
1347 }
1348 refs::SemanticReference::ExternalSource(external) => SemanticReference::External {
1349 raw: external.raw.clone(),
1350 },
1351 refs::SemanticReference::ThreeDimensional(reference) => {
1352 SemanticReference::Unsupported {
1353 text: reference.to_string(),
1354 reason: "3D references are not supported by phase-1 introspection".to_string(),
1355 }
1356 }
1357 refs::SemanticReference::Unsupported(reference) => SemanticReference::Unsupported {
1358 text: reference.to_string(),
1359 reason: "reference form is unsupported by introspection".to_string(),
1360 },
1361 }
1362 }
1363
1364 fn collect_precedents(
1365 &self,
1366 key: CellKey,
1367 max_links: u32,
1368 work: &mut WorkBudget,
1369 ) -> Result<(Vec<Precedent>, TruncationReport), InspectError> {
1370 struct Collector<'a, R> {
1371 engine: &'a Engine<R>,
1372 key: CellKey,
1373 max_links: usize,
1374 work: &'a mut WorkBudget,
1375 precedents: Vec<Precedent>,
1376 truncated: bool,
1377 }
1378 impl<R: EvaluationContext> ReferenceVisitor for Collector<'_, R> {
1379 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool {
1380 if !self.work.charge() {
1381 self.truncated = true;
1382 return false;
1383 }
1384 let reference = self.engine.own_reference(self.key, reference);
1385 if self
1386 .precedents
1387 .iter()
1388 .any(|existing| existing.reference == reference)
1389 {
1390 return true;
1391 }
1392 if self.precedents.len() >= self.max_links {
1393 self.truncated = true;
1394 return false;
1395 }
1396 self.precedents.push(Precedent {
1397 reference,
1398 provenance: Provenance::Declared,
1399 });
1400 true
1401 }
1402 }
1403
1404 let source = self.inspect_source();
1405 let mut collector = Collector {
1406 engine: self,
1407 key,
1408 max_links: max_links as usize,
1409 work,
1410 precedents: Vec::new(),
1411 truncated: false,
1412 };
1413 source.visit_declared_references(key, &mut collector)?;
1414 let truncation = if collector.truncated {
1415 TruncationReport {
1416 incomplete: true,
1417 omitted: Some(OmittedCount::AtLeast(1)),
1418 }
1419 } else {
1420 TruncationReport::default()
1421 };
1422 Ok((collector.precedents, truncation))
1423 }
1424
1425 pub fn precedents(
1428 &self,
1429 cell: &CellAddress,
1430 options: &PrecedentOptions,
1431 ) -> Result<PrecedentReport, InspectError> {
1432 let (key, canonical) = self.canonical_cell(cell)?;
1433 let mut work = WorkBudget::new(options.max_work);
1434 let (precedents, truncation) =
1435 self.collect_precedents(key, options.max_links, &mut work)?;
1436 Ok(PrecedentReport {
1437 stamp: self.inspect_stamp(),
1438 cell: canonical,
1439 precedents,
1440 truncation,
1441 })
1442 }
1443
1444 fn dependency_state_available(&self) -> Result<(), InspectError> {
1445 if self.has_staged_formulas() {
1446 Err(InspectError::DependencyStateUnavailable {
1447 reason: InspectionUnavailableReason::DeferredDependencyGraph,
1448 })
1449 } else {
1450 Ok(())
1451 }
1452 }
1453
1454 fn spill_query_members(&self, key: CellKey) -> Vec<CellKey> {
1455 let source = self.inspect_source();
1456 let Some(InternalSpillRole::Anchor { .. }) = source.spill_role(key) else {
1457 return vec![key];
1458 };
1459 let cell_ref = CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true));
1460 let Some(vertex) = self.graph.get_vertex_for_cell(&cell_ref) else {
1461 return vec![key];
1462 };
1463 self.graph
1464 .spill_cells_for_anchor(vertex)
1465 .unwrap_or(&[])
1466 .iter()
1467 .map(|member| CellKey {
1468 sheet_id: member.sheet_id,
1469 row0: member.coord.row(),
1470 col0: member.coord.col(),
1471 })
1472 .collect()
1473 }
1474
1475 fn collect_dependents(
1476 &self,
1477 key: CellKey,
1478 max_results: u32,
1479 work: &mut WorkBudget,
1480 ) -> Result<(Vec<Dependent>, TruncationReport), InspectError> {
1481 self.dependency_state_available()?;
1482 let source = self.inspect_source();
1483 let mut found: FxHashMap<CellAddress, Vec<CellAddress>> = FxHashMap::default();
1484 let mut incomplete = false;
1485 let max_results = max_results as usize;
1486
1487 let spill_anchor_query = matches!(
1488 source.spill_role(key),
1489 Some(InternalSpillRole::Anchor { .. })
1490 );
1491 let members = self.spill_query_members(key);
1492 for member in members {
1493 if !work.charge() {
1494 incomplete = true;
1495 break;
1496 }
1497 let via = self.address_for_key(member);
1498 let mut record = |dependent_key: CellKey| {
1499 let address = self.address_for_key(dependent_key);
1500 if let Some(via_members) = found.get_mut(&address) {
1501 if spill_anchor_query && !via_members.contains(&via) {
1502 via_members.push(via.clone());
1503 }
1504 return true;
1505 }
1506 found.insert(
1507 address,
1508 if spill_anchor_query {
1509 vec![via.clone()]
1510 } else {
1511 Vec::new()
1512 },
1513 );
1514 true
1515 };
1516
1517 let member_ref = CellRef::new(
1518 member.sheet_id,
1519 Coord::new(member.row0, member.col0, true, true),
1520 );
1521 {
1525 let _ = &member_ref;
1526 let complete = self
1527 .graph
1528 .authority_visit_text_dependents(
1529 (member.sheet_id, member.row0, member.col0),
1530 &mut work.remaining,
1531 &mut |(sheet_id, row0, col0)| {
1532 record(CellKey {
1533 sheet_id,
1534 row0,
1535 col0,
1536 })
1537 },
1538 )
1539 .map_err(|_| InspectError::DependencyStateUnavailable {
1540 reason: InspectionUnavailableReason::DependencyAuthorityUnavailable,
1541 })?;
1542 if !complete {
1543 incomplete = true;
1544 break;
1545 }
1546 }
1547 }
1548
1549 let mut dependents: Vec<_> = found
1550 .into_iter()
1551 .map(|(cell, mut via)| {
1552 via.sort_by(address_cmp);
1553 via.dedup();
1554 Dependent { cell, via }
1555 })
1556 .collect();
1557 dependents.sort_by(|left, right| address_cmp(&left.cell, &right.cell));
1558 let known_omitted_dependent = dependents.len() > max_results;
1559 if known_omitted_dependent {
1560 dependents.truncate(max_results);
1561 incomplete = true;
1562 }
1563 Ok((
1564 dependents,
1565 if incomplete {
1566 TruncationReport {
1567 incomplete: true,
1568 omitted: known_omitted_dependent.then_some(OmittedCount::AtLeast(1)),
1569 }
1570 } else {
1571 TruncationReport::default()
1572 },
1573 ))
1574 }
1575
1576 pub fn dependents(
1579 &self,
1580 cell: &CellAddress,
1581 options: &DependentsOptions,
1582 ) -> Result<DependentsReport, InspectError> {
1583 let (key, canonical) = self.canonical_cell(cell)?;
1584 let mut work = WorkBudget::new(options.max_work);
1585 let (dependents, truncation) =
1586 self.collect_dependents(key, options.max_results, &mut work)?;
1587 Ok(DependentsReport {
1588 stamp: self.inspect_stamp(),
1589 cell: canonical,
1590 dependents,
1591 truncation,
1592 })
1593 }
1594
1595 fn target_addresses(reference: &SemanticReference) -> Option<&RangeAddress> {
1596 match reference {
1597 SemanticReference::Range { resolved, .. } => resolved.as_ref(),
1598 SemanticReference::Name {
1599 resolution: NameResolution::Range { resolved, .. },
1600 ..
1601 } => resolved.as_ref(),
1602 SemanticReference::Table { resolved, .. } => Some(resolved),
1603 _ => None,
1604 }
1605 }
1606
1607 fn target_cell(reference: &SemanticReference) -> Option<&CellAddress> {
1608 match reference {
1609 SemanticReference::Cell(cell) => Some(cell),
1610 SemanticReference::Name {
1611 resolution: NameResolution::Cell(cell),
1612 ..
1613 } => Some(cell),
1614 _ => None,
1615 }
1616 }
1617
1618 fn classify_cycle_dispositions(nodes: &mut [TraceNode]) {
1622 let adjacency: Vec<Vec<usize>> = nodes
1623 .iter()
1624 .map(|node| {
1625 node.links
1626 .iter()
1627 .flat_map(|link| link.targets.iter())
1628 .map(|target| target.node.0 as usize)
1629 .collect()
1630 })
1631 .collect();
1632 let mut reachability = vec![vec![false; nodes.len()]; nodes.len()];
1633 for start in 0..nodes.len() {
1634 let mut stack = adjacency[start].clone();
1635 while let Some(next) = stack.pop() {
1636 if reachability[start][next] {
1637 continue;
1638 }
1639 reachability[start][next] = true;
1640 stack.extend(adjacency[next].iter().copied());
1641 }
1642 }
1643
1644 for (source, node) in nodes.iter_mut().enumerate() {
1645 for target in node
1646 .links
1647 .iter_mut()
1648 .flat_map(|link| link.targets.iter_mut())
1649 {
1650 let target_index = target.node.0 as usize;
1651 if source == target_index || reachability[target_index][source] {
1652 target.disposition = LinkDisposition::Cycle;
1653 } else if target.disposition == LinkDisposition::Cycle {
1654 target.disposition = LinkDisposition::Convergent;
1655 }
1656 }
1657 }
1658 }
1659
1660 pub fn trace(
1662 &self,
1663 roots: &[CellAddress],
1664 options: &TraceOptions,
1665 ) -> Result<TraceGraph, InspectError> {
1666 if roots.is_empty() {
1667 return Err(InspectError::InvalidOptions {
1668 message: "trace requires at least one root".to_string(),
1669 });
1670 }
1671 let canonical_roots: Vec<_> = roots
1672 .iter()
1673 .map(|root| self.canonical_cell(root))
1674 .collect::<Result<_, _>>()?;
1675 let mut unique_roots = FxHashMap::default();
1676 for (key, canonical) in &canonical_roots {
1677 unique_roots.entry(canonical.clone()).or_insert(*key);
1678 }
1679 if unique_roots.len() > options.max_nodes as usize {
1680 return Err(InspectError::InvalidOptions {
1681 message: format!(
1682 "max_nodes ({}) must hold all {} unique roots",
1683 options.max_nodes,
1684 unique_roots.len()
1685 ),
1686 });
1687 }
1688 if options.direction == TraceDirection::Dependents {
1689 self.dependency_state_available()?;
1690 }
1691
1692 let mut nodes = Vec::new();
1693 let mut node_by_address: FxHashMap<CellAddress, TraceNodeId> = FxHashMap::default();
1694 let mut root_ids = Vec::new();
1695 let mut queue = VecDeque::new();
1696 let mut parents: HashMap<TraceNodeId, Option<TraceNodeId>> = HashMap::new();
1697 let mut truncation = TruncationReport::default();
1698
1699 for (key, canonical) in canonical_roots {
1702 if let Some(&id) = node_by_address.get(&canonical) {
1703 root_ids.push(id);
1704 continue;
1705 }
1706 let id = TraceNodeId(nodes.len() as u32);
1707 nodes.push(TraceNode {
1708 id,
1709 cell: self.snapshot_for_key(key, options.include_values)?,
1710 links: Vec::new(),
1711 });
1712 node_by_address.insert(canonical, id);
1713 root_ids.push(id);
1714 queue.push_back((id, key, 0u32));
1715 parents.insert(id, None);
1716 }
1717
1718 let mut work = WorkBudget::new(options.max_work);
1719 let mut links_used = 0u32;
1720 let mut range_members_used = 0u32;
1721
1722 while let Some((source_id, source_key, depth)) = queue.pop_front() {
1723 let can_follow = depth < options.max_depth;
1724 let mut links = Vec::new();
1725 if options.direction == TraceDirection::Precedents {
1726 if let Some(InternalSpillRole::Member { anchor }) =
1727 self.inspect_source().spill_role(source_key)
1728 {
1729 if links_used < options.max_links {
1730 links_used += 1;
1731 let anchor_address = self.address_for_key(anchor);
1732 let mut link = TraceLink {
1733 reference: SemanticReference::Cell(anchor_address.clone()),
1734 kind: TraceLinkKind::SpillAnchor,
1735 targets: Vec::new(),
1736 omitted: None,
1737 };
1738 self.attach_cell_target(
1739 anchor_address,
1740 source_id,
1741 anchor,
1742 can_follow,
1743 depth,
1744 options,
1745 false,
1746 &mut nodes,
1747 &mut node_by_address,
1748 &mut parents,
1749 &mut queue,
1750 &mut link,
1751 &mut truncation,
1752 )?;
1753 links.push(link);
1754 } else {
1755 truncation.incomplete = true;
1756 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
1757 }
1758 } else {
1759 let available_links = options.max_links.saturating_sub(links_used);
1760 let (precedents, local_truncation) =
1761 self.collect_precedents(source_key, available_links, &mut work)?;
1762 if local_truncation.incomplete {
1763 truncation.incomplete = true;
1764 if let Some(omitted) = local_truncation.omitted {
1765 merge_omitted(&mut truncation.omitted, omitted);
1766 }
1767 }
1768 for precedent in precedents {
1769 links_used += 1;
1770 let mut link = TraceLink {
1771 reference: precedent.reference,
1772 kind: TraceLinkKind::Formula {
1773 provenance: precedent.provenance,
1774 },
1775 targets: Vec::new(),
1776 omitted: None,
1777 };
1778 if let Some(cell) = Self::target_cell(&link.reference).cloned() {
1779 let (target_key, canonical) = self.canonical_cell(&cell)?;
1780 self.attach_cell_target(
1781 canonical,
1782 source_id,
1783 target_key,
1784 can_follow,
1785 depth,
1786 options,
1787 false,
1788 &mut nodes,
1789 &mut node_by_address,
1790 &mut parents,
1791 &mut queue,
1792 &mut link,
1793 &mut truncation,
1794 )?;
1795 } else if let Some(range) = Self::target_addresses(&link.reference).cloned()
1796 {
1797 self.attach_range_targets(
1798 &range,
1799 source_id,
1800 can_follow,
1801 depth,
1802 options,
1803 &mut range_members_used,
1804 &mut nodes,
1805 &mut node_by_address,
1806 &mut parents,
1807 &mut queue,
1808 &mut link,
1809 &mut truncation,
1810 )?;
1811 }
1812 links.push(link);
1813 }
1814 }
1815 } else {
1816 let available = options.max_links.saturating_sub(links_used);
1817 let (dependents, local_truncation) =
1818 self.collect_dependents(source_key, available, &mut work)?;
1819 if local_truncation.incomplete {
1820 truncation.incomplete = true;
1821 if let Some(omitted) = local_truncation.omitted {
1822 merge_omitted(&mut truncation.omitted, omitted);
1823 }
1824 }
1825 for dependent in dependents {
1826 links_used += 1;
1827 let spill_reader = dependent
1828 .via
1829 .iter()
1830 .any(|via| via != &self.address_for_key(source_key));
1831 let (target_key, canonical) = self.canonical_cell(&dependent.cell)?;
1832 let mut link = TraceLink {
1833 reference: SemanticReference::Cell(canonical.clone()),
1834 kind: if spill_reader {
1835 TraceLinkKind::SpillReader
1836 } else {
1837 TraceLinkKind::Formula {
1838 provenance: Provenance::Declared,
1839 }
1840 },
1841 targets: Vec::new(),
1842 omitted: None,
1843 };
1844 self.attach_cell_target(
1845 canonical,
1846 source_id,
1847 target_key,
1848 can_follow,
1849 depth,
1850 options,
1851 false,
1852 &mut nodes,
1853 &mut node_by_address,
1854 &mut parents,
1855 &mut queue,
1856 &mut link,
1857 &mut truncation,
1858 )?;
1859 links.push(link);
1860 }
1861 }
1862 nodes[source_id.0 as usize].links = links;
1863 }
1864
1865 Self::classify_cycle_dispositions(&mut nodes);
1866
1867 Ok(TraceGraph {
1868 stamp: self.inspect_stamp(),
1869 direction: options.direction,
1870 roots: root_ids,
1871 nodes,
1872 truncation,
1873 })
1874 }
1875
1876 #[allow(clippy::too_many_arguments)]
1877 fn attach_cell_target(
1878 &self,
1879 address: CellAddress,
1880 source_id: TraceNodeId,
1881 key: CellKey,
1882 can_follow: bool,
1883 depth: u32,
1884 options: &TraceOptions,
1885 defer_missing_omission: bool,
1886 nodes: &mut Vec<TraceNode>,
1887 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
1888 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
1889 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
1890 link: &mut TraceLink,
1891 truncation: &mut TruncationReport,
1892 ) -> Result<(), InspectError> {
1893 if let Some(&target_id) = node_by_address.get(&address) {
1894 link.targets.push(TraceLinkTarget {
1895 node: target_id,
1896 disposition: LinkDisposition::Convergent,
1899 });
1900 return Ok(());
1901 }
1902 if nodes.len() >= options.max_nodes as usize {
1903 link.omitted = Some(OmittedCount::AtLeast(1));
1904 truncation.incomplete = true;
1905 if !defer_missing_omission {
1906 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
1907 }
1908 return Ok(());
1909 }
1910 let target_id = TraceNodeId(nodes.len() as u32);
1911 nodes.push(TraceNode {
1912 id: target_id,
1913 cell: self.snapshot_for_key(key, options.include_values)?,
1914 links: Vec::new(),
1915 });
1916 node_by_address.insert(address, target_id);
1917 parents.insert(target_id, Some(source_id));
1918 link.targets.push(TraceLinkTarget {
1919 node: target_id,
1920 disposition: if can_follow {
1921 LinkDisposition::Expanded
1922 } else {
1923 LinkDisposition::Elided
1924 },
1925 });
1926 if can_follow {
1927 queue.push_back((target_id, key, depth + 1));
1928 } else {
1929 truncation.incomplete = true;
1932 }
1933 Ok(())
1934 }
1935
1936 #[allow(clippy::too_many_arguments)]
1937 fn attach_range_targets(
1938 &self,
1939 range: &RangeAddress,
1940 source_id: TraceNodeId,
1941 can_follow: bool,
1942 depth: u32,
1943 options: &TraceOptions,
1944 range_members_used: &mut u32,
1945 nodes: &mut Vec<TraceNode>,
1946 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
1947 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
1948 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
1949 link: &mut TraceLink,
1950 truncation: &mut TruncationReport,
1951 ) -> Result<(), InspectError> {
1952 let total = u64::from(range.width()) * u64::from(range.height());
1953 let mut attached = 0u64;
1954 let mut compressed_ancestors = Vec::new();
1955 let mut cursor = Some(source_id);
1956 while let Some(ancestor) = cursor {
1957 let address = &nodes[ancestor.0 as usize].cell.address;
1958 if range.sheet == address.sheet
1959 && range.start_row <= address.row
1960 && address.row <= range.end_row
1961 && range.start_col <= address.column
1962 && address.column <= range.end_col
1963 {
1964 compressed_ancestors.push((ancestor, address.row, address.column));
1965 }
1966 cursor = parents.get(&ancestor).copied().flatten();
1967 }
1968
1969 for (ancestor, _, _) in &compressed_ancestors {
1972 link.targets.push(TraceLinkTarget {
1973 node: *ancestor,
1974 disposition: LinkDisposition::Convergent,
1977 });
1978 attached += 1;
1979 }
1980
1981 'rows: for row in range.start_row..=range.end_row {
1982 for column in range.start_col..=range.end_col {
1983 if compressed_ancestors
1984 .iter()
1985 .any(|(_, ancestor_row, ancestor_column)| {
1986 row == *ancestor_row && column == *ancestor_column
1987 })
1988 {
1989 continue;
1990 }
1991 if *range_members_used >= options.range_member_budget {
1992 break 'rows;
1993 }
1994 *range_members_used += 1;
1995 let address = CellAddress {
1996 sheet: range.sheet.clone(),
1997 row,
1998 column,
1999 };
2000 let (key, canonical) = self.canonical_cell(&address)?;
2001 let before = link.targets.len();
2002 self.attach_cell_target(
2003 canonical,
2004 source_id,
2005 key,
2006 can_follow,
2007 depth,
2008 options,
2009 true,
2010 nodes,
2011 node_by_address,
2012 parents,
2013 queue,
2014 link,
2015 truncation,
2016 )?;
2017 if link.targets.len() > before {
2018 attached += 1;
2019 } else if link.omitted.is_some() {
2020 break 'rows;
2021 }
2022 }
2023 }
2024 if attached < total {
2025 let omitted = OmittedCount::Exact(total - attached);
2026 link.omitted = Some(omitted);
2027 truncation.incomplete = true;
2028 merge_omitted(&mut truncation.omitted, omitted);
2029 }
2030 Ok(())
2031 }
2032
2033 pub fn range_page(
2035 &self,
2036 area: &RangeArea,
2037 options: &RangePageOptions,
2038 ) -> Result<RangePage, InspectError> {
2039 if options.limit == 0 {
2040 return Err(InspectError::InvalidOptions {
2041 message: "range page limit must be at least one".to_string(),
2042 });
2043 }
2044 let stamp = self.inspect_stamp();
2045 if let Some(expected) = options.expected_stamp
2046 && expected != stamp
2047 {
2048 return Err(InspectError::RevisionMismatch {
2049 expected,
2050 actual: stamp,
2051 });
2052 }
2053 let (_, declared) = self.canonical_area(area)?;
2054 let resolved = self.resolve_semantic_area(&declared);
2055 let total = resolved.as_ref().map_or(0, |range| {
2056 u64::from(range.width()) * u64::from(range.height())
2057 });
2058 let start = options.offset.min(total);
2059 let end = start.saturating_add(u64::from(options.limit)).min(total);
2060 let mut items = Vec::new();
2061 items
2062 .try_reserve((end - start) as usize)
2063 .map_err(|_| InspectError::ResourceExhausted {
2064 resource: "range page items",
2065 })?;
2066 if let Some(range) = &resolved {
2067 let width = u64::from(range.width());
2068 for offset in start..end {
2069 let row = range.start_row + u32::try_from(offset / width).unwrap_or(u32::MAX);
2070 let column = range.start_col + u32::try_from(offset % width).unwrap_or(u32::MAX);
2071 let (key, _) = self.canonical_cell(&CellAddress {
2072 sheet: range.sheet.clone(),
2073 row,
2074 column,
2075 })?;
2076 items.push(self.snapshot_for_key(key, options.include_values)?);
2077 }
2078 }
2079 Ok(RangePage {
2080 stamp,
2081 declared,
2082 resolved,
2083 total,
2084 offset: options.offset,
2085 items,
2086 next_offset: (end < total).then_some(end),
2087 })
2088 }
2089}