1use super::*;
2use crate::engine::used_extent::{ExtentPolicy, OpenRangeBounds, resolve_used_extent};
3use formualizer_common::LiteralValue;
4use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
5
6#[derive(Clone, Copy, PartialEq, Eq)]
7enum RangeSelfUse {
8 NoMatch,
9 Excluded,
10 IncludedOrUnknown,
11}
12
13impl RangeSelfUse {
14 fn merge(self, other: Self) -> Self {
15 match (self, other) {
16 (Self::IncludedOrUnknown, _) | (_, Self::IncludedOrUnknown) => Self::IncludedOrUnknown,
17 (Self::Excluded, _) | (_, Self::Excluded) => Self::Excluded,
18 _ => Self::NoMatch,
19 }
20 }
21}
22
23#[derive(Clone, Copy, Debug)]
24pub(crate) enum StructuralEdit {
25 InsertRows { before: u32 },
26 DeleteRows { start: u32, end: u32 },
27 InsertColumns { before: u32 },
28 DeleteColumns { start: u32, end: u32 },
29}
30
31#[derive(Clone, Debug, Default)]
32pub(crate) struct StructuralOccupancy {
33 occupied_rows: Vec<u32>,
34 occupied_columns: Vec<u32>,
35 conservative: bool,
36}
37
38impl StructuralOccupancy {
39 pub(crate) fn conservative() -> Self {
40 Self {
41 conservative: true,
42 ..Self::default()
43 }
44 }
45
46 fn finish(&mut self) {
47 self.occupied_rows.sort_unstable();
48 self.occupied_rows.dedup();
49 self.occupied_columns.sort_unstable();
50 self.occupied_columns.dedup();
51 }
52
53 pub(crate) fn include_arrow_sheet(&mut self, sheet: &crate::arrow_store::ArrowSheet) {
54 let shapes = sheet.shape();
55 for (col, column) in sheet.columns.iter().enumerate() {
56 let shape_occupied = shapes.get(col).is_some_and(|shape| {
57 shape.has_num || shape.has_bool || shape.has_text || shape.has_err
58 });
59 let sparse_meta_occupied = column.sparse_chunks.values().any(|chunk| {
60 chunk.meta.non_null_num > 0
61 || chunk.meta.non_null_bool > 0
62 || chunk.meta.non_null_text > 0
63 || chunk.meta.non_null_err > 0
64 });
65 let overlay_occupied = column
66 .chunks
67 .iter()
68 .chain(column.sparse_chunks.values())
69 .any(|chunk| {
70 chunk.overlay.iter().next().is_some()
71 || chunk.computed_overlay.iter().next().is_some()
72 });
73 if shape_occupied || sparse_meta_occupied || overlay_occupied {
74 self.occupied_columns.push(col as u32);
75 }
76 }
77 self.finish();
78 }
79
80 fn intersects(sorted: &[u32], start: u32, end: u32) -> bool {
81 let index = sorted.partition_point(|value| *value < start);
82 sorted.get(index).is_some_and(|value| *value <= end)
83 }
84
85 fn cross_axis_occupied(self_ref: &Self, edit: StructuralEdit, start: u32, end: u32) -> bool {
86 if self_ref.conservative {
87 return true;
88 }
89 match edit {
90 StructuralEdit::InsertRows { .. } | StructuralEdit::DeleteRows { .. } => {
91 Self::intersects(&self_ref.occupied_columns, start, end)
92 }
93 StructuralEdit::InsertColumns { .. } | StructuralEdit::DeleteColumns { .. } => {
94 Self::intersects(&self_ref.occupied_rows, start, end)
95 }
96 }
97 }
98}
99
100impl DependencyGraph {
101 pub(crate) fn has_compressed_range_dependencies(&self) -> bool {
102 !self.formula_to_range_deps.is_empty()
103 }
104
105 pub(crate) fn structural_occupancy(&self, sheet_id: SheetId) -> StructuralOccupancy {
106 let mut occupancy = StructuralOccupancy::default();
107 for (id, coord) in self.grid_vertices_in_sheet(sheet_id) {
108 if self.store.kind(id) != VertexKind::Empty {
109 occupancy.occupied_rows.push(coord.row());
110 occupancy.occupied_columns.push(coord.col());
111 }
112 }
113 occupancy.finish();
114 occupancy
115 }
116
117 pub(crate) fn compressed_range_dependents_for_structural_edit(
118 &self,
119 sheet_id: SheetId,
120 edit: StructuralEdit,
121 occupancy: &StructuralOccupancy,
122 ) -> Vec<VertexId> {
123 self.formula_to_range_deps
124 .iter()
125 .filter_map(|(&dependent, ranges)| {
126 ranges
127 .iter()
128 .any(|range| {
129 let range_sheet_id = self
132 .sheet_reg
133 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dependent))
134 .ok();
135 if range_sheet_id.is_some_and(|resolved| resolved != sheet_id) {
136 return false;
137 }
138 let start_row = range.start_row.map(|bound| bound.index).unwrap_or(0);
139 let end_row = range.end_row.map(|bound| bound.index).unwrap_or(u32::MAX);
140 let start_col = range.start_col.map(|bound| bound.index).unwrap_or(0);
141 let end_col = range.end_col.map(|bound| bound.index).unwrap_or(u32::MAX);
142 let axis_matches = match edit {
143 StructuralEdit::DeleteRows { start, end } => {
144 start_row <= end && end_row >= start
145 }
146 StructuralEdit::InsertRows { before } => {
147 (range.start_row.is_none() || start_row < before)
148 && before <= end_row
149 }
150 StructuralEdit::DeleteColumns { start, end } => {
151 start_col <= end && end_col >= start
152 }
153 StructuralEdit::InsertColumns { before } => {
154 (range.start_col.is_none() || start_col < before)
155 && before <= end_col
156 }
157 };
158 let (cross_start, cross_end) = match edit {
159 StructuralEdit::InsertRows { .. }
160 | StructuralEdit::DeleteRows { .. } => (start_col, end_col),
161 StructuralEdit::InsertColumns { .. }
162 | StructuralEdit::DeleteColumns { .. } => (start_row, end_row),
163 };
164 axis_matches
165 && (range_sheet_id.is_none()
166 || StructuralOccupancy::cross_axis_occupied(
170 occupancy,
171 edit,
172 cross_start,
173 cross_end,
174 ))
175 })
176 .then_some(dependent)
177 })
178 .collect()
179 }
180
181 pub(crate) fn visit_range_dependents_covering_bounded(
190 &self,
191 sheet_id: SheetId,
192 row0: u32,
193 col0: u32,
194 remaining_work: &mut u64,
195 visitor: &mut dyn FnMut(VertexId) -> bool,
196 ) -> bool {
197 if self.stripe_to_dependents.is_empty() {
198 return true;
199 }
200
201 let mut seen = FxHashSet::default();
202 let keys = [
203 StripeKey {
204 sheet_id,
205 stripe_type: StripeType::Column,
206 index: col0,
207 },
208 StripeKey {
209 sheet_id,
210 stripe_type: StripeType::Row,
211 index: row0,
212 },
213 StripeKey {
214 sheet_id,
215 stripe_type: StripeType::Block,
216 index: block_index(row0, col0),
217 },
218 ];
219
220 for key in keys {
221 if key.stripe_type == StripeType::Block && !self.config.enable_block_stripes {
222 continue;
223 }
224 let Some(candidates) = self.stripe_to_dependents.get(&key) else {
225 continue;
226 };
227 for &dependent in candidates {
228 if *remaining_work == 0 {
229 return false;
230 }
231 *remaining_work -= 1;
232 if !seen.insert(dependent) {
233 continue;
234 }
235 let Some(ranges) = self.formula_to_range_deps.get(&dependent) else {
236 continue;
237 };
238 let mut covered = false;
239 for range in ranges {
240 if *remaining_work == 0 {
241 return false;
242 }
243 *remaining_work -= 1;
244 let range_sheet = self
248 .sheet_reg
249 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dependent))
250 .unwrap_or(sheet_id);
251 if range_sheet != sheet_id {
252 continue;
253 }
254 let start_row = range.start_row.map(|bound| bound.index).unwrap_or(0);
255 let end_row = range.end_row.map(|bound| bound.index).unwrap_or(u32::MAX);
256 let start_col = range.start_col.map(|bound| bound.index).unwrap_or(0);
257 let end_col = range.end_col.map(|bound| bound.index).unwrap_or(u32::MAX);
258 if start_row <= row0 && row0 <= end_row && start_col <= col0 && col0 <= end_col
259 {
260 covered = true;
261 break;
262 }
263 }
264 if covered && !visitor(dependent) {
265 return false;
266 }
267 }
268 }
269 true
270 }
271
272 pub fn add_range_edges(
274 &mut self,
275 dependent: VertexId,
276 ranges: &[SharedRangeRef<'static>],
277 current_sheet_id: SheetId,
278 ) {
279 self.add_range_dependent_edges(dependent, ranges, current_sheet_id);
280 }
281
282 pub fn get_range_dependencies(
286 &self,
287 vertex: VertexId,
288 ) -> Option<&Vec<SharedRangeRef<'static>>> {
289 self.formula_to_range_deps.get(&vertex)
290 }
291
292 #[cfg(test)]
293 pub(crate) fn formula_to_range_deps(
294 &self,
295 ) -> &FxHashMap<VertexId, Vec<SharedRangeRef<'static>>> {
296 &self.formula_to_range_deps
297 }
298
299 #[cfg(test)]
300 pub(crate) fn stripe_to_dependents(&self) -> &FxHashMap<StripeKey, FxHashSet<VertexId>> {
301 &self.stripe_to_dependents
302 }
303
304 fn range_region_contains_self(
311 &self,
312 dependent: VertexId,
313 sheet_id: SheetId,
314 s_row: Option<u32>,
315 e_row: Option<u32>,
316 s_col: Option<u32>,
317 e_col: Option<u32>,
318 ) -> bool {
319 if self.store.sheet_id(dependent) != sheet_id {
320 return false;
321 }
322 let Some(coord) = self.store.grid_addr(dependent) else {
324 return false;
325 };
326 let r0 = coord.row();
327 let c0 = coord.col();
328 s_row.is_none_or(|s| r0 >= s)
329 && e_row.is_none_or(|e| r0 <= e)
330 && s_col.is_none_or(|s| c0 >= s)
331 && e_col.is_none_or(|e| c0 <= e)
332 }
333
334 fn record_self_loop(&mut self, vertex: VertexId) {
337 if !self.has_self_loop(vertex) {
338 self.edges.add_edge(vertex, vertex);
339 }
340 }
341
342 pub(crate) fn compressed_range_resolved_bounds(
343 &self,
344 sheet: SheetId,
345 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
346 ) -> Option<(u32, u32, u32, u32)> {
347 let (start_row, end_row, start_col, end_col) = range;
348 let extent = resolve_used_extent(
349 OpenRangeBounds {
350 start_row,
351 start_column: start_col,
352 end_row,
353 end_column: end_col,
354 },
355 ExtentPolicy::GraphCompat {
356 fallback_row: self.config.max_open_ended_rows.saturating_sub(1),
357 fallback_column: self.config.max_open_ended_cols.saturating_sub(1),
358 },
359 |first, last| self.used_row_bounds_for_columns(sheet, first, last),
360 |first, last| self.used_col_bounds_for_rows(sheet, first, last),
361 )?;
362 Some((
363 extent.start_row,
364 extent.end_row,
365 extent.start_column,
366 extent.end_column,
367 ))
368 }
369
370 fn compressed_range_self_use(
377 &self,
378 dependent: VertexId,
379 range_sheet: SheetId,
380 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
381 ) -> RangeSelfUse {
382 let Some(ast) = self.get_formula(dependent) else {
383 return RangeSelfUse::IncludedOrUnknown;
384 };
385
386 fn static_index(node: &ASTNode) -> Option<i64> {
387 match &node.node_type {
388 ASTNodeType::Literal(LiteralValue::Int(value)) => Some(*value),
389 ASTNodeType::Literal(LiteralValue::Number(value)) if value.is_finite() => {
390 Some(*value as i64)
391 }
392 ASTNodeType::UnaryOp { op, expr } if op == "+" => static_index(expr),
393 ASTNodeType::UnaryOp { op, expr } if op == "-" => static_index(expr)?.checked_neg(),
394 _ => None,
395 }
396 }
397
398 fn matching_range(
399 graph: &DependencyGraph,
400 node: &ASTNode,
401 dependent: VertexId,
402 range_sheet: SheetId,
403 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
404 ) -> bool {
405 let ASTNodeType::Reference {
406 reference:
407 ReferenceType::Range {
408 sheet,
409 start_row,
410 start_col,
411 end_row,
412 end_col,
413 ..
414 },
415 ..
416 } = &node.node_type
417 else {
418 return false;
419 };
420 let sheet_id = match sheet.as_deref() {
421 Some(name) => match graph.sheet_id(name) {
422 Some(id) => id,
423 None => return false,
424 },
425 None => graph.get_vertex_sheet_id(dependent),
426 };
427 sheet_id == range_sheet
428 && start_row.map(|index| index.saturating_sub(1)) == range.0
429 && end_row.map(|index| index.saturating_sub(1)) == range.1
430 && start_col.map(|index| index.saturating_sub(1)) == range.2
431 && end_col.map(|index| index.saturating_sub(1)) == range.3
432 }
433
434 fn selected_region_contains_self(
435 graph: &DependencyGraph,
436 dependent: VertexId,
437 range_sheet: SheetId,
438 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
439 position: i64,
440 explicit_col: Option<i64>,
441 ) -> Option<bool> {
442 let (sr, er, sc, ec) = graph.compressed_range_resolved_bounds(range_sheet, range)?;
443 let (row, col) = match explicit_col {
444 Some(col) => (position, col),
445 None if sr == er => (1, position),
446 None => (position, 1),
447 };
448 if row < 0 || col < 0 {
449 return Some(false);
450 }
451 let coord = graph.store.grid_addr(dependent)?;
453 let contains = if row == 0 && col == 0 {
454 coord.row() >= sr && coord.row() <= er && coord.col() >= sc && coord.col() <= ec
455 } else if col == 0 {
456 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
457 selected_row <= er
458 && coord.row() == selected_row
459 && coord.col() >= sc
460 && coord.col() <= ec
461 } else if row == 0 {
462 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
463 selected_col <= ec
464 && coord.col() == selected_col
465 && coord.row() >= sr
466 && coord.row() <= er
467 } else {
468 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
469 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
470 selected_row <= er
471 && selected_col <= ec
472 && coord.row() == selected_row
473 && coord.col() == selected_col
474 };
475 Some(contains)
476 }
477
478 fn visit(
479 graph: &DependencyGraph,
480 node: &ASTNode,
481 dependent: VertexId,
482 range_sheet: SheetId,
483 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
484 index: Option<(i64, Option<i64>)>,
485 ) -> RangeSelfUse {
486 if matching_range(graph, node, dependent, range_sheet, range) {
487 return match index.and_then(|(row, col)| {
488 selected_region_contains_self(graph, dependent, range_sheet, range, row, col)
489 }) {
490 Some(false) => RangeSelfUse::Excluded,
491 Some(true) | None => RangeSelfUse::IncludedOrUnknown,
492 };
493 }
494 match &node.node_type {
495 ASTNodeType::Function { name, args }
496 if name.eq_ignore_ascii_case("INDEX") && (2..=3).contains(&args.len()) =>
497 {
498 let row = static_index(&args[1]);
499 let col = args.get(2).and_then(static_index);
500 let selection = row.and_then(|row| {
501 if args.len() == 2 || col.is_some() {
502 Some((row, col))
503 } else {
504 None
505 }
506 });
507 let mut use_kind =
508 visit(graph, &args[0], dependent, range_sheet, range, selection);
509 for arg in &args[1..] {
510 use_kind =
511 use_kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
512 }
513 use_kind
514 }
515 ASTNodeType::Function { args, .. } => {
516 args.iter().fold(RangeSelfUse::NoMatch, |kind, arg| {
517 kind.merge(visit(graph, arg, dependent, range_sheet, range, None))
518 })
519 }
520 ASTNodeType::UnaryOp { expr, .. } => {
521 visit(graph, expr, dependent, range_sheet, range, None)
522 }
523 ASTNodeType::BinaryOp { left, right, .. } => visit(
524 graph,
525 left,
526 dependent,
527 range_sheet,
528 range,
529 None,
530 )
531 .merge(visit(graph, right, dependent, range_sheet, range, None)),
532 ASTNodeType::Call { callee, args } => {
533 let mut kind = visit(graph, callee, dependent, range_sheet, range, None);
534 for arg in args {
535 kind = kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
536 }
537 kind
538 }
539 ASTNodeType::Array(rows) => {
540 rows.iter()
541 .flatten()
542 .fold(RangeSelfUse::NoMatch, |kind, item| {
543 kind.merge(visit(graph, item, dependent, range_sheet, range, None))
544 })
545 }
546 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {
547 RangeSelfUse::NoMatch
548 }
549 }
550 }
551
552 visit(self, &ast, dependent, range_sheet, range, None)
553 }
554
555 pub(super) fn add_range_dependent_edges(
556 &mut self,
557 dependent: VertexId,
558 ranges: &[SharedRangeRef<'static>],
559 current_sheet_id: SheetId,
560 ) {
561 if ranges.is_empty() {
562 return;
563 }
564
565 self.formula_to_range_deps
566 .insert(dependent, ranges.to_vec());
567
568 for range in ranges {
569 let sheet_id = self
573 .sheet_reg
574 .resolve_locator(&range.sheet, current_sheet_id)
575 .unwrap_or(current_sheet_id);
576
577 let s_row = range.start_row.map(|b| b.index);
578 let e_row = range.end_row.map(|b| b.index);
579 let s_col = range.start_col.map(|b| b.index);
580 let e_col = range.end_col.map(|b| b.index);
581
582 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
587 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
588 != RangeSelfUse::Excluded
589 {
590 self.record_self_loop(dependent);
591 }
592
593 if s_row.is_none() && e_row.is_none() && s_col.is_none() && e_col.is_none() {
601 self.register_whole_sheet_stripes(dependent, sheet_id);
602 continue;
603 }
604
605 let col_stripes = (s_row.is_none() && e_row.is_none())
606 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
607 let row_stripes = (s_col.is_none() && e_col.is_none())
608 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
609
610 if col_stripes && !row_stripes {
611 let sc = s_col.unwrap_or(0);
612 let ec = e_col.unwrap_or(sc);
613 for col in sc..=ec {
614 let key = StripeKey {
615 sheet_id,
616 stripe_type: StripeType::Column,
617 index: col,
618 };
619 self.stripe_to_dependents
620 .entry(key.clone())
621 .or_default()
622 .insert(dependent);
623 #[cfg(test)]
624 {
625 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
626 && let Ok(mut g) = self.instr.lock()
627 {
628 g.stripe_inserts += 1;
629 }
630 }
631 }
632 continue;
633 }
634
635 if row_stripes && !col_stripes {
636 let sr = s_row.unwrap_or(0);
637 let er = e_row.unwrap_or(sr);
638 for row in sr..=er {
639 let key = StripeKey {
640 sheet_id,
641 stripe_type: StripeType::Row,
642 index: row,
643 };
644 self.stripe_to_dependents
645 .entry(key.clone())
646 .or_default()
647 .insert(dependent);
648 #[cfg(test)]
649 {
650 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
651 && let Ok(mut g) = self.instr.lock()
652 {
653 g.stripe_inserts += 1;
654 }
655 }
656 }
657 continue;
658 }
659
660 let start_row = s_row.unwrap_or(0);
661 let start_col = s_col.unwrap_or(0);
662 let end_row = e_row.unwrap_or(start_row);
663 let end_col = e_col.unwrap_or(start_col);
664
665 let height = end_row.saturating_sub(start_row) + 1;
666 let width = end_col.saturating_sub(start_col) + 1;
667
668 if self.config.enable_block_stripes && height > 1 && width > 1 {
669 let start_block_row = start_row / BLOCK_H;
670 let end_block_row = end_row / BLOCK_H;
671 let start_block_col = start_col / BLOCK_W;
672 let end_block_col = end_col / BLOCK_W;
673
674 for block_row in start_block_row..=end_block_row {
675 for block_col in start_block_col..=end_block_col {
676 let key = StripeKey {
677 sheet_id,
678 stripe_type: StripeType::Block,
679 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
680 };
681 self.stripe_to_dependents
682 .entry(key.clone())
683 .or_default()
684 .insert(dependent);
685 #[cfg(test)]
686 {
687 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
688 && let Ok(mut g) = self.instr.lock()
689 {
690 g.stripe_inserts += 1;
691 }
692 }
693 }
694 }
695 } else if height > width {
696 for col in start_col..=end_col {
697 let key = StripeKey {
698 sheet_id,
699 stripe_type: StripeType::Column,
700 index: col,
701 };
702 self.stripe_to_dependents
703 .entry(key.clone())
704 .or_default()
705 .insert(dependent);
706 #[cfg(test)]
707 {
708 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
709 && let Ok(mut g) = self.instr.lock()
710 {
711 g.stripe_inserts += 1;
712 }
713 }
714 }
715 } else {
716 for row in start_row..=end_row {
717 let key = StripeKey {
718 sheet_id,
719 stripe_type: StripeType::Row,
720 index: row,
721 };
722 self.stripe_to_dependents
723 .entry(key.clone())
724 .or_default()
725 .insert(dependent);
726 #[cfg(test)]
727 {
728 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
729 && let Ok(mut g) = self.instr.lock()
730 {
731 g.stripe_inserts += 1;
732 }
733 }
734 }
735 }
736 }
737 }
738
739 fn register_whole_sheet_stripes(&mut self, dependent: VertexId, sheet_id: SheetId) {
744 const SHEET_MAX_COLS: u32 = 16_384;
746 for col in 0..SHEET_MAX_COLS {
747 let key = StripeKey {
748 sheet_id,
749 stripe_type: StripeType::Column,
750 index: col,
751 };
752 self.stripe_to_dependents
753 .entry(key)
754 .or_default()
755 .insert(dependent);
756 }
757 }
758
759 pub fn add_range_deps_from_keys(
761 &mut self,
762 dependent: VertexId,
763 keys: &[crate::engine::plan::RangeKey],
764 current_sheet_id: SheetId,
765 ) {
766 use crate::engine::plan::RangeKey as RK;
767 if keys.is_empty() {
768 return;
769 }
770
771 let mut shared_ranges: Vec<SharedRangeRef<'static>> = Vec::with_capacity(keys.len());
772 for k in keys {
773 let sheet_loc = SharedSheetLocator::Id(match k {
774 RK::Rect { sheet, .. }
775 | RK::WholeRow { sheet, .. }
776 | RK::WholeCol { sheet, .. }
777 | RK::OpenRect { sheet, .. } => *sheet,
778 });
779
780 let mk_axis = |idx0: u32| formualizer_common::AxisBound::new(idx0, false);
781
782 let built = match k {
783 RK::Rect { start, end, .. } => {
784 let sr = mk_axis(start.row());
785 let sc = mk_axis(start.col());
786 let er = mk_axis(end.row());
787 let ec = mk_axis(end.col());
788 SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
789 .ok()
790 }
791 RK::WholeRow { row, .. } => {
792 let r0 = row.saturating_sub(1);
793 let b = mk_axis(r0);
794 SharedRangeRef::from_parts(sheet_loc, Some(b), None, Some(b), None).ok()
795 }
796 RK::WholeCol { col, .. } => {
797 let c0 = col.saturating_sub(1);
798 let b = mk_axis(c0);
799 SharedRangeRef::from_parts(sheet_loc, None, Some(b), None, Some(b)).ok()
800 }
801 RK::OpenRect {
802 start_row,
803 start_col,
804 end_row,
805 end_col,
806 ..
807 } => SharedRangeRef::from_parts(
808 sheet_loc,
809 start_row.map(mk_axis),
810 start_col.map(mk_axis),
811 end_row.map(mk_axis),
812 end_col.map(mk_axis),
813 )
814 .ok(),
815 };
816
817 if let Some(r) = built {
818 shared_ranges.push(r.into_owned());
819 }
820 }
821
822 if shared_ranges.is_empty() {
823 return;
824 }
825
826 self.formula_to_range_deps
827 .insert(dependent, shared_ranges.clone());
828
829 for range in &shared_ranges {
830 let sheet_id = self
832 .sheet_reg
833 .resolve_locator(&range.sheet, current_sheet_id)
834 .unwrap_or(current_sheet_id);
835
836 let s_row = range.start_row.map(|b| b.index);
837 let e_row = range.end_row.map(|b| b.index);
838 let s_col = range.start_col.map(|b| b.index);
839 let e_col = range.end_col.map(|b| b.index);
840
841 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
844 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
845 != RangeSelfUse::Excluded
846 {
847 self.record_self_loop(dependent);
848 }
849
850 if s_row.is_none() && e_row.is_none() && s_col.is_none() && e_col.is_none() {
858 self.register_whole_sheet_stripes(dependent, sheet_id);
859 continue;
860 }
861
862 let col_stripes = (s_row.is_none() && e_row.is_none())
863 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
864 let row_stripes = (s_col.is_none() && e_col.is_none())
865 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
866
867 if col_stripes && !row_stripes {
868 let sc = s_col.unwrap_or(0);
869 let ec = e_col.unwrap_or(sc);
870 for col in sc..=ec {
871 let key = StripeKey {
872 sheet_id,
873 stripe_type: StripeType::Column,
874 index: col,
875 };
876 self.stripe_to_dependents
877 .entry(key)
878 .or_default()
879 .insert(dependent);
880 }
881 continue;
882 }
883
884 if row_stripes && !col_stripes {
885 let sr = s_row.unwrap_or(0);
886 let er = e_row.unwrap_or(sr);
887 for row in sr..=er {
888 let key = StripeKey {
889 sheet_id,
890 stripe_type: StripeType::Row,
891 index: row,
892 };
893 self.stripe_to_dependents
894 .entry(key)
895 .or_default()
896 .insert(dependent);
897 }
898 continue;
899 }
900
901 let start_row = s_row.unwrap_or(0);
902 let start_col = s_col.unwrap_or(0);
903 let end_row = e_row.unwrap_or(start_row);
904 let end_col = e_col.unwrap_or(start_col);
905
906 let height = end_row.saturating_sub(start_row) + 1;
907 let width = end_col.saturating_sub(start_col) + 1;
908
909 if self.config.enable_block_stripes && height > 1 && width > 1 {
910 let start_block_row = start_row / BLOCK_H;
911 let end_block_row = end_row / BLOCK_H;
912 let start_block_col = start_col / BLOCK_W;
913 let end_block_col = end_col / BLOCK_W;
914
915 for block_row in start_block_row..=end_block_row {
916 for block_col in start_block_col..=end_block_col {
917 let key = StripeKey {
918 sheet_id,
919 stripe_type: StripeType::Block,
920 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
921 };
922 self.stripe_to_dependents
923 .entry(key)
924 .or_default()
925 .insert(dependent);
926 }
927 }
928 } else if height > width {
929 for col in start_col..=end_col {
930 let key = StripeKey {
931 sheet_id,
932 stripe_type: StripeType::Column,
933 index: col,
934 };
935 self.stripe_to_dependents
936 .entry(key)
937 .or_default()
938 .insert(dependent);
939 }
940 } else {
941 for row in start_row..=end_row {
942 let key = StripeKey {
943 sheet_id,
944 stripe_type: StripeType::Row,
945 index: row,
946 };
947 self.stripe_to_dependents
948 .entry(key)
949 .or_default()
950 .insert(dependent);
951 }
952 }
953 }
954 }
955}