Skip to main content

formualizer_eval/engine/graph/
names.rs

1use super::*;
2use formualizer_common::parse_a1_1based;
3
4#[inline]
5fn normalize_name_key(name: &str) -> String {
6    name.to_lowercase()
7}
8
9/// Validate that a name conforms to Excel naming rules.
10fn is_valid_excel_name(name: &str) -> bool {
11    // Excel name rules:
12    // 1. Must start with a letter, underscore, or backslash
13    // 2. Can contain letters, numbers, periods, and underscores
14    // 3. Cannot be a cell reference (like A1, B2, etc.)
15    // 4. Cannot exceed 255 characters
16    // 5. Cannot contain spaces
17
18    if name.is_empty() || name.len() > 255 {
19        return false;
20    }
21
22    if parse_a1_1based(name).is_ok() {
23        return false;
24    }
25
26    let mut chars = name.chars();
27
28    // First character must be letter, underscore, or backslash
29    if let Some(first) = chars.next()
30        && !first.is_alphabetic()
31        && first != '_'
32        && first != '\\'
33    {
34        return false;
35    }
36
37    // Remaining characters must be letters, digits, periods, or underscores
38    for c in chars {
39        if !c.is_alphanumeric() && c != '.' && c != '_' {
40            return false;
41        }
42    }
43
44    true
45}
46
47/// Helper function to adjust a named definition during structural operations.
48///
49/// Named definitions deliberately keep the legacy `Pin` policy for absolute
50/// anchors: formula reference adjustment now tracks absolute refs through
51/// structural shifts (issue #168), but flipping named-range definitions to
52/// the same semantics is a separate policy decision that has not been made —
53/// see `AbsShiftPolicy` and the #168 discussion.
54fn adjust_named_definition(
55    definition: &mut NamedDefinition,
56    adjuster: &crate::engine::graph::editor::reference_adjuster::ReferenceAdjuster,
57    operation: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
58    context: &crate::engine::graph::editor::reference_adjuster::ReferenceContext<'_>,
59) -> Result<(), ExcelError> {
60    use crate::engine::graph::editor::reference_adjuster::AbsShiftPolicy;
61    match definition {
62        NamedDefinition::Cell(cell_ref) => {
63            if let Some(adjusted) =
64                adjuster.adjust_cell_ref_with_policy(cell_ref, operation, AbsShiftPolicy::Pin)
65            {
66                *cell_ref = adjusted;
67            } else {
68                return Err(ExcelError::new(ExcelErrorKind::Ref));
69            }
70        }
71        NamedDefinition::Range(range_ref) => {
72            let adjusted_start = adjuster.adjust_cell_ref_with_policy(
73                &range_ref.start,
74                operation,
75                AbsShiftPolicy::Pin,
76            );
77            let adjusted_end = adjuster.adjust_cell_ref_with_policy(
78                &range_ref.end,
79                operation,
80                AbsShiftPolicy::Pin,
81            );
82
83            if let (Some(start), Some(end)) = (adjusted_start, adjusted_end) {
84                range_ref.start = start;
85                range_ref.end = end;
86            } else {
87                return Err(ExcelError::new(ExcelErrorKind::Ref));
88            }
89        }
90        NamedDefinition::Literal(_) => {
91            // Constant names are not affected by structural shifts.
92        }
93        NamedDefinition::Formula {
94            ast,
95            dependencies,
96            range_deps,
97        } => {
98            let adjusted_ast = adjuster.adjust_ast_with_policy_in_context(
99                ast,
100                operation,
101                AbsShiftPolicy::Pin,
102                context,
103            );
104            *ast = adjusted_ast;
105
106            dependencies.clear();
107            range_deps.clear();
108        }
109    }
110    Ok(())
111}
112
113impl DependencyGraph {
114    #[inline]
115    pub(crate) fn name_lookup_key(&self, name: &str) -> String {
116        if self.config.case_sensitive_names {
117            name.to_string()
118        } else {
119            normalize_name_key(name)
120        }
121    }
122
123    fn canonical_name_in_scope(&self, scope: NameScope, name: &str) -> Option<String> {
124        let key = self.name_lookup_key(name);
125        match scope {
126            NameScope::Workbook => self.named_ranges_lookup.get(&key).cloned(),
127            NameScope::Sheet(sheet_id) => self
128                .sheet_named_ranges_lookup
129                .get(&(sheet_id, key))
130                .cloned(),
131        }
132    }
133
134    fn next_name_coord(&mut self) -> AbsCoord {
135        let seq = self.name_vertex_seq;
136        self.name_vertex_seq = self.name_vertex_seq.wrapping_add(1);
137        let row = (seq / 16_384).min(0x000F_FFFF);
138        let col = seq % 16_384;
139        AbsCoord::new(row, col)
140    }
141
142    pub(super) fn allocate_name_vertex(&mut self, scope: NameScope) -> VertexId {
143        let coord = self.next_name_coord();
144        let sheet_id = match scope {
145            NameScope::Sheet(id) => id,
146            NameScope::Workbook => self.default_sheet_id,
147        };
148        let vertex_id = self.store.allocate(coord, sheet_id, 0x01);
149        self.store.set_kind(vertex_id, VertexKind::NamedScalar);
150        self.mark_vertex_dirty(vertex_id);
151        self.edges.add_vertex(coord, vertex_id.0);
152        vertex_id
153    }
154
155    // Named Range Methods
156
157    pub(crate) fn validate_define_name(
158        &self,
159        name: &str,
160        scope: NameScope,
161    ) -> Result<(), ExcelError> {
162        if !is_valid_excel_name(name) {
163            return Err(
164                ExcelError::new(ExcelErrorKind::Name).with_message(format!("Invalid name: {name}"))
165            );
166        }
167
168        let lookup_key = self.name_lookup_key(name);
169        match scope {
170            NameScope::Workbook => {
171                if let Some(existing) = self.named_ranges_lookup.get(&lookup_key) {
172                    return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
173                        "Name collision under normalization: '{name}' conflicts with '{existing}'"
174                    )));
175                }
176            }
177            NameScope::Sheet(sheet_id) => {
178                if let Some(existing) = self.sheet_named_ranges_lookup.get(&(sheet_id, lookup_key))
179                {
180                    return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
181                        "Name collision under normalization in sheet: '{name}' conflicts with '{existing}'"
182                    )));
183                }
184            }
185        }
186        Ok(())
187    }
188
189    pub(crate) fn validate_existing_name(
190        &self,
191        name: &str,
192        scope: NameScope,
193    ) -> Result<(), ExcelError> {
194        self.canonical_name_in_scope(scope, name)
195            .map(|_| ())
196            .ok_or_else(|| {
197                ExcelError::new(ExcelErrorKind::Name)
198                    .with_message(format!("Name not found: {name}"))
199            })
200    }
201
202    /// Define a new named range
203    pub fn define_name(
204        &mut self,
205        name: &str,
206        definition: NamedDefinition,
207        scope: NameScope,
208    ) -> Result<(), ExcelError> {
209        self.validate_define_name(name, scope)?;
210
211        let mut final_definition = definition;
212        // Extract dependencies if formula
213        if let NamedDefinition::Formula { ref ast, .. } = final_definition {
214            let (deps, range_deps, _, _) = self.extract_dependencies(
215                ast,
216                match scope {
217                    NameScope::Sheet(id) => id,
218                    NameScope::Workbook => self.default_sheet_id,
219                },
220            )?;
221            final_definition = NamedDefinition::Formula {
222                ast: ast.clone(),
223                dependencies: deps,
224                range_deps,
225            };
226        }
227
228        // Allocate vertex only after dependency extraction succeeds
229        let vertex_id = self.allocate_name_vertex(scope);
230
231        let named_range = NamedRange {
232            definition: final_definition,
233            scope,
234            dependents: FxHashSet::default(),
235            vertex: vertex_id,
236        };
237
238        if matches!(named_range.definition, NamedDefinition::Range(_)) {
239            self.store.set_kind(vertex_id, VertexKind::NamedArray);
240        } else {
241            self.store.set_kind(vertex_id, VertexKind::NamedScalar);
242        }
243
244        let referenced_names =
245            self.rebuild_name_dependencies(vertex_id, &named_range.definition, scope);
246        if !referenced_names.is_empty() {
247            self.attach_vertex_to_names(vertex_id, &referenced_names);
248        }
249
250        let key = name.to_string();
251
252        match scope {
253            NameScope::Workbook => {
254                self.named_ranges.insert(key.clone(), named_range);
255                self.named_ranges_lookup
256                    .insert(self.name_lookup_key(&key), key.clone());
257            }
258            NameScope::Sheet(id) => {
259                self.sheet_named_ranges
260                    .insert((id, key.clone()), named_range);
261                self.sheet_named_ranges_lookup
262                    .insert((id, self.name_lookup_key(&key)), key.clone());
263            }
264        }
265
266        self.name_vertex_lookup.insert(vertex_id, (scope, key));
267        self.resolve_pending_name_references(scope, name);
268        self.bump_symbol_revision();
269
270        Ok(())
271    }
272
273    /// Iterate workbook-scoped named ranges (for bindings/testing)
274    pub fn named_ranges_iter(&self) -> impl Iterator<Item = (&String, &NamedRange)> {
275        self.named_ranges.iter()
276    }
277
278    /// Iterate sheet-scoped named ranges (for bindings/testing)
279    pub fn sheet_named_ranges_iter(
280        &self,
281    ) -> impl Iterator<Item = (&(SheetId, String), &NamedRange)> {
282        self.sheet_named_ranges.iter()
283    }
284
285    pub fn resolve_name_entry(&self, name: &str, current_sheet: SheetId) -> Option<&NamedRange> {
286        if self.config.case_sensitive_names {
287            self.sheet_named_ranges
288                .get(&(current_sheet, name.to_string()))
289                .or_else(|| self.named_ranges.get(name))
290        } else {
291            let key = self.name_lookup_key(name);
292            self.sheet_named_ranges_lookup
293                .get(&(current_sheet, key.clone()))
294                .and_then(|canon| self.sheet_named_ranges.get(&(current_sheet, canon.clone())))
295                .or_else(|| {
296                    self.named_ranges_lookup
297                        .get(&key)
298                        .and_then(|canon| self.named_ranges.get(canon))
299                })
300        }
301    }
302
303    /// Resolve a named range to its definition
304    pub fn resolve_name(&self, name: &str, current_sheet: SheetId) -> Option<&NamedDefinition> {
305        self.resolve_name_entry(name, current_sheet)
306            .map(|nr| &nr.definition)
307    }
308
309    /// The folded lookup key (see [`Self::name_lookup_key`]) of the name
310    /// represented by `vertex`, if it is a name vertex. Used by SCC tasks for
311    /// deterministic member ordering and live name-read matching (RFC #112).
312    pub(crate) fn name_key_for_vertex(&self, vertex: VertexId) -> Option<String> {
313        self.name_vertex_lookup
314            .get(&vertex)
315            .map(|(_, name)| self.name_lookup_key(name))
316    }
317
318    pub fn named_range_by_vertex(&self, vertex: VertexId) -> Option<&NamedRange> {
319        self.name_vertex_lookup
320            .get(&vertex)
321            .and_then(|(scope, name)| match scope {
322                NameScope::Workbook => self.named_ranges.get(name),
323                NameScope::Sheet(sheet_id) => {
324                    self.sheet_named_ranges.get(&(*sheet_id, name.clone()))
325                }
326            })
327    }
328
329    /// Update an existing named range definition
330    pub fn update_name(
331        &mut self,
332        name: &str,
333        new_definition: NamedDefinition,
334        scope: NameScope,
335    ) -> Result<(), ExcelError> {
336        let Some(canon_name) = self.canonical_name_in_scope(scope, name) else {
337            return Err(ExcelError::new(ExcelErrorKind::Name)
338                .with_message(format!("Name not found: {name}")));
339        };
340
341        // First collect dependents to avoid borrow checker issues
342        let dependents_to_dirty = match scope {
343            NameScope::Workbook => self
344                .named_ranges
345                .get(&canon_name)
346                .map(|nr| nr.dependents.iter().copied().collect::<Vec<_>>()),
347            NameScope::Sheet(id) => self
348                .sheet_named_ranges
349                .get(&(id, canon_name.clone()))
350                .map(|nr| nr.dependents.iter().copied().collect::<Vec<_>>()),
351        };
352
353        if let Some(dependents) = dependents_to_dirty {
354            // Mark all dependents as dirty
355            for vertex_id in dependents {
356                self.mark_vertex_dirty(vertex_id);
357            }
358
359            // Now update the definition
360            let named_range = match scope {
361                NameScope::Workbook => self.named_ranges.get_mut(&canon_name),
362                NameScope::Sheet(id) => self.sheet_named_ranges.get_mut(&(id, canon_name.clone())),
363            };
364
365            let mut update_data: Option<(VertexId, NameScope, NamedDefinition, bool)> = None;
366            if let Some(named_range) = named_range {
367                named_range.definition = new_definition;
368                let is_range = matches!(named_range.definition, NamedDefinition::Range(_));
369                update_data = Some((
370                    named_range.vertex,
371                    named_range.scope,
372                    named_range.definition.clone(),
373                    is_range,
374                ));
375            }
376
377            if let Some((vertex, scope_value, definition_snapshot, is_range)) = update_data {
378                self.detach_vertex_from_names(vertex);
379
380                if is_range {
381                    self.store.set_kind(vertex, VertexKind::NamedArray);
382                } else {
383                    self.store.set_kind(vertex, VertexKind::NamedScalar);
384                }
385                self.mark_vertex_dirty(vertex);
386
387                let referenced_names =
388                    self.rebuild_name_dependencies(vertex, &definition_snapshot, scope_value);
389                if !referenced_names.is_empty() {
390                    self.attach_vertex_to_names(vertex, &referenced_names);
391                }
392            }
393
394            self.bump_symbol_revision();
395            Ok(())
396        } else {
397            Err(ExcelError::new(ExcelErrorKind::Name)
398                .with_message(format!("Name not found: {name}")))
399        }
400    }
401
402    /// Delete a named range
403    pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
404        let Some(canon_name) = self.canonical_name_in_scope(scope, name) else {
405            return Err(ExcelError::new(ExcelErrorKind::Name)
406                .with_message(format!("Name not found: {name}")));
407        };
408
409        let named_range = match scope {
410            NameScope::Workbook => {
411                let removed = self.named_ranges.remove(&canon_name);
412                let key = self.name_lookup_key(&canon_name);
413                self.named_ranges_lookup.remove(&key);
414                removed
415            }
416            NameScope::Sheet(id) => {
417                let removed = self.sheet_named_ranges.remove(&(id, canon_name.clone()));
418                let key = self.name_lookup_key(&canon_name);
419                self.sheet_named_ranges_lookup.remove(&(id, key));
420                removed
421            }
422        };
423
424        if let Some(named_range) = named_range {
425            let mut affected: FxHashSet<VertexId> = FxHashSet::default();
426            for &vertex_id in &named_range.dependents {
427                affected.insert(vertex_id);
428            }
429            for (vertex_id, names) in self.vertex_to_names.iter() {
430                if names.contains(&named_range.vertex) {
431                    affected.insert(*vertex_id);
432                }
433            }
434            for vertex_id in affected {
435                self.mark_vertex_dirty(vertex_id);
436                if let Some(names) = self.vertex_to_names.get_mut(&vertex_id) {
437                    names.retain(|vid| *vid != named_range.vertex);
438                    if names.is_empty() {
439                        self.vertex_to_names.remove(&vertex_id);
440                    }
441                }
442            }
443            self.mark_named_vertex_deleted(&named_range);
444            self.bump_symbol_revision();
445            Ok(())
446        } else {
447            Err(ExcelError::new(ExcelErrorKind::Name)
448                .with_message(format!("Name not found: {name}")))
449        }
450    }
451
452    pub(super) fn detach_vertex_from_names(&mut self, vertex: VertexId) {
453        if let Some(prior) = self.vertex_to_names.remove(&vertex) {
454            for name_vertex in prior {
455                if let Some((scope, name)) = self.name_vertex_lookup.get(&name_vertex).cloned() {
456                    match scope {
457                        NameScope::Workbook => {
458                            if let Some(entry) = self.named_ranges.get_mut(&name) {
459                                entry.dependents.remove(&vertex);
460                            }
461                        }
462                        NameScope::Sheet(sheet_id) => {
463                            if let Some(entry) =
464                                self.sheet_named_ranges.get_mut(&(sheet_id, name.clone()))
465                            {
466                                entry.dependents.remove(&vertex);
467                            }
468                        }
469                    }
470                }
471            }
472        }
473    }
474
475    pub(crate) fn attach_vertex_to_names(&mut self, vertex: VertexId, names: &[VertexId]) {
476        if names.is_empty() {
477            return;
478        }
479        let mut unique = FxHashSet::default();
480        let mut recorded = Vec::new();
481        for &name_vertex in names {
482            if !unique.insert(name_vertex) {
483                continue;
484            }
485            if let Some((scope, name)) = self.name_vertex_lookup.get(&name_vertex).cloned() {
486                match scope {
487                    NameScope::Workbook => {
488                        if let Some(entry) = self.named_ranges.get_mut(&name) {
489                            entry.dependents.insert(vertex);
490                        }
491                    }
492                    NameScope::Sheet(sheet_id) => {
493                        if let Some(entry) =
494                            self.sheet_named_ranges.get_mut(&(sheet_id, name.clone()))
495                        {
496                            entry.dependents.insert(vertex);
497                        }
498                    }
499                }
500                recorded.push(name_vertex);
501            }
502        }
503        if !recorded.is_empty() {
504            self.vertex_to_names.insert(vertex, recorded);
505        }
506    }
507
508    pub(super) fn unregister_name_cell_dependencies(&mut self, name_vertex: VertexId) {
509        if let Some(prev) = self.name_to_cell_dependencies.remove(&name_vertex) {
510            for dep in prev {
511                if let Some(set) = self.cell_to_name_dependents.get_mut(&dep) {
512                    set.remove(&name_vertex);
513                    if set.is_empty() {
514                        self.cell_to_name_dependents.remove(&dep);
515                    }
516                }
517            }
518        }
519    }
520
521    pub(super) fn register_name_cell_dependencies(
522        &mut self,
523        name_vertex: VertexId,
524        dependencies: &[VertexId],
525    ) {
526        self.unregister_name_cell_dependencies(name_vertex);
527        if dependencies.is_empty() {
528            return;
529        }
530        for dep in dependencies {
531            self.cell_to_name_dependents
532                .entry(*dep)
533                .or_default()
534                .insert(name_vertex);
535        }
536        self.name_to_cell_dependencies
537            .insert(name_vertex, dependencies.to_vec());
538    }
539
540    pub(crate) fn record_pending_name_reference(
541        &mut self,
542        sheet_id: SheetId,
543        name: &str,
544        formula_vertex: VertexId,
545    ) {
546        let key = self.name_lookup_key(name);
547        self.pending_name_links
548            .entry(key.clone())
549            .or_default()
550            .insert((sheet_id, formula_vertex));
551        self.vertex_to_pending_names
552            .entry(formula_vertex)
553            .or_default()
554            .insert(key);
555    }
556
557    pub(crate) fn clear_pending_name_references(&mut self, formula_vertex: VertexId) {
558        let Some(keys) = self.vertex_to_pending_names.remove(&formula_vertex) else {
559            return;
560        };
561
562        for key in keys {
563            let mut remove_key = false;
564            if let Some(entries) = self.pending_name_links.get_mut(&key) {
565                entries.retain(|(_, vertex_id)| *vertex_id != formula_vertex);
566                remove_key = entries.is_empty();
567            }
568            if remove_key {
569                self.pending_name_links.remove(&key);
570            }
571        }
572    }
573
574    pub(super) fn resolve_pending_name_references(&mut self, scope: NameScope, name: &str) {
575        let key = self.name_lookup_key(name);
576        if let Some(entries) = self.pending_name_links.remove(&key) {
577            for (sheet_id, formula_vertex) in entries {
578                let attach = match scope {
579                    NameScope::Workbook => true,
580                    NameScope::Sheet(expected) => expected == sheet_id,
581                };
582                if attach {
583                    if let Some(ast) = self.get_formula(formula_vertex) {
584                        self.rebuild_formula_dependencies(formula_vertex, &ast);
585                    } else {
586                        self.clear_pending_name_references(formula_vertex);
587                    }
588                } else {
589                    self.record_pending_name_reference(sheet_id, name, formula_vertex);
590                }
591            }
592        }
593    }
594
595    pub(super) fn name_depends_on_vertex(
596        &self,
597        name_vertex: VertexId,
598        target: VertexId,
599        visited: &mut FxHashSet<VertexId>,
600    ) -> bool {
601        if !visited.insert(name_vertex) {
602            return false;
603        }
604
605        for dependency in self.edges.out_edges(name_vertex).iter().copied() {
606            if dependency == target {
607                return true;
608            }
609
610            if matches!(
611                self.store.kind(dependency),
612                VertexKind::NamedScalar | VertexKind::NamedArray
613            ) && self.name_depends_on_vertex(dependency, target, visited)
614            {
615                return true;
616            }
617        }
618
619        false
620    }
621
622    pub(super) fn rebuild_name_dependencies(
623        &mut self,
624        vertex: VertexId,
625        definition: &NamedDefinition,
626        scope: NameScope,
627    ) -> Vec<VertexId> {
628        self.remove_dependent_edges(vertex);
629        self.unregister_name_cell_dependencies(vertex);
630
631        let mut dependencies: Vec<VertexId> = Vec::new();
632        let mut range_dependencies: Vec<SharedRangeRef<'static>> = Vec::new();
633        let mut placeholders = Vec::new();
634
635        match definition {
636            NamedDefinition::Cell(cell_ref) => {
637                let vertex_id = self.get_or_create_vertex(cell_ref, &mut placeholders);
638                dependencies.push(vertex_id);
639            }
640            NamedDefinition::Range(range_ref) => {
641                let height = range_ref
642                    .end
643                    .coord
644                    .row()
645                    .saturating_sub(range_ref.start.coord.row())
646                    + 1;
647                let width = range_ref
648                    .end
649                    .coord
650                    .col()
651                    .saturating_sub(range_ref.start.coord.col())
652                    + 1;
653                let size = (width * height) as usize;
654
655                if size <= self.config.range_expansion_limit {
656                    for row in range_ref.start.coord.row()..=range_ref.end.coord.row() {
657                        for col in range_ref.start.coord.col()..=range_ref.end.coord.col() {
658                            let coord = Coord::new(row, col, true, true);
659                            let addr = CellRef::new(range_ref.start.sheet_id, coord);
660                            let vertex_id = self.get_or_create_vertex(&addr, &mut placeholders);
661                            dependencies.push(vertex_id);
662                        }
663                    }
664                } else {
665                    let sheet_loc = SharedSheetLocator::Id(range_ref.start.sheet_id);
666                    let sr = formualizer_common::AxisBound::new(
667                        range_ref.start.coord.row(),
668                        range_ref.start.coord.row_abs(),
669                    );
670                    let sc = formualizer_common::AxisBound::new(
671                        range_ref.start.coord.col(),
672                        range_ref.start.coord.col_abs(),
673                    );
674                    let er = formualizer_common::AxisBound::new(
675                        range_ref.end.coord.row(),
676                        range_ref.end.coord.row_abs(),
677                    );
678                    let ec = formualizer_common::AxisBound::new(
679                        range_ref.end.coord.col(),
680                        range_ref.end.coord.col_abs(),
681                    );
682                    if let Ok(r) = SharedRangeRef::from_parts(
683                        sheet_loc,
684                        Some(sr),
685                        Some(sc),
686                        Some(er),
687                        Some(ec),
688                    ) {
689                        range_dependencies.push(r.into_owned());
690                    }
691                }
692            }
693            NamedDefinition::Literal(_) => {
694                // No dependencies.
695            }
696            NamedDefinition::Formula {
697                dependencies: formula_deps,
698                range_deps,
699                ..
700            } => {
701                dependencies.extend(formula_deps.iter().copied());
702                range_dependencies.extend(range_deps.iter().cloned());
703            }
704        }
705
706        if !dependencies.is_empty() {
707            self.add_dependent_edges(vertex, &dependencies);
708        }
709        self.register_name_cell_dependencies(vertex, &dependencies);
710
711        if !range_dependencies.is_empty() {
712            let sheet_id = match scope {
713                NameScope::Sheet(id) => id,
714                NameScope::Workbook => self.default_sheet_id,
715            };
716            self.add_range_dependent_edges(vertex, &range_dependencies, sheet_id);
717        }
718
719        dependencies
720            .iter()
721            .filter(|vid| {
722                matches!(
723                    self.store.kind(**vid),
724                    VertexKind::NamedScalar | VertexKind::NamedArray
725                )
726            })
727            .copied()
728            .collect()
729    }
730
731    pub fn adjust_named_ranges(
732        &mut self,
733        operation: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
734    ) -> Result<(), ExcelError> {
735        let adjuster = crate::engine::graph::editor::reference_adjuster::ReferenceAdjuster::new();
736
737        let changed = !self.named_ranges.is_empty() || !self.sheet_named_ranges.is_empty();
738        // Workbook-scoped formulas bind unqualified references to the default sheet.
739        let workbook_context =
740            crate::engine::graph::editor::reference_adjuster::ReferenceContext::new(
741                self.default_sheet_id,
742                &self.sheet_reg,
743            );
744        for named_range in self.named_ranges.values_mut() {
745            adjust_named_definition(
746                &mut named_range.definition,
747                &adjuster,
748                operation,
749                &workbook_context,
750            )?;
751        }
752
753        // Sheet-scoped formulas bind unqualified references to their scope sheet.
754        for ((scope_sheet_id, _), named_range) in self.sheet_named_ranges.iter_mut() {
755            let context = crate::engine::graph::editor::reference_adjuster::ReferenceContext::new(
756                *scope_sheet_id,
757                &self.sheet_reg,
758            );
759            adjust_named_definition(&mut named_range.definition, &adjuster, operation, &context)?;
760        }
761        if changed {
762            self.bump_symbol_revision();
763        }
764
765        Ok(())
766    }
767
768    /// Mark a vertex as having a #NAME! error
769    pub fn mark_as_name_error(&mut self, vertex_id: VertexId) {
770        // Mark the vertex as dirty
771        self.mark_vertex_dirty(vertex_id);
772    }
773
774    pub(super) fn mark_named_vertex_deleted(&mut self, named_range: &NamedRange) {
775        self.detach_vertex_from_names(named_range.vertex);
776        self.remove_dependent_edges(named_range.vertex);
777        self.unregister_name_cell_dependencies(named_range.vertex);
778        self.store.mark_deleted(named_range.vertex, true);
779        self.vertex_values.remove(&named_range.vertex);
780        self.vertex_formulas.remove(&named_range.vertex);
781        self.clear_formula_vertex_dirty(named_range.vertex);
782        self.volatile_vertices.remove(&named_range.vertex);
783        self.vertex_to_names.remove(&named_range.vertex);
784        self.name_vertex_lookup.remove(&named_range.vertex);
785    }
786}