Skip to main content

nu_engine/
eval_ir.rs

1use std::{borrow::Cow, fs::File, sync::Arc};
2
3use nu_path::{expand_path, expand_path_with};
4#[cfg(feature = "os")]
5use nu_protocol::process::check_exit_status_future;
6use nu_protocol::{
7    DeclId, ENV_VARIABLE_ID, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest,
8    PipelineData, PipelineExecutionData, PositionalArg, Range, Record, RegId, ShellError, Signals,
9    Signature, Span, Spanned, Type, Value, VarId,
10    ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
11    combined_type_string,
12    debugger::DebugContext,
13    engine::{
14        Argument, Closure, EngineState, EnvName, ErrorHandler, Matcher, Redirection, Stack,
15        StateWorkingSet,
16    },
17    ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
18    shell_error::io::IoError,
19};
20use nu_utils::IgnoreCaseExt;
21
22use crate::{
23    ENV_CONVERSIONS, convert_env_vars, eval::is_automatic_env_var, eval_block_with_early_return,
24};
25
26pub fn eval_ir_block<D: DebugContext>(
27    engine_state: &EngineState,
28    stack: &mut Stack,
29    block: &Block,
30    input: PipelineData,
31) -> Result<PipelineExecutionData, ShellError> {
32    // Rust does not check recursion limits outside of const evaluation.
33    // But nu programs run in the same process as the shell.
34    // To prevent a stack overflow in user code from crashing the shell,
35    // we limit the recursion depth of function calls.
36    let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64;
37    if stack.recursion_count > maximum_call_stack_depth {
38        return Err(ShellError::RecursionLimitReached {
39            recursion_limit: maximum_call_stack_depth,
40            span: block.span,
41        });
42    }
43
44    if let Some(ir_block) = &block.ir_block {
45        D::enter_block(engine_state, block);
46
47        let args_base = stack.arguments.get_base();
48        let error_handler_base = stack.error_handlers.get_base();
49        let finally_handler_base = stack.finally_run_handlers.get_base();
50
51        // Allocate and initialize registers. I've found that it's not really worth trying to avoid
52        // the heap allocation here by reusing buffers - our allocator is fast enough
53        let mut registers = Vec::with_capacity(ir_block.register_count as usize);
54        for _ in 0..ir_block.register_count {
55            registers.push(PipelineExecutionData::empty());
56        }
57
58        // Initialize file storage.
59        let mut files = vec![None; ir_block.file_count as usize];
60
61        let result = eval_ir_block_impl::<D>(
62            &mut EvalContext {
63                engine_state,
64                stack,
65                data: &ir_block.data,
66                block_span: &block.span,
67                args_base,
68                error_handler_base,
69                finally_handler_base,
70                redirect_out: None,
71                redirect_err: None,
72                matches: vec![],
73                registers: &mut registers[..],
74                files: &mut files[..],
75            },
76            ir_block,
77            input,
78        );
79
80        stack.error_handlers.leave_frame(error_handler_base);
81        stack.finally_run_handlers.leave_frame(finally_handler_base);
82        stack.arguments.leave_frame(args_base);
83
84        D::leave_block(engine_state, block);
85
86        result
87    } else {
88        // FIXME blocks having IR should not be optional
89        Err(ShellError::GenericError {
90            error: "Can't evaluate block in IR mode".into(),
91            msg: "block is missing compiled representation".into(),
92            span: block.span,
93            help: Some("the IrBlock is probably missing due to a compilation error".into()),
94            inner: vec![],
95        })
96    }
97}
98
99/// All of the pointers necessary for evaluation
100struct EvalContext<'a> {
101    engine_state: &'a EngineState,
102    stack: &'a mut Stack,
103    data: &'a Arc<[u8]>,
104    /// The span of the block
105    block_span: &'a Option<Span>,
106    /// Base index on the argument stack to reset to after a call
107    args_base: usize,
108    /// Base index on the error handler stack to reset to after a call
109    error_handler_base: usize,
110    /// Base index on the finally handler stack to reset to after a call
111    finally_handler_base: usize,
112    /// State set by redirect-out
113    redirect_out: Option<Redirection>,
114    /// State set by redirect-err
115    redirect_err: Option<Redirection>,
116    /// Scratch space to use for `match`
117    matches: Vec<(VarId, Value)>,
118    /// Intermediate pipeline data storage used by instructions, indexed by RegId
119    registers: &'a mut [PipelineExecutionData],
120    /// Holds open files used by redirections
121    files: &'a mut [Option<Arc<File>>],
122}
123
124impl<'a> EvalContext<'a> {
125    /// Replace the contents of a register with a new value
126    #[inline]
127    fn put_reg(&mut self, reg_id: RegId, new_value: PipelineExecutionData) {
128        // log::trace!("{reg_id} <- {new_value:?}");
129        self.registers[reg_id.get() as usize] = new_value;
130    }
131
132    /// Borrow the contents of a register.
133    #[inline]
134    fn borrow_reg(&self, reg_id: RegId) -> &PipelineData {
135        &self.registers[reg_id.get() as usize]
136    }
137
138    /// Replace the contents of a register with `Empty` and then return the value that it contained
139    #[inline]
140    fn take_reg(&mut self, reg_id: RegId) -> PipelineExecutionData {
141        // log::trace!("<- {reg_id}");
142        std::mem::replace(
143            &mut self.registers[reg_id.get() as usize],
144            PipelineExecutionData::empty(),
145        )
146    }
147
148    /// Clone data from a register. Must be collected first.
149    fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result<PipelineData, ShellError> {
150        // NOTE: here just clone the inner PipelineData
151        // it's suitable for current usage.
152        match &self.registers[reg_id.get() as usize].body {
153            PipelineData::Empty => Ok(PipelineData::empty()),
154            PipelineData::Value(val, meta) => Ok(PipelineData::value(val.clone(), meta.clone())),
155            _ => Err(ShellError::IrEvalError {
156                msg: "Must collect to value before using instruction that clones from a register"
157                    .into(),
158                span: Some(error_span),
159            }),
160        }
161    }
162
163    /// Clone a value from a register. Must be collected first.
164    fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
165        match self.clone_reg(reg_id, fallback_span)? {
166            PipelineData::Empty => Ok(Value::nothing(fallback_span)),
167            PipelineData::Value(val, _) => Ok(val),
168            _ => unreachable!("clone_reg should never return stream data"),
169        }
170    }
171
172    /// Take and implicitly collect a register to a value
173    fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
174        // NOTE: in collect, it maybe good to pick the inner PipelineData
175        // directly, and drop the ExitStatus queue.
176        let data = self.take_reg(reg_id);
177        let body = data.body;
178        let span = body.span().unwrap_or(fallback_span);
179        let result = body.into_value(span);
180        #[cfg(feature = "os")]
181        if nu_experimental::PIPE_FAIL.get() {
182            check_exit_status_future(data.exit)?
183        }
184        result
185    }
186
187    /// Get a string from data or produce evaluation error if it's invalid UTF-8
188    fn get_str(&self, slice: DataSlice, error_span: Span) -> Result<&'a str, ShellError> {
189        std::str::from_utf8(&self.data[slice]).map_err(|_| ShellError::IrEvalError {
190            msg: format!("data slice does not refer to valid UTF-8: {slice:?}"),
191            span: Some(error_span),
192        })
193    }
194}
195
196/// Eval an IR block on the provided slice of registers.
197fn eval_ir_block_impl<D: DebugContext>(
198    ctx: &mut EvalContext<'_>,
199    ir_block: &IrBlock,
200    input: PipelineData,
201) -> Result<PipelineExecutionData, ShellError> {
202    if !ctx.registers.is_empty() {
203        ctx.registers[0] = PipelineExecutionData::from(input);
204    }
205
206    // Program counter, starts at zero.
207    let mut pc = 0;
208    let need_backtrace = ctx.engine_state.get_env_var("NU_BACKTRACE").is_some();
209    let mut ret_val = None;
210
211    while pc < ir_block.instructions.len() {
212        let instruction = &ir_block.instructions[pc];
213        let span = &ir_block.spans[pc];
214        let ast = &ir_block.ast[pc];
215
216        D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers);
217
218        let result = eval_instruction::<D>(ctx, instruction, span, ast, need_backtrace);
219
220        D::leave_instruction(
221            ctx.engine_state,
222            ir_block,
223            pc,
224            ctx.registers,
225            result.as_ref().err(),
226        );
227
228        match result {
229            Ok(InstructionResult::Continue) => {
230                pc += 1;
231            }
232            Ok(InstructionResult::Branch(next_pc)) => {
233                pc = next_pc;
234            }
235            Ok(InstructionResult::Return(reg_id)) => {
236                // need to check if the return value is set by
237                // `Shell::Return` first. If so, we need to respect that value.
238                match ret_val {
239                    Some(err) => return Err(err),
240                    None => return Ok(ctx.take_reg(reg_id)),
241                }
242            }
243            Err(err @ (ShellError::Continue { .. } | ShellError::Break { .. })) => {
244                return Err(err);
245            }
246            Err(err @ (ShellError::Return { .. } | ShellError::Exit { .. })) => {
247                if let Some(always_run_handler) =
248                    ctx.stack.finally_run_handlers.pop(ctx.finally_handler_base)
249                {
250                    // need to run finally block before return.
251                    // and record the return value firstly.
252                    prepare_error_handler(ctx, always_run_handler, None);
253                    pc = always_run_handler.handler_index;
254                    ret_val = Some(err);
255                } else {
256                    // These block control related errors should be passed through
257                    return Err(err);
258                }
259            }
260            Err(err) => {
261                if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
262                    // If an error handler is set, branch there
263                    prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span)));
264                    pc = error_handler.handler_index;
265                } else if let Some(always_run_handler) =
266                    ctx.stack.finally_run_handlers.pop(ctx.finally_handler_base)
267                {
268                    prepare_error_handler(ctx, always_run_handler, Some(err.into_spanned(*span)));
269                    pc = always_run_handler.handler_index;
270                } else if need_backtrace {
271                    let err = ShellError::into_chained(err, *span);
272                    return Err(err);
273                } else {
274                    return Err(err);
275                }
276            }
277        }
278    }
279
280    // Fell out of the loop, without encountering a Return.
281    Err(ShellError::IrEvalError {
282        msg: format!(
283            "Program counter out of range (pc={pc}, len={len})",
284            len = ir_block.instructions.len(),
285        ),
286        span: *ctx.block_span,
287    })
288}
289
290/// Prepare the context for an error handler
291fn prepare_error_handler(
292    ctx: &mut EvalContext<'_>,
293    error_handler: ErrorHandler,
294    error: Option<Spanned<ShellError>>,
295) {
296    if let Some(reg_id) = error_handler.error_register {
297        if let Some(error) = error {
298            // Stack state has to be updated for stuff like LAST_EXIT_CODE
299            ctx.stack.set_last_error(&error.item);
300            // Create the error value and put it in the register
301            ctx.put_reg(
302                reg_id,
303                PipelineExecutionData::from(
304                    error
305                        .item
306                        .into_full_value(
307                            &StateWorkingSet::new(ctx.engine_state),
308                            ctx.stack,
309                            error.span,
310                        )
311                        .into_pipeline_data(),
312                ),
313            );
314        } else {
315            // Set the register to empty
316            ctx.put_reg(reg_id, PipelineExecutionData::empty());
317        }
318    }
319}
320
321/// The result of performing an instruction. Describes what should happen next
322#[derive(Debug)]
323enum InstructionResult {
324    Continue,
325    Branch(usize),
326    Return(RegId),
327}
328
329/// Perform an instruction
330fn eval_instruction<D: DebugContext>(
331    ctx: &mut EvalContext<'_>,
332    instruction: &Instruction,
333    span: &Span,
334    ast: &Option<IrAstRef>,
335    need_backtrace: bool,
336) -> Result<InstructionResult, ShellError> {
337    use self::InstructionResult::*;
338
339    // Check for interrupt if necessary
340    instruction.check_interrupt(ctx.engine_state, span)?;
341
342    // See the docs for `Instruction` for more information on what these instructions are supposed
343    // to do.
344    match instruction {
345        Instruction::Unreachable => Err(ShellError::IrEvalError {
346            msg: "Reached unreachable code".into(),
347            span: Some(*span),
348        }),
349        Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span),
350        Instruction::LoadValue { dst, val } => {
351            ctx.put_reg(
352                *dst,
353                PipelineExecutionData::from(Value::clone(val).into_pipeline_data()),
354            );
355            Ok(Continue)
356        }
357        Instruction::Move { dst, src } => {
358            let val = ctx.take_reg(*src);
359            ctx.put_reg(*dst, val);
360            Ok(Continue)
361        }
362        Instruction::Clone { dst, src } => {
363            let data = ctx.clone_reg(*src, *span)?;
364            ctx.put_reg(*dst, PipelineExecutionData::from(data));
365            Ok(Continue)
366        }
367        Instruction::Collect { src_dst } => {
368            let data = ctx.take_reg(*src_dst);
369            let value = collect(data, *span)?;
370            ctx.put_reg(*src_dst, PipelineExecutionData::from(value));
371            Ok(Continue)
372        }
373        Instruction::Span { src_dst } => {
374            let mut data = ctx.take_reg(*src_dst);
375            data.body = data.body.with_span(*span);
376            ctx.put_reg(*src_dst, data);
377            Ok(Continue)
378        }
379        Instruction::Drop { src } => {
380            ctx.take_reg(*src);
381            Ok(Continue)
382        }
383        Instruction::Drain { src } => {
384            let data = ctx.take_reg(*src);
385            drain(ctx, data)
386        }
387        Instruction::DrainIfEnd { src } => {
388            let data = ctx.take_reg(*src);
389            let res = drain_if_end(ctx, data)?;
390            ctx.put_reg(*src, PipelineExecutionData::from(res));
391            Ok(Continue)
392        }
393        Instruction::LoadVariable { dst, var_id } => {
394            let value = get_var(ctx, *var_id, *span)?;
395            ctx.put_reg(
396                *dst,
397                PipelineExecutionData::from(value.into_pipeline_data()),
398            );
399            Ok(Continue)
400        }
401        Instruction::StoreVariable { var_id, src } => {
402            let value = ctx.collect_reg(*src, *span)?;
403            // Perform runtime type checking and conversion for variable assignment
404            if nu_experimental::ENFORCE_RUNTIME_ANNOTATIONS.get() {
405                let variable = ctx.engine_state.get_var(*var_id);
406                let converted_value = check_assignment_type(value, &variable.ty)?;
407                ctx.stack.add_var(*var_id, converted_value);
408            } else {
409                ctx.stack.add_var(*var_id, value);
410            }
411            Ok(Continue)
412        }
413        Instruction::DropVariable { var_id } => {
414            ctx.stack.remove_var(*var_id);
415            Ok(Continue)
416        }
417        Instruction::LoadEnv { dst, key } => {
418            let key = ctx.get_str(*key, *span)?;
419            if let Some(value) = get_env_var(ctx, key) {
420                let new_value = value.clone().into_pipeline_data();
421                ctx.put_reg(*dst, PipelineExecutionData::from(new_value));
422                Ok(Continue)
423            } else {
424                // FIXME: using the same span twice, shouldn't this really be
425                // EnvVarNotFoundAtRuntime? There are tests that depend on CantFindColumn though...
426                Err(ShellError::CantFindColumn {
427                    col_name: key.into(),
428                    span: Some(*span),
429                    src_span: *span,
430                })
431            }
432        }
433        Instruction::LoadEnvOpt { dst, key } => {
434            let key = ctx.get_str(*key, *span)?;
435            let value = get_env_var(ctx, key)
436                .cloned()
437                .unwrap_or(Value::nothing(*span));
438            ctx.put_reg(
439                *dst,
440                PipelineExecutionData::from(value.into_pipeline_data()),
441            );
442            Ok(Continue)
443        }
444        Instruction::StoreEnv { key, src } => {
445            let key = ctx.get_str(*key, *span)?;
446            let value = ctx.collect_reg(*src, *span)?;
447
448            let key = get_env_var_name(ctx, key);
449
450            if !is_automatic_env_var(&key) {
451                let is_config = key == "config";
452                let update_conversions = key == ENV_CONVERSIONS;
453
454                ctx.stack.add_env_var(key.into_owned(), value.clone());
455
456                if is_config {
457                    ctx.stack.update_config(ctx.engine_state)?;
458                }
459                if update_conversions {
460                    convert_env_vars(ctx.stack, ctx.engine_state, &value)?;
461                }
462                Ok(Continue)
463            } else {
464                Err(ShellError::AutomaticEnvVarSetManually {
465                    envvar_name: key.into(),
466                    span: *span,
467                })
468            }
469        }
470        Instruction::PushPositional { src } => {
471            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
472            ctx.stack.arguments.push(Argument::Positional {
473                span: *span,
474                val,
475                ast: ast.clone().map(|ast_ref| ast_ref.0),
476            });
477            Ok(Continue)
478        }
479        Instruction::AppendRest { src } => {
480            let vals = ctx.collect_reg(*src, *span)?.with_span(*span);
481            ctx.stack.arguments.push(Argument::Spread {
482                span: *span,
483                vals,
484                ast: ast.clone().map(|ast_ref| ast_ref.0),
485            });
486            Ok(Continue)
487        }
488        Instruction::PushFlag { name } => {
489            let data = ctx.data.clone();
490            ctx.stack.arguments.push(Argument::Flag {
491                data,
492                name: *name,
493                short: DataSlice::empty(),
494                span: *span,
495            });
496            Ok(Continue)
497        }
498        Instruction::PushShortFlag { short } => {
499            let data = ctx.data.clone();
500            ctx.stack.arguments.push(Argument::Flag {
501                data,
502                name: DataSlice::empty(),
503                short: *short,
504                span: *span,
505            });
506            Ok(Continue)
507        }
508        Instruction::PushNamed { name, src } => {
509            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
510            let data = ctx.data.clone();
511            ctx.stack.arguments.push(Argument::Named {
512                data,
513                name: *name,
514                short: DataSlice::empty(),
515                span: *span,
516                val,
517                ast: ast.clone().map(|ast_ref| ast_ref.0),
518            });
519            Ok(Continue)
520        }
521        Instruction::PushShortNamed { short, src } => {
522            let val = ctx.collect_reg(*src, *span)?.with_span(*span);
523            let data = ctx.data.clone();
524            ctx.stack.arguments.push(Argument::Named {
525                data,
526                name: DataSlice::empty(),
527                short: *short,
528                span: *span,
529                val,
530                ast: ast.clone().map(|ast_ref| ast_ref.0),
531            });
532            Ok(Continue)
533        }
534        Instruction::PushParserInfo { name, info } => {
535            let data = ctx.data.clone();
536            ctx.stack.arguments.push(Argument::ParserInfo {
537                data,
538                name: *name,
539                info: info.clone(),
540            });
541            Ok(Continue)
542        }
543        Instruction::RedirectOut { mode } => {
544            ctx.redirect_out = eval_redirection(ctx, mode, *span, RedirectionStream::Out)?;
545            Ok(Continue)
546        }
547        Instruction::RedirectErr { mode } => {
548            ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?;
549            Ok(Continue)
550        }
551        Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) {
552            #[cfg(feature = "os")]
553            PipelineData::ByteStream(stream, _)
554                if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) =>
555            {
556                Ok(Continue)
557            }
558            _ => Err(ShellError::GenericError {
559                error: "Can't redirect stderr of internal command output".into(),
560                msg: "piping stderr only works on external commands".into(),
561                span: Some(*span),
562                help: None,
563                inner: vec![],
564            }),
565        },
566        Instruction::OpenFile {
567            file_num,
568            path,
569            append,
570        } => {
571            let path = ctx.collect_reg(*path, *span)?;
572            let file = open_file(ctx, &path, *append)?;
573            ctx.files[*file_num as usize] = Some(file);
574            Ok(Continue)
575        }
576        Instruction::WriteFile { file_num, src } => {
577            let src = ctx.take_reg(*src);
578            let file = ctx
579                .files
580                .get(*file_num as usize)
581                .cloned()
582                .flatten()
583                .ok_or_else(|| ShellError::IrEvalError {
584                    msg: format!("Tried to write to file #{file_num}, but it is not open"),
585                    span: Some(*span),
586                })?;
587            let is_external = if let PipelineData::ByteStream(stream, ..) = &src.body {
588                stream.source().is_external()
589            } else {
590                false
591            };
592            if let Err(err) = src.body.write_to(file.as_ref()) {
593                if is_external {
594                    ctx.stack.set_last_error(&err);
595                }
596                Err(err)?
597            } else {
598                Ok(Continue)
599            }
600        }
601        Instruction::CloseFile { file_num } => {
602            if ctx.files[*file_num as usize].take().is_some() {
603                Ok(Continue)
604            } else {
605                Err(ShellError::IrEvalError {
606                    msg: format!("Tried to close file #{file_num}, but it is not open"),
607                    span: Some(*span),
608                })
609            }
610        }
611        Instruction::Call { decl_id, src_dst } => {
612            let input = ctx.take_reg(*src_dst);
613            // take out exit status future first.
614            let input_data = input.body;
615            let mut result = eval_call::<D>(ctx, *decl_id, *span, input_data)?;
616            if need_backtrace {
617                match &mut result {
618                    PipelineData::ByteStream(s, ..) => s.push_caller_span(*span),
619                    PipelineData::ListStream(s, ..) => s.push_caller_span(*span),
620                    _ => (),
621                };
622            }
623            // After eval_call, attach result's exit_status_future
624            // to `original_exit`, so all exit_status_future are tracked
625            // in the new PipelineData, and wrap it into `PipelineExecutionData`
626            #[cfg(feature = "os")]
627            {
628                let mut original_exit = input.exit;
629                // `complete` converts external process status into data (`exit_code`).
630                // Drop inherited exit futures so downstream collect/print/assignment
631                // does not re-raise the same non-zero status via `pipefail`.
632                if ctx.engine_state.get_decl(*decl_id).name() == "complete" {
633                    original_exit.clear();
634                }
635                let result_exit_status_future = result
636                    .clone_exit_status_future()
637                    .map(|f| f.with_span(*span));
638                original_exit.push(result_exit_status_future);
639                ctx.put_reg(
640                    *src_dst,
641                    PipelineExecutionData {
642                        body: result,
643                        exit: original_exit,
644                    },
645                );
646            }
647            #[cfg(not(feature = "os"))]
648            ctx.put_reg(*src_dst, PipelineExecutionData { body: result });
649            Ok(Continue)
650        }
651        Instruction::StringAppend { src_dst, val } => {
652            let string_value = ctx.collect_reg(*src_dst, *span)?;
653            let operand_value = ctx.collect_reg(*val, *span)?;
654            let string_span = string_value.span();
655
656            let mut string = string_value.into_string()?;
657            let operand = if let Value::String { val, .. } = operand_value {
658                // Small optimization, so we don't have to copy the string *again*
659                val
660            } else {
661                operand_value.to_expanded_string(", ", &ctx.stack.get_config(ctx.engine_state))
662            };
663            string.push_str(&operand);
664
665            let new_string_value = Value::string(string, string_span);
666            ctx.put_reg(
667                *src_dst,
668                PipelineExecutionData::from(new_string_value.into_pipeline_data()),
669            );
670            Ok(Continue)
671        }
672        Instruction::GlobFrom { src_dst, no_expand } => {
673            let string_value = ctx.collect_reg(*src_dst, *span)?;
674            let glob_value = if let Value::Glob { .. } = string_value {
675                // It already is a glob, so don't touch it.
676                string_value
677            } else {
678                // Treat it as a string, then cast
679                let string = string_value.into_string()?;
680                Value::glob(string, *no_expand, *span)
681            };
682            ctx.put_reg(
683                *src_dst,
684                PipelineExecutionData::from(glob_value.into_pipeline_data()),
685            );
686            Ok(Continue)
687        }
688        Instruction::ListPush { src_dst, item } => {
689            let list_value = ctx.collect_reg(*src_dst, *span)?;
690            let item = ctx.collect_reg(*item, *span)?;
691            let list_span = list_value.span();
692            let mut list = list_value.into_list()?;
693            list.push(item);
694            ctx.put_reg(
695                *src_dst,
696                PipelineExecutionData::from(Value::list(list, list_span).into_pipeline_data()),
697            );
698            Ok(Continue)
699        }
700        Instruction::ListSpread { src_dst, items } => {
701            let list_value = ctx.collect_reg(*src_dst, *span)?;
702            let items = ctx.collect_reg(*items, *span)?;
703            let list_span = list_value.span();
704            let items_span = items.span();
705            let items = match items {
706                Value::List { vals, .. } => vals,
707                Value::Nothing { .. } => Vec::new(),
708                _ => return Err(ShellError::CannotSpreadAsList { span: items_span }),
709            };
710            let mut list = list_value.into_list()?;
711            list.extend(items);
712            ctx.put_reg(
713                *src_dst,
714                PipelineExecutionData::from(Value::list(list, list_span).into_pipeline_data()),
715            );
716            Ok(Continue)
717        }
718        Instruction::RecordInsert { src_dst, key, val } => {
719            let record_value = ctx.collect_reg(*src_dst, *span)?;
720            let key = ctx.collect_reg(*key, *span)?;
721            let val = ctx.collect_reg(*val, *span)?;
722            let record_span = record_value.span();
723            let mut record = record_value.into_record()?;
724
725            let key = key.coerce_into_string()?;
726            if let Some(old_value) = record.insert(&key, val) {
727                return Err(ShellError::ColumnDefinedTwice {
728                    col_name: key,
729                    second_use: *span,
730                    first_use: old_value.span(),
731                });
732            }
733
734            ctx.put_reg(
735                *src_dst,
736                PipelineExecutionData::from(
737                    Value::record(record, record_span).into_pipeline_data(),
738                ),
739            );
740            Ok(Continue)
741        }
742        Instruction::RecordSpread { src_dst, items } => {
743            let record_value = ctx.collect_reg(*src_dst, *span)?;
744            let items = ctx.collect_reg(*items, *span)?;
745            let record_span = record_value.span();
746            let items_span = items.span();
747            let mut record = record_value.into_record()?;
748            let items = match items {
749                Value::Record { val, .. } => val.into_owned(),
750                Value::Nothing { .. } => Record::new(),
751                _ => return Err(ShellError::CannotSpreadAsRecord { span: items_span }),
752            };
753            // Not using .extend() here because it doesn't handle duplicates
754            for (key, val) in items {
755                if let Some(first_value) = record.insert(&key, val) {
756                    return Err(ShellError::ColumnDefinedTwice {
757                        col_name: key,
758                        second_use: *span,
759                        first_use: first_value.span(),
760                    });
761                }
762            }
763            ctx.put_reg(
764                *src_dst,
765                PipelineExecutionData::from(
766                    Value::record(record, record_span).into_pipeline_data(),
767                ),
768            );
769            Ok(Continue)
770        }
771        Instruction::Not { src_dst } => {
772            let bool = ctx.collect_reg(*src_dst, *span)?;
773            let negated = !bool.as_bool()?;
774            ctx.put_reg(
775                *src_dst,
776                PipelineExecutionData::from(Value::bool(negated, bool.span()).into_pipeline_data()),
777            );
778            Ok(Continue)
779        }
780        Instruction::BinaryOp { lhs_dst, op, rhs } => binary_op(ctx, *lhs_dst, op, *rhs, *span),
781        Instruction::FollowCellPath { src_dst, path } => {
782            let data = ctx.take_reg(*src_dst);
783            let path = ctx.take_reg(*path);
784            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
785                let value = data.body.follow_cell_path(&path.members, *span)?;
786                ctx.put_reg(
787                    *src_dst,
788                    PipelineExecutionData::from(value.into_pipeline_data()),
789                );
790                Ok(Continue)
791            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
792                Err(*error)
793            } else {
794                Err(ShellError::TypeMismatch {
795                    err_message: "expected cell path".into(),
796                    span: path.span().unwrap_or(*span),
797                })
798            }
799        }
800        Instruction::CloneCellPath { dst, src, path } => {
801            let value = ctx.clone_reg_value(*src, *span)?;
802            let path = ctx.take_reg(*path);
803            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
804                let value = value.follow_cell_path(&path.members)?;
805                ctx.put_reg(
806                    *dst,
807                    PipelineExecutionData::from(value.into_owned().into_pipeline_data()),
808                );
809                Ok(Continue)
810            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
811                Err(*error)
812            } else {
813                Err(ShellError::TypeMismatch {
814                    err_message: "expected cell path".into(),
815                    span: path.span().unwrap_or(*span),
816                })
817            }
818        }
819        Instruction::UpsertCellPath {
820            src_dst,
821            path,
822            new_value,
823        } => {
824            let data = ctx.take_reg(*src_dst);
825            let metadata = data.metadata();
826            // Change the span because we're modifying it
827            let mut value = data.body.into_value(*span)?;
828            let path = ctx.take_reg(*path);
829            let new_value = ctx.collect_reg(*new_value, *span)?;
830            if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path.body {
831                value.upsert_data_at_cell_path(&path.members, new_value)?;
832                ctx.put_reg(
833                    *src_dst,
834                    PipelineExecutionData::from(value.into_pipeline_data_with_metadata(metadata)),
835                );
836                Ok(Continue)
837            } else if let PipelineData::Value(Value::Error { error, .. }, _) = path.body {
838                Err(*error)
839            } else {
840                Err(ShellError::TypeMismatch {
841                    err_message: "expected cell path".into(),
842                    span: path.span().unwrap_or(*span),
843                })
844            }
845        }
846        Instruction::Jump { index } => Ok(Branch(*index)),
847        Instruction::BranchIf { cond, index } => {
848            let data = ctx.take_reg(*cond);
849            let data_span = data.span();
850            let val = match data.body {
851                PipelineData::Value(Value::Bool { val, .. }, _) => val,
852                PipelineData::Value(Value::Error { error, .. }, _) => {
853                    return Err(*error);
854                }
855                _ => {
856                    return Err(ShellError::TypeMismatch {
857                        err_message: "expected bool".into(),
858                        span: data_span.unwrap_or(*span),
859                    });
860                }
861            };
862            if val {
863                Ok(Branch(*index))
864            } else {
865                Ok(Continue)
866            }
867        }
868        Instruction::BranchIfEmpty { src, index } => {
869            let is_empty = matches!(
870                ctx.borrow_reg(*src),
871                PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _)
872            );
873
874            if is_empty {
875                Ok(Branch(*index))
876            } else {
877                Ok(Continue)
878            }
879        }
880        Instruction::Match {
881            pattern,
882            src,
883            index,
884        } => {
885            let value = ctx.clone_reg_value(*src, *span)?;
886            ctx.matches.clear();
887            if pattern.match_value(&value, &mut ctx.matches) {
888                // Match succeeded: set variables and branch
889                for (var_id, match_value) in ctx.matches.drain(..) {
890                    ctx.stack.add_var(var_id, match_value);
891                }
892                Ok(Branch(*index))
893            } else {
894                // Failed to match, put back original value
895                ctx.matches.clear();
896                Ok(Continue)
897            }
898        }
899        Instruction::CheckMatchGuard { src } => {
900            if matches!(
901                ctx.borrow_reg(*src),
902                PipelineData::Value(Value::Bool { .. }, _)
903            ) {
904                Ok(Continue)
905            } else {
906                Err(ShellError::MatchGuardNotBool { span: *span })
907            }
908        }
909        Instruction::Iterate {
910            dst,
911            stream,
912            end_index,
913        } => eval_iterate(ctx, *dst, *stream, *end_index),
914        Instruction::OnError { index } => {
915            ctx.stack.error_handlers.push(ErrorHandler {
916                handler_index: *index,
917                error_register: None,
918            });
919            Ok(Continue)
920        }
921        Instruction::OnErrorInto { index, dst } => {
922            ctx.stack.error_handlers.push(ErrorHandler {
923                handler_index: *index,
924                error_register: Some(*dst),
925            });
926            Ok(Continue)
927        }
928        Instruction::Finally { index } => {
929            ctx.stack.finally_run_handlers.push(ErrorHandler {
930                handler_index: *index,
931                error_register: None,
932            });
933            Ok(Continue)
934        }
935        Instruction::FinallyInto { index, dst } => {
936            ctx.stack.finally_run_handlers.push(ErrorHandler {
937                handler_index: *index,
938                error_register: Some(*dst),
939            });
940            Ok(Continue)
941        }
942        Instruction::PopErrorHandler => {
943            ctx.stack.error_handlers.pop(ctx.error_handler_base);
944            Ok(Continue)
945        }
946        Instruction::PopFinallyRun => {
947            ctx.stack.finally_run_handlers.pop(ctx.finally_handler_base);
948            Ok(Continue)
949        }
950        Instruction::ReturnEarly { src } => {
951            let val = ctx.collect_reg(*src, *span)?;
952            Err(ShellError::Return {
953                span: *span,
954                value: Box::new(val),
955            })
956        }
957        Instruction::Return { src } => Ok(Return(*src)),
958    }
959}
960
961/// Load a literal value into a register
962fn load_literal(
963    ctx: &mut EvalContext<'_>,
964    dst: RegId,
965    lit: &Literal,
966    span: Span,
967) -> Result<InstructionResult, ShellError> {
968    // `Literal::Empty` represents "no pipeline input" and should produce
969    // `PipelineData::Empty`. This is distinct from `Literal::Nothing` which
970    // represents the `null` value and should produce `PipelineData::Value(Value::Nothing)`.
971    // Some commands (like `metadata`) distinguish between these when deciding
972    // whether positional args are allowed.
973    if matches!(lit, Literal::Empty) {
974        ctx.put_reg(dst, PipelineExecutionData::empty());
975    } else {
976        let value = literal_value(ctx, lit, span)?;
977        ctx.put_reg(
978            dst,
979            PipelineExecutionData::from(PipelineData::value(value, None)),
980        );
981    }
982    Ok(InstructionResult::Continue)
983}
984
985fn literal_value(
986    ctx: &mut EvalContext<'_>,
987    lit: &Literal,
988    span: Span,
989) -> Result<Value, ShellError> {
990    Ok(match lit {
991        Literal::Bool(b) => Value::bool(*b, span),
992        Literal::Int(i) => Value::int(*i, span),
993        Literal::Float(f) => Value::float(*f, span),
994        Literal::Filesize(q) => Value::filesize(*q, span),
995        Literal::Duration(q) => Value::duration(*q, span),
996        Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span),
997        Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => {
998            let block = ctx.engine_state.get_block(*block_id);
999            let captures = block
1000                .captures
1001                .iter()
1002                .map(|(var_id, span)| get_var(ctx, *var_id, *span).map(|val| (*var_id, val)))
1003                .collect::<Result<Vec<_>, ShellError>>()?;
1004            Value::closure(
1005                Closure {
1006                    block_id: *block_id,
1007                    captures,
1008                },
1009                span,
1010            )
1011        }
1012        Literal::Range {
1013            start,
1014            step,
1015            end,
1016            inclusion,
1017        } => {
1018            let start = ctx.collect_reg(*start, span)?;
1019            let step = ctx.collect_reg(*step, span)?;
1020            let end = ctx.collect_reg(*end, span)?;
1021            let range = Range::new(start, step, end, *inclusion, span)?;
1022            Value::range(range, span)
1023        }
1024        Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span),
1025        Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span),
1026        Literal::Filepath {
1027            val: path,
1028            no_expand,
1029        } => {
1030            let path = ctx.get_str(*path, span)?;
1031            if *no_expand {
1032                Value::string(path, span)
1033            } else {
1034                let path = expand_path(path, true);
1035                Value::string(path.to_string_lossy(), span)
1036            }
1037        }
1038        Literal::Directory {
1039            val: path,
1040            no_expand,
1041        } => {
1042            let path = ctx.get_str(*path, span)?;
1043            if path == "-" {
1044                Value::string("-", span)
1045            } else if *no_expand {
1046                Value::string(path, span)
1047            } else {
1048                let path = expand_path(path, true);
1049                Value::string(path.to_string_lossy(), span)
1050            }
1051        }
1052        Literal::GlobPattern { val, no_expand } => {
1053            Value::glob(ctx.get_str(*val, span)?, *no_expand, span)
1054        }
1055        Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span),
1056        Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span),
1057        Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span),
1058        Literal::Date(dt) => Value::date(**dt, span),
1059        Literal::Nothing => Value::nothing(span),
1060        // Empty is handled specially in load_literal and should never reach here
1061        Literal::Empty => Value::nothing(span),
1062    })
1063}
1064
1065fn binary_op(
1066    ctx: &mut EvalContext<'_>,
1067    lhs_dst: RegId,
1068    op: &Operator,
1069    rhs: RegId,
1070    span: Span,
1071) -> Result<InstructionResult, ShellError> {
1072    let lhs_val = ctx.collect_reg(lhs_dst, span)?;
1073    let rhs_val = ctx.collect_reg(rhs, span)?;
1074
1075    // Handle binary op errors early
1076    if let Value::Error { error, .. } = lhs_val {
1077        return Err(*error);
1078    }
1079    if let Value::Error { error, .. } = rhs_val {
1080        return Err(*error);
1081    }
1082
1083    // We only have access to one span here, but the generated code usually adds a `span`
1084    // instruction to set the output span to the right span.
1085    let op_span = span;
1086
1087    let result = match op {
1088        Operator::Comparison(cmp) => match cmp {
1089            Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?,
1090            Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?,
1091            Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?,
1092            Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?,
1093            Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?,
1094            Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?,
1095            Comparison::RegexMatch => {
1096                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)?
1097            }
1098            Comparison::NotRegexMatch => {
1099                lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)?
1100            }
1101            Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
1102            Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
1103            Comparison::Has => lhs_val.has(op_span, &rhs_val, span)?,
1104            Comparison::NotHas => lhs_val.not_has(op_span, &rhs_val, span)?,
1105            Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
1106            Comparison::NotStartsWith => lhs_val.not_starts_with(op_span, &rhs_val, span)?,
1107            Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
1108            Comparison::NotEndsWith => lhs_val.not_ends_with(op_span, &rhs_val, span)?,
1109        },
1110        Operator::Math(mat) => match mat {
1111            Math::Add => lhs_val.add(op_span, &rhs_val, span)?,
1112            Math::Subtract => lhs_val.sub(op_span, &rhs_val, span)?,
1113            Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?,
1114            Math::Divide => lhs_val.div(op_span, &rhs_val, span)?,
1115            Math::FloorDivide => lhs_val.floor_div(op_span, &rhs_val, span)?,
1116            Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?,
1117            Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?,
1118            Math::Concatenate => lhs_val.concat(op_span, &rhs_val, span)?,
1119        },
1120        Operator::Boolean(bl) => match bl {
1121            Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?,
1122            Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?,
1123            Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
1124        },
1125        Operator::Bits(bit) => match bit {
1126            Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?,
1127            Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?,
1128            Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?,
1129            Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?,
1130            Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?,
1131        },
1132        Operator::Assignment(_asg) => {
1133            return Err(ShellError::IrEvalError {
1134                msg: "can't eval assignment with the `binary-op` instruction".into(),
1135                span: Some(span),
1136            });
1137        }
1138    };
1139
1140    ctx.put_reg(
1141        lhs_dst,
1142        PipelineExecutionData::from(PipelineData::value(result, None)),
1143    );
1144
1145    Ok(InstructionResult::Continue)
1146}
1147
1148/// Evaluate a call
1149fn eval_call<D: DebugContext>(
1150    ctx: &mut EvalContext<'_>,
1151    decl_id: DeclId,
1152    head: Span,
1153    mut input: PipelineData,
1154) -> Result<PipelineData, ShellError> {
1155    let EvalContext {
1156        engine_state,
1157        stack: caller_stack,
1158        args_base,
1159        redirect_out,
1160        redirect_err,
1161        ..
1162    } = ctx;
1163
1164    let args_len = caller_stack.arguments.get_len(*args_base);
1165    let decl = engine_state.get_decl(decl_id);
1166
1167    // Set up redirect modes
1168    let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take());
1169
1170    let result = (|| {
1171        if let Some(block_id) = decl.block_id() {
1172            // If the decl is a custom command
1173            let block = engine_state.get_block(block_id);
1174
1175            // check types after acquiring block to avoid unnecessarily cloning Signature
1176            check_input_types(&input, &block.signature, head)?;
1177
1178            // Set up a callee stack with the captures and move arguments from the stack into variables
1179            let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
1180
1181            gather_arguments(
1182                engine_state,
1183                block,
1184                &mut caller_stack,
1185                &mut callee_stack,
1186                *args_base,
1187                args_len,
1188                head,
1189            )?;
1190
1191            // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not
1192            // recoverable in Rust.
1193            callee_stack.recursion_count += 1;
1194
1195            let result =
1196                eval_block_with_early_return::<D>(engine_state, &mut callee_stack, block, input)
1197                    .map(|p| p.body);
1198
1199            // Move environment variables back into the caller stack scope if requested to do so
1200            if block.redirect_env {
1201                redirect_env(engine_state, &mut caller_stack, &callee_stack);
1202            }
1203
1204            result
1205        } else {
1206            check_input_types(&input, &decl.signature(), head)?;
1207            // FIXME: precalculate this and save it somewhere
1208            let span = Span::merge_many(
1209                std::iter::once(head).chain(
1210                    caller_stack
1211                        .arguments
1212                        .get_args(*args_base, args_len)
1213                        .iter()
1214                        .flat_map(|arg| arg.span()),
1215                ),
1216            );
1217
1218            let call = Call {
1219                decl_id,
1220                head,
1221                span,
1222                args_base: *args_base,
1223                args_len,
1224            };
1225
1226            // Make sure that iterating value itself can be interrupted.
1227            // e.g: 0..inf | to md
1228            if let PipelineData::Value(v, ..) = &mut input {
1229                v.inject_signals(engine_state);
1230            }
1231            // Run the call
1232            decl.run(engine_state, &mut caller_stack, &(&call).into(), input)
1233        }
1234    })();
1235
1236    drop(caller_stack);
1237
1238    // Important that this runs, to reset state post-call:
1239    ctx.stack.arguments.leave_frame(ctx.args_base);
1240    ctx.redirect_out = None;
1241    ctx.redirect_err = None;
1242
1243    result
1244}
1245
1246fn find_named_var_id(
1247    sig: &Signature,
1248    name: &[u8],
1249    short: &[u8],
1250    span: Span,
1251) -> Result<VarId, ShellError> {
1252    sig.named
1253        .iter()
1254        .find(|n| {
1255            if !n.long.is_empty() {
1256                n.long.as_bytes() == name
1257            } else {
1258                // It's possible to only have a short name and no long name
1259                n.short
1260                    .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short)
1261            }
1262        })
1263        .ok_or_else(|| ShellError::IrEvalError {
1264            msg: format!(
1265                "block does not have an argument named `{}`",
1266                String::from_utf8_lossy(name)
1267            ),
1268            span: Some(span),
1269        })
1270        .and_then(|flag| expect_named_var_id(flag, span))
1271}
1272
1273fn expect_named_var_id(arg: &Flag, span: Span) -> Result<VarId, ShellError> {
1274    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1275        msg: format!(
1276            "block signature is missing var id for named arg `{}`",
1277            arg.long
1278        ),
1279        span: Some(span),
1280    })
1281}
1282
1283fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result<VarId, ShellError> {
1284    arg.var_id.ok_or_else(|| ShellError::IrEvalError {
1285        msg: format!(
1286            "block signature is missing var id for positional arg `{}`",
1287            arg.name
1288        ),
1289        span: Some(span),
1290    })
1291}
1292
1293/// Move arguments from the stack into variables for a custom command
1294fn gather_arguments(
1295    engine_state: &EngineState,
1296    block: &Block,
1297    caller_stack: &mut Stack,
1298    callee_stack: &mut Stack,
1299    args_base: usize,
1300    args_len: usize,
1301    call_head: Span,
1302) -> Result<(), ShellError> {
1303    let mut positional_iter = block
1304        .signature
1305        .required_positional
1306        .iter()
1307        .map(|p| (p, true))
1308        .chain(
1309            block
1310                .signature
1311                .optional_positional
1312                .iter()
1313                .map(|p| (p, false)),
1314        );
1315
1316    // Arguments that didn't get consumed by required/optional
1317    let mut rest = vec![];
1318    let mut rest_span: Option<Span> = None;
1319
1320    // If we encounter a spread, all further positionals should go to rest
1321    let mut always_spread = false;
1322
1323    for arg in caller_stack.arguments.drain_args(args_base, args_len) {
1324        match arg {
1325            Argument::Positional { span, val, .. } => {
1326                // Don't check next positional arg if we encountered a spread previously
1327                let next = (!always_spread).then(|| positional_iter.next()).flatten();
1328                if let Some((positional_arg, required)) = next {
1329                    let var_id = expect_positional_var_id(positional_arg, span)?;
1330                    if required {
1331                        // By checking the type of the bound variable rather than converting the
1332                        // SyntaxShape here, we might be able to save some allocations and effort
1333                        let variable = engine_state.get_var(var_id);
1334                        check_type(&val, &variable.ty)?;
1335                    }
1336                    callee_stack.add_var(var_id, val);
1337                } else {
1338                    rest_span = Some(rest_span.map_or(val.span(), |s| s.append(val.span())));
1339                    rest.push(val);
1340                }
1341            }
1342            Argument::Spread {
1343                vals,
1344                span: spread_span,
1345                ..
1346            } => match vals {
1347                Value::List { vals, .. } => {
1348                    rest.extend(vals);
1349                    rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
1350                    always_spread = true;
1351                }
1352                Value::Nothing { .. } => {
1353                    rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
1354                    always_spread = true;
1355                }
1356                Value::Error { error, .. } => return Err(*error),
1357                _ => return Err(ShellError::CannotSpreadAsList { span: vals.span() }),
1358            },
1359            Argument::Flag {
1360                data,
1361                name,
1362                short,
1363                span,
1364            } => {
1365                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1366                callee_stack.add_var(var_id, Value::bool(true, span))
1367            }
1368            Argument::Named {
1369                data,
1370                name,
1371                short,
1372                span,
1373                val,
1374                ..
1375            } => {
1376                let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?;
1377                callee_stack.add_var(var_id, val)
1378            }
1379            Argument::ParserInfo { .. } => (),
1380        }
1381    }
1382
1383    // Add the collected rest of the arguments if a spread argument exists
1384    if let Some(rest_arg) = &block.signature.rest_positional {
1385        let rest_span = rest_span.unwrap_or(call_head);
1386        let var_id = expect_positional_var_id(rest_arg, rest_span)?;
1387        callee_stack.add_var(var_id, Value::list(rest, rest_span));
1388    }
1389
1390    // Check for arguments that haven't yet been set and set them to their defaults
1391    for (positional_arg, _) in positional_iter {
1392        let var_id = expect_positional_var_id(positional_arg, call_head)?;
1393        callee_stack.add_var(
1394            var_id,
1395            positional_arg
1396                .default_value
1397                .clone()
1398                .unwrap_or(Value::nothing(call_head)),
1399        );
1400    }
1401
1402    for named_arg in &block.signature.named {
1403        if let Some(var_id) = named_arg.var_id {
1404            // For named arguments, we do this check by looking to see if the variable was set yet on
1405            // the stack. This assumes that the stack's variables was previously empty, but that's a
1406            // fair assumption for a brand new callee stack.
1407            if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) {
1408                let val = if named_arg.arg.is_none() {
1409                    Value::bool(false, call_head)
1410                } else if let Some(value) = &named_arg.default_value {
1411                    value.clone()
1412                } else {
1413                    Value::nothing(call_head)
1414                };
1415                callee_stack.add_var(var_id, val);
1416            }
1417        }
1418    }
1419
1420    Ok(())
1421}
1422
1423/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`.
1424fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> {
1425    match val {
1426        Value::Error { error, .. } => Err(*error.clone()),
1427        _ if val.is_subtype_of(ty) => Ok(()),
1428        _ => Err(ShellError::CantConvert {
1429            to_type: ty.to_string(),
1430            from_type: val.get_type().to_string(),
1431            span: val.span(),
1432            help: None,
1433        }),
1434    }
1435}
1436
1437/// Type check and convert value for assignment.
1438fn check_assignment_type(val: Value, target_ty: &Type) -> Result<Value, ShellError> {
1439    match val {
1440        Value::Error { error, .. } => Err(*error),
1441        _ if val.is_subtype_of(target_ty) => Ok(val), // No conversion needed, but compatible
1442        _ => Err(ShellError::CantConvert {
1443            to_type: target_ty.to_string(),
1444            from_type: val.get_type().to_string(),
1445            span: val.span(),
1446            help: None,
1447        }),
1448    }
1449}
1450
1451/// Type check pipeline input against command's input types
1452fn check_input_types(
1453    input: &PipelineData,
1454    signature: &Signature,
1455    head: Span,
1456) -> Result<(), ShellError> {
1457    let io_types = &signature.input_output_types;
1458
1459    // If a command doesn't have any input/output types, then treat command input type as any
1460    if io_types.is_empty() {
1461        return Ok(());
1462    }
1463
1464    // If a command only has a nothing input type, then allow any input data
1465    if io_types.iter().all(|(intype, _)| intype == &Type::Nothing) {
1466        return Ok(());
1467    }
1468
1469    match input {
1470        // early return error directly if detected
1471        PipelineData::Value(Value::Error { error, .. }, ..) => return Err(*error.clone()),
1472        // bypass run-time typechecking for custom types
1473        PipelineData::Value(Value::Custom { .. }, ..) => return Ok(()),
1474        _ => (),
1475    }
1476
1477    // Check if the input type is compatible with *any* of the command's possible input types
1478    if io_types
1479        .iter()
1480        .any(|(command_type, _)| input.is_subtype_of(command_type))
1481    {
1482        return Ok(());
1483    }
1484
1485    let input_types: Vec<Type> = io_types.iter().map(|(input, _)| input.clone()).collect();
1486    let expected_string = combined_type_string(&input_types, "and");
1487
1488    match (input, expected_string) {
1489        (PipelineData::Empty, _) => Err(ShellError::PipelineEmpty { dst_span: head }),
1490        (_, Some(expected_string)) => Err(ShellError::OnlySupportsThisInputType {
1491            exp_input_type: expected_string,
1492            wrong_type: input.get_type().to_string(),
1493            dst_span: head,
1494            src_span: input.span().unwrap_or(Span::unknown()),
1495        }),
1496        // expected_string didn't generate properly, so we can't show the proper error
1497        (_, None) => Err(ShellError::NushellFailed {
1498            msg: "Command input type strings is empty, despite being non-zero earlier".to_string(),
1499        }),
1500    }
1501}
1502
1503/// Get variable from [`Stack`] or [`EngineState`]
1504fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result<Value, ShellError> {
1505    match var_id {
1506        // $env
1507        ENV_VARIABLE_ID => {
1508            let env_vars = ctx.stack.get_env_vars(ctx.engine_state);
1509            let env_columns = env_vars.keys();
1510            let env_values = env_vars.values();
1511
1512            let mut pairs = env_columns
1513                .map(|x| x.to_string())
1514                .zip(env_values.cloned())
1515                .collect::<Vec<(String, Value)>>();
1516
1517            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1518
1519            Ok(Value::record(pairs.into_iter().collect(), span))
1520        }
1521        _ => ctx.stack.get_var(var_id, span).or_else(|err| {
1522            // $nu is handled by getting constant
1523            if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() {
1524                Ok(const_val.with_span(span))
1525            } else {
1526                Err(err)
1527            }
1528        }),
1529    }
1530}
1531
1532/// Get an environment variable (case-insensitive lookup is handled by EnvName)
1533fn get_env_var<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> {
1534    // Read scopes in order
1535    for overlays in ctx
1536        .stack
1537        .env_vars
1538        .iter()
1539        .rev()
1540        .chain(std::iter::once(&ctx.engine_state.env_vars))
1541    {
1542        // Read overlays in order
1543        for overlay_name in ctx.stack.active_overlays.iter().rev() {
1544            let Some(map) = overlays.get(overlay_name) else {
1545                // Skip if overlay doesn't exist in this scope
1546                continue;
1547            };
1548            let hidden = ctx.stack.env_hidden.get(overlay_name);
1549            let is_hidden = |key: &EnvName| hidden.is_some_and(|hidden| hidden.contains(key));
1550
1551            if let Some(val) = map
1552                // Check for exact match (now case-insensitive due to EnvName)
1553                .get(&EnvName::from(key))
1554                // Skip when encountering an overlay where the key is hidden
1555                .filter(|_| !is_hidden(&EnvName::from(key)))
1556            {
1557                return Some(val);
1558            }
1559        }
1560    }
1561    // Not found
1562    None
1563}
1564
1565/// Get the existing name of an environment variable (case-insensitive lookup is handled by EnvName).
1566/// This is used to implement case preservation of environment variables, so that changing an
1567/// environment variable that already exists always uses the same case.
1568fn get_env_var_name<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> {
1569    // Read scopes in order
1570    ctx.stack
1571        .env_vars
1572        .iter()
1573        .rev()
1574        .chain(std::iter::once(&ctx.engine_state.env_vars))
1575        .flat_map(|overlays| {
1576            // Read overlays in order
1577            ctx.stack
1578                .active_overlays
1579                .iter()
1580                .rev()
1581                .filter_map(|name| overlays.get(name))
1582        })
1583        .find_map(|map| {
1584            // Check if it exists (case-insensitive due to EnvName)
1585            if map.contains_key(&EnvName::from(key)) {
1586                // Find the existing key to preserve its case
1587                map.keys()
1588                    .find(|k| k.as_str().eq_ignore_case(key))
1589                    .map(|k| Cow::Owned(k.as_str().to_owned()))
1590            } else {
1591                None
1592            }
1593        })
1594        // didn't exist, use the provided key
1595        .unwrap_or(Cow::Borrowed(key))
1596}
1597
1598/// Helper to collect values into [`PipelineData`], preserving original span and metadata
1599///
1600/// The metadata is removed if it is the file data source, as that's just meant to mark streams.
1601fn collect(pipe: PipelineExecutionData, fallback_span: Span) -> Result<PipelineData, ShellError> {
1602    let data = pipe.body;
1603    let span = data.span().unwrap_or(fallback_span);
1604    let metadata = data.metadata().and_then(|m| m.for_collect());
1605    let value = data.into_value(span)?;
1606    #[cfg(feature = "os")]
1607    if nu_experimental::PIPE_FAIL.get() {
1608        check_exit_status_future(pipe.exit)?
1609    }
1610    Ok(PipelineData::value(value, metadata))
1611}
1612
1613/// Helper for drain behavior.
1614fn drain(
1615    ctx: &mut EvalContext<'_>,
1616    data: PipelineExecutionData,
1617) -> Result<InstructionResult, ShellError> {
1618    use self::InstructionResult::*;
1619
1620    match data.body {
1621        PipelineData::ByteStream(stream, ..) => {
1622            let span = stream.span();
1623            let callback_spans = stream.get_caller_spans().clone();
1624            if let Err(mut err) = stream.drain() {
1625                ctx.stack.set_last_error(&err);
1626                if callback_spans.is_empty() {
1627                    return Err(err);
1628                } else {
1629                    for s in callback_spans {
1630                        err = ShellError::EvalBlockWithInput {
1631                            span: s,
1632                            sources: vec![err],
1633                        }
1634                    }
1635                    return Err(err);
1636                }
1637            } else {
1638                ctx.stack.set_last_exit_code(0, span);
1639            }
1640        }
1641        PipelineData::ListStream(stream, ..) => {
1642            let callback_spans = stream.get_caller_spans().clone();
1643            if let Err(mut err) = stream.drain() {
1644                if callback_spans.is_empty() {
1645                    return Err(err);
1646                } else {
1647                    for s in callback_spans {
1648                        err = ShellError::EvalBlockWithInput {
1649                            span: s,
1650                            sources: vec![err],
1651                        }
1652                    }
1653                    return Err(err);
1654                }
1655            }
1656        }
1657        PipelineData::Value(..) | PipelineData::Empty => {}
1658    }
1659
1660    let pipefail = nu_experimental::PIPE_FAIL.get();
1661    if !pipefail {
1662        return Ok(Continue);
1663    }
1664    #[cfg(feature = "os")]
1665    {
1666        check_exit_status_future(data.exit).map(|_| Continue)
1667    }
1668    #[cfg(not(feature = "os"))]
1669    Ok(Continue)
1670}
1671
1672/// Helper for drainIfEnd behavior
1673fn drain_if_end(
1674    ctx: &mut EvalContext<'_>,
1675    data: PipelineExecutionData,
1676) -> Result<PipelineData, ShellError> {
1677    let stack = &mut ctx
1678        .stack
1679        .push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
1680    let result = data.body.drain_to_out_dests(ctx.engine_state, stack)?;
1681
1682    let pipefail = nu_experimental::PIPE_FAIL.get();
1683    if !pipefail {
1684        return Ok(result);
1685    }
1686    #[cfg(feature = "os")]
1687    {
1688        check_exit_status_future(data.exit).map(|_| result)
1689    }
1690    #[cfg(not(feature = "os"))]
1691    Ok(result)
1692}
1693
1694enum RedirectionStream {
1695    Out,
1696    Err,
1697}
1698
1699/// Open a file for redirection
1700fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
1701    let path_expanded =
1702        expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
1703    let mut options = File::options();
1704    if append {
1705        options.append(true);
1706    } else {
1707        options.write(true).truncate(true);
1708    }
1709    let file = options
1710        .create(true)
1711        .open(&path_expanded)
1712        .map_err(|err| IoError::new(err, path.span(), path_expanded))?;
1713    Ok(Arc::new(file))
1714}
1715
1716/// Set up a [`Redirection`] from a [`RedirectMode`]
1717fn eval_redirection(
1718    ctx: &mut EvalContext<'_>,
1719    mode: &RedirectMode,
1720    span: Span,
1721    which: RedirectionStream,
1722) -> Result<Option<Redirection>, ShellError> {
1723    match mode {
1724        RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))),
1725        RedirectMode::PipeSeparate => Ok(Some(Redirection::Pipe(OutDest::PipeSeparate))),
1726        RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
1727        RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
1728        RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
1729        RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
1730        RedirectMode::File { file_num } => {
1731            let file = ctx
1732                .files
1733                .get(*file_num as usize)
1734                .cloned()
1735                .flatten()
1736                .ok_or_else(|| ShellError::IrEvalError {
1737                    msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
1738                    span: Some(span),
1739                })?;
1740            Ok(Some(Redirection::File(file)))
1741        }
1742        RedirectMode::Caller => Ok(match which {
1743            RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
1744            RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe),
1745        }),
1746    }
1747}
1748
1749/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable
1750fn eval_iterate(
1751    ctx: &mut EvalContext<'_>,
1752    dst: RegId,
1753    stream: RegId,
1754    end_index: usize,
1755) -> Result<InstructionResult, ShellError> {
1756    let mut data = ctx.take_reg(stream);
1757    if let PipelineData::ListStream(list_stream, _) = &mut data.body {
1758        // Modify the stream, taking one value off, and branching if it's empty
1759        if let Some(val) = list_stream.next_value() {
1760            ctx.put_reg(dst, PipelineExecutionData::from(val.into_pipeline_data()));
1761            ctx.put_reg(stream, data); // put the stream back so it can be iterated on again
1762            Ok(InstructionResult::Continue)
1763        } else {
1764            ctx.put_reg(dst, PipelineExecutionData::empty());
1765            Ok(InstructionResult::Branch(end_index))
1766        }
1767    } else {
1768        // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be
1769        // iterated on
1770        let metadata = data.metadata();
1771        let span = data.span().unwrap_or(Span::unknown());
1772        ctx.put_reg(
1773            stream,
1774            PipelineExecutionData::from(PipelineData::list_stream(
1775                ListStream::new(data.body.into_iter(), span, Signals::EMPTY),
1776                metadata,
1777            )),
1778        );
1779        eval_iterate(ctx, dst, stream, end_index)
1780    }
1781}
1782
1783/// Redirect environment from the callee stack to the caller stack
1784fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
1785    // TODO: make this more efficient
1786    // Grab all environment variables from the callee
1787    let caller_env_vars = caller_stack.get_env_var_names(engine_state);
1788
1789    // remove env vars that are present in the caller but not in the callee
1790    // (the callee hid them)
1791    for var in caller_env_vars.iter() {
1792        if !callee_stack.has_env_var(engine_state, var) {
1793            caller_stack.remove_env_var(engine_state, var);
1794        }
1795    }
1796
1797    // add new env vars from callee to caller
1798    for (var, value) in callee_stack.get_stack_env_vars() {
1799        caller_stack.add_env_var(var, value);
1800    }
1801
1802    // set config to callee config, to capture any updates to that
1803    caller_stack.config.clone_from(&callee_stack.config);
1804}