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
23impl DependencyGraph {
24 pub(crate) fn compressed_range_dependents_intersecting_deleted_rows(
25 &self,
26 sheet_id: SheetId,
27 start_row: u32,
28 end_row: u32,
29 ) -> Vec<VertexId> {
30 self.formula_to_range_deps
31 .iter()
32 .filter_map(|(&dependent, ranges)| {
33 ranges
34 .iter()
35 .any(|range| {
36 let range_sheet_id = match range.sheet {
37 SharedSheetLocator::Id(id) => id,
38 _ => sheet_id,
41 };
42 let range_start = range.start_row.map(|bound| bound.index).unwrap_or(0);
43 let range_end = range.end_row.map(|bound| bound.index).unwrap_or(u32::MAX);
44 range_sheet_id == sheet_id
45 && range_start <= end_row
46 && range_end >= start_row
47 })
48 .then_some(dependent)
49 })
50 .collect()
51 }
52
53 pub(crate) fn compressed_range_dependents_intersecting_deleted_columns(
54 &self,
55 sheet_id: SheetId,
56 start_col: u32,
57 end_col: u32,
58 ) -> Vec<VertexId> {
59 self.formula_to_range_deps
60 .iter()
61 .filter_map(|(&dependent, ranges)| {
62 ranges
63 .iter()
64 .any(|range| {
65 let range_sheet_id = match range.sheet {
66 SharedSheetLocator::Id(id) => id,
67 _ => sheet_id,
70 };
71 let range_start = range.start_col.map(|bound| bound.index).unwrap_or(0);
72 let range_end = range.end_col.map(|bound| bound.index).unwrap_or(u32::MAX);
73 range_sheet_id == sheet_id
74 && range_start <= end_col
75 && range_end >= start_col
76 })
77 .then_some(dependent)
78 })
79 .collect()
80 }
81
82 pub(crate) fn visit_range_dependents_covering_bounded(
91 &self,
92 sheet_id: SheetId,
93 row0: u32,
94 col0: u32,
95 remaining_work: &mut u64,
96 visitor: &mut dyn FnMut(VertexId) -> bool,
97 ) -> bool {
98 if self.stripe_to_dependents.is_empty() {
99 return true;
100 }
101
102 let mut seen = FxHashSet::default();
103 let keys = [
104 StripeKey {
105 sheet_id,
106 stripe_type: StripeType::Column,
107 index: col0,
108 },
109 StripeKey {
110 sheet_id,
111 stripe_type: StripeType::Row,
112 index: row0,
113 },
114 StripeKey {
115 sheet_id,
116 stripe_type: StripeType::Block,
117 index: block_index(row0, col0),
118 },
119 ];
120
121 for key in keys {
122 if key.stripe_type == StripeType::Block && !self.config.enable_block_stripes {
123 continue;
124 }
125 let Some(candidates) = self.stripe_to_dependents.get(&key) else {
126 continue;
127 };
128 for &dependent in candidates {
129 if *remaining_work == 0 {
130 return false;
131 }
132 *remaining_work -= 1;
133 if !seen.insert(dependent) {
134 continue;
135 }
136 let Some(ranges) = self.formula_to_range_deps.get(&dependent) else {
137 continue;
138 };
139 let mut covered = false;
140 for range in ranges {
141 if *remaining_work == 0 {
142 return false;
143 }
144 *remaining_work -= 1;
145 let range_sheet = match range.sheet {
148 SharedSheetLocator::Id(id) => id,
149 _ => sheet_id,
150 };
151 if range_sheet != sheet_id {
152 continue;
153 }
154 let start_row = range.start_row.map(|bound| bound.index).unwrap_or(0);
155 let end_row = range.end_row.map(|bound| bound.index).unwrap_or(u32::MAX);
156 let start_col = range.start_col.map(|bound| bound.index).unwrap_or(0);
157 let end_col = range.end_col.map(|bound| bound.index).unwrap_or(u32::MAX);
158 if start_row <= row0 && row0 <= end_row && start_col <= col0 && col0 <= end_col
159 {
160 covered = true;
161 break;
162 }
163 }
164 if covered && !visitor(dependent) {
165 return false;
166 }
167 }
168 }
169 true
170 }
171
172 pub fn add_range_edges(
174 &mut self,
175 dependent: VertexId,
176 ranges: &[SharedRangeRef<'static>],
177 current_sheet_id: SheetId,
178 ) {
179 self.add_range_dependent_edges(dependent, ranges, current_sheet_id);
180 }
181
182 pub fn get_range_dependencies(
186 &self,
187 vertex: VertexId,
188 ) -> Option<&Vec<SharedRangeRef<'static>>> {
189 self.formula_to_range_deps.get(&vertex)
190 }
191
192 #[cfg(test)]
193 pub(crate) fn formula_to_range_deps(
194 &self,
195 ) -> &FxHashMap<VertexId, Vec<SharedRangeRef<'static>>> {
196 &self.formula_to_range_deps
197 }
198
199 #[cfg(test)]
200 pub(crate) fn stripe_to_dependents(&self) -> &FxHashMap<StripeKey, FxHashSet<VertexId>> {
201 &self.stripe_to_dependents
202 }
203
204 fn range_region_contains_self(
211 &self,
212 dependent: VertexId,
213 sheet_id: SheetId,
214 s_row: Option<u32>,
215 e_row: Option<u32>,
216 s_col: Option<u32>,
217 e_col: Option<u32>,
218 ) -> bool {
219 if self.store.sheet_id(dependent) != sheet_id {
220 return false;
221 }
222 let coord = self.store.coord(dependent);
223 let r0 = coord.row();
224 let c0 = coord.col();
225 s_row.is_none_or(|s| r0 >= s)
226 && e_row.is_none_or(|e| r0 <= e)
227 && s_col.is_none_or(|s| c0 >= s)
228 && e_col.is_none_or(|e| c0 <= e)
229 }
230
231 fn record_self_loop(&mut self, vertex: VertexId) {
234 if !self.has_self_loop(vertex) {
235 self.edges.add_edge(vertex, vertex);
236 }
237 }
238
239 pub(crate) fn compressed_range_resolved_bounds(
240 &self,
241 sheet: SheetId,
242 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
243 ) -> Option<(u32, u32, u32, u32)> {
244 let (start_row, end_row, start_col, end_col) = range;
245 let extent = resolve_used_extent(
246 OpenRangeBounds {
247 start_row,
248 start_column: start_col,
249 end_row,
250 end_column: end_col,
251 },
252 ExtentPolicy::GraphCompat {
253 fallback_row: self.config.max_open_ended_rows.saturating_sub(1),
254 fallback_column: self.config.max_open_ended_cols.saturating_sub(1),
255 },
256 |first, last| self.used_row_bounds_for_columns(sheet, first, last),
257 |first, last| self.used_col_bounds_for_rows(sheet, first, last),
258 )?;
259 Some((
260 extent.start_row,
261 extent.end_row,
262 extent.start_column,
263 extent.end_column,
264 ))
265 }
266
267 fn compressed_range_self_use(
274 &self,
275 dependent: VertexId,
276 range_sheet: SheetId,
277 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
278 ) -> RangeSelfUse {
279 let Some(ast) = self.get_formula(dependent) else {
280 return RangeSelfUse::IncludedOrUnknown;
281 };
282
283 fn static_index(node: &ASTNode) -> Option<i64> {
284 match &node.node_type {
285 ASTNodeType::Literal(LiteralValue::Int(value)) => Some(*value),
286 ASTNodeType::Literal(LiteralValue::Number(value)) if value.is_finite() => {
287 Some(*value as i64)
288 }
289 ASTNodeType::UnaryOp { op, expr } if op == "+" => static_index(expr),
290 ASTNodeType::UnaryOp { op, expr } if op == "-" => static_index(expr)?.checked_neg(),
291 _ => None,
292 }
293 }
294
295 fn matching_range(
296 graph: &DependencyGraph,
297 node: &ASTNode,
298 dependent: VertexId,
299 range_sheet: SheetId,
300 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
301 ) -> bool {
302 let ASTNodeType::Reference {
303 reference:
304 ReferenceType::Range {
305 sheet,
306 start_row,
307 start_col,
308 end_row,
309 end_col,
310 ..
311 },
312 ..
313 } = &node.node_type
314 else {
315 return false;
316 };
317 let sheet_id = match sheet.as_deref() {
318 Some(name) => match graph.sheet_id(name) {
319 Some(id) => id,
320 None => return false,
321 },
322 None => graph.get_vertex_sheet_id(dependent),
323 };
324 sheet_id == range_sheet
325 && start_row.map(|index| index.saturating_sub(1)) == range.0
326 && end_row.map(|index| index.saturating_sub(1)) == range.1
327 && start_col.map(|index| index.saturating_sub(1)) == range.2
328 && end_col.map(|index| index.saturating_sub(1)) == range.3
329 }
330
331 fn selected_region_contains_self(
332 graph: &DependencyGraph,
333 dependent: VertexId,
334 range_sheet: SheetId,
335 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
336 position: i64,
337 explicit_col: Option<i64>,
338 ) -> Option<bool> {
339 let (sr, er, sc, ec) = graph.compressed_range_resolved_bounds(range_sheet, range)?;
340 let (row, col) = match explicit_col {
341 Some(col) => (position, col),
342 None if sr == er => (1, position),
343 None => (position, 1),
344 };
345 if row < 0 || col < 0 {
346 return Some(false);
347 }
348 let coord = graph.store.coord(dependent);
349 let contains = if row == 0 && col == 0 {
350 coord.row() >= sr && coord.row() <= er && coord.col() >= sc && coord.col() <= ec
351 } else if col == 0 {
352 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
353 selected_row <= er
354 && coord.row() == selected_row
355 && coord.col() >= sc
356 && coord.col() <= ec
357 } else if row == 0 {
358 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
359 selected_col <= ec
360 && coord.col() == selected_col
361 && coord.row() >= sr
362 && coord.row() <= er
363 } else {
364 let selected_row = sr.checked_add(u32::try_from(row).ok()?.saturating_sub(1))?;
365 let selected_col = sc.checked_add(u32::try_from(col).ok()?.saturating_sub(1))?;
366 selected_row <= er
367 && selected_col <= ec
368 && coord.row() == selected_row
369 && coord.col() == selected_col
370 };
371 Some(contains)
372 }
373
374 fn visit(
375 graph: &DependencyGraph,
376 node: &ASTNode,
377 dependent: VertexId,
378 range_sheet: SheetId,
379 range: (Option<u32>, Option<u32>, Option<u32>, Option<u32>),
380 index: Option<(i64, Option<i64>)>,
381 ) -> RangeSelfUse {
382 if matching_range(graph, node, dependent, range_sheet, range) {
383 return match index.and_then(|(row, col)| {
384 selected_region_contains_self(graph, dependent, range_sheet, range, row, col)
385 }) {
386 Some(false) => RangeSelfUse::Excluded,
387 Some(true) | None => RangeSelfUse::IncludedOrUnknown,
388 };
389 }
390 match &node.node_type {
391 ASTNodeType::Function { name, args }
392 if name.eq_ignore_ascii_case("INDEX") && (2..=3).contains(&args.len()) =>
393 {
394 let row = static_index(&args[1]);
395 let col = args.get(2).and_then(static_index);
396 let selection = row.and_then(|row| {
397 if args.len() == 2 || col.is_some() {
398 Some((row, col))
399 } else {
400 None
401 }
402 });
403 let mut use_kind =
404 visit(graph, &args[0], dependent, range_sheet, range, selection);
405 for arg in &args[1..] {
406 use_kind =
407 use_kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
408 }
409 use_kind
410 }
411 ASTNodeType::Function { args, .. } => {
412 args.iter().fold(RangeSelfUse::NoMatch, |kind, arg| {
413 kind.merge(visit(graph, arg, dependent, range_sheet, range, None))
414 })
415 }
416 ASTNodeType::UnaryOp { expr, .. } => {
417 visit(graph, expr, dependent, range_sheet, range, None)
418 }
419 ASTNodeType::BinaryOp { left, right, .. } => visit(
420 graph,
421 left,
422 dependent,
423 range_sheet,
424 range,
425 None,
426 )
427 .merge(visit(graph, right, dependent, range_sheet, range, None)),
428 ASTNodeType::Call { callee, args } => {
429 let mut kind = visit(graph, callee, dependent, range_sheet, range, None);
430 for arg in args {
431 kind = kind.merge(visit(graph, arg, dependent, range_sheet, range, None));
432 }
433 kind
434 }
435 ASTNodeType::Array(rows) => {
436 rows.iter()
437 .flatten()
438 .fold(RangeSelfUse::NoMatch, |kind, item| {
439 kind.merge(visit(graph, item, dependent, range_sheet, range, None))
440 })
441 }
442 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {
443 RangeSelfUse::NoMatch
444 }
445 }
446 }
447
448 visit(self, &ast, dependent, range_sheet, range, None)
449 }
450
451 pub(super) fn add_range_dependent_edges(
452 &mut self,
453 dependent: VertexId,
454 ranges: &[SharedRangeRef<'static>],
455 current_sheet_id: SheetId,
456 ) {
457 if ranges.is_empty() {
458 return;
459 }
460
461 self.formula_to_range_deps
462 .insert(dependent, ranges.to_vec());
463
464 for range in ranges {
465 let sheet_id = match range.sheet {
466 SharedSheetLocator::Id(id) => id,
467 _ => current_sheet_id,
468 };
469
470 let s_row = range.start_row.map(|b| b.index);
471 let e_row = range.end_row.map(|b| b.index);
472 let s_col = range.start_col.map(|b| b.index);
473 let e_col = range.end_col.map(|b| b.index);
474
475 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
480 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
481 != RangeSelfUse::Excluded
482 {
483 self.record_self_loop(dependent);
484 }
485
486 let col_stripes = (s_row.is_none() && e_row.is_none())
487 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
488 let row_stripes = (s_col.is_none() && e_col.is_none())
489 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
490
491 if col_stripes && !row_stripes {
492 let sc = s_col.unwrap_or(0);
493 let ec = e_col.unwrap_or(sc);
494 for col in sc..=ec {
495 let key = StripeKey {
496 sheet_id,
497 stripe_type: StripeType::Column,
498 index: col,
499 };
500 self.stripe_to_dependents
501 .entry(key.clone())
502 .or_default()
503 .insert(dependent);
504 #[cfg(test)]
505 {
506 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
507 && let Ok(mut g) = self.instr.lock()
508 {
509 g.stripe_inserts += 1;
510 }
511 }
512 }
513 continue;
514 }
515
516 if row_stripes && !col_stripes {
517 let sr = s_row.unwrap_or(0);
518 let er = e_row.unwrap_or(sr);
519 for row in sr..=er {
520 let key = StripeKey {
521 sheet_id,
522 stripe_type: StripeType::Row,
523 index: row,
524 };
525 self.stripe_to_dependents
526 .entry(key.clone())
527 .or_default()
528 .insert(dependent);
529 #[cfg(test)]
530 {
531 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
532 && let Ok(mut g) = self.instr.lock()
533 {
534 g.stripe_inserts += 1;
535 }
536 }
537 }
538 continue;
539 }
540
541 let start_row = s_row.unwrap_or(0);
542 let start_col = s_col.unwrap_or(0);
543 let end_row = e_row.unwrap_or(start_row);
544 let end_col = e_col.unwrap_or(start_col);
545
546 let height = end_row.saturating_sub(start_row) + 1;
547 let width = end_col.saturating_sub(start_col) + 1;
548
549 if self.config.enable_block_stripes && height > 1 && width > 1 {
550 let start_block_row = start_row / BLOCK_H;
551 let end_block_row = end_row / BLOCK_H;
552 let start_block_col = start_col / BLOCK_W;
553 let end_block_col = end_col / BLOCK_W;
554
555 for block_row in start_block_row..=end_block_row {
556 for block_col in start_block_col..=end_block_col {
557 let key = StripeKey {
558 sheet_id,
559 stripe_type: StripeType::Block,
560 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
561 };
562 self.stripe_to_dependents
563 .entry(key.clone())
564 .or_default()
565 .insert(dependent);
566 #[cfg(test)]
567 {
568 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
569 && let Ok(mut g) = self.instr.lock()
570 {
571 g.stripe_inserts += 1;
572 }
573 }
574 }
575 }
576 } else if height > width {
577 for col in start_col..=end_col {
578 let key = StripeKey {
579 sheet_id,
580 stripe_type: StripeType::Column,
581 index: col,
582 };
583 self.stripe_to_dependents
584 .entry(key.clone())
585 .or_default()
586 .insert(dependent);
587 #[cfg(test)]
588 {
589 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
590 && let Ok(mut g) = self.instr.lock()
591 {
592 g.stripe_inserts += 1;
593 }
594 }
595 }
596 } else {
597 for row in start_row..=end_row {
598 let key = StripeKey {
599 sheet_id,
600 stripe_type: StripeType::Row,
601 index: row,
602 };
603 self.stripe_to_dependents
604 .entry(key.clone())
605 .or_default()
606 .insert(dependent);
607 #[cfg(test)]
608 {
609 if self.stripe_to_dependents.get(&key).map(|s| s.len()) == Some(1)
610 && let Ok(mut g) = self.instr.lock()
611 {
612 g.stripe_inserts += 1;
613 }
614 }
615 }
616 }
617 }
618 }
619
620 pub fn add_range_deps_from_keys(
622 &mut self,
623 dependent: VertexId,
624 keys: &[crate::engine::plan::RangeKey],
625 current_sheet_id: SheetId,
626 ) {
627 use crate::engine::plan::RangeKey as RK;
628 if keys.is_empty() {
629 return;
630 }
631
632 let mut shared_ranges: Vec<SharedRangeRef<'static>> = Vec::with_capacity(keys.len());
633 for k in keys {
634 let sheet_loc = SharedSheetLocator::Id(match k {
635 RK::Rect { sheet, .. }
636 | RK::WholeRow { sheet, .. }
637 | RK::WholeCol { sheet, .. }
638 | RK::OpenRect { sheet, .. } => *sheet,
639 });
640
641 let mk_axis = |idx0: u32| formualizer_common::AxisBound::new(idx0, false);
642
643 let built = match k {
644 RK::Rect { start, end, .. } => {
645 let sr = mk_axis(start.row());
646 let sc = mk_axis(start.col());
647 let er = mk_axis(end.row());
648 let ec = mk_axis(end.col());
649 SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
650 .ok()
651 }
652 RK::WholeRow { row, .. } => {
653 let r0 = row.saturating_sub(1);
654 let b = mk_axis(r0);
655 SharedRangeRef::from_parts(sheet_loc, Some(b), None, Some(b), None).ok()
656 }
657 RK::WholeCol { col, .. } => {
658 let c0 = col.saturating_sub(1);
659 let b = mk_axis(c0);
660 SharedRangeRef::from_parts(sheet_loc, None, Some(b), None, Some(b)).ok()
661 }
662 RK::OpenRect { start, end, .. } => {
663 let (sr, sc) = match start {
664 Some(p) => (Some(mk_axis(p.row())), Some(mk_axis(p.col()))),
665 None => (None, None),
666 };
667 let (er, ec) = match end {
668 Some(p) => (Some(mk_axis(p.row())), Some(mk_axis(p.col()))),
669 None => (None, None),
670 };
671 SharedRangeRef::from_parts(sheet_loc, sr, sc, er, ec).ok()
672 }
673 };
674
675 if let Some(r) = built {
676 shared_ranges.push(r.into_owned());
677 }
678 }
679
680 if shared_ranges.is_empty() {
681 return;
682 }
683
684 self.formula_to_range_deps
685 .insert(dependent, shared_ranges.clone());
686
687 for range in &shared_ranges {
688 let sheet_id = match range.sheet {
689 SharedSheetLocator::Id(id) => id,
690 _ => current_sheet_id,
691 };
692
693 let s_row = range.start_row.map(|b| b.index);
694 let e_row = range.end_row.map(|b| b.index);
695 let s_col = range.start_col.map(|b| b.index);
696 let e_col = range.end_col.map(|b| b.index);
697
698 if self.range_region_contains_self(dependent, sheet_id, s_row, e_row, s_col, e_col)
701 && self.compressed_range_self_use(dependent, sheet_id, (s_row, e_row, s_col, e_col))
702 != RangeSelfUse::Excluded
703 {
704 self.record_self_loop(dependent);
705 }
706
707 let col_stripes = (s_row.is_none() && e_row.is_none())
708 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
709 let row_stripes = (s_col.is_none() && e_col.is_none())
710 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
711
712 if col_stripes && !row_stripes {
713 let sc = s_col.unwrap_or(0);
714 let ec = e_col.unwrap_or(sc);
715 for col in sc..=ec {
716 let key = StripeKey {
717 sheet_id,
718 stripe_type: StripeType::Column,
719 index: col,
720 };
721 self.stripe_to_dependents
722 .entry(key)
723 .or_default()
724 .insert(dependent);
725 }
726 continue;
727 }
728
729 if row_stripes && !col_stripes {
730 let sr = s_row.unwrap_or(0);
731 let er = e_row.unwrap_or(sr);
732 for row in sr..=er {
733 let key = StripeKey {
734 sheet_id,
735 stripe_type: StripeType::Row,
736 index: row,
737 };
738 self.stripe_to_dependents
739 .entry(key)
740 .or_default()
741 .insert(dependent);
742 }
743 continue;
744 }
745
746 let start_row = s_row.unwrap_or(0);
747 let start_col = s_col.unwrap_or(0);
748 let end_row = e_row.unwrap_or(start_row);
749 let end_col = e_col.unwrap_or(start_col);
750
751 let height = end_row.saturating_sub(start_row) + 1;
752 let width = end_col.saturating_sub(start_col) + 1;
753
754 if self.config.enable_block_stripes && height > 1 && width > 1 {
755 let start_block_row = start_row / BLOCK_H;
756 let end_block_row = end_row / BLOCK_H;
757 let start_block_col = start_col / BLOCK_W;
758 let end_block_col = end_col / BLOCK_W;
759
760 for block_row in start_block_row..=end_block_row {
761 for block_col in start_block_col..=end_block_col {
762 let key = StripeKey {
763 sheet_id,
764 stripe_type: StripeType::Block,
765 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
766 };
767 self.stripe_to_dependents
768 .entry(key)
769 .or_default()
770 .insert(dependent);
771 }
772 }
773 } else if height > width {
774 for col in start_col..=end_col {
775 let key = StripeKey {
776 sheet_id,
777 stripe_type: StripeType::Column,
778 index: col,
779 };
780 self.stripe_to_dependents
781 .entry(key)
782 .or_default()
783 .insert(dependent);
784 }
785 } else {
786 for row in start_row..=end_row {
787 let key = StripeKey {
788 sheet_id,
789 stripe_type: StripeType::Row,
790 index: row,
791 };
792 self.stripe_to_dependents
793 .entry(key)
794 .or_default()
795 .insert(dependent);
796 }
797 }
798 }
799 }
800}