Skip to main content

nu_protocol/engine/
stack.rs

1use crate::{
2    Config, ENV_VARIABLE_ID, IntoValue, NU_VARIABLE_ID, OutDest, ShellError, Span, Value, VarId,
3    engine::{
4        ArgumentStack, DEFAULT_OVERLAY_NAME, EngineState, EnvName, ErrorHandlerStack, Redirection,
5        StackCallArgGuard, StackCollectValueGuard, StackIoGuard, StackOutDest,
6    },
7    report_shell_warning,
8    shell_error::generic::GenericError,
9};
10use std::{
11    collections::{HashMap, HashSet},
12    fs::File,
13    path::{Component, MAIN_SEPARATOR},
14    sync::Arc,
15};
16
17/// Environment variables per overlay
18pub type EnvVars = HashMap<String, HashMap<EnvName, Value>>;
19
20/// A runtime value stack used during evaluation
21///
22/// A note on implementation:
23///
24/// We previously set up the stack in a traditional way, where stack frames had parents which would
25/// represent other frames that you might return to when exiting a function.
26///
27/// While experimenting with blocks, we found that we needed to have closure captures of variables
28/// seen outside of the blocks, so that they blocks could be run in a way that was both thread-safe
29/// and followed the restrictions for closures applied to iterators. The end result left us with
30/// closure-captured single stack frames that blocks could see.
31///
32/// Blocks make up the only scope and stack definition abstraction in Nushell. As a result, we were
33/// creating closure captures at any point we wanted to have a Block value we could safely evaluate
34/// in any context. This meant that the parents were going largely unused, with captured variables
35/// taking their place. The end result is this, where we no longer have separate frames, but instead
36/// use the Stack as a way of representing the local and closure-captured state.
37#[derive(Debug, Clone)]
38pub struct Stack {
39    /// Variables
40    pub vars: Vec<(VarId, Value)>,
41    /// Environment variables arranged as a stack to be able to recover values from parent scopes
42    pub env_vars: Vec<Arc<EnvVars>>,
43    /// Tells which environment variables from engine state are hidden, per overlay.
44    pub env_hidden: Arc<HashMap<String, HashSet<EnvName>>>,
45    /// Tracks env vars hidden in this stack context to report repeated `hide-env` calls.
46    ///
47    /// This is separate from `env_hidden`: `env_hidden` controls runtime visibility for engine
48    /// state values, while `env_hide_history` preserves command semantics for repeated hides.
49    pub env_hide_history: Arc<HashMap<String, HashSet<EnvName>>>,
50    /// List of active overlays
51    pub active_overlays: Vec<String>,
52    /// Argument stack for IR evaluation
53    pub arguments: ArgumentStack,
54    /// Error handler stack for IR evaluation
55    pub error_handlers: ErrorHandlerStack,
56    /// Finally handler stack for IR evaluation
57    pub finally_run_handlers: ErrorHandlerStack,
58    pub recursion_count: u64,
59    pub parent_stack: Option<Arc<Stack>>,
60    /// Variables that have been deleted (this is used to hide values from parent stack lookups)
61    pub parent_deletions: Vec<VarId>,
62    /// Variables deleted in this stack
63    pub deletions: Vec<VarId>,
64    /// Locally updated config. Use [`.get_config()`](Self::get_config) to access correctly.
65    pub config: Option<Arc<Config>>,
66    pub(crate) out_dest: StackOutDest,
67}
68
69impl Default for Stack {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl Stack {
76    /// Create a new stack.
77    ///
78    /// stdout and stderr will be set to [`OutDest::Inherit`]. So, if the last command is an external command,
79    /// then its output will be forwarded to the terminal/stdio streams.
80    ///
81    /// Use [`Stack::collect_value`] afterwards if you need to evaluate an expression to a [`Value`]
82    /// (as opposed to a [`PipelineData`](crate::PipelineData)).
83    pub fn new() -> Self {
84        Self {
85            vars: Vec::new(),
86            env_vars: Vec::new(),
87            env_hidden: Arc::new(HashMap::new()),
88            env_hide_history: Arc::new(HashMap::new()),
89            active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
90            arguments: ArgumentStack::new(),
91            error_handlers: ErrorHandlerStack::new(),
92            finally_run_handlers: ErrorHandlerStack::new(),
93            recursion_count: 0,
94            parent_stack: None,
95            parent_deletions: vec![],
96            deletions: vec![],
97            config: None,
98            out_dest: StackOutDest::new(),
99        }
100    }
101
102    /// Create a new child stack from a parent.
103    ///
104    /// Changes from this child can be merged back into the parent with
105    /// [`Stack::with_changes_from_child`]
106    pub fn with_parent(parent: Arc<Stack>) -> Stack {
107        Stack {
108            // here we are still cloning environment variable-related information
109            env_vars: parent.env_vars.clone(),
110            env_hidden: parent.env_hidden.clone(),
111            env_hide_history: parent.env_hide_history.clone(),
112            active_overlays: parent.active_overlays.clone(),
113            arguments: ArgumentStack::new(),
114            error_handlers: ErrorHandlerStack::new(),
115            finally_run_handlers: ErrorHandlerStack::new(),
116            recursion_count: parent.recursion_count,
117            vars: vec![],
118            parent_deletions: vec![],
119            deletions: vec![],
120            config: parent.config.clone(),
121            out_dest: parent.out_dest.clone(),
122            parent_stack: Some(parent),
123        }
124    }
125
126    /// Take an [`Arc`] parent, and a child, and apply all the changes from a child back to the parent.
127    ///
128    /// Here it is assumed that `child` was created by a call to [`Stack::with_parent`] with `parent`.
129    ///
130    /// For this to be performant and not clone `parent`, `child` should be the only other
131    /// referencer of `parent`.
132    pub fn with_changes_from_child(parent: Arc<Stack>, child: Stack) -> Stack {
133        // we're going to drop the link to the parent stack on our new stack
134        // so that we can unwrap the Arc as a unique reference
135        drop(child.parent_stack);
136        let mut unique_stack = Arc::unwrap_or_clone(parent);
137
138        unique_stack
139            .vars
140            .retain(|(var, _)| !child.parent_deletions.contains(var));
141        for (var, value) in child.vars {
142            unique_stack.add_var(var, value);
143        }
144        unique_stack.env_vars = child.env_vars;
145        unique_stack.env_hidden = child.env_hidden;
146        unique_stack.env_hide_history = child.env_hide_history;
147        unique_stack.active_overlays = child.active_overlays;
148        unique_stack.config = child.config;
149        unique_stack
150    }
151
152    pub fn with_env(
153        &mut self,
154        env_vars: &[Arc<EnvVars>],
155        env_hidden: &Arc<HashMap<String, HashSet<EnvName>>>,
156    ) {
157        // Do not clone the environment if it hasn't changed
158        if self.env_vars.iter().any(|scope| !scope.is_empty()) {
159            env_vars.clone_into(&mut self.env_vars);
160        }
161
162        if !self.env_hidden.is_empty() {
163            self.env_hidden.clone_from(env_hidden);
164        }
165    }
166
167    /// Lookup a variable, returning None if it is not present
168    fn lookup_var(&self, var_id: VarId) -> Option<Value> {
169        for (id, val) in &self.vars {
170            if var_id == *id {
171                return Some(val.clone());
172            }
173        }
174
175        if let Some(stack) = &self.parent_stack
176            && !self.parent_deletions.contains(&var_id)
177        {
178            return stack.lookup_var(var_id);
179        }
180        None
181    }
182
183    /// Lookup a variable, erroring if it is not found
184    ///
185    /// The passed-in span will be used to tag the value
186    pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
187        match self.lookup_var(var_id) {
188            Some(v) => Ok(v.with_span(span)),
189            None => Err(ShellError::VariableNotFoundAtRuntime { span }),
190        }
191    }
192
193    /// Lookup a variable, erroring if it is not found
194    ///
195    /// While the passed-in span will be used for errors, the returned value
196    /// has the span from where it was originally defined
197    pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
198        match self.lookup_var(var_id) {
199            Some(v) => Ok(v),
200            None => {
201                if var_id == NU_VARIABLE_ID || var_id == ENV_VARIABLE_ID {
202                    return Err(ShellError::Generic(GenericError::new(
203                        "Built-in variables `$env` and `$nu` have no metadata",
204                        "no metadata available",
205                        span,
206                    )));
207                }
208                Err(ShellError::VariableNotFoundAtRuntime { span })
209            }
210        }
211    }
212
213    /// Get the local config if set, otherwise the config from the engine state.
214    ///
215    /// This is the canonical way to get [`Config`] when [`Stack`] is available.
216    pub fn get_config(&self, engine_state: &EngineState) -> Arc<Config> {
217        self.config
218            .clone()
219            .unwrap_or_else(|| engine_state.config.clone())
220    }
221
222    /// Update the local config with the config stored in the `config` environment variable. Run
223    /// this after assigning to `$env.config`.
224    ///
225    /// The config will be updated with successfully parsed values even if an error occurs.
226    pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> {
227        if let Some(value) = self.get_env_var(engine_state, "config") {
228            let old = self.get_config(engine_state);
229            let mut config = (*old).clone();
230            let result = config.update_from_value_with_options(
231                &old,
232                value,
233                engine_state.history_locked_after_startup,
234            );
235            // The config value is modified by the update, so we should add it again
236            self.add_env_var("config".into(), config.clone().into_value(value.span()));
237            self.config = Some(config.into());
238            if let Some(warning) = result? {
239                report_shell_warning(Some(self), engine_state, &warning);
240            }
241        } else {
242            self.config = None;
243        }
244        Ok(())
245    }
246
247    pub fn add_var(&mut self, var_id: VarId, value: Value) {
248        //self.vars.insert(var_id, value);
249        for (id, val) in &mut self.vars {
250            if *id == var_id {
251                *val = value;
252                return;
253            }
254        }
255        self.vars.push((var_id, value));
256    }
257
258    pub fn remove_var(&mut self, var_id: VarId) {
259        for (idx, (id, _)) in self.vars.iter().enumerate() {
260            if *id == var_id {
261                self.vars.remove(idx);
262                break;
263            }
264        }
265        // even if we did have it in the original layer, we need to make sure to remove it here
266        // as well (since the previous update might have simply hid the parent value)
267        if self.parent_stack.is_some() {
268            self.parent_deletions.push(var_id);
269        }
270        self.deletions.push(var_id);
271    }
272
273    pub fn add_env_var(&mut self, var: String, value: Value) {
274        if let Some(last_overlay) = self.active_overlays.last().cloned() {
275            let env_name = EnvName::from(var);
276            self.clear_env_var_marks_in_active_overlay(&last_overlay, &env_name);
277
278            if let Some(scope) = self.env_vars.last_mut() {
279                let scope = Arc::make_mut(scope);
280                if let Some(env_vars) = scope.get_mut(&last_overlay) {
281                    env_vars.insert(env_name, value);
282                } else {
283                    scope.insert(last_overlay, [(env_name, value)].into_iter().collect());
284                }
285            } else {
286                self.env_vars.push(Arc::new(
287                    [(last_overlay, [(env_name, value)].into_iter().collect())]
288                        .into_iter()
289                        .collect(),
290                ));
291            }
292        } else {
293            // TODO: Remove panic
294            panic!("internal error: no active overlay");
295        }
296    }
297
298    fn clear_env_var_marks_in_active_overlay(&mut self, overlay: &str, env_name: &EnvName) {
299        if let Some(env_hidden) = Arc::make_mut(&mut self.env_hidden).get_mut(overlay) {
300            // Re-assigning re-activates a previously hidden env var in this overlay.
301            env_hidden.remove(env_name);
302        }
303
304        if let Some(hide_history) = Arc::make_mut(&mut self.env_hide_history).get_mut(overlay) {
305            hide_history.remove(env_name);
306        }
307    }
308
309    pub fn set_last_exit_code(&mut self, code: i32, span: Span) {
310        self.add_env_var("LAST_EXIT_CODE".into(), Value::int(code.into(), span));
311    }
312
313    pub fn set_last_error(&mut self, error: &ShellError) {
314        if let Some(code) = error.external_exit_code() {
315            self.set_last_exit_code(code.item, code.span);
316        } else if let Some(code) = error.exit_code() {
317            self.set_last_exit_code(code, Span::unknown());
318        }
319    }
320
321    pub fn last_overlay_name(&self) -> Result<String, ShellError> {
322        self.active_overlays
323            .last()
324            .cloned()
325            .ok_or_else(|| ShellError::NushellFailed {
326                msg: "No active overlay".into(),
327            })
328    }
329
330    /// Like [`captures_to_stack_preserve_out_dest`], but sets the new scope up to collect output into a Value.
331    pub fn captures_to_stack(&self, captures: Vec<(VarId, Value)>) -> Stack {
332        self.captures_to_stack_preserve_out_dest(captures)
333            .collect_value()
334    }
335
336    /// Creates a derived stack for a new scope, with the given captures.
337    pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack {
338        let mut env_vars = self.env_vars.clone();
339        env_vars.push(Arc::new(HashMap::new()));
340
341        Stack {
342            vars: captures,
343            env_vars,
344            env_hidden: self.env_hidden.clone(),
345            env_hide_history: self.env_hide_history.clone(),
346            active_overlays: self.active_overlays.clone(),
347            arguments: ArgumentStack::new(),
348            error_handlers: ErrorHandlerStack::new(),
349            finally_run_handlers: ErrorHandlerStack::new(),
350            recursion_count: self.recursion_count,
351            parent_stack: None,
352            parent_deletions: vec![],
353            deletions: vec![],
354            config: self.config.clone(),
355            out_dest: self.out_dest.clone(),
356        }
357    }
358
359    pub fn gather_captures(&self, engine_state: &EngineState, captures: &[(VarId, Span)]) -> Stack {
360        let mut vars = Vec::with_capacity(captures.len());
361
362        let fake_span = Span::new(0, 0);
363
364        for (capture, _) in captures {
365            // Note: this assumes we have calculated captures correctly and that commands
366            // that take in a var decl will manually set this into scope when running the blocks
367            if let Ok(value) = self.get_var(*capture, fake_span) {
368                vars.push((*capture, value));
369            } else if let Some(const_val) = &engine_state.get_var(*capture).const_val {
370                vars.push((*capture, const_val.clone()));
371            }
372        }
373
374        let mut env_vars = self.env_vars.clone();
375        env_vars.push(Arc::new(HashMap::new()));
376
377        Stack {
378            vars,
379            env_vars,
380            env_hidden: self.env_hidden.clone(),
381            env_hide_history: self.env_hide_history.clone(),
382            active_overlays: self.active_overlays.clone(),
383            arguments: ArgumentStack::new(),
384            error_handlers: ErrorHandlerStack::new(),
385            finally_run_handlers: ErrorHandlerStack::new(),
386            recursion_count: self.recursion_count,
387            parent_stack: None,
388            parent_deletions: vec![],
389            deletions: vec![],
390            config: self.config.clone(),
391            out_dest: self.out_dest.clone(),
392        }
393    }
394
395    /// Flatten the env var scope frames into one frame
396    pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
397        let mut result = HashMap::new();
398
399        for active_overlay in self.active_overlays.iter() {
400            if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
401                result.extend(
402                    env_vars
403                        .iter()
404                        .filter(|(k, _)| {
405                            if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
406                                !env_hidden.contains(*k)
407                            } else {
408                                // nothing has been hidden in this overlay
409                                true
410                            }
411                        })
412                        .map(|(k, v)| (k.as_str().to_string(), v.clone()))
413                        .collect::<HashMap<String, Value>>(),
414                );
415            }
416        }
417
418        result.extend(self.get_stack_env_vars());
419
420        result
421    }
422
423    /// Get flattened environment variables only from the stack
424    pub fn get_stack_env_vars(&self) -> HashMap<String, Value> {
425        let mut result = HashMap::new();
426
427        for scope in &self.env_vars {
428            for active_overlay in self.active_overlays.iter() {
429                if let Some(env_vars) = scope.get(active_overlay) {
430                    result.extend(
431                        env_vars
432                            .iter()
433                            .map(|(k, v)| (k.as_str().to_string(), v.clone())),
434                    );
435                }
436            }
437        }
438
439        result
440    }
441
442    /// Get flattened environment variables only from the stack and one overlay
443    pub fn get_stack_overlay_env_vars(&self, overlay_name: &str) -> HashMap<String, Value> {
444        let mut result = HashMap::new();
445
446        for scope in &self.env_vars {
447            if let Some(active_overlay) = self.active_overlays.iter().find(|n| n == &overlay_name)
448                && let Some(env_vars) = scope.get(active_overlay)
449            {
450                result.extend(
451                    env_vars
452                        .iter()
453                        .map(|(k, v)| (k.as_str().to_string(), v.clone())),
454                );
455            }
456        }
457
458        result
459    }
460
461    /// Get hidden envs, but without envs defined previously in `excluded_overlay_name`.
462    pub fn get_hidden_env_vars(
463        &self,
464        excluded_overlay_name: &str,
465        engine_state: &EngineState,
466    ) -> HashMap<String, Value> {
467        let mut result = HashMap::new();
468
469        for overlay_name in self.active_overlays.iter().rev() {
470            if overlay_name == excluded_overlay_name {
471                continue;
472            }
473            if let Some(env_names) = self.env_hidden.get(overlay_name) {
474                for n in env_names {
475                    if result.contains_key(n.as_str()) {
476                        continue;
477                    }
478                    // get env value.
479                    if let Some(Some(v)) = engine_state
480                        .env_vars
481                        .get(overlay_name)
482                        .map(|env_vars| env_vars.get(n))
483                    {
484                        result.insert(n.as_str().to_string(), v.clone());
485                    }
486                }
487            }
488        }
489        result
490    }
491
492    /// Same as get_env_vars, but returns only the names as a HashSet
493    pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
494        let mut result = HashSet::new();
495
496        for active_overlay in self.active_overlays.iter() {
497            if let Some(env_vars) = engine_state.env_vars.get(active_overlay) {
498                result.extend(
499                    env_vars
500                        .keys()
501                        .filter(|k| {
502                            if let Some(env_hidden) = self.env_hidden.get(active_overlay) {
503                                !env_hidden.contains(*k)
504                            } else {
505                                // nothing has been hidden in this overlay
506                                true
507                            }
508                        })
509                        .map(|k| k.as_str().to_string())
510                        .collect::<HashSet<String>>(),
511                );
512            }
513        }
514
515        for scope in &self.env_vars {
516            for active_overlay in self.active_overlays.iter() {
517                if let Some(env_vars) = scope.get(active_overlay) {
518                    result.extend(
519                        env_vars
520                            .keys()
521                            .map(|k| k.as_str().to_string())
522                            .collect::<HashSet<String>>(),
523                    );
524                }
525            }
526        }
527
528        result
529    }
530
531    pub fn get_env_var<'a>(
532        &'a self,
533        engine_state: &'a EngineState,
534        name: &str,
535    ) -> Option<&'a Value> {
536        let env_name = EnvName::from(name);
537
538        for scope in self.env_vars.iter().rev() {
539            for active_overlay in self.active_overlays.iter().rev() {
540                if let Some(env_vars) = scope.get(active_overlay)
541                    && let Some(v) = env_vars.get(&env_name)
542                {
543                    return Some(v);
544                }
545            }
546        }
547
548        for active_overlay in self.active_overlays.iter().rev() {
549            if !self.is_env_hidden_in_overlay(active_overlay, &env_name)
550                && let Some(env_vars) = engine_state.env_vars.get(active_overlay)
551                && let Some(v) = env_vars.get(&env_name)
552            {
553                return Some(v);
554            }
555        }
556        None
557    }
558
559    pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool {
560        let env_name = EnvName::from(name);
561
562        for scope in self.env_vars.iter().rev() {
563            for active_overlay in self.active_overlays.iter().rev() {
564                if let Some(env_vars) = scope.get(active_overlay)
565                    && env_vars.contains_key(&env_name)
566                {
567                    return true;
568                }
569            }
570        }
571
572        for active_overlay in self.active_overlays.iter().rev() {
573            if !self.is_env_hidden_in_overlay(active_overlay, &env_name)
574                && let Some(env_vars) = engine_state.env_vars.get(active_overlay)
575                && env_vars.contains_key(&env_name)
576            {
577                return true;
578            }
579        }
580
581        false
582    }
583
584    /// Removes `name` from the stack. If it was not on the stack and lives in `engine_state`,
585    /// marks it hidden in `env_hidden`. Returns `true` if the variable was found and removed.
586    ///
587    /// Use this for temporary bookkeeping removals (e.g. `FILE_PWD`, canary variables) where
588    /// the goal is to clean up a stack-level value without necessarily hiding the engine-state
589    /// baseline. Use [`Self::hide_env_var`] when the intent is to make the variable invisible
590    /// to subsequent lookups (e.g. `hide-env`).
591    pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool {
592        let env_name = EnvName::from(name);
593
594        self.remove_env_var_from_stack(&env_name)
595            || self.hide_engine_state_env_var(engine_state, &env_name)
596    }
597
598    /// Removes `env_name` from all stack scopes and returns `true` if it was found.
599    /// Does not affect `env_hidden`; use [`Self::hide_env_var`] for full hiding semantics.
600    fn remove_env_var_from_stack(&mut self, env_name: &EnvName) -> bool {
601        self.env_vars
602            .iter_mut()
603            .rev()
604            .map(Arc::make_mut)
605            .find_map(|scope| {
606                self.active_overlays
607                    .iter()
608                    .rev()
609                    .find_map(|active_overlay| scope.get_mut(active_overlay)?.remove(env_name))
610            })
611            .is_some()
612    }
613
614    /// Marks `env_name` as hidden in `env_hidden` for the active overlay where it exists in
615    /// `engine_state`.
616    ///
617    /// Returns `true` only when the baseline variable exists and was newly hidden.
618    fn hide_engine_state_env_var(
619        &mut self,
620        engine_state: &EngineState,
621        env_name: &EnvName,
622    ) -> bool {
623        let overlay_containing_env_var = self.active_overlays.iter().rev().find(|active_overlay| {
624            engine_state
625                .env_vars
626                .get(active_overlay.as_str())
627                .is_some_and(|env_vars| env_vars.contains_key(env_name))
628        });
629
630        let Some(overlay_containing_env_var) = overlay_containing_env_var else {
631            return false;
632        };
633
634        let env_hidden = Arc::make_mut(&mut self.env_hidden);
635
636        if env_hidden
637            .get(overlay_containing_env_var.as_str())
638            .is_some_and(|hidden_vars| hidden_vars.contains(env_name))
639        {
640            return false;
641        }
642
643        env_hidden
644            .entry(overlay_containing_env_var.clone())
645            .or_default()
646            .insert(env_name.clone());
647        true
648    }
649
650    /// Records that `env_name` has been hidden in the active overlay and returns `false` if it
651    /// was already recorded as hidden there.
652    fn record_env_var_hide_in_active_overlay(&mut self, env_name: &EnvName) -> bool {
653        let Some(active_overlay) = self.active_overlays.last().cloned() else {
654            return false;
655        };
656
657        Arc::make_mut(&mut self.env_hide_history)
658            .entry(active_overlay)
659            .or_default()
660            .insert(env_name.clone())
661    }
662
663    fn is_env_var_hide_recorded(&self, env_name: &EnvName) -> bool {
664        self.active_overlays
665            .iter()
666            .rev()
667            .filter_map(|overlay| self.env_hide_history.get(overlay))
668            .any(|hidden_vars| hidden_vars.contains(env_name))
669    }
670
671    fn is_env_hidden_in_overlay(&self, overlay: &str, env_name: &EnvName) -> bool {
672        self.env_hidden
673            .get(overlay)
674            .is_some_and(|hidden_vars| hidden_vars.contains(env_name))
675    }
676
677    /// Hides `name` so it is no longer visible to subsequent lookups. Removes it from the stack
678    /// and, if no stack shadowing remains, also marks the `engine_state` baseline as hidden in
679    /// `env_hidden`. Returns `true` if the variable was found.
680    ///
681    /// This is the correct method for `hide-env` and `redirect_env`; it ensures that a variable
682    /// set in engine_state (from a previous REPL merge) cannot be seen after hiding even when a
683    /// stack-level override (e.g. an empty-string assignment) was present at hide time.
684    pub fn hide_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool {
685        let env_name = EnvName::from(name);
686
687        // Re-hiding the same env var in the same scope should report not found.
688        if self.is_env_var_hide_recorded(&env_name) {
689            return false;
690        }
691
692        if self.remove_env_var_from_stack(&env_name) {
693            self.record_env_var_hide_in_active_overlay(&env_name);
694
695            if !self.has_env_var_in_stack(&env_name) {
696                self.hide_engine_state_env_var(engine_state, &env_name);
697            }
698            return true;
699        }
700
701        if self.hide_engine_state_env_var(engine_state, &env_name) {
702            self.record_env_var_hide_in_active_overlay(&env_name);
703            return true;
704        }
705
706        false
707    }
708
709    /// Returns `true` if `name` exists in any stack scope (without consulting `engine_state`).
710    fn has_env_var_in_stack(&self, name: &EnvName) -> bool {
711        self.env_vars.iter().rev().any(|scope| {
712            self.active_overlays
713                .iter()
714                .rev()
715                .filter_map(|active_overlay| scope.get(active_overlay))
716                .any(|env_vars| env_vars.contains_key(name))
717        })
718    }
719
720    pub fn has_env_overlay(&self, name: &str, engine_state: &EngineState) -> bool {
721        for scope in self.env_vars.iter().rev() {
722            if scope.contains_key(name) {
723                return true;
724            }
725        }
726
727        engine_state.env_vars.contains_key(name)
728    }
729
730    pub fn is_overlay_active(&self, name: &str) -> bool {
731        self.active_overlays.iter().any(|n| n == name)
732    }
733
734    pub fn add_overlay(&mut self, name: String) {
735        self.active_overlays.retain(|o| o != &name);
736        self.active_overlays.push(name);
737    }
738
739    pub fn remove_overlay(&mut self, name: &str) {
740        self.active_overlays.retain(|o| o != name);
741    }
742
743    /// Returns the [`OutDest`] to use for the current command's stdout.
744    ///
745    /// This will be the pipe redirection if one is set,
746    /// otherwise it will be the current file redirection,
747    /// otherwise it will be the process's stdout indicated by [`OutDest::Inherit`].
748    pub fn stdout(&self) -> &OutDest {
749        self.out_dest.stdout()
750    }
751
752    /// Returns the [`OutDest`] to use for the current command's stderr.
753    ///
754    /// This will be the pipe redirection if one is set,
755    /// otherwise it will be the current file redirection,
756    /// otherwise it will be the process's stderr indicated by [`OutDest::Inherit`].
757    pub fn stderr(&self) -> &OutDest {
758        self.out_dest.stderr()
759    }
760
761    /// Returns the [`OutDest`] of the pipe redirection applied to the current command's stdout.
762    pub fn pipe_stdout(&self) -> Option<&OutDest> {
763        self.out_dest.pipe_stdout.as_ref()
764    }
765
766    /// Returns the [`OutDest`] of the pipe redirection applied to the current command's stderr.
767    pub fn pipe_stderr(&self) -> Option<&OutDest> {
768        self.out_dest.pipe_stderr.as_ref()
769    }
770
771    /// Temporarily set the pipe stdout redirection to [`OutDest::Value`].
772    ///
773    /// This is used before evaluating an expression into a `Value`.
774    pub fn start_collect_value(&mut self) -> StackCollectValueGuard<'_> {
775        StackCollectValueGuard::new(self)
776    }
777
778    /// Temporarily use the output redirections in the parent scope.
779    ///
780    /// This is used before evaluating an argument to a call.
781    pub fn use_call_arg_out_dest(&mut self) -> StackCallArgGuard<'_> {
782        StackCallArgGuard::new(self)
783    }
784
785    /// Temporarily apply redirections to stdout and/or stderr.
786    pub fn push_redirection(
787        &mut self,
788        stdout: Option<Redirection>,
789        stderr: Option<Redirection>,
790    ) -> StackIoGuard<'_> {
791        StackIoGuard::new(self, stdout, stderr)
792    }
793
794    /// Mark stdout for the last command as [`OutDest::Value`].
795    ///
796    /// This will irreversibly alter the output redirections, and so it only makes sense to use this on an owned `Stack`
797    /// (which is why this function does not take `&mut self`).
798    ///
799    /// See [`Stack::start_collect_value`] which can temporarily set stdout as [`OutDest::Value`] for a mutable `Stack` reference.
800    pub fn collect_value(mut self) -> Self {
801        self.out_dest.pipe_stdout = Some(OutDest::Value);
802        self.out_dest.pipe_stderr = None;
803        self
804    }
805
806    /// Mark both stdout and stderr for the last command as [`OutDest::Value`].
807    ///
808    /// This captures all output (stdout and stderr) instead of letting it inherit
809    /// to the process's terminal. Useful for programmatic contexts like MCP servers
810    /// where all output must be captured and returned.
811    ///
812    /// This will irreversibly alter the output redirections, and so it only makes sense to use this on an owned `Stack`
813    /// (which is why this function does not take `&mut self`).
814    pub fn capture_all(mut self) -> Self {
815        self.out_dest.pipe_stdout = Some(OutDest::Value);
816        self.out_dest.pipe_stderr = Some(OutDest::Value);
817        self
818    }
819
820    /// Clears any pipe and file redirections and resets stdout and stderr to [`OutDest::Inherit`].
821    ///
822    /// This will irreversibly reset the output redirections, and so it only makes sense to use this on an owned `Stack`
823    /// (which is why this function does not take `&mut self`).
824    pub fn reset_out_dest(mut self) -> Self {
825        self.out_dest = StackOutDest::new();
826        self
827    }
828
829    /// Clears any pipe redirections, keeping the current stdout and stderr.
830    ///
831    /// This will irreversibly reset some of the output redirections, and so it only makes sense to use this on an owned `Stack`
832    /// (which is why this function does not take `&mut self`).
833    pub fn reset_pipes(mut self) -> Self {
834        self.out_dest.pipe_stdout = None;
835        self.out_dest.pipe_stderr = None;
836        self
837    }
838
839    /// Replaces the default stdout of the stack with a given file.
840    ///
841    /// This method configures the default stdout to redirect to a specified file.
842    /// It is primarily useful for applications using `nu` as a language, where the stdout of
843    /// external commands that are not explicitly piped can be redirected to a file.
844    ///
845    /// # Using Pipes
846    ///
847    /// For use in third-party applications pipes might be very useful as they allow using the
848    /// stdout of external commands for different uses.
849    /// For example the [`os_pipe`](https://docs.rs/os_pipe) crate provides an elegant way to
850    /// access the stdout.
851    ///
852    /// ```
853    /// # use std::{fs::File, io::{self, Read}, thread, error};
854    /// # use nu_protocol::engine::Stack;
855    /// #
856    /// let (mut reader, writer) = os_pipe::pipe().unwrap();
857    /// // Use a thread to avoid blocking the execution of the called command.
858    /// let reader = thread::spawn(move || {
859    ///     let mut buf: Vec<u8> = Vec::new();
860    ///     reader.read_to_end(&mut buf)?;
861    ///     Ok::<_, io::Error>(buf)
862    /// });
863    ///
864    /// #[cfg(windows)]
865    /// let file = std::os::windows::io::OwnedHandle::from(writer).into();
866    /// #[cfg(unix)]
867    /// let file = std::os::unix::io::OwnedFd::from(writer).into();
868    ///
869    /// let stack = Stack::new().stdout_file(file);
870    ///
871    /// // Execute some nu code.
872    ///
873    /// drop(stack); // drop the stack so that the writer will be dropped too
874    /// let buf = reader.join().unwrap().unwrap();
875    /// // Do with your buffer whatever you want.
876    /// ```
877    pub fn stdout_file(mut self, file: File) -> Self {
878        self.out_dest.stdout = OutDest::File(Arc::new(file));
879        self
880    }
881
882    /// Replaces the default stderr of the stack with a given file.
883    ///
884    /// For more info, see [`stdout_file`](Self::stdout_file).
885    pub fn stderr_file(mut self, file: File) -> Self {
886        self.out_dest.stderr = OutDest::File(Arc::new(file));
887        self
888    }
889
890    /// Set the PWD environment variable to `path`.
891    ///
892    /// This method accepts `path` with trailing slashes, but they're removed
893    /// before writing the value into PWD.
894    pub fn set_cwd(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), ShellError> {
895        // Helper function to create a simple generic error.
896        // Its messages are not especially helpful, but these errors don't occur often, so it's probably fine.
897        fn error(msg: &str) -> Result<(), ShellError> {
898            Err(ShellError::Generic(GenericError::new_internal(
899                msg.to_string(),
900                "",
901            )))
902        }
903
904        let path = path.as_ref();
905
906        if !path.is_absolute() {
907            if let Some(Component::Prefix(_)) = path.components().next() {
908                return Err(ShellError::Generic(
909                    GenericError::new_internal("Cannot set $env.PWD to a prefix-only path", "")
910                        .with_help(format!(
911                            "Try to use {}{MAIN_SEPARATOR} instead",
912                            path.display()
913                        )),
914                ));
915            }
916
917            error("Cannot set $env.PWD to a non-absolute path")
918        } else if !path.exists() {
919            error("Cannot set $env.PWD to a non-existent directory")
920        } else if !path.is_dir() {
921            error("Cannot set $env.PWD to a non-directory")
922        } else {
923            // Strip trailing slashes, if any.
924            let path = nu_path::strip_trailing_slash(path);
925            let value = Value::string(path.to_string_lossy(), Span::unknown());
926            self.add_env_var("PWD".into(), value);
927            Ok(())
928        }
929    }
930}
931
932#[cfg(test)]
933mod test {
934    use std::sync::Arc;
935
936    use crate::{Span, Value, VarId, engine::EngineState};
937
938    use super::Stack;
939
940    #[test]
941    fn test_children_see_inner_values() {
942        let mut original = Stack::new();
943        original.add_var(VarId::new(0), Value::test_string("hello"));
944
945        let cloned = Stack::with_parent(Arc::new(original));
946        assert_eq!(
947            cloned.get_var(VarId::new(0), Span::test_data()),
948            Ok(Value::test_string("hello"))
949        );
950    }
951
952    #[test]
953    fn test_children_dont_see_deleted_values() {
954        let mut original = Stack::new();
955        original.add_var(VarId::new(0), Value::test_string("hello"));
956
957        let mut cloned = Stack::with_parent(Arc::new(original));
958        cloned.remove_var(VarId::new(0));
959
960        assert_eq!(
961            cloned.get_var(VarId::new(0), Span::test_data()),
962            Err(crate::ShellError::VariableNotFoundAtRuntime {
963                span: Span::test_data()
964            })
965        );
966    }
967
968    #[test]
969    fn test_children_changes_override_parent() {
970        let mut original = Stack::new();
971        original.add_var(VarId::new(0), Value::test_string("hello"));
972
973        let mut cloned = Stack::with_parent(Arc::new(original));
974        cloned.add_var(VarId::new(0), Value::test_string("there"));
975        assert_eq!(
976            cloned.get_var(VarId::new(0), Span::test_data()),
977            Ok(Value::test_string("there"))
978        );
979
980        cloned.remove_var(VarId::new(0));
981        // the underlying value shouldn't magically re-appear
982        assert_eq!(
983            cloned.get_var(VarId::new(0), Span::test_data()),
984            Err(crate::ShellError::VariableNotFoundAtRuntime {
985                span: Span::test_data()
986            })
987        );
988    }
989    #[test]
990    fn test_children_changes_persist_in_offspring() {
991        let mut original = Stack::new();
992        original.add_var(VarId::new(0), Value::test_string("hello"));
993
994        let mut cloned = Stack::with_parent(Arc::new(original));
995        cloned.add_var(VarId::new(1), Value::test_string("there"));
996
997        cloned.remove_var(VarId::new(0));
998        let cloned = Stack::with_parent(Arc::new(cloned));
999
1000        assert_eq!(
1001            cloned.get_var(VarId::new(0), Span::test_data()),
1002            Err(crate::ShellError::VariableNotFoundAtRuntime {
1003                span: Span::test_data()
1004            })
1005        );
1006
1007        assert_eq!(
1008            cloned.get_var(VarId::new(1), Span::test_data()),
1009            Ok(Value::test_string("there"))
1010        );
1011    }
1012
1013    #[test]
1014    fn test_merging_children_back_to_parent() {
1015        let mut original = Stack::new();
1016        let engine_state = EngineState::new();
1017        original.add_var(VarId::new(0), Value::test_string("hello"));
1018
1019        let original_arc = Arc::new(original);
1020        let mut cloned = Stack::with_parent(original_arc.clone());
1021        cloned.add_var(VarId::new(1), Value::test_string("there"));
1022
1023        cloned.remove_var(VarId::new(0));
1024
1025        cloned.add_env_var(
1026            "ADDED_IN_CHILD".to_string(),
1027            Value::test_string("New Env Var"),
1028        );
1029
1030        let original = Stack::with_changes_from_child(original_arc, cloned);
1031
1032        assert_eq!(
1033            original.get_var(VarId::new(0), Span::test_data()),
1034            Err(crate::ShellError::VariableNotFoundAtRuntime {
1035                span: Span::test_data()
1036            })
1037        );
1038
1039        assert_eq!(
1040            original.get_var(VarId::new(1), Span::test_data()),
1041            Ok(Value::test_string("there"))
1042        );
1043
1044        assert_eq!(
1045            original
1046                .get_env_var(&engine_state, "ADDED_IN_CHILD")
1047                .cloned(),
1048            Some(Value::test_string("New Env Var")),
1049        );
1050    }
1051}