Skip to main content

nu_engine/
eval.rs

1#[allow(deprecated)]
2use crate::get_full_help;
3use crate::{EvalBlockWithEarlyReturnFn, eval_ir::eval_ir_block};
4use nu_protocol::{
5    BlockId, Config, ENV_VARIABLE_ID, IntoPipelineData, PipelineData, PipelineExecutionData,
6    ShellError, Signature, Span, Value, VarId,
7    ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember},
8    debugger::{DebugContext, WithDebug, WithoutDebug},
9    engine::{Closure, EngineState, EnvName, EnvVars, Stack},
10    eval_base::Eval,
11};
12use nu_utils::IgnoreCaseExt;
13use std::borrow::Cow;
14use std::collections::{HashMap, HashSet};
15use std::sync::Arc;
16
17/// [`CallEval`] is used to evaluate a command or closure call.
18///
19/// It is intended as an internal interface in the engine, to make sure
20/// command and closure calls behave the same.
21/// If you want to evaluate a closure in a command, use [`ClosureEval`] or
22/// [`ClosureEvalOnce`]. If you want to call a command, use [`eval_call`].
23///
24/// [`CallEval`] has a builder API.
25/// It is first created vial [`CallEval::new`],
26/// then has arguments added via [`CallEval::add_positional`] and [`CallEval::add_named`],
27/// and then can be run using [`CallEval::run`].
28#[derive(Clone)]
29pub struct CallEval {
30    callee_stack: Stack,
31    head_span: Span,
32    callee_span: Span,
33    arg_index: usize,
34    named_args: Vec<String>,
35    rest_args: Vec<Value>,
36    eval: EvalBlockWithEarlyReturnFn,
37}
38
39impl CallEval {
40    /// Create a new [`CallEval`] context
41    pub fn new(
42        callee_stack: Stack,
43        call_head: Span,
44        callee_span: Span,
45        eval: EvalBlockWithEarlyReturnFn,
46    ) -> Self {
47        Self {
48            callee_stack,
49            head_span: call_head,
50            callee_span,
51            arg_index: 0,
52            named_args: Vec::new(),
53            rest_args: Vec::new(),
54            eval,
55        }
56    }
57
58    /// Add a positional argument to the call stack.
59    ///
60    /// Returns an error if the given `value` does not match the type of
61    /// the argument according to the signature (see [`CallEval::new`]).
62    pub fn add_positional(
63        &mut self,
64        signature: &Signature,
65        value: Cow<Value>,
66    ) -> Result<&mut Self, ShellError> {
67        let maybe_param = match self
68            .arg_index
69            .checked_sub(signature.required_positional.len())
70        {
71            // arg_index < required_len
72            None => signature.required_positional.get(self.arg_index),
73            // required_len <= arg_index < (required_len + optional_len)
74            Some(opt_idx) if opt_idx < signature.optional_positional.len() => {
75                signature.optional_positional.get(opt_idx)
76            }
77            // (required_len + optional_len) <= arg_index
78            _ => None,
79        };
80
81        if let Some(param) = maybe_param {
82            let param_type = param.shape.to_type();
83            if !value.is_subtype_of(&param_type) {
84                return Err(ShellError::CantConvert {
85                    to_type: param_type.to_string(),
86                    from_type: value.get_type().to_string(),
87                    span: value.span(),
88                    help: None,
89                });
90            }
91
92            let var_id = param
93                .var_id
94                .expect("internal error: all custom parameters must have var_ids");
95            self.callee_stack.add_var(var_id, value.into_owned());
96            self.arg_index += 1;
97            Ok(self)
98        } else {
99            // assign arg to rest params
100            let Some(rest_positional) = &signature.rest_positional else {
101                // We do not consider it an error if more arguments
102                // are added than the closure takes. This makes it possible
103                // to omit any unused arguments in the closure definition.
104                return Ok(self);
105            };
106
107            let param_type = rest_positional.shape.to_type();
108            if !value.is_subtype_of(&param_type) {
109                return Err(ShellError::CantConvert {
110                    to_type: param_type.to_string(),
111                    from_type: value.get_type().to_string(),
112                    span: value.span(),
113                    help: None,
114                });
115            }
116
117            self.rest_args.push(value.into_owned());
118            Ok(self)
119        }
120    }
121
122    /// Add a named parameter to the call stack.
123    pub fn add_named(
124        &mut self,
125        signature: &Signature,
126        long: &str,
127        short: Option<String>,
128        value: Option<Cow<Value>>,
129    ) -> Result<&mut Self, ShellError> {
130        let named = signature.named.iter().find(|named| {
131            long == named.long
132                || short
133                    .as_deref()
134                    .zip(named.short)
135                    .is_some_and(|(arg, param)| {
136                        let mut buf = [0; 4];
137                        param.encode_utf8(&mut buf) == arg
138                    })
139        });
140
141        if let Some(named) = named {
142            let var_id = named
143                .var_id
144                .expect("internal error: all custom parameters must have var_ids");
145
146            let value = value
147                .or_else(|| named.default_value.as_ref().map(Cow::Borrowed))
148                .unwrap_or_else(|| Cow::Owned(Value::bool(true, self.head_span)));
149
150            self.callee_stack.add_var(var_id, value.into_owned());
151            self.named_args.push(long.to_string());
152        }
153
154        Ok(self)
155    }
156
157    /// Sets the environment variables for the call.
158    pub fn with_env(
159        &mut self,
160        env_vars: &[Arc<EnvVars>],
161        env_hidden: &Arc<HashMap<String, HashSet<EnvName>>>,
162    ) -> &mut Self {
163        self.callee_stack.with_env(env_vars, env_hidden);
164        self
165    }
166
167    /// Sets whether to enable debugging when evaluating the closure.
168    pub fn debug(&mut self, debug: bool) -> &mut Self {
169        if debug {
170            self.eval = eval_block_with_early_return::<WithDebug>
171        } else {
172            self.eval = eval_block_with_early_return::<WithoutDebug>
173        };
174        self
175    }
176
177    /// Run the given block.
178    pub fn run(
179        &mut self,
180        engine_state: &EngineState,
181        block: &Block,
182        input: PipelineData,
183    ) -> Result<PipelineData, ShellError> {
184        self.finalize_arguments(&block.signature)?;
185        self.arg_index = 0;
186        self.rest_args.clear();
187        (self.eval)(engine_state, &mut self.callee_stack, block, input).map(|p| p.body)
188    }
189
190    /// Export the modified environment from callee to the caller.
191    pub fn redirect_env(&self, engine_state: &EngineState, stack: &mut Stack) {
192        redirect_env(engine_state, stack, &self.callee_stack);
193    }
194
195    /// Add default and rest values to the stack, raise error on
196    /// missing parameters.
197    fn finalize_arguments(&mut self, signature: &Signature) -> Result<(), ShellError> {
198        let remaining_positionals = signature
199            .required_positional
200            .iter()
201            .map(|p| (p, true))
202            .chain(signature.optional_positional.iter().map(|p| (p, false)))
203            // skip positional args added with add_positional
204            .skip(self.arg_index);
205
206        for (param, required) in remaining_positionals {
207            let var_id = param
208                .var_id
209                .expect("internal error: all custom parameters must have var_ids");
210
211            let maybe_value = param
212                .default_value
213                .clone()
214                .or((!required).then_some(Value::nothing(self.callee_span)));
215
216            if let Some(value) = maybe_value {
217                self.callee_stack.add_var(var_id, value);
218            } else {
219                return Err(ShellError::MissingParameter {
220                    param_name: param.name.to_string(),
221                    span: self.callee_span,
222                });
223            }
224        }
225
226        if let Some(rest_positional) = &signature.rest_positional {
227            let span = self
228                .rest_args
229                .first()
230                .map(|x| x.span())
231                .unwrap_or(self.callee_span);
232
233            self.callee_stack.add_var(
234                rest_positional
235                    .var_id
236                    .expect("Internal error: rest positional parameter lackes var_id"),
237                Value::list(self.rest_args.to_owned(), span),
238            );
239        }
240
241        let remaining_flags = signature
242            .named
243            .iter()
244            // Skip provided flags
245            .filter(|flag| !self.named_args.contains(&flag.long))
246            // Ignore named arguments without var_id.
247            // There is some code in nu_cli::completions that relies on this behavior of `eval_call`.
248            .filter_map(|flag| Some((flag.var_id?, flag)));
249
250        for (var_id, flag) in remaining_flags {
251            if flag.arg.is_none() {
252                self.callee_stack
253                    .add_var(var_id, Value::bool(false, self.head_span));
254            } else {
255                let value = flag
256                    .default_value
257                    .clone()
258                    .unwrap_or(Value::nothing(self.head_span));
259                self.callee_stack.add_var(var_id, value);
260            }
261        }
262
263        Ok(())
264    }
265}
266
267/// Evaluate a call to a command (builtin, custom or external)
268pub fn eval_call<D: DebugContext>(
269    engine_state: &EngineState,
270    caller_stack: &mut Stack,
271    call: &Call,
272    input: PipelineData,
273) -> Result<PipelineData, ShellError> {
274    engine_state.signals().check(&call.head)?;
275    let decl = engine_state.get_decl(call.decl_id);
276
277    if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
278        let help = get_full_help(decl, engine_state, caller_stack, call.head);
279        Ok(Value::string(help, call.head).into_pipeline_data())
280    } else if let Some(block_id) = decl.block_id() {
281        // call is a custom command
282        let block = engine_state.get_block(block_id);
283        let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
284
285        // Rust does not check recursion limits outside of const evaluation.
286        // But nu programs run in the same process as the shell.
287        // To prevent a stack overflow in user code from crashing the shell,
288        // we limit the recursion depth of function calls.
289        // Picked 50 arbitrarily, should work on all architectures.
290        let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64;
291        callee_stack.recursion_count += 1;
292        if callee_stack.recursion_count > maximum_call_stack_depth {
293            callee_stack.recursion_count = 0;
294            return Err(ShellError::RecursionLimitReached {
295                recursion_limit: maximum_call_stack_depth,
296                span: block.span,
297            });
298        }
299
300        let mut call_eval = CallEval::new(
301            callee_stack,
302            call.head,
303            block.span.unwrap_or(Span::unknown()),
304            eval_block_with_early_return::<D>,
305        );
306        for arg in call.positional_iter() {
307            let result = eval_expression::<D>(engine_state, caller_stack, arg)?;
308            call_eval.add_positional(&decl.signature(), Cow::Owned(result))?;
309        }
310        for call_named in call.named_iter() {
311            let result: Option<Cow<Value>> = if let Some(arg) = &call_named.2 {
312                Some(Cow::Owned(eval_expression::<D>(
313                    engine_state,
314                    caller_stack,
315                    arg,
316                )?))
317            } else {
318                None
319            };
320            call_eval.add_named(
321                &decl.signature(),
322                &call_named.0.item,
323                call_named.1.clone().map(|x| x.item),
324                result,
325            )?;
326        }
327
328        let result = call_eval.run(engine_state, block, input);
329
330        if block.redirect_env {
331            call_eval.redirect_env(engine_state, caller_stack);
332        }
333
334        result
335    } else {
336        // call is a builtin or external command
337        // We pass caller_stack here with the knowledge that internal commands
338        // are going to be specifically looking for global state in the stack
339        // rather than any local state.
340        decl.run(engine_state, caller_stack, &call.into(), input)
341    }
342}
343
344/// Redirect the environment from callee to the caller.
345pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
346    // Grab all environment variables from the callee
347    let caller_env_vars = caller_stack.get_env_var_names(engine_state);
348
349    // remove env vars that are present in the caller but not in the callee
350    // (the callee hid them)
351    for var in caller_env_vars.iter() {
352        if !callee_stack.has_env_var(engine_state, var) {
353            caller_stack.hide_env_var(engine_state, var);
354        }
355    }
356
357    // add new env vars from callee to caller
358    for (var, value) in callee_stack.get_stack_env_vars() {
359        caller_stack.add_env_var(var, value);
360    }
361
362    // set config to callee config, to capture any updates to that
363    caller_stack.config.clone_from(&callee_stack.config);
364}
365
366fn eval_external(
367    engine_state: &EngineState,
368    stack: &mut Stack,
369    head: &Expression,
370    args: &[ExternalArgument],
371    input: PipelineData,
372) -> Result<PipelineData, ShellError> {
373    let decl_id = engine_state
374        .find_decl("run-external".as_bytes(), &[])
375        .ok_or(ShellError::ExternalNotSupported {
376            span: head.span(&engine_state),
377        })?;
378
379    let command = engine_state.get_decl(decl_id);
380
381    let mut call = Call::new(head.span(&engine_state));
382
383    call.add_positional(head.clone());
384
385    for arg in args {
386        match arg {
387            ExternalArgument::Regular(expr) => call.add_positional(expr.clone()),
388            ExternalArgument::Spread(expr) => call.add_spread(expr.clone()),
389        }
390    }
391
392    command.run(engine_state, stack, &(&call).into(), input)
393}
394
395pub fn eval_expression<D: DebugContext>(
396    engine_state: &EngineState,
397    stack: &mut Stack,
398    expr: &Expression,
399) -> Result<Value, ShellError> {
400    let stack = &mut stack.start_collect_value();
401    <EvalRuntime as Eval>::eval::<D>(engine_state, stack, expr)
402}
403
404/// Checks the expression to see if it's a internal or external call. If so, passes the input
405/// into the call and gets out the result
406/// Otherwise, invokes the expression
407///
408/// It returns PipelineData with a boolean flag, indicating if the external failed to run.
409/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
410pub fn eval_expression_with_input<D: DebugContext>(
411    engine_state: &EngineState,
412    stack: &mut Stack,
413    expr: &Expression,
414    mut input: PipelineData,
415) -> Result<PipelineData, ShellError> {
416    match &expr.expr {
417        Expr::Call(call) => {
418            input = eval_call::<D>(engine_state, stack, call, input)?;
419        }
420        Expr::ExternalCall(head, args) => {
421            input = eval_external(engine_state, stack, head, args, input)?;
422        }
423
424        Expr::Collect(var_id, expr) => {
425            input = eval_collect::<D>(engine_state, stack, *var_id, expr, input)?;
426        }
427
428        Expr::Subexpression(block_id) => {
429            let block = engine_state.get_block(*block_id);
430            // FIXME: protect this collect with ctrl-c
431            input = eval_subexpression::<D>(engine_state, stack, block, input)?;
432        }
433
434        Expr::FullCellPath(full_cell_path) => match &full_cell_path.head {
435            Expression {
436                expr: Expr::Subexpression(block_id),
437                span,
438                ..
439            } => {
440                let block = engine_state.get_block(*block_id);
441
442                if !full_cell_path.tail.is_empty() {
443                    let stack = &mut stack.start_collect_value();
444                    // FIXME: protect this collect with ctrl-c
445                    input = eval_subexpression::<D>(engine_state, stack, block, input)?
446                        .into_value(*span)?
447                        .follow_cell_path(&full_cell_path.tail)?
448                        .into_owned()
449                        .into_pipeline_data()
450                } else {
451                    input = eval_subexpression::<D>(engine_state, stack, block, input)?;
452                }
453            }
454            _ => {
455                let input_value = input.into_value(expr.span)?;
456                stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
457                input = eval_expression::<D>(engine_state, stack, expr)?.into_pipeline_data();
458            }
459        },
460
461        Expr::StringInterpolation(_) | Expr::GlobInterpolation(_, _) => {
462            let input_value = input.into_value(expr.span)?;
463            stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
464            let value = eval_expression::<D>(engine_state, stack, expr)?;
465            input = PipelineData::Value(value, None);
466        }
467
468        _ => {
469            let input_value = input.into_value(expr.span)?;
470            stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
471            let value = eval_expression::<D>(engine_state, stack, expr)?;
472            input = PipelineData::Value(value, None);
473        }
474    };
475
476    Ok(input)
477}
478
479pub fn eval_block<D: DebugContext>(
480    engine_state: &EngineState,
481    stack: &mut Stack,
482    block: &Block,
483    input: PipelineData,
484) -> Result<PipelineExecutionData, ShellError> {
485    let result = eval_ir_block::<D>(engine_state, stack, block, input);
486    if let Err(ShellError::Exit { code }) = &result {
487        std::process::exit(*code)
488    }
489    if let Err(err) = &result {
490        stack.set_last_error(err);
491    }
492    result
493}
494
495pub fn eval_block_with_early_return<D: DebugContext>(
496    engine_state: &EngineState,
497    stack: &mut Stack,
498    block: &Block,
499    input: PipelineData,
500) -> Result<PipelineExecutionData, ShellError> {
501    match eval_block::<D>(engine_state, stack, block, input) {
502        Err(ShellError::Return { span: _, value }) => Ok(PipelineExecutionData::from(
503            PipelineData::value(*value, None),
504        )),
505        Err(ShellError::Exit { code }) => std::process::exit(code),
506        x => x,
507    }
508}
509
510pub fn eval_collect<D: DebugContext>(
511    engine_state: &EngineState,
512    stack: &mut Stack,
513    var_id: VarId,
514    expr: &Expression,
515    mut input: PipelineData,
516) -> Result<PipelineData, ShellError> {
517    // Evaluate the expression with the variable set to the collected input
518    let span = input.span().unwrap_or(expr.span);
519
520    let metadata = input.take_metadata().and_then(|m| m.for_collect());
521
522    let input = input.into_value(span)?;
523
524    stack.add_var(var_id, input.clone());
525
526    let result = eval_expression_with_input::<D>(
527        engine_state,
528        stack,
529        expr,
530        // We still have to pass it as input
531        input.into_pipeline_data_with_metadata(metadata),
532    );
533
534    stack.remove_var(var_id);
535
536    result
537}
538
539pub fn eval_subexpression<D: DebugContext>(
540    engine_state: &EngineState,
541    stack: &mut Stack,
542    block: &Block,
543    input: PipelineData,
544) -> Result<PipelineData, ShellError> {
545    eval_block::<D>(engine_state, stack, block, input).map(|p| p.body)
546}
547
548pub fn eval_variable(
549    engine_state: &EngineState,
550    stack: &Stack,
551    var_id: VarId,
552    span: Span,
553) -> Result<Value, ShellError> {
554    match var_id {
555        // $nu
556        nu_protocol::NU_VARIABLE_ID => {
557            if let Some(val) = engine_state.get_constant(var_id) {
558                Ok(val.clone())
559            } else {
560                Err(ShellError::VariableNotFoundAtRuntime { span })
561            }
562        }
563        // $env
564        ENV_VARIABLE_ID => {
565            let env_vars = stack.get_env_vars(engine_state);
566            let env_columns = env_vars.keys();
567            let env_values = env_vars.values();
568
569            let mut pairs = env_columns
570                .map(|x| x.to_string())
571                .zip(env_values.cloned())
572                .collect::<Vec<(String, Value)>>();
573
574            pairs.sort_by(|a, b| a.0.cmp(&b.0));
575
576            Ok(Value::record(pairs.into_iter().collect(), span))
577        }
578        var_id => stack.get_var(var_id, span),
579    }
580}
581
582struct EvalRuntime;
583
584impl Eval for EvalRuntime {
585    type State<'a> = &'a EngineState;
586
587    type MutState = Stack;
588
589    fn get_config(engine_state: Self::State<'_>, stack: &mut Stack) -> Arc<Config> {
590        stack.get_config(engine_state)
591    }
592
593    fn eval_var(
594        engine_state: &EngineState,
595        stack: &mut Stack,
596        var_id: VarId,
597        span: Span,
598    ) -> Result<Value, ShellError> {
599        eval_variable(engine_state, stack, var_id, span)
600    }
601
602    fn eval_call<D: DebugContext>(
603        engine_state: &EngineState,
604        stack: &mut Stack,
605        call: &Call,
606        _: Span,
607    ) -> Result<Value, ShellError> {
608        // FIXME: protect this collect with ctrl-c
609        eval_call::<D>(engine_state, stack, call, PipelineData::empty())?.into_value(call.head)
610    }
611
612    fn eval_external_call(
613        engine_state: &EngineState,
614        stack: &mut Stack,
615        head: &Expression,
616        args: &[ExternalArgument],
617        _: Span,
618    ) -> Result<Value, ShellError> {
619        let span = head.span(&engine_state);
620        // FIXME: protect this collect with ctrl-c
621        eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
622    }
623
624    fn eval_collect<D: DebugContext>(
625        engine_state: &EngineState,
626        stack: &mut Stack,
627        var_id: VarId,
628        expr: &Expression,
629    ) -> Result<Value, ShellError> {
630        // It's a little bizarre, but the expression can still have some kind of result even with
631        // nothing input
632        eval_collect::<D>(engine_state, stack, var_id, expr, PipelineData::empty())?
633            .into_value(expr.span)
634    }
635
636    fn eval_subexpression<D: DebugContext>(
637        engine_state: &EngineState,
638        stack: &mut Stack,
639        block_id: BlockId,
640        span: Span,
641    ) -> Result<Value, ShellError> {
642        let block = engine_state.get_block(block_id);
643        // FIXME: protect this collect with ctrl-c
644        eval_subexpression::<D>(engine_state, stack, block, PipelineData::empty())?.into_value(span)
645    }
646
647    fn regex_match(
648        engine_state: &EngineState,
649        op_span: Span,
650        lhs: &Value,
651        rhs: &Value,
652        invert: bool,
653        expr_span: Span,
654    ) -> Result<Value, ShellError> {
655        lhs.regex_match(engine_state, op_span, rhs, invert, expr_span)
656    }
657
658    fn eval_assignment<D: DebugContext>(
659        engine_state: &EngineState,
660        stack: &mut Stack,
661        lhs: &Expression,
662        rhs: &Expression,
663        assignment: Assignment,
664        op_span: Span,
665        _expr_span: Span,
666    ) -> Result<Value, ShellError> {
667        let rhs = eval_expression::<D>(engine_state, stack, rhs)?;
668
669        let rhs = match assignment {
670            Assignment::Assign => rhs,
671            Assignment::AddAssign => {
672                let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
673                lhs.add(op_span, &rhs, op_span)?
674            }
675            Assignment::SubtractAssign => {
676                let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
677                lhs.sub(op_span, &rhs, op_span)?
678            }
679            Assignment::MultiplyAssign => {
680                let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
681                lhs.mul(op_span, &rhs, op_span)?
682            }
683            Assignment::DivideAssign => {
684                let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
685                lhs.div(op_span, &rhs, op_span)?
686            }
687            Assignment::ConcatenateAssign => {
688                let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
689                lhs.concat(op_span, &rhs, op_span)?
690            }
691        };
692
693        match &lhs.expr {
694            Expr::Var(var_id) | Expr::VarDecl(var_id) => {
695                let var_info = engine_state.get_var(*var_id);
696                if var_info.mutable {
697                    stack.add_var(*var_id, rhs);
698                    Ok(Value::nothing(lhs.span(&engine_state)))
699                } else {
700                    Err(ShellError::AssignmentRequiresMutableVar {
701                        lhs_span: lhs.span(&engine_state),
702                    })
703                }
704            }
705            Expr::FullCellPath(cell_path) => {
706                match &cell_path.head.expr {
707                    Expr::Var(var_id) | Expr::VarDecl(var_id) => {
708                        // The $env variable is considered "mutable" in Nushell.
709                        // As such, give it special treatment here.
710                        let is_env = var_id == &ENV_VARIABLE_ID;
711                        if is_env || engine_state.get_var(*var_id).mutable {
712                            let mut lhs =
713                                eval_expression::<D>(engine_state, stack, &cell_path.head)?;
714                            if is_env {
715                                // Reject attempts to assign to the entire $env
716                                if cell_path.tail.is_empty() {
717                                    return Err(ShellError::CannotReplaceEnv {
718                                        span: cell_path.head.span(&engine_state),
719                                    });
720                                }
721
722                                // Updating environment variables should be case-preserving,
723                                // so we need to figure out the original key before we do anything.
724                                let (key, span) = match &cell_path.tail[0] {
725                                    PathMember::String { val, span, .. } => (val.to_string(), span),
726                                    PathMember::Int { val, span, .. } => (val.to_string(), span),
727                                };
728                                let original_key = if let Value::Record { val: record, .. } = &lhs {
729                                    record
730                                        .iter()
731                                        .rev()
732                                        .map(|(k, _)| k)
733                                        .find(|x| x.eq_ignore_case(&key))
734                                        .cloned()
735                                        .unwrap_or(key)
736                                } else {
737                                    key
738                                };
739
740                                // Retrieve the updated environment value.
741                                lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
742                                let value = lhs.follow_cell_path(&[{
743                                    let mut pm = cell_path.tail[0].clone();
744                                    pm.make_insensitive();
745                                    pm
746                                }])?;
747
748                                // Reject attempts to set automatic environment variables.
749                                if is_automatic_env_var(&original_key) {
750                                    return Err(ShellError::AutomaticEnvVarSetManually {
751                                        envvar_name: original_key,
752                                        span: *span,
753                                    });
754                                }
755
756                                let is_config = original_key == "config";
757
758                                stack.add_env_var(original_key, value.into_owned());
759
760                                // Trigger the update to config, if we modified that.
761                                if is_config {
762                                    stack.update_config(engine_state)?;
763                                }
764                            } else {
765                                lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
766                                stack.add_var(*var_id, lhs);
767                            }
768                            Ok(Value::nothing(cell_path.head.span(&engine_state)))
769                        } else {
770                            Err(ShellError::AssignmentRequiresMutableVar {
771                                lhs_span: lhs.span(&engine_state),
772                            })
773                        }
774                    }
775                    _ => Err(ShellError::AssignmentRequiresVar {
776                        lhs_span: lhs.span(&engine_state),
777                    }),
778                }
779            }
780            _ => Err(ShellError::AssignmentRequiresVar {
781                lhs_span: lhs.span(&engine_state),
782            }),
783        }
784    }
785
786    fn eval_row_condition_or_closure(
787        engine_state: &EngineState,
788        stack: &mut Stack,
789        block_id: BlockId,
790        span: Span,
791    ) -> Result<Value, ShellError> {
792        let captures = engine_state
793            .get_block(block_id)
794            .captures
795            .iter()
796            .map(|(id, span)| {
797                stack
798                    .get_var(*id, *span)
799                    .or_else(|_| {
800                        engine_state
801                            .get_var(*id)
802                            .const_val
803                            .clone()
804                            .ok_or(ShellError::VariableNotFoundAtRuntime { span: *span })
805                    })
806                    .map(|var| (*id, var))
807            })
808            .collect::<Result<_, _>>()?;
809
810        Ok(Value::closure(Closure { block_id, captures }, span))
811    }
812
813    fn eval_overlay(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
814        let name = String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string();
815
816        Ok(Value::string(name, span))
817    }
818
819    fn unreachable(engine_state: &EngineState, expr: &Expression) -> Result<Value, ShellError> {
820        Ok(Value::nothing(expr.span(&engine_state)))
821    }
822}
823
824/// Returns whether a string, when used as the name of an environment variable,
825/// is considered an automatic environment variable.
826///
827/// An automatic environment variable cannot be assigned to by user code.
828/// Current there are three of them: $env.PWD, $env.FILE_PWD, $env.CURRENT_FILE
829pub(crate) fn is_automatic_env_var(var: &str) -> bool {
830    let names = ["PWD", "FILE_PWD", "CURRENT_FILE"];
831    names.iter().any(|&name| {
832        if cfg!(windows) {
833            name.eq_ignore_case(var)
834        } else {
835            name.eq(var)
836        }
837    })
838}