Skip to main content

nu_engine/
eval_ir.rs

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