Skip to main content

nu_protocol/engine/
state_working_set.rs

1use crate::{
2    BlockId, Category, CompileError, Config, DeclId, FileId, GetSpan, Module, ModuleId, OverlayId,
3    ParseError, ParseWarning, ResolvedImportPattern, ResolvedSpan, Signature, Span, SpanId, Type,
4    Value, VarId, VirtualPathId,
5    ast::Block,
6    engine::{
7        CachedFile, Command, CommandType, EngineState, OverlayFrame, ScopeBindings, StateDelta,
8        Variable, VirtualPath, Visibility, VisibilityStack, description::build_desc,
9    },
10};
11use core::panic;
12use std::{
13    collections::{HashMap, HashSet},
14    path::{Path, PathBuf},
15    sync::{Arc, Mutex},
16};
17
18#[cfg(feature = "plugin")]
19use crate::{PluginIdentity, PluginRegistryItem, RegisteredPlugin};
20
21/// A temporary extension to the global state. This handles bridging between the global state and the
22/// additional declarations and scope changes that are not yet part of the global scope.
23///
24/// This working set is created by the parser as a way of handling declarations and scope changes that
25/// may later be merged or dropped (and not merged) depending on the needs of the code calling the parser.
26pub struct StateWorkingSet<'a> {
27    pub permanent_state: &'a EngineState,
28    pub delta: StateDelta,
29    pub files: FileStack,
30    /// Whether or not predeclarations are searched when looking up a command (used with aliases)
31    pub search_predecls: bool,
32    /// When true, `use` / `export use` / `overlay use` / `module <file>` parse as
33    /// syntax only and do not load modules from disk or the virtual filesystem.
34    /// The REPL highlighter sets this so typing `use std` does not parse-time-load
35    /// the standard library on every keystroke.
36    pub skip_module_load: bool,
37    pub parse_errors: Vec<ParseError>,
38    pub parse_warnings: Vec<ParseWarning>,
39    pub compile_errors: Vec<CompileError>,
40    /// Signatures of *permanent* declarations, built lazily the first time the parser needs
41    /// them and shared for the rest of this working set's life. `Command::signature()` rebuilds
42    /// a `Signature` (several heap allocations) on every call, and the parser asks for it at
43    /// least twice per call site (argument parsing and pipeline type checking), so a file that
44    /// calls the same command many times would otherwise rebuild it many times.
45    ///
46    /// Only permanent declarations are cached: they, and the permanent blocks that back custom
47    /// commands, cannot change while this working set borrows the `EngineState`. Declarations
48    /// in the delta are never cached because `def` replaces a predeclaration's signature in
49    /// place while parsing.
50    ///
51    /// The two caches mirror the two ways the parser reads a signature: the effective signature
52    /// from [`StateWorkingSet::get_signature`] (block-backed commands report their block's
53    /// signature) and the declaration's own `Command::signature()`. They are maps rather than
54    /// id-indexed vectors so that a tiny parse (a REPL line) only pays for the few commands it
55    /// uses. A `Mutex` (never contended; the working set is single-threaded) keeps the type
56    /// `Sync` for miette.
57    permanent_signatures: Mutex<HashMap<DeclId, Arc<Signature>>>,
58    permanent_decl_signatures: Mutex<HashMap<DeclId, Arc<Signature>>>,
59}
60
61impl<'a> StateWorkingSet<'a> {
62    pub fn new(permanent_state: &'a EngineState) -> Self {
63        // Initialize the file stack with the top-level file.
64        let files = if let Some(file) = permanent_state.file.clone() {
65            FileStack::with_file(file)
66        } else {
67            FileStack::new()
68        };
69
70        Self {
71            delta: StateDelta::new(permanent_state),
72            permanent_state,
73            files,
74            search_predecls: true,
75            skip_module_load: false,
76            parse_errors: vec![],
77            parse_warnings: vec![],
78            compile_errors: vec![],
79            permanent_signatures: Mutex::new(HashMap::new()),
80            permanent_decl_signatures: Mutex::new(HashMap::new()),
81        }
82    }
83
84    pub fn permanent(&self) -> &EngineState {
85        self.permanent_state
86    }
87
88    pub fn error(&mut self, parse_error: ParseError) {
89        self.parse_errors.push(parse_error)
90    }
91
92    pub fn warning(&mut self, parse_warning: ParseWarning) {
93        self.parse_warnings.push(parse_warning)
94    }
95
96    pub fn num_files(&self) -> usize {
97        self.delta.num_files() + self.permanent_state.num_files()
98    }
99
100    pub fn num_virtual_paths(&self) -> usize {
101        self.delta.num_virtual_paths() + self.permanent_state.num_virtual_paths()
102    }
103
104    pub fn num_vars(&self) -> usize {
105        self.delta.num_vars() + self.permanent_state.num_vars()
106    }
107
108    pub fn num_decls(&self) -> usize {
109        self.delta.num_decls() + self.permanent_state.num_decls()
110    }
111
112    pub fn num_blocks(&self) -> usize {
113        self.delta.num_blocks() + self.permanent_state.num_blocks()
114    }
115
116    pub fn num_modules(&self) -> usize {
117        self.delta.num_modules() + self.permanent_state.num_modules()
118    }
119
120    pub fn unique_overlay_names(&self) -> HashSet<&[u8]> {
121        let mut names: HashSet<&[u8]> = self.permanent_state.active_overlay_names(&[]).collect();
122
123        for scope_frame in self.delta.scope.iter().rev() {
124            for overlay_id in scope_frame.active_overlays.iter().rev() {
125                let (overlay_name, _) = scope_frame
126                    .overlays
127                    .get(overlay_id.get())
128                    .expect("internal error: missing overlay");
129
130                names.insert(overlay_name);
131                names.retain(|n| !scope_frame.removed_overlays.iter().any(|m| n == m));
132            }
133        }
134
135        names
136    }
137
138    pub fn num_overlays(&self) -> usize {
139        self.unique_overlay_names().len()
140    }
141
142    pub fn add_decl(&mut self, decl: Box<dyn Command>) -> DeclId {
143        let name = decl.name().as_bytes().to_vec();
144
145        self.delta.decls.push(decl);
146        let decl_id = self.num_decls() - 1;
147        let decl_id = DeclId::new(decl_id);
148
149        self.last_overlay_mut().insert_decl(name, decl_id);
150
151        decl_id
152    }
153
154    pub fn use_decls(&mut self, decls: Vec<(Vec<u8>, DeclId)>) {
155        let overlay_frame = self.last_overlay_mut();
156
157        for (name, decl_id) in decls {
158            overlay_frame.insert_decl(name, decl_id);
159            overlay_frame.visibility.use_decl_id(&decl_id);
160        }
161    }
162
163    pub fn use_modules(&mut self, modules: Vec<(Vec<u8>, ModuleId)>) {
164        let overlay_frame = self.last_overlay_mut();
165
166        for (name, module_id) in modules {
167            overlay_frame.insert_module(name, module_id);
168            // overlay_frame.visibility.use_module_id(&module_id);  // TODO: Add hiding modules
169        }
170    }
171
172    pub fn use_variables(&mut self, variables: Vec<(Vec<u8>, VarId)>) {
173        for (name, var_id) in variables {
174            self.insert_variable_into_scope(name, var_id);
175        }
176    }
177
178    pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
179        let name = decl.name().as_bytes().to_vec();
180
181        self.delta.decls.push(decl);
182        let decl_id = self.num_decls() - 1;
183        let decl_id = DeclId::new(decl_id);
184
185        self.delta
186            .last_scope_frame_mut()
187            .predecls
188            .insert(name, decl_id)
189    }
190
191    #[cfg(feature = "plugin")]
192    pub fn find_or_create_plugin(
193        &mut self,
194        identity: &PluginIdentity,
195        make: impl FnOnce() -> Arc<dyn RegisteredPlugin>,
196    ) -> Arc<dyn RegisteredPlugin> {
197        // Check in delta first, then permanent_state
198        if let Some(plugin) = self
199            .delta
200            .plugins
201            .iter()
202            .chain(self.permanent_state.plugins())
203            .find(|p| p.identity() == identity)
204        {
205            plugin.clone()
206        } else {
207            let plugin = make();
208            self.delta.plugins.push(plugin.clone());
209            plugin
210        }
211    }
212
213    #[cfg(feature = "plugin")]
214    pub fn update_plugin_registry(&mut self, item: PluginRegistryItem) {
215        self.delta.plugin_registry_items.push(item);
216    }
217
218    pub fn merge_predecl(&mut self, name: &[u8]) -> Option<DeclId> {
219        self.move_one_predecl_to_overlay(name);
220
221        let overlay_frame = self.last_overlay_mut();
222
223        if let Some(decl_id) = overlay_frame.predecls.remove(name) {
224            overlay_frame.insert_decl(name.into(), decl_id);
225
226            return Some(decl_id);
227        }
228
229        None
230    }
231
232    fn move_one_predecl_to_overlay(&mut self, name: &[u8]) {
233        self.delta
234            .last_scope_frame_mut()
235            .predecls
236            .remove_entry(name)
237            .map(|(name, decl_id)| self.last_overlay_mut().predecls.insert(name, decl_id));
238    }
239
240    pub fn hide_decl(&mut self, name: &[u8]) -> Option<DeclId> {
241        let mut removed_overlays = vec![];
242        let mut visibility: Visibility = Visibility::new();
243
244        // Since we can mutate scope frames in delta, remove the id directly
245        for scope_frame in self.delta.scope.iter_mut().rev() {
246            for overlay_id in scope_frame
247                .active_overlay_ids(&mut removed_overlays)
248                .iter()
249                .rev()
250            {
251                let overlay_frame = scope_frame.get_overlay_mut(*overlay_id);
252
253                visibility.append(&overlay_frame.visibility);
254
255                if let Some(decl_id) = overlay_frame.get_decl(name)
256                    && visibility.is_decl_id_visible(&decl_id)
257                {
258                    // Hide decl only if it's not already hidden
259                    overlay_frame.visibility.hide_decl_id(&decl_id);
260                    return Some(decl_id);
261                }
262            }
263        }
264
265        // We cannot mutate the permanent state => store the information in the current overlay frame
266        // for scope in self.permanent_state.scope.iter().rev() {
267        for overlay_frame in self
268            .permanent_state
269            .active_overlays(&removed_overlays)
270            .rev()
271        {
272            visibility.append(&overlay_frame.visibility);
273
274            if let Some(decl_id) = overlay_frame.get_decl(name)
275                && visibility.is_decl_id_visible(&decl_id)
276            {
277                // Hide decl only if it's not already hidden
278                self.last_overlay_mut().visibility.hide_decl_id(&decl_id);
279                return Some(decl_id);
280            }
281        }
282
283        None
284    }
285
286    pub fn hide_decls(&mut self, decls: &[Vec<u8>]) {
287        for decl in decls.iter() {
288            self.hide_decl(decl); // let's assume no errors
289        }
290    }
291
292    pub fn add_block(&mut self, block: Arc<Block>) -> BlockId {
293        log::trace!(
294            "block id={} added, has IR = {:?}",
295            self.num_blocks(),
296            block.ir_block.is_some()
297        );
298
299        self.delta.blocks.push(block);
300
301        BlockId::new(self.num_blocks() - 1)
302    }
303
304    pub fn add_module(&mut self, name: &str, module: Module, comments: Vec<Span>) -> ModuleId {
305        let name = name.as_bytes().to_vec();
306
307        self.delta.modules.push(Arc::new(module));
308        let module_id = self.num_modules() - 1;
309        let module_id = ModuleId::new(module_id);
310
311        if !comments.is_empty() {
312            self.delta
313                .doccomments
314                .add_module_comments(module_id, comments);
315        }
316
317        self.last_overlay_mut().modules.insert(name, module_id);
318
319        module_id
320    }
321
322    pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
323        self.delta
324            .doccomments
325            .get_module_comments(module_id)
326            .or_else(|| self.permanent_state.get_module_comments(module_id))
327    }
328
329    pub fn next_span_start(&self) -> usize {
330        let permanent_span_start = self.permanent_state.next_span_start();
331
332        if let Some(cached_file) = self.delta.files.last() {
333            cached_file.covered_span.end
334        } else {
335            permanent_span_start
336        }
337    }
338
339    pub fn files(&self) -> impl DoubleEndedIterator<Item = &CachedFile> {
340        self.permanent_state.files().chain(self.delta.files.iter())
341    }
342
343    pub fn get_contents_of_file(&self, file_id: FileId) -> Option<&[u8]> {
344        if let Some(cached_file) = self.permanent_state.get_file_contents().get(file_id.get()) {
345            return Some(&cached_file.content);
346        }
347        // The index subtraction will not underflow, if we hit the permanent state first.
348        // Check if you try reordering for locality
349        if let Some(cached_file) = self
350            .delta
351            .get_file_contents()
352            .get(file_id.get() - self.permanent_state.num_files())
353        {
354            return Some(&cached_file.content);
355        }
356
357        None
358    }
359
360    #[must_use]
361    pub fn add_file(&mut self, filename: &str, contents: &[u8]) -> FileId {
362        // First, look for the file to see if we already have it
363        for (idx, cached_file) in self.files().enumerate() {
364            if &*cached_file.name == filename && &*cached_file.content == contents {
365                return FileId::new(idx);
366            }
367        }
368
369        let next_span_start = self.next_span_start();
370        let next_span_end = next_span_start + contents.len();
371
372        let covered_span = Span::new(next_span_start, next_span_end);
373
374        self.delta.files.push(CachedFile {
375            name: filename.into(),
376            content: contents.into(),
377            covered_span,
378        });
379
380        FileId::new(self.num_files() - 1)
381    }
382
383    #[must_use]
384    pub fn add_virtual_path(&mut self, name: String, virtual_path: VirtualPath) -> VirtualPathId {
385        self.delta.virtual_paths.push((name, virtual_path));
386
387        VirtualPathId::new(self.num_virtual_paths() - 1)
388    }
389
390    pub fn get_span_for_filename(&self, filename: &str) -> Option<Span> {
391        let predicate = |file: &CachedFile| &*file.name == filename;
392        // search from end to start, in case there're duplicated files with the same name
393        let file_id = self
394            .delta
395            .files
396            .iter()
397            .rposition(predicate)
398            .map(|idx| idx + self.permanent_state.num_files())
399            .or_else(|| self.permanent_state.files().rposition(predicate))?;
400        let file_id = FileId::new(file_id);
401
402        Some(self.get_span_for_file(file_id))
403    }
404
405    /// Panics:
406    /// On invalid `FileId`
407    ///
408    /// Use with care
409    pub fn get_span_for_file(&self, file_id: FileId) -> Span {
410        let result = self
411            .files()
412            .nth(file_id.get())
413            .expect("internal error: could not find source for previously parsed file");
414
415        result.covered_span
416    }
417
418    #[inline]
419    pub fn get_span_contents(&self, span: Span) -> &[u8] {
420        let permanent_end = self.permanent_state.next_span_start();
421        if permanent_end <= span.start {
422            for cached_file in &self.delta.files {
423                if cached_file.covered_span.contains_span(span) {
424                    return &cached_file.content[span.start - cached_file.covered_span.start
425                        ..span.end - cached_file.covered_span.start];
426                }
427            }
428        }
429
430        // if no files with span were found, fall back on permanent ones
431        self.permanent_state.get_span_contents(span)
432    }
433
434    pub fn enter_scope(&mut self) {
435        self.delta.enter_scope();
436    }
437
438    pub fn exit_scope(&mut self) {
439        self.delta.exit_scope();
440    }
441
442    /// Find the [`DeclId`](crate::DeclId) corresponding to a predeclaration with `name`.
443    pub fn find_predecl(&self, name: &[u8]) -> Option<DeclId> {
444        let mut removed_overlays = vec![];
445
446        for scope_frame in self.delta.scope.iter().rev() {
447            if let Some(decl_id) = scope_frame.predecls.get(name) {
448                return Some(*decl_id);
449            }
450
451            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
452                if let Some(decl_id) = overlay_frame.predecls.get(name) {
453                    return Some(*decl_id);
454                }
455            }
456        }
457
458        None
459    }
460
461    /// Find the [`DeclId`](crate::DeclId) corresponding to a declaration with `name`.
462    ///
463    /// Extends [`EngineState::find_decl`] to also search for predeclarations
464    /// (if [`StateWorkingSet::search_predecls`] is set), and declarations from scopes existing
465    /// only in [`StateDelta`].
466    pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
467        let mut removed_overlays = vec![];
468
469        let mut visibility = VisibilityStack::default();
470
471        for scope_frame in self.delta.scope.iter().rev() {
472            if self.search_predecls
473                && let Some(decl_id) = scope_frame.predecls.get(name)
474                && visibility.is_decl_id_visible(decl_id)
475            {
476                return Some(*decl_id);
477            }
478
479            // check overlay in delta
480            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
481                visibility.push(&overlay_frame.visibility);
482
483                if self.search_predecls
484                    && let Some(decl_id) = overlay_frame.predecls.get(name)
485                    && visibility.is_decl_id_visible(decl_id)
486                {
487                    return Some(*decl_id);
488                }
489
490                if let Some(decl_id) = overlay_frame.get_decl(name)
491                    && visibility.is_decl_id_visible(&decl_id)
492                {
493                    return Some(decl_id);
494                }
495            }
496        }
497
498        // check overlay in perma
499        self.permanent_state.find_decl(name, &removed_overlays)
500    }
501
502    /// Find the name of the declaration corresponding to `decl_id`.
503    ///
504    /// Extends [`EngineState::find_decl_name`] to also search for predeclarations (if [`StateWorkingSet::search_predecls`] is set),
505    /// and declarations from scopes existing only in [`StateDelta`].
506    pub fn find_decl_name(&self, decl_id: DeclId) -> Option<&[u8]> {
507        let mut removed_overlays = vec![];
508
509        let mut visibility = VisibilityStack::default();
510
511        for scope_frame in self.delta.scope.iter().rev() {
512            if self.search_predecls {
513                for (name, id) in scope_frame.predecls.iter() {
514                    if id == &decl_id {
515                        return Some(name);
516                    }
517                }
518            }
519
520            // check overlay in delta
521            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
522                visibility.push(&overlay_frame.visibility);
523
524                if self.search_predecls {
525                    for (name, id) in overlay_frame.predecls.iter() {
526                        if id == &decl_id {
527                            return Some(name);
528                        }
529                    }
530                }
531
532                if visibility.is_decl_id_visible(&decl_id) {
533                    for (name, id) in overlay_frame.decls.iter() {
534                        if id == &decl_id {
535                            return Some(name);
536                        }
537                    }
538                }
539            }
540        }
541
542        // check overlay in perma
543        self.permanent_state
544            .find_decl_name(decl_id, &removed_overlays)
545    }
546
547    /// Find the [`ModuleId`](crate::ModuleId) corresponding to `name`.
548    ///
549    /// Extends [`EngineState::find_module`] to also search for ,
550    /// and declarations from scopes existing only in [`StateDelta`].
551    pub fn find_module(&self, name: &[u8]) -> Option<ModuleId> {
552        let mut removed_overlays = vec![];
553
554        for scope_frame in self.delta.scope.iter().rev() {
555            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
556                if let Some(module_id) = overlay_frame.modules.get(name) {
557                    return Some(*module_id);
558                }
559            }
560        }
561
562        for overlay_frame in self
563            .permanent_state
564            .active_overlays(&removed_overlays)
565            .rev()
566        {
567            if let Some(module_id) = overlay_frame.modules.get(name) {
568                return Some(*module_id);
569            }
570        }
571
572        None
573    }
574
575    pub fn next_var_id(&self) -> VarId {
576        let num_permanent_vars = self.permanent_state.num_vars();
577        VarId::new(num_permanent_vars + self.delta.vars.len())
578    }
579
580    pub fn list_variables(&self) -> Vec<&[u8]> {
581        let mut removed_overlays = vec![];
582        let mut variables = HashSet::new();
583        for scope_frame in self.delta.scope.iter() {
584            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays) {
585                variables.extend(overlay_frame.vars.keys().map(|k| &k[..]));
586            }
587        }
588
589        let permanent_vars = self
590            .permanent_state
591            .active_overlays(&removed_overlays)
592            .flat_map(|overlay_frame| overlay_frame.vars.keys().map(|k| &k[..]));
593
594        variables.extend(permanent_vars);
595        variables.into_iter().collect()
596    }
597
598    pub fn find_variable(&self, name: &[u8]) -> Option<VarId> {
599        let mut name = name.to_vec();
600        if !name.starts_with(b"$") {
601            name.insert(0, b'$');
602        }
603        let mut removed_overlays = vec![];
604
605        for scope_frame in self.delta.scope.iter().rev() {
606            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
607                if let Some(var_id) = overlay_frame.vars.get(&name) {
608                    return Some(*var_id);
609                }
610            }
611        }
612
613        for overlay_frame in self
614            .permanent_state
615            .active_overlays(&removed_overlays)
616            .rev()
617        {
618            if let Some(var_id) = overlay_frame.vars.get(&name) {
619                return Some(*var_id);
620            }
621        }
622
623        None
624    }
625
626    pub fn find_variable_in_current_frame(&self, name: &[u8]) -> Option<VarId> {
627        let mut removed_overlays = vec![];
628
629        for scope_frame in self.delta.scope.iter().rev().take(1) {
630            for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
631                if let Some(var_id) = overlay_frame.vars.get(name) {
632                    return Some(*var_id);
633                }
634            }
635        }
636
637        None
638    }
639
640    pub fn add_variable(&mut self, name: Vec<u8>, span: Span, ty: Type, mutable: bool) -> VarId {
641        let var_id = self.add_variable_without_scope(span, ty, mutable);
642        self.insert_variable_into_scope(name, var_id);
643        var_id
644    }
645
646    /// Like [`add_variable`](Self::add_variable) but does **not** insert the
647    /// name→VarId mapping into the current overlay scope. The caller must
648    /// later call [`insert_variable_into_scope`](Self::insert_variable_into_scope)
649    /// to make the variable visible by name.
650    pub fn add_variable_without_scope(&mut self, span: Span, ty: Type, mutable: bool) -> VarId {
651        let next_id = self.next_var_id();
652        self.delta.vars.push(Variable::new(span, ty, mutable));
653        next_id
654    }
655
656    /// Insert a previously created variable into the current overlay's scope.
657    /// The `name` will have a `$` prefix prepended if it doesn't already have one.
658    pub fn insert_variable_into_scope(&mut self, mut name: Vec<u8>, var_id: VarId) {
659        if !name.starts_with(b"$") {
660            name.insert(0, b'$');
661        }
662        // Record the name on delta variables so `scope variables` can list stack locals
663        // that never enter permanent overlays. Permanent vars keep `name: None` here;
664        // their names remain available through permanent overlay maps.
665        if let Some(var) = self.get_variable_mut(var_id) {
666            var.name = Some(name.clone());
667        }
668        self.last_overlay_mut().insert_variable(name, var_id);
669    }
670
671    /// Returns the current working directory as a String, which is guaranteed to be canonicalized.
672    /// Returns an empty string if $env.PWD doesn't exist, is not a String, or is not an absolute path.
673    ///
674    /// It does NOT consider modifications to the working directory made on a stack.
675    #[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
676    pub fn get_cwd(&self) -> String {
677        self.permanent_state
678            .cwd(None)
679            .map(|path| path.to_string_lossy().to_string())
680            .unwrap_or_default()
681    }
682
683    pub fn get_env_var(&self, name: &str) -> Option<&Value> {
684        self.permanent_state.get_env_var(name)
685    }
686
687    /// Returns a reference to the config stored at permanent state
688    ///
689    /// At runtime, you most likely want to call [`Stack::get_config()`][super::Stack::get_config()]
690    /// because this method does not capture environment updates during runtime.
691    pub fn get_config(&self) -> &Arc<Config> {
692        &self.permanent_state.config
693    }
694
695    pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) {
696        let num_permanent_vars = self.permanent_state.num_vars();
697        if var_id.get() < num_permanent_vars {
698            panic!("Internal error: attempted to set into permanent state from working set")
699        } else {
700            self.delta.vars[var_id.get() - num_permanent_vars].ty = ty;
701        }
702    }
703
704    pub fn set_variable_const_val(&mut self, var_id: VarId, val: Value) {
705        let num_permanent_vars = self.permanent_state.num_vars();
706        if var_id.get() < num_permanent_vars {
707            panic!("Internal error: attempted to set into permanent state from working set")
708        } else {
709            self.delta.vars[var_id.get() - num_permanent_vars].const_val = Some(val);
710        }
711    }
712
713    pub fn get_variable(&self, var_id: VarId) -> &Variable {
714        let num_permanent_vars = self.permanent_state.num_vars();
715        if var_id.get() < num_permanent_vars {
716            self.permanent_state.get_var(var_id)
717        } else {
718            self.delta
719                .vars
720                .get(var_id.get() - num_permanent_vars)
721                .expect("internal error: missing variable")
722        }
723    }
724
725    /// Mutable access to a variable that still lives in the working-set delta.
726    ///
727    /// Returns `None` for variables that already belong to the permanent engine state.
728    pub fn get_variable_mut(&mut self, var_id: VarId) -> Option<&mut Variable> {
729        let num_permanent_vars = self.permanent_state.num_vars();
730        if var_id.get() < num_permanent_vars {
731            None
732        } else {
733            self.delta.vars.get_mut(var_id.get() - num_permanent_vars)
734        }
735    }
736
737    pub fn get_variable_if_possible(&self, var_id: VarId) -> Option<&Variable> {
738        let num_permanent_vars = self.permanent_state.num_vars();
739        if var_id.get() < num_permanent_vars {
740            Some(self.permanent_state.get_var(var_id))
741        } else {
742            self.delta.vars.get(var_id.get() - num_permanent_vars)
743        }
744    }
745
746    /// Snapshot command/module bindings from the **innermost** scope frame.
747    ///
748    /// # Invariant
749    ///
750    /// Must run on the scope frame that owns the block's locals, **immediately before** the
751    /// matching `exit_scope` (which discards that frame). Call sites:
752    /// `parse_block_expression`, `parse_closure_expression`, and scoped `parse_block`.
753    /// Keep those three call sites explicit so a new scoped construct is forced to opt in.
754    pub fn snapshot_scope_bindings(&self) -> Option<Arc<ScopeBindings>> {
755        let frame = self.delta.last_scope_frame();
756        let mut bindings = ScopeBindings::default();
757        let mut removed_overlays = vec![];
758
759        for overlay in frame.active_overlays(&mut removed_overlays) {
760            bindings.extend_from_overlay(overlay);
761        }
762
763        if bindings.is_empty() {
764            None
765        } else {
766            Some(Arc::new(bindings))
767        }
768    }
769
770    pub fn get_constant(&self, var_id: VarId) -> Result<&Value, ParseError> {
771        let var = self.get_variable(var_id);
772
773        if let Some(const_val) = &var.const_val {
774            Ok(const_val)
775        } else {
776            Err(ParseError::InternalError(
777                "constant does not have a constant value".into(),
778                var.declaration_span,
779            ))
780        }
781    }
782
783    pub fn get_decl(&self, decl_id: DeclId) -> &dyn Command {
784        let num_permanent_decls = self.permanent_state.num_decls();
785        if decl_id.get() < num_permanent_decls {
786            self.permanent_state.get_decl(decl_id)
787        } else {
788            self.delta
789                .decls
790                .get(decl_id.get() - num_permanent_decls)
791                .expect("internal error: missing declaration")
792                .as_ref()
793        }
794    }
795
796    pub fn get_decl_mut(&mut self, decl_id: DeclId) -> &mut Box<dyn Command> {
797        let num_permanent_decls = self.permanent_state.num_decls();
798        if decl_id.get() < num_permanent_decls {
799            panic!("internal error: can only mutate declarations in working set")
800        } else {
801            self.delta
802                .decls
803                .get_mut(decl_id.get() - num_permanent_decls)
804                .expect("internal error: missing declaration")
805        }
806    }
807
808    pub fn get_signature(&self, decl: &dyn Command) -> Signature {
809        if let Some(block_id) = decl.block_id() {
810            *self.get_block(block_id).signature.clone()
811        } else {
812            decl.signature()
813        }
814    }
815
816    /// Shared version of [`StateWorkingSet::get_signature`] for the declaration `decl_id`.
817    ///
818    /// Permanent declarations are built once per working set and then returned from a cache;
819    /// declarations in the delta are rebuilt on every call, exactly like `get_signature`.
820    pub fn get_signature_shared(&self, decl_id: DeclId) -> Arc<Signature> {
821        if decl_id.get() >= self.permanent_state.num_decls() {
822            return Arc::new(self.get_signature(self.get_decl(decl_id)));
823        }
824        let mut cache = self
825            .permanent_signatures
826            .lock()
827            .unwrap_or_else(|poisoned| poisoned.into_inner());
828        Arc::clone(
829            cache
830                .entry(decl_id)
831                .or_insert_with(|| Arc::new(self.get_signature(self.get_decl(decl_id)))),
832        )
833    }
834
835    /// Shared version of `Command::signature()` for the declaration `decl_id`, i.e. the
836    /// declaration's own signature rather than the one on its block.
837    ///
838    /// Cached for permanent declarations, rebuilt on every call for delta declarations.
839    pub fn get_decl_signature_shared(&self, decl_id: DeclId) -> Arc<Signature> {
840        if decl_id.get() >= self.permanent_state.num_decls() {
841            return Arc::new(self.get_decl(decl_id).signature());
842        }
843        let mut cache = self
844            .permanent_decl_signatures
845            .lock()
846            .unwrap_or_else(|poisoned| poisoned.into_inner());
847        Arc::clone(
848            cache
849                .entry(decl_id)
850                .or_insert_with(|| Arc::new(self.get_decl(decl_id).signature())),
851        )
852    }
853
854    /// Apply a function to all commands. The function accepts a command name and its DeclId
855    pub fn traverse_commands(&self, mut f: impl FnMut(&[u8], DeclId)) {
856        for scope_frame in self.delta.scope.iter().rev() {
857            for overlay_id in scope_frame.active_overlays.iter().rev() {
858                let overlay_frame = scope_frame.get_overlay(*overlay_id);
859
860                for (name, decl_id) in &overlay_frame.decls {
861                    if overlay_frame.visibility.is_decl_id_visible(decl_id) {
862                        f(name, *decl_id);
863                    }
864                }
865            }
866        }
867
868        self.permanent_state.traverse_commands(f);
869    }
870
871    pub fn find_commands_by_predicate(
872        &self,
873        mut predicate: impl FnMut(&[u8]) -> bool,
874        ignore_deprecated: bool,
875    ) -> Vec<(DeclId, Vec<u8>, Option<String>, CommandType)> {
876        let mut output = vec![];
877
878        self.traverse_commands(|name, decl_id| {
879            if !predicate(name) {
880                return;
881            }
882            let command = self.get_decl(decl_id);
883            if ignore_deprecated && command.signature().category == Category::Removed {
884                return;
885            }
886            output.push((
887                decl_id,
888                name.to_vec(),
889                Some(command.description().to_string()),
890                command.command_type(),
891            ));
892        });
893
894        output
895    }
896
897    pub fn get_block(&self, block_id: BlockId) -> &Arc<Block> {
898        let num_permanent_blocks = self.permanent_state.num_blocks();
899        if block_id.get() < num_permanent_blocks {
900            self.permanent_state.get_block(block_id)
901        } else {
902            self.delta
903                .blocks
904                .get(block_id.get() - num_permanent_blocks)
905                .expect("internal error: missing block")
906        }
907    }
908
909    pub fn get_module(&self, module_id: ModuleId) -> &Module {
910        let num_permanent_modules = self.permanent_state.num_modules();
911        if module_id.get() < num_permanent_modules {
912            self.permanent_state.get_module(module_id)
913        } else {
914            self.delta
915                .modules
916                .get(module_id.get() - num_permanent_modules)
917                .expect("internal error: missing module")
918        }
919    }
920
921    pub fn get_block_mut(&mut self, block_id: BlockId) -> &mut Block {
922        let num_permanent_blocks = self.permanent_state.num_blocks();
923        if block_id.get() < num_permanent_blocks {
924            panic!("Attempt to mutate a block that is in the permanent (immutable) state")
925        } else {
926            self.delta
927                .blocks
928                .get_mut(block_id.get() - num_permanent_blocks)
929                .map(Arc::make_mut)
930                .expect("internal error: missing block")
931        }
932    }
933
934    /// Find the overlay corresponding to `name`.
935    pub fn find_overlay(&self, name: &[u8]) -> Option<&OverlayFrame> {
936        for scope_frame in self.delta.scope.iter().rev() {
937            if let Some(overlay_id) = scope_frame.find_overlay(name) {
938                return Some(scope_frame.get_overlay(overlay_id));
939            }
940        }
941
942        self.permanent_state
943            .find_overlay(name)
944            .map(|id| self.permanent_state.get_overlay(id))
945    }
946
947    pub fn last_overlay_name(&self) -> &[u8] {
948        let mut removed_overlays = vec![];
949
950        for scope_frame in self.delta.scope.iter().rev() {
951            if let Some(last_name) = scope_frame
952                .active_overlay_names(&mut removed_overlays)
953                .iter()
954                .rev()
955                .next_back()
956            {
957                return last_name;
958            }
959        }
960
961        self.permanent_state.last_overlay_name(&removed_overlays)
962    }
963
964    pub fn last_overlay(&self) -> &OverlayFrame {
965        let mut removed_overlays = vec![];
966
967        for scope_frame in self.delta.scope.iter().rev() {
968            if let Some(last_overlay) = scope_frame
969                .active_overlays(&mut removed_overlays)
970                .rev()
971                .next_back()
972            {
973                return last_overlay;
974            }
975        }
976
977        self.permanent_state.last_overlay(&removed_overlays)
978    }
979
980    pub fn last_overlay_mut(&mut self) -> &mut OverlayFrame {
981        if self.delta.last_overlay_mut().is_none() {
982            // If there is no overlay, automatically activate the last one
983            let overlay_frame = self.last_overlay();
984            let name = self.last_overlay_name().to_vec();
985            let origin = overlay_frame.origin;
986            let prefixed = overlay_frame.prefixed;
987            self.add_overlay(
988                name,
989                origin,
990                ResolvedImportPattern::new(vec![], vec![], vec![], vec![]),
991                prefixed,
992            );
993        }
994
995        self.delta
996            .last_overlay_mut()
997            .expect("internal error: missing added overlay")
998    }
999
1000    /// Collect all decls that belong to an overlay
1001    pub fn decls_of_overlay(&self, name: &[u8]) -> HashMap<Vec<u8>, DeclId> {
1002        let mut result = HashMap::new();
1003
1004        if let Some(overlay_id) = self.permanent_state.find_overlay(name) {
1005            let overlay_frame = self.permanent_state.get_overlay(overlay_id);
1006
1007            for (decl_key, decl_id) in &overlay_frame.decls {
1008                result.insert(decl_key.to_owned(), *decl_id);
1009            }
1010        }
1011
1012        for scope_frame in self.delta.scope.iter() {
1013            if let Some(overlay_id) = scope_frame.find_overlay(name) {
1014                let overlay_frame = scope_frame.get_overlay(overlay_id);
1015
1016                for (decl_key, decl_id) in &overlay_frame.decls {
1017                    result.insert(decl_key.to_owned(), *decl_id);
1018                }
1019            }
1020        }
1021
1022        result
1023    }
1024
1025    pub fn add_overlay(
1026        &mut self,
1027        name: Vec<u8>,
1028        origin: ModuleId,
1029        definitions: ResolvedImportPattern,
1030        prefixed: bool,
1031    ) {
1032        let last_scope_frame = self.delta.last_scope_frame_mut();
1033
1034        last_scope_frame
1035            .removed_overlays
1036            .retain(|removed_name| removed_name != &name);
1037
1038        let overlay_id = if let Some(overlay_id) = last_scope_frame.find_overlay(&name) {
1039            last_scope_frame.get_overlay_mut(overlay_id).origin = origin;
1040
1041            overlay_id
1042        } else {
1043            last_scope_frame
1044                .overlays
1045                .push((name, OverlayFrame::from_origin(origin, prefixed)));
1046            OverlayId::new(last_scope_frame.overlays.len() - 1)
1047        };
1048
1049        last_scope_frame
1050            .active_overlays
1051            .retain(|id| id != &overlay_id);
1052        last_scope_frame.active_overlays.push(overlay_id);
1053
1054        self.use_decls(definitions.decls);
1055        self.use_modules(definitions.modules);
1056
1057        let mut constants = vec![];
1058
1059        for (name, const_vid) in definitions.constants {
1060            constants.push((name, const_vid));
1061        }
1062
1063        for (name, const_val) in definitions.constant_values {
1064            let const_var_id =
1065                self.add_variable(name.clone(), Span::unknown(), const_val.get_type(), false);
1066            self.set_variable_const_val(const_var_id, const_val);
1067            constants.push((name, const_var_id));
1068        }
1069        self.use_variables(constants);
1070    }
1071
1072    pub fn remove_overlay(&mut self, name: &[u8], keep_custom: bool) {
1073        let last_scope_frame = self.delta.last_scope_frame_mut();
1074
1075        let maybe_module_id = if let Some(overlay_id) = last_scope_frame.find_overlay(name) {
1076            last_scope_frame
1077                .active_overlays
1078                .retain(|id| id != &overlay_id);
1079
1080            Some(last_scope_frame.get_overlay(overlay_id).origin)
1081        } else {
1082            self.permanent_state
1083                .find_overlay(name)
1084                .map(|id| self.permanent_state.get_overlay(id).origin)
1085        };
1086
1087        if let Some(module_id) = maybe_module_id {
1088            last_scope_frame.removed_overlays.push(name.to_owned());
1089
1090            if keep_custom {
1091                let origin_module = self.get_module(module_id);
1092
1093                let decls = self
1094                    .decls_of_overlay(name)
1095                    .into_iter()
1096                    .filter(|(n, _)| !origin_module.has_decl(n))
1097                    .collect();
1098
1099                self.use_decls(decls);
1100            }
1101        }
1102    }
1103
1104    pub fn render(self) -> StateDelta {
1105        self.delta
1106    }
1107
1108    pub fn build_desc(&self, spans: &[Span]) -> (String, String) {
1109        let comment_lines: Vec<&[u8]> = spans
1110            .iter()
1111            .map(|span| self.get_span_contents(*span))
1112            .collect();
1113        build_desc(&comment_lines)
1114    }
1115
1116    pub fn find_block_by_span(&self, span: Span) -> Option<Arc<Block>> {
1117        for block in &self.delta.blocks {
1118            if Some(span) == block.span {
1119                return Some(block.clone());
1120            }
1121        }
1122
1123        for block in self.permanent_state.blocks.iter() {
1124            if Some(span) == block.span {
1125                return Some(block.clone());
1126            }
1127        }
1128
1129        None
1130    }
1131
1132    /// Blocks covering `span`, newest first (delta before permanent).
1133    pub fn blocks_with_span_newest_first(&self, span: Span) -> Vec<Arc<Block>> {
1134        let mut blocks = Vec::new();
1135        for block in self.delta.blocks.iter().rev() {
1136            if block.span == Some(span) {
1137                blocks.push(block.clone());
1138            }
1139        }
1140        for block in self.permanent_state.blocks.iter().rev() {
1141            if block.span == Some(span) {
1142                blocks.push(block.clone());
1143            }
1144        }
1145        blocks
1146    }
1147
1148    /// Identity lookup so a cache hit can keep the existing `BlockId`.
1149    pub fn find_block_id_of(&self, block: &Arc<Block>) -> Option<BlockId> {
1150        for (idx, existing) in self.delta.blocks.iter().enumerate() {
1151            if Arc::ptr_eq(existing, block) {
1152                return Some(BlockId::new(self.permanent_state.num_blocks() + idx));
1153            }
1154        }
1155        for (idx, existing) in self.permanent_state.blocks.iter().enumerate() {
1156            if Arc::ptr_eq(existing, block) {
1157                return Some(BlockId::new(idx));
1158            }
1159        }
1160        None
1161    }
1162
1163    pub fn find_module_by_span(&self, span: Span) -> Option<ModuleId> {
1164        for (id, module) in self.delta.modules.iter().enumerate() {
1165            if Some(span) == module.span {
1166                return Some(ModuleId::new(self.permanent_state.num_modules() + id));
1167            }
1168        }
1169
1170        for (module_id, module) in self.permanent_state.modules.iter().enumerate() {
1171            if Some(span) == module.span {
1172                return Some(ModuleId::new(module_id));
1173            }
1174        }
1175
1176        None
1177    }
1178
1179    /// Find the file which contains the given [`Span`]
1180    pub fn find_file_by_span(&self, span: Span) -> Option<&CachedFile> {
1181        self.files()
1182            // the span we're looking for is much more likely to be in a recently added file
1183            .rev()
1184            .find(|file| file.covered_span.contains_span(span))
1185    }
1186
1187    pub fn find_virtual_path(&self, name: &str) -> Option<&VirtualPath> {
1188        // Platform appropriate virtual path (slashes or backslashes)
1189        let virtual_path_name = Path::new(name);
1190
1191        for (virtual_name, virtual_path) in self.delta.virtual_paths.iter().rev() {
1192            if Path::new(virtual_name) == virtual_path_name {
1193                return Some(virtual_path);
1194            }
1195        }
1196
1197        for (virtual_name, virtual_path) in self.permanent_state.virtual_paths.iter().rev() {
1198            if Path::new(virtual_name) == virtual_path_name {
1199                return Some(virtual_path);
1200            }
1201        }
1202
1203        None
1204    }
1205
1206    pub fn get_virtual_path(&self, virtual_path_id: VirtualPathId) -> &(String, VirtualPath) {
1207        let num_permanent_virtual_paths = self.permanent_state.num_virtual_paths();
1208        if virtual_path_id.get() < num_permanent_virtual_paths {
1209            self.permanent_state.get_virtual_path(virtual_path_id)
1210        } else {
1211            self.delta
1212                .virtual_paths
1213                .get(virtual_path_id.get() - num_permanent_virtual_paths)
1214                .expect("internal error: missing virtual path")
1215        }
1216    }
1217
1218    pub fn add_span(&mut self, span: Span) -> SpanId {
1219        let num_permanent_spans = self.permanent_state.spans.len();
1220        self.delta.spans.push(span);
1221        SpanId::new(num_permanent_spans + self.delta.spans.len() - 1)
1222    }
1223
1224    pub fn resolve_span<'s>(&'s self, span: Span) -> Option<ResolvedSpan<'s>> {
1225        let cached_file = self.find_file_by_span(span)?;
1226        let file = cached_file.name.as_ref().into();
1227        let span = span.offset(cached_file.covered_span.start);
1228        Some(ResolvedSpan { file, span })
1229    }
1230}
1231
1232impl<'a> GetSpan for &'a StateWorkingSet<'a> {
1233    fn get_span(&self, span_id: SpanId) -> Span {
1234        let num_permanent_spans = self.permanent_state.num_spans();
1235        if span_id.get() < num_permanent_spans {
1236            self.permanent_state.get_span(span_id)
1237        } else {
1238            *self
1239                .delta
1240                .spans
1241                .get(span_id.get() - num_permanent_spans)
1242                .expect("internal error: missing span")
1243        }
1244    }
1245}
1246
1247impl miette::SourceCode for &StateWorkingSet<'_> {
1248    fn read_span<'b>(
1249        &'b self,
1250        span: &miette::SourceSpan,
1251        context_lines_before: usize,
1252        context_lines_after: usize,
1253    ) -> Result<Box<dyn miette::SpanContents<'b> + 'b>, miette::MietteError> {
1254        let debugging = std::env::var("MIETTE_DEBUG").is_ok();
1255        if debugging {
1256            let finding_span = "Finding span in StateWorkingSet";
1257            dbg!(finding_span, span);
1258        }
1259        for cached_file in self.files() {
1260            let (filename, start, end) = (
1261                &cached_file.name,
1262                cached_file.covered_span.start,
1263                cached_file.covered_span.end,
1264            );
1265            if debugging {
1266                dbg!(&filename, start, end);
1267            }
1268            if span.offset() >= start && span.offset() + span.len() <= end {
1269                if debugging {
1270                    let found_file = "Found matching file";
1271                    dbg!(found_file);
1272                }
1273                let our_span = cached_file.covered_span;
1274                // We need to move to a local span because we're only reading
1275                // the specific file contents via self.get_span_contents.
1276                let local_span = (span.offset() - start, span.len()).into();
1277                if debugging {
1278                    dbg!(&local_span);
1279                }
1280                let span_contents = self.get_span_contents(our_span);
1281                if debugging {
1282                    dbg!(String::from_utf8_lossy(span_contents));
1283                }
1284                let span_contents = span_contents.read_span(
1285                    &local_span,
1286                    context_lines_before,
1287                    context_lines_after,
1288                )?;
1289                let content_span = span_contents.span();
1290                // Back to "global" indexing
1291                let retranslated = (content_span.offset() + start, content_span.len()).into();
1292                if debugging {
1293                    dbg!(&retranslated);
1294                }
1295
1296                let data = span_contents.data();
1297                if &**filename == "<cli>" {
1298                    if debugging {
1299                        let success_cli = "Successfully read CLI span";
1300                        dbg!(success_cli, String::from_utf8_lossy(data));
1301                    }
1302                    return Ok(Box::new(miette::MietteSpanContents::new(
1303                        data,
1304                        retranslated,
1305                        span_contents.line(),
1306                        span_contents.column(),
1307                        span_contents.line_count(),
1308                    )));
1309                } else {
1310                    if debugging {
1311                        let success_file = "Successfully read file span";
1312                        dbg!(success_file);
1313                    }
1314                    return Ok(Box::new(miette::MietteSpanContents::new_named(
1315                        (**filename).to_owned(),
1316                        data,
1317                        retranslated,
1318                        span_contents.line(),
1319                        span_contents.column(),
1320                        span_contents.line_count(),
1321                    )));
1322                }
1323            }
1324        }
1325        Err(miette::MietteError::OutOfBounds)
1326    }
1327}
1328
1329/// Files being evaluated, arranged as a stack.
1330///
1331/// The current active file is on the top of the stack.
1332/// When a file source/import another file, the new file is pushed onto the stack.
1333/// Attempting to add files that are already in the stack (circular import) results in an error.
1334///
1335/// Note that file paths are compared without canonicalization, so the same
1336/// physical file may still appear multiple times under different paths.
1337/// This doesn't affect circular import detection though.
1338#[derive(Debug, Default)]
1339pub struct FileStack(Vec<PathBuf>);
1340
1341impl FileStack {
1342    /// Creates an empty stack.
1343    pub fn new() -> Self {
1344        Self(vec![])
1345    }
1346
1347    /// Creates a stack with a single file on top.
1348    ///
1349    /// This is a convenience method that creates an empty stack, then pushes the file onto it.
1350    /// It skips the circular import check and always succeeds.
1351    pub fn with_file(path: PathBuf) -> Self {
1352        Self(vec![path])
1353    }
1354
1355    /// Adds a file to the stack.
1356    ///
1357    /// If the same file is already present in the stack, returns `ParseError::CircularImport`.
1358    pub fn push(&mut self, path: PathBuf, span: Span) -> Result<(), ParseError> {
1359        // Check for circular import.
1360        if let Some(i) = self.0.iter().rposition(|p| p == &path) {
1361            let filenames: Vec<String> = self.0[i..]
1362                .iter()
1363                .chain(std::iter::once(&path))
1364                .map(|p| p.to_string_lossy().to_string())
1365                .collect();
1366            let msg = filenames.join("\nuses ");
1367            return Err(ParseError::CircularImport(msg, span));
1368        }
1369
1370        self.0.push(path);
1371        Ok(())
1372    }
1373
1374    /// Removes a file from the stack and returns its path, or None if the stack is empty.
1375    pub fn pop(&mut self) -> Option<PathBuf> {
1376        self.0.pop()
1377    }
1378
1379    /// Returns the active file (that is, the file on the top of the stack), or None if the stack is empty.
1380    pub fn top(&self) -> Option<&Path> {
1381        self.0.last().map(PathBuf::as_path)
1382    }
1383
1384    /// Returns the parent directory of the active file, or None if the stack is empty
1385    /// or the active file doesn't have a parent directory as part of its path.
1386    pub fn current_working_directory(&self) -> Option<&Path> {
1387        self.0.last().and_then(|path| path.parent())
1388    }
1389}