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