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, FormulaPlaneMode, VertexId, VertexKind};
32use crate::formula_plane::producer::{
33 AxisProjection, DirtyProjectionRule, FormulaProducerId, ProducerDirtyDomain, ProjectionResult,
34 compute_dirty_closure,
35};
36use crate::formula_plane::region_index::{BoundedRegionQueryResult, Region, RegionKey};
37use crate::formula_plane::runtime::{FormulaResolution, FormulaSpanRef, PlacementCoord};
38use crate::reference::{CellRef, Coord};
39use crate::traits::EvaluationContext;
40
41const DEFAULT_MAX_LINKS: u32 = 256;
42const DEFAULT_MAX_WORK: u64 = 100_000;
43
44#[cfg(test)]
45#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
46pub(crate) struct FormulaPlaneReferencePathCounts {
47 pub(crate) template: u32,
48 pub(crate) ast_fallback: u32,
49}
50
51#[cfg(test)]
52thread_local! {
53 static FORMULA_PLANE_REFERENCE_PATH_COUNTS:
54 std::cell::Cell<FormulaPlaneReferencePathCounts> = const {
55 std::cell::Cell::new(FormulaPlaneReferencePathCounts {
56 template: 0,
57 ast_fallback: 0,
58 })
59 };
60}
61
62#[cfg(test)]
63pub(crate) fn reset_formula_plane_reference_path_counts() {
64 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| counts.set(Default::default()));
65}
66
67#[cfg(test)]
68pub(crate) fn formula_plane_reference_path_counts() -> FormulaPlaneReferencePathCounts {
69 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(std::cell::Cell::get)
70}
71
72#[cfg(test)]
73fn record_formula_plane_template_path() {
74 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| {
75 let mut current = counts.get();
76 current.template += 1;
77 counts.set(current);
78 });
79}
80
81#[cfg(test)]
82fn record_formula_plane_ast_fallback_path() {
83 FORMULA_PLANE_REFERENCE_PATH_COUNTS.with(|counts| {
84 let mut current = counts.get();
85 current.ast_fallback += 1;
86 counts.set(current);
87 });
88}
89
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
94pub struct StateStamp {
95 pub mutation_revision: u64,
96 pub recalc_epoch: u64,
97}
98
99#[cfg_attr(feature = "serde", derive(serde::Serialize))]
100#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
101#[non_exhaustive]
102pub enum Staleness {
103 Current,
104 Dirty,
105 NeverEvaluated,
106}
107
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114#[derive(Clone, Debug, Eq, PartialEq, Hash)]
115#[non_exhaustive]
116pub enum SpillRole {
117 Anchor { extent: RangeAddress },
118 Member { anchor: CellAddress },
119}
120
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122#[derive(Clone, Debug, PartialEq)]
123#[non_exhaustive]
124pub struct CellSnapshot {
125 pub address: CellAddress,
126 pub formula: Option<String>,
127 pub value: Option<LiteralValue>,
128 pub value_included: bool,
129 pub staleness: Staleness,
130 pub volatile: bool,
131 pub spill: Option<SpillRole>,
134}
135
136#[cfg_attr(feature = "serde", derive(serde::Serialize))]
137#[derive(Clone, Debug, PartialEq)]
138#[non_exhaustive]
139pub struct CellSnapshotReport {
140 pub stamp: StateStamp,
141 pub cell: CellSnapshot,
142}
143
144#[cfg_attr(feature = "serde", derive(serde::Serialize))]
145#[derive(Clone, Debug, PartialEq)]
146#[non_exhaustive]
147pub enum NameResolution {
148 Cell(CellAddress),
149 Range {
150 declared: RangeArea,
151 resolved: Option<RangeAddress>,
152 },
153 Literal(LiteralValue),
154 Formula {
155 formula: String,
156 value: Option<LiteralValue>,
157 },
158 Unresolved,
159}
160
161#[cfg_attr(feature = "serde", derive(serde::Serialize))]
162#[derive(Clone, Debug, PartialEq)]
163#[non_exhaustive]
164pub enum SemanticReference {
165 Cell(CellAddress),
166 Range {
167 declared: RangeArea,
168 resolved: Option<RangeAddress>,
169 cell_count: u64,
170 },
171 Name {
172 name: String,
173 resolution: NameResolution,
174 },
175 Table {
176 name: String,
177 specifier: String,
178 resolved: RangeAddress,
179 },
180 External {
181 raw: String,
182 },
183 Unsupported {
184 text: String,
185 reason: String,
186 },
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
191#[non_exhaustive]
192pub enum Provenance {
193 Declared,
194 Observed,
195}
196
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
199#[non_exhaustive]
200pub enum TraceLinkKind {
201 Formula { provenance: Provenance },
202 SpillAnchor,
203 SpillReader,
204}
205
206#[cfg_attr(feature = "serde", derive(serde::Serialize))]
207#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
208#[non_exhaustive]
209pub enum LinkDisposition {
210 Expanded,
211 Convergent,
212 Cycle,
213 Elided,
214}
215
216#[cfg_attr(feature = "serde", derive(serde::Serialize))]
217#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
218#[non_exhaustive]
219pub enum OmittedCount {
220 Exact(u64),
221 AtLeast(u64),
222}
223
224#[cfg_attr(feature = "serde", derive(serde::Serialize))]
225#[derive(Clone, Debug, Default, Eq, PartialEq)]
226#[non_exhaustive]
227pub struct TruncationReport {
228 pub incomplete: bool,
229 pub omitted: Option<OmittedCount>,
233}
234
235#[cfg_attr(feature = "serde", derive(serde::Serialize))]
236#[derive(Clone, Debug, PartialEq)]
237#[non_exhaustive]
238pub struct Precedent {
239 pub reference: SemanticReference,
240 pub provenance: Provenance,
241}
242
243#[cfg_attr(feature = "serde", derive(serde::Serialize))]
244#[derive(Clone, Debug, PartialEq)]
245#[non_exhaustive]
246pub struct PrecedentReport {
247 pub stamp: StateStamp,
248 pub cell: CellAddress,
249 pub precedents: Vec<Precedent>,
250 pub truncation: TruncationReport,
251}
252
253#[cfg_attr(feature = "serde", derive(serde::Serialize))]
254#[derive(Clone, Debug, Eq, PartialEq)]
255#[non_exhaustive]
256pub struct Dependent {
257 pub cell: CellAddress,
258 pub via: Vec<CellAddress>,
262}
263
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265#[derive(Clone, Debug, Eq, PartialEq)]
266#[non_exhaustive]
267pub struct DependentsReport {
268 pub stamp: StateStamp,
269 pub cell: CellAddress,
270 pub dependents: Vec<Dependent>,
271 pub truncation: TruncationReport,
272}
273
274#[cfg_attr(feature = "serde", derive(serde::Serialize))]
275#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
276pub struct TraceNodeId(pub u32);
277
278#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
279#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
280#[non_exhaustive]
281pub enum TraceDirection {
282 Precedents,
283 Dependents,
284}
285
286#[cfg_attr(feature = "serde", derive(serde::Serialize))]
287#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
288#[non_exhaustive]
289pub struct TraceLinkTarget {
290 pub node: TraceNodeId,
291 pub disposition: LinkDisposition,
292}
293
294#[cfg_attr(feature = "serde", derive(serde::Serialize))]
295#[derive(Clone, Debug, PartialEq)]
296#[non_exhaustive]
297pub struct TraceLink {
298 pub reference: SemanticReference,
299 pub kind: TraceLinkKind,
300 pub targets: Vec<TraceLinkTarget>,
301 pub omitted: Option<OmittedCount>,
302}
303
304#[cfg_attr(feature = "serde", derive(serde::Serialize))]
305#[derive(Clone, Debug, PartialEq)]
306#[non_exhaustive]
307pub struct TraceNode {
308 pub id: TraceNodeId,
309 pub cell: CellSnapshot,
310 pub links: Vec<TraceLink>,
311}
312
313#[cfg_attr(feature = "serde", derive(serde::Serialize))]
314#[derive(Clone, Debug, PartialEq)]
315#[non_exhaustive]
316pub struct TraceGraph {
317 pub stamp: StateStamp,
318 pub direction: TraceDirection,
319 pub roots: Vec<TraceNodeId>,
322 pub nodes: Vec<TraceNode>,
323 pub truncation: TruncationReport,
324}
325
326#[cfg_attr(feature = "serde", derive(serde::Serialize))]
327#[derive(Clone, Debug, PartialEq)]
328#[non_exhaustive]
329pub struct RangePage {
330 pub stamp: StateStamp,
331 pub declared: RangeArea,
332 pub resolved: Option<RangeAddress>,
333 pub total: u64,
334 pub offset: u64,
337 pub items: Vec<CellSnapshot>,
338 pub next_offset: Option<u64>,
339}
340
341#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
347#[derive(Clone, Copy, Debug, Eq, PartialEq)]
348pub struct SnapshotOptions {
349 pub include_values: bool,
350}
351
352impl Default for SnapshotOptions {
353 fn default() -> Self {
354 Self {
355 include_values: true,
356 }
357 }
358}
359
360impl SnapshotOptions {
361 pub fn with_include_values(mut self, include_values: bool) -> Self {
362 self.include_values = include_values;
363 self
364 }
365}
366
367#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
373#[derive(Clone, Copy, Debug, Eq, PartialEq)]
374pub struct PrecedentOptions {
375 pub max_links: u32,
376 pub max_work: u64,
377}
378
379impl Default for PrecedentOptions {
380 fn default() -> Self {
381 Self {
382 max_links: DEFAULT_MAX_LINKS,
383 max_work: DEFAULT_MAX_WORK,
384 }
385 }
386}
387
388impl PrecedentOptions {
389 pub fn with_max_links(mut self, max_links: u32) -> Self {
390 self.max_links = max_links;
391 self
392 }
393
394 pub fn with_max_work(mut self, max_work: u64) -> Self {
395 self.max_work = max_work;
396 self
397 }
398}
399
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
406#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407pub struct DependentsOptions {
408 pub max_results: u32,
413 pub max_work: u64,
414}
415
416impl Default for DependentsOptions {
417 fn default() -> Self {
418 Self {
419 max_results: 256,
420 max_work: DEFAULT_MAX_WORK,
421 }
422 }
423}
424
425impl DependentsOptions {
426 pub fn with_max_results(mut self, max_results: u32) -> Self {
427 self.max_results = max_results;
428 self
429 }
430
431 pub fn with_max_work(mut self, max_work: u64) -> Self {
432 self.max_work = max_work;
433 self
434 }
435}
436
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
443#[derive(Clone, Copy, Debug, Eq, PartialEq)]
444pub struct TraceOptions {
445 pub direction: TraceDirection,
446 pub max_depth: u32,
449 pub max_nodes: u32,
452 pub max_links: u32,
453 pub max_work: u64,
454 pub range_member_budget: u32,
455 pub include_values: bool,
456}
457
458impl Default for TraceOptions {
459 fn default() -> Self {
460 Self {
461 direction: TraceDirection::Precedents,
462 max_depth: 6,
463 max_nodes: 512,
464 max_links: 1_024,
465 max_work: DEFAULT_MAX_WORK,
466 range_member_budget: 256,
467 include_values: true,
468 }
469 }
470}
471
472impl TraceOptions {
473 pub fn with_direction(mut self, direction: TraceDirection) -> Self {
474 self.direction = direction;
475 self
476 }
477
478 pub fn with_max_depth(mut self, max_depth: u32) -> Self {
479 self.max_depth = max_depth;
480 self
481 }
482
483 pub fn with_max_nodes(mut self, max_nodes: u32) -> Self {
484 self.max_nodes = max_nodes;
485 self
486 }
487
488 pub fn with_max_links(mut self, max_links: u32) -> Self {
489 self.max_links = max_links;
490 self
491 }
492
493 pub fn with_max_work(mut self, max_work: u64) -> Self {
494 self.max_work = max_work;
495 self
496 }
497
498 pub fn with_range_member_budget(mut self, range_member_budget: u32) -> Self {
499 self.range_member_budget = range_member_budget;
500 self
501 }
502
503 pub fn with_include_values(mut self, include_values: bool) -> Self {
504 self.include_values = include_values;
505 self
506 }
507}
508
509#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515#[derive(Clone, Copy, Debug, Eq, PartialEq)]
516pub struct RangePageOptions {
517 pub offset: u64,
518 pub limit: u32,
519 pub include_values: bool,
520 pub expected_stamp: Option<StateStamp>,
521}
522
523impl Default for RangePageOptions {
524 fn default() -> Self {
525 Self {
526 offset: 0,
527 limit: 100,
528 include_values: true,
529 expected_stamp: None,
530 }
531 }
532}
533
534impl RangePageOptions {
535 pub fn with_offset(mut self, offset: u64) -> Self {
536 self.offset = offset;
537 self
538 }
539
540 pub fn with_limit(mut self, limit: u32) -> Self {
541 self.limit = limit;
542 self
543 }
544
545 pub fn with_include_values(mut self, include_values: bool) -> Self {
546 self.include_values = include_values;
547 self
548 }
549
550 pub fn with_expected_stamp(mut self, expected_stamp: StateStamp) -> Self {
551 self.expected_stamp = Some(expected_stamp);
552 self
553 }
554}
555
556#[cfg_attr(feature = "serde", derive(serde::Serialize))]
557#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
558#[non_exhaustive]
559pub enum InspectionUnavailableReason {
560 DeferredDependencyGraph,
561}
562
563#[cfg_attr(feature = "serde", derive(serde::Serialize))]
564#[derive(Clone, Debug, Eq, PartialEq)]
565#[non_exhaustive]
566pub enum InspectError {
567 SheetNotFound {
568 sheet: String,
569 },
570 InvalidAddress {
571 message: String,
572 },
573 InvalidOptions {
574 message: String,
575 },
576 DependencyStateUnavailable {
577 reason: InspectionUnavailableReason,
578 },
579 RevisionMismatch {
580 expected: StateStamp,
581 actual: StateStamp,
582 },
583 ResourceExhausted {
585 resource: &'static str,
586 },
587}
588
589impl fmt::Display for InspectError {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 match self {
592 Self::SheetNotFound { sheet } => write!(f, "sheet not found: {sheet}"),
593 Self::InvalidAddress { message } => write!(f, "invalid address: {message}"),
594 Self::InvalidOptions { message } => write!(f, "invalid inspection options: {message}"),
595 Self::DependencyStateUnavailable { reason } => {
596 write!(f, "dependency state unavailable: {reason:?}")
597 }
598 Self::RevisionMismatch { expected, actual } => write!(
599 f,
600 "inspection revision mismatch: expected {expected:?}, actual {actual:?}"
601 ),
602 Self::ResourceExhausted { resource } => {
603 write!(f, "inspection resource exhausted: {resource}")
604 }
605 }
606 }
607}
608
609impl Error for InspectError {}
610
611#[cfg_attr(feature = "serde", derive(serde::Serialize))]
612#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
613pub(crate) struct CellKey {
614 sheet_id: SheetId,
615 row0: u32,
616 col0: u32,
617}
618
619#[derive(Clone, Debug)]
620pub(crate) struct FormulaView {
621 ast: ASTNode,
622 volatile: bool,
623 dirty: bool,
624}
625
626#[derive(Clone, Debug)]
627pub(crate) enum InternalSpillRole {
628 Anchor { extent: RangeAddress },
629 Member { anchor: CellKey },
630}
631
632#[derive(Clone, Copy, Debug, Eq, PartialEq)]
633pub(crate) enum QueryCompleteness {
634 Complete,
635 Incomplete,
636}
637
638#[derive(Clone, Copy, Debug)]
639pub(crate) struct WorkBudget {
640 remaining: u64,
641}
642
643impl WorkBudget {
644 fn new(limit: u64) -> Self {
645 Self { remaining: limit }
646 }
647
648 fn charge(&mut self) -> bool {
649 if self.remaining == 0 {
650 false
651 } else {
652 self.remaining -= 1;
653 true
654 }
655 }
656}
657
658pub(crate) trait ReferenceVisitor {
659 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool;
661}
662
663pub(crate) trait DependentVisitor {
664 fn visit(&mut self, dependent: CellKey) -> bool;
666}
667
668pub(crate) trait InspectSource {
672 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError>;
673 fn visit_declared_references(
674 &self,
675 cell: CellKey,
676 visitor: &mut dyn ReferenceVisitor,
677 ) -> Result<(), InspectError>;
678 fn visit_dependents_covering(
679 &self,
680 cell: CellKey,
681 budget: &mut WorkBudget,
682 visitor: &mut dyn DependentVisitor,
683 ) -> Result<QueryCompleteness, InspectError>;
684 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole>;
685}
686
687struct LegacyInspectSource<'a, R> {
688 engine: &'a Engine<R>,
689}
690
691impl<R: EvaluationContext> LegacyInspectSource<'_, R> {
692 fn cell_ref(&self, key: CellKey) -> CellRef {
693 CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true))
694 }
695}
696
697fn visit_formula_ast_references(
698 ast: &ASTNode,
699 visitor: &mut dyn ReferenceVisitor,
700) -> Result<(), InspectError> {
701 struct Context<'a> {
702 visitor: &'a mut dyn ReferenceVisitor,
703 stopped: bool,
704 }
705 fn local_bindings(_: &Context<'_>, name: &str, _: usize) -> refs::LocalBindingStyle {
706 match name
707 .rsplit('.')
708 .next()
709 .unwrap_or(name)
710 .to_ascii_uppercase()
711 .as_str()
712 {
713 "LET" => refs::LocalBindingStyle::LocalBindingPairs,
714 "LAMBDA" => refs::LocalBindingStyle::LambdaParameters,
715 _ => refs::LocalBindingStyle::None,
716 }
717 }
718 fn consume(
719 context: &mut Context<'_>,
720 reference: refs::SemanticReference<'_>,
721 ) -> Result<(), ExcelError> {
722 if context.visitor.visit(reference) {
723 Ok(())
724 } else {
725 context.stopped = true;
726 Err(ExcelError::new(ExcelErrorKind::NImpl)
727 .with_message("inspection visitor requested stop"))
728 }
729 }
730
731 let mut context = Context {
732 visitor,
733 stopped: false,
734 };
735 let result = refs::visit_tree_references(ast, &mut context, local_bindings, consume);
736 if context.stopped {
737 Ok(())
738 } else {
739 result.map_err(|error| InspectError::InvalidAddress {
740 message: error.to_string(),
741 })
742 }
743}
744
745impl<R: EvaluationContext> InspectSource for LegacyInspectSource<'_, R> {
746 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError> {
747 let sheet = self.engine.graph.sheet_name(cell.sheet_id);
748 let row = cell.row0 + 1;
749 let col = cell.col0 + 1;
750 if let Some(text) = self.engine.get_staged_formula_text(sheet, row, col) {
751 let text = if text.starts_with('=') {
754 text
755 } else {
756 format!("={text}")
757 };
758 let ast =
759 formualizer_parse::parse(&text).map_err(|error| InspectError::InvalidAddress {
760 message: format!("staged formula at {sheet}!R{row}C{col} is invalid: {error}"),
761 })?;
762 let volatile = self.engine.graph.fp8_parity_is_ast_volatile(&ast);
763 return Ok(Some(FormulaView {
764 ast,
765 volatile,
766 dirty: true,
767 }));
768 }
769
770 let cell_ref = self.cell_ref(cell);
771 let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref) else {
772 return Ok(None);
773 };
774 let Some(ast) = self.engine.graph.get_formula(vertex) else {
775 return Ok(None);
776 };
777 Ok(Some(FormulaView {
778 ast,
779 volatile: self.engine.graph.is_volatile(vertex),
780 dirty: self.engine.graph.is_dirty(vertex),
781 }))
782 }
783
784 fn visit_declared_references(
785 &self,
786 cell: CellKey,
787 visitor: &mut dyn ReferenceVisitor,
788 ) -> Result<(), InspectError> {
789 let Some(formula) = self.formula_at(cell)? else {
790 return Ok(());
791 };
792
793 visit_formula_ast_references(&formula.ast, visitor)
794 }
795
796 fn visit_dependents_covering(
797 &self,
798 cell: CellKey,
799 budget: &mut WorkBudget,
800 visitor: &mut dyn DependentVisitor,
801 ) -> Result<QueryCompleteness, InspectError> {
802 let complete = self.engine.graph.visit_range_dependents_covering_bounded(
803 cell.sheet_id,
804 cell.row0,
805 cell.col0,
806 &mut budget.remaining,
807 &mut |vertex| {
808 self.engine
809 .graph
810 .get_cell_ref(vertex)
811 .is_none_or(|cell_ref| {
812 visitor.visit(CellKey {
813 sheet_id: cell_ref.sheet_id,
814 row0: cell_ref.coord.row(),
815 col0: cell_ref.coord.col(),
816 })
817 })
818 },
819 );
820 Ok(if complete {
821 QueryCompleteness::Complete
822 } else {
823 QueryCompleteness::Incomplete
824 })
825 }
826
827 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole> {
828 let cell_ref = self.cell_ref(cell);
829 if let Some(vertex) = self.engine.graph.get_vertex_for_cell(&cell_ref)
830 && let Some(cells) = self.engine.graph.spill_cells_for_anchor(vertex)
831 {
832 let mut bounds: Option<(u32, u32, u32, u32)> = None;
833 for member in cells {
834 bounds = Some(match bounds {
835 None => (
836 member.coord.row(),
837 member.coord.col(),
838 member.coord.row(),
839 member.coord.col(),
840 ),
841 Some((sr, sc, er, ec)) => (
842 sr.min(member.coord.row()),
843 sc.min(member.coord.col()),
844 er.max(member.coord.row()),
845 ec.max(member.coord.col()),
846 ),
847 });
848 }
849 if let Some((sr, sc, er, ec)) = bounds {
850 let sheet = self.engine.graph.sheet_name(cell.sheet_id).to_string();
851 return Some(InternalSpillRole::Anchor {
852 extent: RangeAddress {
853 sheet,
854 start_row: sr + 1,
855 start_col: sc + 1,
856 end_row: er + 1,
857 end_col: ec + 1,
858 },
859 });
860 }
861 }
862 let anchor = self.engine.graph.spill_registry_anchor_for_cell(cell_ref)?;
863 let anchor_ref = self.engine.graph.get_cell_ref(anchor)?;
864 Some(InternalSpillRole::Member {
865 anchor: CellKey {
866 sheet_id: anchor_ref.sheet_id,
867 row0: anchor_ref.coord.row(),
868 col0: anchor_ref.coord.col(),
869 },
870 })
871 }
872}
873
874struct FormulaPlaneInspectSource<'a, R> {
879 engine: &'a Engine<R>,
880 legacy: LegacyInspectSource<'a, R>,
881}
882
883impl<'a, R: EvaluationContext> FormulaPlaneInspectSource<'a, R> {
884 fn new(engine: &'a Engine<R>) -> Self {
885 Self {
886 engine,
887 legacy: LegacyInspectSource { engine },
888 }
889 }
890
891 fn cell_ref(&self, key: CellKey) -> CellRef {
892 CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true))
893 }
894
895 fn span_placement(&self, key: CellKey) -> Option<(FormulaSpanRef, PlacementCoord)> {
896 if self.engine.config.formula_plane_mode != FormulaPlaneMode::AuthoritativeExperimental {
900 return None;
901 }
902 let placement = PlacementCoord::new(key.sheet_id, key.row0, key.col0);
903 let legacy_vertex = self.engine.graph.get_vertex_for_cell(&self.cell_ref(key));
904 let handle = self
905 .engine
906 .graph
907 .formula_authority()
908 .plane
909 .resolve_formula_at(placement, legacy_vertex);
910 match handle.resolution {
911 FormulaResolution::SpanPlacement {
912 span, placement, ..
913 } => Some((span, placement)),
914 FormulaResolution::StagedFormula { .. }
915 | FormulaResolution::Overlay(_)
916 | FormulaResolution::LegacyVertex(_)
917 | FormulaResolution::Empty
918 | FormulaResolution::Stale => None,
919 }
920 }
921
922 fn dirty_domain_contains(dirty: &ProducerDirtyDomain, placement: PlacementCoord) -> bool {
923 match dirty {
924 ProducerDirtyDomain::Whole => true,
925 ProducerDirtyDomain::Cells(cells) => cells.contains(&RegionKey::from(placement)),
926 ProducerDirtyDomain::Regions(regions) => {
927 let key = RegionKey::from(placement);
928 regions.iter().any(|region| region.contains_key(key))
929 }
930 }
931 }
932
933 fn span_placement_is_dirty(&self, span_ref: FormulaSpanRef, placement: PlacementCoord) -> bool {
934 if self
935 .engine
936 .graph
937 .pending_formula_dirty_whole_spans()
938 .any(|pending| pending == span_ref)
939 {
940 return true;
941 }
942 if self
943 .engine
944 .graph
945 .pending_formula_dirty_span_regions()
946 .any(|(pending, region)| {
947 pending == span_ref && region.contains_key(RegionKey::from(placement))
948 })
949 {
950 return true;
951 }
952
953 let changed = self
954 .engine
955 .graph
956 .pending_formula_dirty_regions()
957 .collect::<Vec<_>>();
958 if changed.is_empty() {
959 return false;
960 }
961 let authority = self.engine.graph.formula_authority();
962 let closure = compute_dirty_closure(&authority.consumer_reads, changed, |producer| {
963 authority.producer_results.producer_result_region(producer)
964 });
965 if closure.incomplete {
966 return true;
967 }
968 let producer = FormulaProducerId::Span(span_ref.id);
969 closure.work.iter().any(|work| {
970 work.producer == producer && Self::dirty_domain_contains(&work.dirty, placement)
971 }) || closure
972 .fallbacks
973 .iter()
974 .any(|fallback| fallback.consumer == producer)
975 }
976
977 fn instantiate_axis(projection: AxisProjection, placement: u32) -> Option<(u32, bool)> {
978 match projection {
979 AxisProjection::Relative { offset } => {
980 let value = i64::from(placement).checked_add(offset)?;
981 let value = u32::try_from(value).ok()?.checked_add(1)?;
982 Some((value, false))
983 }
984 AxisProjection::Absolute { index } => index.checked_add(1).map(|value| (value, true)),
985 }
986 }
987
988 fn instantiated_span_references(
989 &self,
990 span_ref: FormulaSpanRef,
991 placement: PlacementCoord,
992 ) -> Option<Vec<ReferenceType>> {
993 let authority = self.engine.graph.formula_authority();
994 let span = authority.plane.spans.get(span_ref)?;
995 let summary = authority
996 .plane
997 .span_read_summaries
998 .get(span.read_summary_id?)?;
999 let mut references = Vec::with_capacity(summary.dependencies.len());
1000 for dependency in &summary.dependencies {
1001 let sheet = Some(
1002 self.engine
1003 .graph
1004 .sheet_name(dependency.read_region.sheet_id())
1005 .to_string(),
1006 );
1007 let reference = match dependency.projection {
1008 DirtyProjectionRule::AffineCell { row, col } => {
1009 let (row, row_abs) = Self::instantiate_axis(row, placement.row)?;
1010 let (col, col_abs) = Self::instantiate_axis(col, placement.col)?;
1011 ReferenceType::Cell {
1012 sheet,
1013 row,
1014 col,
1015 row_abs,
1016 col_abs,
1017 }
1018 }
1019 DirtyProjectionRule::AffineRange {
1020 row_start,
1021 row_end,
1022 col_start,
1023 col_end,
1024 } => {
1025 let (start_row, start_row_abs) =
1026 Self::instantiate_axis(row_start, placement.row)?;
1027 let (end_row, end_row_abs) = Self::instantiate_axis(row_end, placement.row)?;
1028 let (start_col, start_col_abs) =
1029 Self::instantiate_axis(col_start, placement.col)?;
1030 let (end_col, end_col_abs) = Self::instantiate_axis(col_end, placement.col)?;
1031 ReferenceType::Range {
1032 sheet,
1033 start_row: Some(start_row),
1034 start_col: Some(start_col),
1035 end_row: Some(end_row),
1036 end_col: Some(end_col),
1037 start_row_abs,
1038 start_col_abs,
1039 end_row_abs,
1040 end_col_abs,
1041 }
1042 }
1043 DirtyProjectionRule::WholeColumnRange { col_start, col_end } => {
1044 let (start_col, start_col_abs) =
1045 Self::instantiate_axis(col_start, placement.col)?;
1046 let (end_col, end_col_abs) = Self::instantiate_axis(col_end, placement.col)?;
1047 ReferenceType::Range {
1048 sheet,
1049 start_row: None,
1050 start_col: Some(start_col),
1051 end_row: None,
1052 end_col: Some(end_col),
1053 start_row_abs: true,
1054 start_col_abs,
1055 end_row_abs: true,
1056 end_col_abs,
1057 }
1058 }
1059 DirtyProjectionRule::WholeResult => return None,
1062 };
1063 references.push(reference);
1064 }
1065 #[cfg(test)]
1066 record_formula_plane_template_path();
1067 Some(references)
1068 }
1069
1070 fn visit_formula_plane_dependents(
1071 &self,
1072 cell: CellKey,
1073 budget: &mut WorkBudget,
1074 visitor: &mut dyn DependentVisitor,
1075 ) -> QueryCompleteness {
1076 if self.engine.config.formula_plane_mode != FormulaPlaneMode::AuthoritativeExperimental {
1077 return QueryCompleteness::Complete;
1078 }
1079 let authority = self.engine.graph.formula_authority();
1080 let candidate_limit = usize::try_from(budget.remaining).unwrap_or(usize::MAX);
1081 let query = authority.consumer_reads.query_changed_region_bounded(
1082 Region::point(cell.sheet_id, cell.row0, cell.col0),
1083 candidate_limit,
1084 );
1085 let mut query = match query {
1086 BoundedRegionQueryResult::Complete(query) => query,
1087 BoundedRegionQueryResult::Incomplete {
1088 observed_candidates,
1089 } => {
1090 budget.remaining = budget.remaining.saturating_sub(observed_candidates as u64);
1091 return QueryCompleteness::Incomplete;
1092 }
1093 };
1094 budget.remaining = budget
1095 .remaining
1096 .saturating_sub(query.stats.candidate_count as u64);
1097 query.matches.sort_by(|left, right| {
1098 let key = |producer| {
1099 let FormulaProducerId::Span(span_id) = producer else {
1100 return None;
1101 };
1102 let span_ref = authority.plane.spans.current_ref(span_id)?;
1103 let span = authority.plane.spans.get(span_ref)?;
1104 let placement = span.domain.iter().next()?;
1105 Some((
1106 self.engine.graph.sheet_name(placement.sheet_id),
1107 placement.row,
1108 placement.col,
1109 ))
1110 };
1111 key(left.value.consumer).cmp(&key(right.value.consumer))
1112 });
1113
1114 for matched in query.matches {
1115 let FormulaProducerId::Span(span_id) = matched.value.consumer else {
1116 continue;
1117 };
1118 let Some(span_ref) = authority.plane.spans.current_ref(span_id) else {
1119 continue;
1120 };
1121 let Some(span) = authority.plane.spans.get(span_ref) else {
1122 continue;
1123 };
1124 let whole = ProducerDirtyDomain::Whole;
1125 let dirty = match &matched.value.dirty {
1126 ProjectionResult::Exact(dirty) | ProjectionResult::Conservative { dirty, .. } => {
1127 dirty
1128 }
1129 ProjectionResult::NoIntersection => continue,
1130 ProjectionResult::Unsupported(_) => &whole,
1131 };
1132 for placement in span.domain.iter() {
1133 if !Self::dirty_domain_contains(dirty, placement) {
1134 continue;
1135 }
1136 if !budget.charge()
1137 || !visitor.visit(CellKey {
1138 sheet_id: placement.sheet_id,
1139 row0: placement.row,
1140 col0: placement.col,
1141 })
1142 {
1143 return QueryCompleteness::Incomplete;
1144 }
1145 }
1146 }
1147 QueryCompleteness::Complete
1148 }
1149}
1150
1151impl<R: EvaluationContext> InspectSource for FormulaPlaneInspectSource<'_, R> {
1152 fn formula_at(&self, cell: CellKey) -> Result<Option<FormulaView>, InspectError> {
1153 let Some((span_ref, placement)) = self.span_placement(cell) else {
1154 return self.legacy.formula_at(cell);
1155 };
1156 let sheet = self.engine.graph.sheet_name(cell.sheet_id);
1157 let ast = self
1160 .engine
1161 .get_cell(sheet, cell.row0 + 1, cell.col0 + 1)
1162 .and_then(|(ast, _)| ast);
1163 Ok(ast.map(|ast| FormulaView {
1164 ast,
1165 volatile: false,
1168 dirty: self.span_placement_is_dirty(span_ref, placement),
1169 }))
1170 }
1171
1172 fn visit_declared_references(
1173 &self,
1174 cell: CellKey,
1175 visitor: &mut dyn ReferenceVisitor,
1176 ) -> Result<(), InspectError> {
1177 let Some((span_ref, placement)) = self.span_placement(cell) else {
1178 return self.legacy.visit_declared_references(cell, visitor);
1179 };
1180 let Some(references) = self.instantiated_span_references(span_ref, placement) else {
1181 #[cfg(test)]
1182 record_formula_plane_ast_fallback_path();
1183 let Some(formula) = self.formula_at(cell)? else {
1186 return Ok(());
1187 };
1188 return visit_formula_ast_references(&formula.ast, visitor);
1189 };
1190 for reference in &references {
1191 if !visitor.visit(refs::classify(reference)) {
1192 break;
1193 }
1194 }
1195 Ok(())
1196 }
1197
1198 fn visit_dependents_covering(
1199 &self,
1200 cell: CellKey,
1201 budget: &mut WorkBudget,
1202 visitor: &mut dyn DependentVisitor,
1203 ) -> Result<QueryCompleteness, InspectError> {
1204 if self
1205 .legacy
1206 .visit_dependents_covering(cell, budget, visitor)?
1207 == QueryCompleteness::Incomplete
1208 {
1209 return Ok(QueryCompleteness::Incomplete);
1210 }
1211 Ok(self.visit_formula_plane_dependents(cell, budget, visitor))
1212 }
1213
1214 fn spill_role(&self, cell: CellKey) -> Option<InternalSpillRole> {
1215 self.legacy.spill_role(cell)
1218 }
1219}
1220
1221fn merge_omitted(target: &mut Option<OmittedCount>, addition: OmittedCount) {
1222 *target = Some(match (target.take(), addition) {
1223 (None, value) => value,
1224 (Some(OmittedCount::Exact(a)), OmittedCount::Exact(b)) => {
1225 OmittedCount::Exact(a.saturating_add(b))
1226 }
1227 (Some(OmittedCount::Exact(a)), OmittedCount::AtLeast(b))
1228 | (Some(OmittedCount::AtLeast(a)), OmittedCount::Exact(b))
1229 | (Some(OmittedCount::AtLeast(a)), OmittedCount::AtLeast(b)) => {
1230 OmittedCount::AtLeast(a.saturating_add(b))
1231 }
1232 });
1233}
1234
1235fn address_cmp(left: &CellAddress, right: &CellAddress) -> std::cmp::Ordering {
1236 left.sheet
1237 .cmp(&right.sheet)
1238 .then_with(|| left.row.cmp(&right.row))
1239 .then_with(|| left.column.cmp(&right.column))
1240}
1241
1242impl<R: EvaluationContext> Engine<R> {
1243 fn inspect_stamp(&self) -> StateStamp {
1244 StateStamp {
1245 mutation_revision: self.inspection_mutation_revision(),
1246 recalc_epoch: self.recalc_epoch,
1247 }
1248 }
1249
1250 fn inspect_source(&self) -> FormulaPlaneInspectSource<'_, R> {
1251 FormulaPlaneInspectSource::new(self)
1252 }
1253
1254 fn canonical_cell(
1255 &self,
1256 address: &CellAddress,
1257 ) -> Result<(CellKey, CellAddress), InspectError> {
1258 CellAddress::new(address.sheet.clone(), address.row, address.column).map_err(|error| {
1259 InspectError::InvalidAddress {
1260 message: error.to_string(),
1261 }
1262 })?;
1263 let Some(sheet_id) = self.graph.sheet_id(&address.sheet) else {
1264 return Err(InspectError::SheetNotFound {
1265 sheet: address.sheet.clone(),
1266 });
1267 };
1268 let canonical = CellAddress {
1269 sheet: self.graph.sheet_name(sheet_id).to_string(),
1270 row: address.row,
1271 column: address.column,
1272 };
1273 Ok((
1274 CellKey {
1275 sheet_id,
1276 row0: address.row - 1,
1277 col0: address.column - 1,
1278 },
1279 canonical,
1280 ))
1281 }
1282
1283 fn canonical_area(&self, area: &RangeArea) -> Result<(SheetId, RangeArea), InspectError> {
1284 RangeArea::new(
1285 area.sheet.clone(),
1286 area.start_row,
1287 area.start_column,
1288 area.end_row,
1289 area.end_column,
1290 )
1291 .map_err(|error| InspectError::InvalidAddress {
1292 message: error.to_string(),
1293 })?;
1294 let Some(sheet_id) = self.graph.sheet_id(&area.sheet) else {
1295 return Err(InspectError::SheetNotFound {
1296 sheet: area.sheet.clone(),
1297 });
1298 };
1299 Ok((
1300 sheet_id,
1301 RangeArea {
1302 sheet: self.graph.sheet_name(sheet_id).to_string(),
1303 start_row: area.start_row,
1304 start_column: area.start_column,
1305 end_row: area.end_row,
1306 end_column: area.end_column,
1307 },
1308 ))
1309 }
1310
1311 fn address_for_key(&self, key: CellKey) -> CellAddress {
1312 CellAddress {
1313 sheet: self.graph.sheet_name(key.sheet_id).to_string(),
1314 row: key.row0 + 1,
1315 column: key.col0 + 1,
1316 }
1317 }
1318
1319 fn key_for_vertex(&self, vertex: VertexId) -> Option<CellKey> {
1320 if !matches!(
1321 self.graph.get_vertex_kind(vertex),
1322 VertexKind::FormulaScalar | VertexKind::FormulaArray
1323 ) {
1324 return None;
1325 }
1326 let cell = self.graph.get_cell_ref(vertex)?;
1327 Some(CellKey {
1328 sheet_id: cell.sheet_id,
1329 row0: cell.coord.row(),
1330 col0: cell.coord.col(),
1331 })
1332 }
1333
1334 fn resolve_semantic_area(&self, area: &RangeArea) -> Option<RangeAddress> {
1335 let extent = resolve_used_extent(
1336 OpenRangeBounds {
1337 start_row: area.start_row,
1338 start_column: area.start_column,
1339 end_row: area.end_row,
1340 end_column: area.end_column,
1341 },
1342 ExtentPolicy::Semantic,
1343 |first, last| self.semantic_used_rows_for_columns(&area.sheet, first, last),
1344 |first, last| self.semantic_used_cols_for_rows(&area.sheet, first, last),
1345 )?;
1346 Some(Self::range_from_extent(&area.sheet, extent))
1347 }
1348
1349 fn range_from_extent(sheet: &str, extent: ResolvedExtent) -> RangeAddress {
1350 RangeAddress {
1351 sheet: sheet.to_string(),
1352 start_row: extent.start_row,
1353 start_col: extent.start_column,
1354 end_row: extent.end_row,
1355 end_col: extent.end_column,
1356 }
1357 }
1358
1359 fn snapshot_for_key(
1360 &self,
1361 key: CellKey,
1362 include_value: bool,
1363 ) -> Result<CellSnapshot, InspectError> {
1364 let source = self.inspect_source();
1365 let formula = source.formula_at(key)?;
1366 let address = self.address_for_key(key);
1367 let cached_value = self.read_cell_value(&address.sheet, address.row, address.column);
1368 let (canonical_formula, volatile, staleness) = match formula {
1369 Some(view) => {
1370 let staleness = if cached_value.is_none() {
1375 Staleness::NeverEvaluated
1376 } else if view.dirty {
1377 Staleness::Dirty
1378 } else {
1379 Staleness::Current
1380 };
1381 (
1382 Some(formualizer_parse::pretty::canonical_formula(&view.ast)),
1383 view.volatile,
1384 staleness,
1385 )
1386 }
1387 None => (None, false, Staleness::Current),
1388 };
1389 let spill = source.spill_role(key).map(|role| match role {
1390 InternalSpillRole::Anchor { extent } => SpillRole::Anchor { extent },
1391 InternalSpillRole::Member { anchor } => SpillRole::Member {
1392 anchor: self.address_for_key(anchor),
1393 },
1394 });
1395 Ok(CellSnapshot {
1396 address,
1397 formula: canonical_formula,
1398 value: include_value.then_some(cached_value).flatten(),
1399 value_included: include_value,
1400 staleness,
1401 volatile,
1402 spill,
1403 })
1404 }
1405
1406 pub fn inspect_cell(
1408 &self,
1409 cell: &CellAddress,
1410 options: &SnapshotOptions,
1411 ) -> Result<CellSnapshotReport, InspectError> {
1412 let (key, _) = self.canonical_cell(cell)?;
1413 Ok(CellSnapshotReport {
1414 stamp: self.inspect_stamp(),
1415 cell: self.snapshot_for_key(key, options.include_values)?,
1416 })
1417 }
1418
1419 fn resolve_name(&self, key: CellKey, name: &str) -> NameResolution {
1420 let Some(named) = self.graph.resolve_name_entry(name, key.sheet_id) else {
1421 return NameResolution::Unresolved;
1422 };
1423 match &named.definition {
1424 NamedDefinition::Cell(cell) => NameResolution::Cell(CellAddress {
1425 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
1426 row: cell.coord.row() + 1,
1427 column: cell.coord.col() + 1,
1428 }),
1429 NamedDefinition::Range(range) => {
1430 let resolved = RangeAddress {
1431 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
1432 start_row: range.start.coord.row() + 1,
1433 start_col: range.start.coord.col() + 1,
1434 end_row: range.end.coord.row() + 1,
1435 end_col: range.end.coord.col() + 1,
1436 };
1437 NameResolution::Range {
1438 declared: RangeArea::from_finite(&resolved),
1439 resolved: Some(resolved),
1440 }
1441 }
1442 NamedDefinition::Literal(value) => NameResolution::Literal(value.clone()),
1443 NamedDefinition::Formula { ast, .. } => NameResolution::Formula {
1444 formula: formualizer_parse::pretty::canonical_formula(ast),
1445 value: self.graph.get_value(named.vertex),
1446 },
1447 }
1448 }
1449
1450 fn resolve_table_area(
1451 &self,
1452 key: CellKey,
1453 table_ref: &TableReference,
1454 ) -> Option<(String, String, RangeAddress)> {
1455 let metadata = self.table_metadata(&table_ref.name)?;
1456 let canonical_name = metadata.name.clone();
1457 let specifier = table_ref
1458 .specifier
1459 .as_ref()
1460 .map(ToString::to_string)
1461 .unwrap_or_default();
1462 let mut start_row = metadata.start_row;
1463 let mut end_row = metadata.end_row;
1464 let mut start_col = metadata.start_col;
1465 let mut end_col = metadata.end_col;
1466 let data_start = start_row + u32::from(metadata.header_row);
1467 let data_end = end_row.saturating_sub(u32::from(metadata.totals_row));
1468
1469 fn col_index(headers: &[String], name: &str) -> Option<u32> {
1470 headers
1471 .iter()
1472 .position(|header| header.eq_ignore_ascii_case(name))
1473 .and_then(|index| u32::try_from(index).ok())
1474 }
1475
1476 match table_ref.specifier.as_ref()? {
1477 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1478 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1479 start_row = data_start;
1480 end_row = data_end;
1481 }
1482 TableSpecifier::Headers | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1483 if !metadata.header_row {
1484 return None;
1485 }
1486 end_row = start_row;
1487 }
1488 TableSpecifier::Totals | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1489 if !metadata.totals_row {
1490 return None;
1491 }
1492 start_row = end_row;
1493 }
1494 TableSpecifier::Column(name) => {
1495 let index = col_index(&metadata.headers, name)?;
1496 start_col += index;
1497 end_col = start_col;
1498 start_row = data_start;
1499 end_row = data_end;
1500 }
1501 TableSpecifier::ColumnRange(first, last) => {
1502 let mut first = col_index(&metadata.headers, first)?;
1503 let mut last = col_index(&metadata.headers, last)?;
1504 if first > last {
1505 std::mem::swap(&mut first, &mut last);
1506 }
1507 start_col += first;
1508 end_col = metadata.start_col + last;
1509 start_row = data_start;
1510 end_row = data_end;
1511 }
1512 TableSpecifier::SpecialItem(SpecialItem::ThisRow)
1513 | TableSpecifier::Row(formualizer_parse::parser::TableRowSpecifier::Current) => {
1514 let row = key.row0 + 1;
1515 if row < data_start || row > data_end {
1516 return None;
1517 }
1518 start_row = row;
1519 end_row = row;
1520 }
1521 TableSpecifier::Row(_) => return None,
1522 TableSpecifier::Combination(parts) => {
1523 let mut this_row = false;
1524 let mut selected_column: Option<(u32, u32)> = None;
1525 for part in parts {
1526 match part.as_ref() {
1527 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => this_row = true,
1528 TableSpecifier::Column(name) => {
1529 let column = col_index(&metadata.headers, name)?;
1530 selected_column = Some((column, column));
1531 }
1532 TableSpecifier::ColumnRange(first, last) => {
1533 let mut first = col_index(&metadata.headers, first)?;
1534 let mut last = col_index(&metadata.headers, last)?;
1535 if first > last {
1536 std::mem::swap(&mut first, &mut last);
1537 }
1538 selected_column = Some((first, last));
1539 }
1540 TableSpecifier::Data | TableSpecifier::SpecialItem(SpecialItem::Data) => {
1541 start_row = data_start;
1542 end_row = data_end;
1543 }
1544 TableSpecifier::Headers
1545 | TableSpecifier::SpecialItem(SpecialItem::Headers) => {
1546 if !metadata.header_row {
1547 return None;
1548 }
1549 end_row = start_row;
1550 }
1551 TableSpecifier::Totals
1552 | TableSpecifier::SpecialItem(SpecialItem::Totals) => {
1553 if !metadata.totals_row {
1554 return None;
1555 }
1556 start_row = end_row;
1557 }
1558 TableSpecifier::All | TableSpecifier::SpecialItem(SpecialItem::All) => {}
1559 TableSpecifier::Row(_) | TableSpecifier::Combination(_) => return None,
1560 }
1561 }
1562 if this_row {
1563 let row = key.row0 + 1;
1564 if row < data_start || row > data_end {
1565 return None;
1566 }
1567 start_row = row;
1568 end_row = row;
1569 }
1570 if let Some((first, last)) = selected_column {
1571 start_col = metadata.start_col + first;
1572 end_col = metadata.start_col + last;
1573 if !this_row {
1574 start_row = data_start;
1575 end_row = data_end;
1576 }
1577 }
1578 }
1579 }
1580 if start_row > end_row || start_col > end_col {
1581 return None;
1582 }
1583 Some((
1584 canonical_name,
1585 specifier,
1586 RangeAddress {
1587 sheet: metadata.sheet,
1588 start_row,
1589 start_col,
1590 end_row,
1591 end_col,
1592 },
1593 ))
1594 }
1595
1596 fn own_reference(
1597 &self,
1598 key: CellKey,
1599 reference: refs::SemanticReference<'_>,
1600 ) -> SemanticReference {
1601 match reference {
1602 refs::SemanticReference::Cell(cell) => {
1603 let sheet_id = cell
1604 .sheet
1605 .name()
1606 .and_then(|name| self.graph.sheet_id(name))
1607 .unwrap_or(key.sheet_id);
1608 SemanticReference::Cell(CellAddress {
1609 sheet: self.graph.sheet_name(sheet_id).to_string(),
1610 row: cell.row,
1611 column: cell.col,
1612 })
1613 }
1614 refs::SemanticReference::FiniteRange(range)
1615 | refs::SemanticReference::OpenRange(range) => {
1616 let sheet_id = range
1617 .sheet
1618 .name()
1619 .and_then(|name| self.graph.sheet_id(name))
1620 .unwrap_or(key.sheet_id);
1621 let declared = RangeArea {
1622 sheet: self.graph.sheet_name(sheet_id).to_string(),
1623 start_row: range.start_row,
1624 start_column: range.start_col,
1625 end_row: range.end_row,
1626 end_column: range.end_col,
1627 };
1628 let resolved = self.resolve_semantic_area(&declared);
1629 let cell_count = resolved.as_ref().map_or(0, |range| {
1630 u64::from(range.width()) * u64::from(range.height())
1631 });
1632 SemanticReference::Range {
1633 declared,
1634 resolved,
1635 cell_count,
1636 }
1637 }
1638 refs::SemanticReference::Name(name) => SemanticReference::Name {
1639 name: name.to_string(),
1640 resolution: self.resolve_name(key, name),
1641 },
1642 refs::SemanticReference::Table(table) => {
1643 if let Some((name, specifier, resolved)) = self.resolve_table_area(key, table) {
1644 SemanticReference::Table {
1645 name,
1646 specifier,
1647 resolved,
1648 }
1649 } else {
1650 SemanticReference::Unsupported {
1651 text: ReferenceType::Table(table.clone()).to_string(),
1652 reason: "structured reference could not be resolved at this placement"
1653 .to_string(),
1654 }
1655 }
1656 }
1657 refs::SemanticReference::ExternalSource(external) => SemanticReference::External {
1658 raw: external.raw.clone(),
1659 },
1660 refs::SemanticReference::ThreeDimensional(reference) => {
1661 SemanticReference::Unsupported {
1662 text: reference.to_string(),
1663 reason: "3D references are not supported by phase-1 introspection".to_string(),
1664 }
1665 }
1666 refs::SemanticReference::Unsupported(reference) => SemanticReference::Unsupported {
1667 text: reference.to_string(),
1668 reason: "reference form is unsupported by introspection".to_string(),
1669 },
1670 }
1671 }
1672
1673 fn collect_precedents(
1674 &self,
1675 key: CellKey,
1676 max_links: u32,
1677 work: &mut WorkBudget,
1678 ) -> Result<(Vec<Precedent>, TruncationReport), InspectError> {
1679 struct Collector<'a, R> {
1680 engine: &'a Engine<R>,
1681 key: CellKey,
1682 max_links: usize,
1683 work: &'a mut WorkBudget,
1684 precedents: Vec<Precedent>,
1685 truncated: bool,
1686 }
1687 impl<R: EvaluationContext> ReferenceVisitor for Collector<'_, R> {
1688 fn visit(&mut self, reference: refs::SemanticReference<'_>) -> bool {
1689 if !self.work.charge() {
1690 self.truncated = true;
1691 return false;
1692 }
1693 let reference = self.engine.own_reference(self.key, reference);
1694 if self
1695 .precedents
1696 .iter()
1697 .any(|existing| existing.reference == reference)
1698 {
1699 return true;
1700 }
1701 if self.precedents.len() >= self.max_links {
1702 self.truncated = true;
1703 return false;
1704 }
1705 self.precedents.push(Precedent {
1706 reference,
1707 provenance: Provenance::Declared,
1708 });
1709 true
1710 }
1711 }
1712
1713 let source = self.inspect_source();
1714 let mut collector = Collector {
1715 engine: self,
1716 key,
1717 max_links: max_links as usize,
1718 work,
1719 precedents: Vec::new(),
1720 truncated: false,
1721 };
1722 source.visit_declared_references(key, &mut collector)?;
1723 let truncation = if collector.truncated {
1724 TruncationReport {
1725 incomplete: true,
1726 omitted: Some(OmittedCount::AtLeast(1)),
1727 }
1728 } else {
1729 TruncationReport::default()
1730 };
1731 Ok((collector.precedents, truncation))
1732 }
1733
1734 pub fn precedents(
1737 &self,
1738 cell: &CellAddress,
1739 options: &PrecedentOptions,
1740 ) -> Result<PrecedentReport, InspectError> {
1741 let (key, canonical) = self.canonical_cell(cell)?;
1742 let mut work = WorkBudget::new(options.max_work);
1743 let (precedents, truncation) =
1744 self.collect_precedents(key, options.max_links, &mut work)?;
1745 Ok(PrecedentReport {
1746 stamp: self.inspect_stamp(),
1747 cell: canonical,
1748 precedents,
1749 truncation,
1750 })
1751 }
1752
1753 fn dependency_state_available(&self) -> Result<(), InspectError> {
1754 if self.has_staged_formulas() {
1755 Err(InspectError::DependencyStateUnavailable {
1756 reason: InspectionUnavailableReason::DeferredDependencyGraph,
1757 })
1758 } else {
1759 Ok(())
1760 }
1761 }
1762
1763 fn spill_query_members(&self, key: CellKey) -> Vec<CellKey> {
1764 let source = self.inspect_source();
1765 let Some(InternalSpillRole::Anchor { .. }) = source.spill_role(key) else {
1766 return vec![key];
1767 };
1768 let cell_ref = CellRef::new(key.sheet_id, Coord::new(key.row0, key.col0, true, true));
1769 let Some(vertex) = self.graph.get_vertex_for_cell(&cell_ref) else {
1770 return vec![key];
1771 };
1772 self.graph
1773 .spill_cells_for_anchor(vertex)
1774 .unwrap_or(&[])
1775 .iter()
1776 .map(|member| CellKey {
1777 sheet_id: member.sheet_id,
1778 row0: member.coord.row(),
1779 col0: member.coord.col(),
1780 })
1781 .collect()
1782 }
1783
1784 fn collect_dependents(
1785 &self,
1786 key: CellKey,
1787 max_results: u32,
1788 work: &mut WorkBudget,
1789 ) -> Result<(Vec<Dependent>, TruncationReport), InspectError> {
1790 self.dependency_state_available()?;
1791 let source = self.inspect_source();
1792 let mut found: FxHashMap<CellAddress, Vec<CellAddress>> = FxHashMap::default();
1793 let mut incomplete = false;
1794 let max_results = max_results as usize;
1795
1796 let spill_anchor_query = matches!(
1797 source.spill_role(key),
1798 Some(InternalSpillRole::Anchor { .. })
1799 );
1800 let members = self.spill_query_members(key);
1801 for member in members {
1802 if !work.charge() {
1803 incomplete = true;
1804 break;
1805 }
1806 let via = self.address_for_key(member);
1807 let mut record = |dependent_key: CellKey| {
1808 let address = self.address_for_key(dependent_key);
1809 if let Some(via_members) = found.get_mut(&address) {
1810 if spill_anchor_query && !via_members.contains(&via) {
1811 via_members.push(via.clone());
1812 }
1813 return true;
1814 }
1815 found.insert(
1816 address,
1817 if spill_anchor_query {
1818 vec![via.clone()]
1819 } else {
1820 Vec::new()
1821 },
1822 );
1823 true
1824 };
1825
1826 let member_ref = CellRef::new(
1827 member.sheet_id,
1828 Coord::new(member.row0, member.col0, true, true),
1829 );
1830 if let Some(vertex) = self.graph.get_vertex_for_cell(&member_ref) {
1831 let complete = self.graph.visit_direct_dependents_bounded(
1832 vertex,
1833 &mut work.remaining,
1834 &mut |dependent| self.key_for_vertex(dependent).is_none_or(&mut record),
1835 );
1836 if !complete {
1837 incomplete = true;
1838 break;
1839 }
1840 }
1841
1842 struct Visitor<'a, F>(&'a mut F);
1843 impl<F: FnMut(CellKey) -> bool> DependentVisitor for Visitor<'_, F> {
1844 fn visit(&mut self, dependent: CellKey) -> bool {
1845 (self.0)(dependent)
1846 }
1847 }
1848 let mut visitor = Visitor(&mut record);
1849 if source.visit_dependents_covering(member, work, &mut visitor)?
1850 == QueryCompleteness::Incomplete
1851 {
1852 incomplete = true;
1853 break;
1854 }
1855 }
1856
1857 let mut dependents: Vec<_> = found
1858 .into_iter()
1859 .map(|(cell, mut via)| {
1860 via.sort_by(address_cmp);
1861 via.dedup();
1862 Dependent { cell, via }
1863 })
1864 .collect();
1865 dependents.sort_by(|left, right| address_cmp(&left.cell, &right.cell));
1866 let known_omitted_dependent = dependents.len() > max_results;
1867 if known_omitted_dependent {
1868 dependents.truncate(max_results);
1869 incomplete = true;
1870 }
1871 Ok((
1872 dependents,
1873 if incomplete {
1874 TruncationReport {
1875 incomplete: true,
1876 omitted: known_omitted_dependent.then_some(OmittedCount::AtLeast(1)),
1877 }
1878 } else {
1879 TruncationReport::default()
1880 },
1881 ))
1882 }
1883
1884 pub fn dependents(
1887 &self,
1888 cell: &CellAddress,
1889 options: &DependentsOptions,
1890 ) -> Result<DependentsReport, InspectError> {
1891 let (key, canonical) = self.canonical_cell(cell)?;
1892 let mut work = WorkBudget::new(options.max_work);
1893 let (dependents, truncation) =
1894 self.collect_dependents(key, options.max_results, &mut work)?;
1895 Ok(DependentsReport {
1896 stamp: self.inspect_stamp(),
1897 cell: canonical,
1898 dependents,
1899 truncation,
1900 })
1901 }
1902
1903 fn target_addresses(reference: &SemanticReference) -> Option<&RangeAddress> {
1904 match reference {
1905 SemanticReference::Range { resolved, .. } => resolved.as_ref(),
1906 SemanticReference::Name {
1907 resolution: NameResolution::Range { resolved, .. },
1908 ..
1909 } => resolved.as_ref(),
1910 SemanticReference::Table { resolved, .. } => Some(resolved),
1911 _ => None,
1912 }
1913 }
1914
1915 fn target_cell(reference: &SemanticReference) -> Option<&CellAddress> {
1916 match reference {
1917 SemanticReference::Cell(cell) => Some(cell),
1918 SemanticReference::Name {
1919 resolution: NameResolution::Cell(cell),
1920 ..
1921 } => Some(cell),
1922 _ => None,
1923 }
1924 }
1925
1926 fn classify_cycle_dispositions(nodes: &mut [TraceNode]) {
1930 let adjacency: Vec<Vec<usize>> = nodes
1931 .iter()
1932 .map(|node| {
1933 node.links
1934 .iter()
1935 .flat_map(|link| link.targets.iter())
1936 .map(|target| target.node.0 as usize)
1937 .collect()
1938 })
1939 .collect();
1940 let mut reachability = vec![vec![false; nodes.len()]; nodes.len()];
1941 for start in 0..nodes.len() {
1942 let mut stack = adjacency[start].clone();
1943 while let Some(next) = stack.pop() {
1944 if reachability[start][next] {
1945 continue;
1946 }
1947 reachability[start][next] = true;
1948 stack.extend(adjacency[next].iter().copied());
1949 }
1950 }
1951
1952 for (source, node) in nodes.iter_mut().enumerate() {
1953 for target in node
1954 .links
1955 .iter_mut()
1956 .flat_map(|link| link.targets.iter_mut())
1957 {
1958 let target_index = target.node.0 as usize;
1959 if source == target_index || reachability[target_index][source] {
1960 target.disposition = LinkDisposition::Cycle;
1961 } else if target.disposition == LinkDisposition::Cycle {
1962 target.disposition = LinkDisposition::Convergent;
1963 }
1964 }
1965 }
1966 }
1967
1968 pub fn trace(
1970 &self,
1971 roots: &[CellAddress],
1972 options: &TraceOptions,
1973 ) -> Result<TraceGraph, InspectError> {
1974 if roots.is_empty() {
1975 return Err(InspectError::InvalidOptions {
1976 message: "trace requires at least one root".to_string(),
1977 });
1978 }
1979 let canonical_roots: Vec<_> = roots
1980 .iter()
1981 .map(|root| self.canonical_cell(root))
1982 .collect::<Result<_, _>>()?;
1983 let mut unique_roots = FxHashMap::default();
1984 for (key, canonical) in &canonical_roots {
1985 unique_roots.entry(canonical.clone()).or_insert(*key);
1986 }
1987 if unique_roots.len() > options.max_nodes as usize {
1988 return Err(InspectError::InvalidOptions {
1989 message: format!(
1990 "max_nodes ({}) must hold all {} unique roots",
1991 options.max_nodes,
1992 unique_roots.len()
1993 ),
1994 });
1995 }
1996 if options.direction == TraceDirection::Dependents {
1997 self.dependency_state_available()?;
1998 }
1999
2000 let mut nodes = Vec::new();
2001 let mut node_by_address: FxHashMap<CellAddress, TraceNodeId> = FxHashMap::default();
2002 let mut root_ids = Vec::new();
2003 let mut queue = VecDeque::new();
2004 let mut parents: HashMap<TraceNodeId, Option<TraceNodeId>> = HashMap::new();
2005 let mut truncation = TruncationReport::default();
2006
2007 for (key, canonical) in canonical_roots {
2010 if let Some(&id) = node_by_address.get(&canonical) {
2011 root_ids.push(id);
2012 continue;
2013 }
2014 let id = TraceNodeId(nodes.len() as u32);
2015 nodes.push(TraceNode {
2016 id,
2017 cell: self.snapshot_for_key(key, options.include_values)?,
2018 links: Vec::new(),
2019 });
2020 node_by_address.insert(canonical, id);
2021 root_ids.push(id);
2022 queue.push_back((id, key, 0u32));
2023 parents.insert(id, None);
2024 }
2025
2026 let mut work = WorkBudget::new(options.max_work);
2027 let mut links_used = 0u32;
2028 let mut range_members_used = 0u32;
2029
2030 while let Some((source_id, source_key, depth)) = queue.pop_front() {
2031 let can_follow = depth < options.max_depth;
2032 let mut links = Vec::new();
2033 if options.direction == TraceDirection::Precedents {
2034 if let Some(InternalSpillRole::Member { anchor }) =
2035 self.inspect_source().spill_role(source_key)
2036 {
2037 if links_used < options.max_links {
2038 links_used += 1;
2039 let anchor_address = self.address_for_key(anchor);
2040 let mut link = TraceLink {
2041 reference: SemanticReference::Cell(anchor_address.clone()),
2042 kind: TraceLinkKind::SpillAnchor,
2043 targets: Vec::new(),
2044 omitted: None,
2045 };
2046 self.attach_cell_target(
2047 anchor_address,
2048 source_id,
2049 anchor,
2050 can_follow,
2051 depth,
2052 options,
2053 false,
2054 &mut nodes,
2055 &mut node_by_address,
2056 &mut parents,
2057 &mut queue,
2058 &mut link,
2059 &mut truncation,
2060 )?;
2061 links.push(link);
2062 } else {
2063 truncation.incomplete = true;
2064 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
2065 }
2066 } else {
2067 let available_links = options.max_links.saturating_sub(links_used);
2068 let (precedents, local_truncation) =
2069 self.collect_precedents(source_key, available_links, &mut work)?;
2070 if local_truncation.incomplete {
2071 truncation.incomplete = true;
2072 if let Some(omitted) = local_truncation.omitted {
2073 merge_omitted(&mut truncation.omitted, omitted);
2074 }
2075 }
2076 for precedent in precedents {
2077 links_used += 1;
2078 let mut link = TraceLink {
2079 reference: precedent.reference,
2080 kind: TraceLinkKind::Formula {
2081 provenance: precedent.provenance,
2082 },
2083 targets: Vec::new(),
2084 omitted: None,
2085 };
2086 if let Some(cell) = Self::target_cell(&link.reference).cloned() {
2087 let (target_key, canonical) = self.canonical_cell(&cell)?;
2088 self.attach_cell_target(
2089 canonical,
2090 source_id,
2091 target_key,
2092 can_follow,
2093 depth,
2094 options,
2095 false,
2096 &mut nodes,
2097 &mut node_by_address,
2098 &mut parents,
2099 &mut queue,
2100 &mut link,
2101 &mut truncation,
2102 )?;
2103 } else if let Some(range) = Self::target_addresses(&link.reference).cloned()
2104 {
2105 self.attach_range_targets(
2106 &range,
2107 source_id,
2108 can_follow,
2109 depth,
2110 options,
2111 &mut range_members_used,
2112 &mut nodes,
2113 &mut node_by_address,
2114 &mut parents,
2115 &mut queue,
2116 &mut link,
2117 &mut truncation,
2118 )?;
2119 }
2120 links.push(link);
2121 }
2122 }
2123 } else {
2124 let available = options.max_links.saturating_sub(links_used);
2125 let (dependents, local_truncation) =
2126 self.collect_dependents(source_key, available, &mut work)?;
2127 if local_truncation.incomplete {
2128 truncation.incomplete = true;
2129 if let Some(omitted) = local_truncation.omitted {
2130 merge_omitted(&mut truncation.omitted, omitted);
2131 }
2132 }
2133 for dependent in dependents {
2134 links_used += 1;
2135 let spill_reader = dependent
2136 .via
2137 .iter()
2138 .any(|via| via != &self.address_for_key(source_key));
2139 let (target_key, canonical) = self.canonical_cell(&dependent.cell)?;
2140 let mut link = TraceLink {
2141 reference: SemanticReference::Cell(canonical.clone()),
2142 kind: if spill_reader {
2143 TraceLinkKind::SpillReader
2144 } else {
2145 TraceLinkKind::Formula {
2146 provenance: Provenance::Declared,
2147 }
2148 },
2149 targets: Vec::new(),
2150 omitted: None,
2151 };
2152 self.attach_cell_target(
2153 canonical,
2154 source_id,
2155 target_key,
2156 can_follow,
2157 depth,
2158 options,
2159 false,
2160 &mut nodes,
2161 &mut node_by_address,
2162 &mut parents,
2163 &mut queue,
2164 &mut link,
2165 &mut truncation,
2166 )?;
2167 links.push(link);
2168 }
2169 }
2170 nodes[source_id.0 as usize].links = links;
2171 }
2172
2173 Self::classify_cycle_dispositions(&mut nodes);
2174
2175 Ok(TraceGraph {
2176 stamp: self.inspect_stamp(),
2177 direction: options.direction,
2178 roots: root_ids,
2179 nodes,
2180 truncation,
2181 })
2182 }
2183
2184 #[allow(clippy::too_many_arguments)]
2185 fn attach_cell_target(
2186 &self,
2187 address: CellAddress,
2188 source_id: TraceNodeId,
2189 key: CellKey,
2190 can_follow: bool,
2191 depth: u32,
2192 options: &TraceOptions,
2193 defer_missing_omission: bool,
2194 nodes: &mut Vec<TraceNode>,
2195 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
2196 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
2197 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
2198 link: &mut TraceLink,
2199 truncation: &mut TruncationReport,
2200 ) -> Result<(), InspectError> {
2201 if let Some(&target_id) = node_by_address.get(&address) {
2202 link.targets.push(TraceLinkTarget {
2203 node: target_id,
2204 disposition: LinkDisposition::Convergent,
2207 });
2208 return Ok(());
2209 }
2210 if nodes.len() >= options.max_nodes as usize {
2211 link.omitted = Some(OmittedCount::AtLeast(1));
2212 truncation.incomplete = true;
2213 if !defer_missing_omission {
2214 merge_omitted(&mut truncation.omitted, OmittedCount::AtLeast(1));
2215 }
2216 return Ok(());
2217 }
2218 let target_id = TraceNodeId(nodes.len() as u32);
2219 nodes.push(TraceNode {
2220 id: target_id,
2221 cell: self.snapshot_for_key(key, options.include_values)?,
2222 links: Vec::new(),
2223 });
2224 node_by_address.insert(address, target_id);
2225 parents.insert(target_id, Some(source_id));
2226 link.targets.push(TraceLinkTarget {
2227 node: target_id,
2228 disposition: if can_follow {
2229 LinkDisposition::Expanded
2230 } else {
2231 LinkDisposition::Elided
2232 },
2233 });
2234 if can_follow {
2235 queue.push_back((target_id, key, depth + 1));
2236 } else {
2237 truncation.incomplete = true;
2240 }
2241 Ok(())
2242 }
2243
2244 #[allow(clippy::too_many_arguments)]
2245 fn attach_range_targets(
2246 &self,
2247 range: &RangeAddress,
2248 source_id: TraceNodeId,
2249 can_follow: bool,
2250 depth: u32,
2251 options: &TraceOptions,
2252 range_members_used: &mut u32,
2253 nodes: &mut Vec<TraceNode>,
2254 node_by_address: &mut FxHashMap<CellAddress, TraceNodeId>,
2255 parents: &mut HashMap<TraceNodeId, Option<TraceNodeId>>,
2256 queue: &mut VecDeque<(TraceNodeId, CellKey, u32)>,
2257 link: &mut TraceLink,
2258 truncation: &mut TruncationReport,
2259 ) -> Result<(), InspectError> {
2260 let total = u64::from(range.width()) * u64::from(range.height());
2261 let mut attached = 0u64;
2262 let mut compressed_ancestors = Vec::new();
2263 let mut cursor = Some(source_id);
2264 while let Some(ancestor) = cursor {
2265 let address = &nodes[ancestor.0 as usize].cell.address;
2266 if range.sheet == address.sheet
2267 && range.start_row <= address.row
2268 && address.row <= range.end_row
2269 && range.start_col <= address.column
2270 && address.column <= range.end_col
2271 {
2272 compressed_ancestors.push((ancestor, address.row, address.column));
2273 }
2274 cursor = parents.get(&ancestor).copied().flatten();
2275 }
2276
2277 for (ancestor, _, _) in &compressed_ancestors {
2280 link.targets.push(TraceLinkTarget {
2281 node: *ancestor,
2282 disposition: LinkDisposition::Convergent,
2285 });
2286 attached += 1;
2287 }
2288
2289 'rows: for row in range.start_row..=range.end_row {
2290 for column in range.start_col..=range.end_col {
2291 if compressed_ancestors
2292 .iter()
2293 .any(|(_, ancestor_row, ancestor_column)| {
2294 row == *ancestor_row && column == *ancestor_column
2295 })
2296 {
2297 continue;
2298 }
2299 if *range_members_used >= options.range_member_budget {
2300 break 'rows;
2301 }
2302 *range_members_used += 1;
2303 let address = CellAddress {
2304 sheet: range.sheet.clone(),
2305 row,
2306 column,
2307 };
2308 let (key, canonical) = self.canonical_cell(&address)?;
2309 let before = link.targets.len();
2310 self.attach_cell_target(
2311 canonical,
2312 source_id,
2313 key,
2314 can_follow,
2315 depth,
2316 options,
2317 true,
2318 nodes,
2319 node_by_address,
2320 parents,
2321 queue,
2322 link,
2323 truncation,
2324 )?;
2325 if link.targets.len() > before {
2326 attached += 1;
2327 } else if link.omitted.is_some() {
2328 break 'rows;
2329 }
2330 }
2331 }
2332 if attached < total {
2333 let omitted = OmittedCount::Exact(total - attached);
2334 link.omitted = Some(omitted);
2335 truncation.incomplete = true;
2336 merge_omitted(&mut truncation.omitted, omitted);
2337 }
2338 Ok(())
2339 }
2340
2341 pub fn range_page(
2343 &self,
2344 area: &RangeArea,
2345 options: &RangePageOptions,
2346 ) -> Result<RangePage, InspectError> {
2347 if options.limit == 0 {
2348 return Err(InspectError::InvalidOptions {
2349 message: "range page limit must be at least one".to_string(),
2350 });
2351 }
2352 let stamp = self.inspect_stamp();
2353 if let Some(expected) = options.expected_stamp
2354 && expected != stamp
2355 {
2356 return Err(InspectError::RevisionMismatch {
2357 expected,
2358 actual: stamp,
2359 });
2360 }
2361 let (_, declared) = self.canonical_area(area)?;
2362 let resolved = self.resolve_semantic_area(&declared);
2363 let total = resolved.as_ref().map_or(0, |range| {
2364 u64::from(range.width()) * u64::from(range.height())
2365 });
2366 let start = options.offset.min(total);
2367 let end = start.saturating_add(u64::from(options.limit)).min(total);
2368 let mut items = Vec::new();
2369 items
2370 .try_reserve((end - start) as usize)
2371 .map_err(|_| InspectError::ResourceExhausted {
2372 resource: "range page items",
2373 })?;
2374 if let Some(range) = &resolved {
2375 let width = u64::from(range.width());
2376 for offset in start..end {
2377 let row = range.start_row + u32::try_from(offset / width).unwrap_or(u32::MAX);
2378 let column = range.start_col + u32::try_from(offset % width).unwrap_or(u32::MAX);
2379 let (key, _) = self.canonical_cell(&CellAddress {
2380 sheet: range.sheet.clone(),
2381 row,
2382 column,
2383 })?;
2384 items.push(self.snapshot_for_key(key, options.include_values)?);
2385 }
2386 }
2387 Ok(RangePage {
2388 stamp,
2389 declared,
2390 resolved,
2391 total,
2392 offset: options.offset,
2393 items,
2394 next_offset: (end < total).then_some(end),
2395 })
2396 }
2397}