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