Skip to main content

nu_command/system/
run_internal.rs

1use nu_engine::{CallExt, command_prelude::*, find_builtin_decl};
2use nu_protocol::ir;
3
4/// Internal command used by the `%($cmd)`/`%$cmd` dynamic builtin dispatch syntax.
5///
6/// The `%` sigil statically resolves a builtin at parse time. When the head is a runtime
7/// expression (`%($cmd)` or `%$cmd`), the parser defers resolution and the IR compiler
8/// rewrites the call as `run-internal <head-expr> ...args`. This command then looks up the
9/// target builtin at runtime and enforces that it is a `CommandType::Builtin`.
10#[derive(Clone)]
11pub struct RunInternal;
12
13impl Command for RunInternal {
14    fn name(&self) -> &str {
15        "run-internal"
16    }
17
18    fn description(&self) -> &str {
19        "Run a built-in command by name. Used internally by `%($cmd)` dynamic dispatch."
20    }
21
22    fn signature(&self) -> Signature {
23        Signature::build("run-internal")
24            .input_output_types(vec![(Type::Any, Type::Any)])
25            .required(
26                "name",
27                SyntaxShape::String,
28                "The name of the built-in command to run.",
29            )
30            .rest(
31                "args",
32                SyntaxShape::Any,
33                "Arguments to pass to the built-in command.",
34            )
35    }
36
37    fn run(
38        &self,
39        engine_state: &EngineState,
40        stack: &mut Stack,
41        call: &Call,
42        input: PipelineData,
43    ) -> Result<PipelineData, ShellError> {
44        let head = call.head;
45        let name: String = call.req(engine_state, stack, 0)?;
46
47        let decl_id = find_builtin_decl(engine_state, &name)
48            .ok_or(ShellError::CommandNotFound { span: head })?;
49
50        let decl = engine_state.get_decl(decl_id);
51        // Preserve spread markers here so `%($cmd) ...$args` forwards the same argument shape the
52        // target builtin would receive in a direct call.
53        let rest_args: Vec<(Value, bool)> = call.rest_preserving_spreads(engine_state, stack, 1)?;
54
55        // Build an IR call frame for the target builtin, preserving spread arguments.
56        let mut builder = ir::Call::build(decl_id, head);
57        for (val, is_spread) in rest_args {
58            if is_spread {
59                // Skip empty spreads so builtins with no-argument defaults (e.g. `ls`
60                // listing the cwd when called with no path) are not confused by an
61                // empty `...$args` forwarded as an empty list.
62                match val {
63                    Value::List { ref vals, .. } if vals.is_empty() => continue,
64                    Value::Nothing { .. } => continue,
65                    _ => {
66                        builder.add_spread(stack, head, val);
67                    }
68                }
69            } else {
70                builder.add_positional(stack, head, val);
71            }
72        }
73
74        // `builder.with` is a scoped guard: it registers temporary IR argument slots,
75        // calls the closure, then always deallocates those slots on exit.
76        builder.with(stack, |stack, engine_call| {
77            decl.run(engine_state, stack, engine_call, input)
78        })
79    }
80}