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