Skip to main content

nu_command/debug/
view_ir.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::shell_error::generic::GenericError;
3use nu_protocol::{BlockId, DeclId};
4
5#[derive(Clone)]
6pub struct ViewIr;
7
8impl Command for ViewIr {
9    fn name(&self) -> &str {
10        "view ir"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build(self.name())
15            .required(
16                "target",
17                SyntaxShape::Any,
18                "The name or block to view compiled code for.",
19            )
20            .switch(
21                "json",
22                "Dump the raw block data as JSON (unstable).",
23                Some('j'),
24            )
25            .switch(
26                "decl-id",
27                "Integer is a declaration ID rather than a block ID.",
28                Some('d'),
29            )
30            .input_output_type(Type::Nothing, Type::String)
31            .category(Category::Debug)
32    }
33
34    fn description(&self) -> &str {
35        "View the compiled IR code for a block of code."
36    }
37
38    fn extra_description(&self) -> &str {
39        "
40The target can be a closure, the name of a custom command, or an internal block
41ID. Closure literals within IR dumps often reference the block by ID (e.g.
42`closure(3231)`), so this provides an easy way to read the IR of any embedded
43closures.
44
45The --decl-id option is provided to use a declaration ID instead, which can be
46found on `call` instructions. This is sometimes better than using the name, as
47the declaration may not be in scope.
48"
49        .trim()
50    }
51
52    fn run(
53        &self,
54        engine_state: &EngineState,
55        stack: &mut Stack,
56        call: &Call,
57        _input: PipelineData,
58    ) -> Result<PipelineData, ShellError> {
59        let target: Value = call.req(engine_state, stack, 0)?;
60        let json = call.has_flag(engine_state, stack, "json")?;
61        let is_decl_id = call.has_flag(engine_state, stack, "decl-id")?;
62
63        let block_id = match target {
64            Value::Closure { ref val, .. } => val.block_id,
65            // Decl by name
66            Value::String { ref val, .. } => {
67                if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
68                    let decl = engine_state.get_decl(decl_id);
69                    decl.block_id().ok_or_else(|| {
70                        ShellError::Generic(
71                            GenericError::new(
72                                format!("Can't view IR for `{val}`"),
73                                "not a custom command",
74                                target.span(),
75                            )
76                            .with_help("internal commands don't have Nushell source code"),
77                        )
78                    })?
79                } else {
80                    return Err(ShellError::Generic(GenericError::new(
81                        format!("Can't view IR for `{val}`"),
82                        "can't find a command with this name",
83                        target.span(),
84                    )));
85                }
86            }
87            // Decl by ID - IR dump always shows name of decl, but sometimes it isn't in scope
88            Value::Int { val, .. } if is_decl_id => {
89                let decl_id = val
90                    .try_into()
91                    .ok()
92                    .map(DeclId::new)
93                    .filter(|id| id.get() < engine_state.num_decls())
94                    .ok_or_else(|| ShellError::IncorrectValue {
95                        msg: "not a valid decl id".into(),
96                        val_span: target.span(),
97                        call_span: call.head,
98                    })?;
99                let decl = engine_state.get_decl(decl_id);
100                decl.block_id().ok_or_else(|| {
101                    ShellError::Generic(
102                        GenericError::new(
103                            format!("Can't view IR for `{}`", decl.name()),
104                            "not a custom command",
105                            target.span(),
106                        )
107                        .with_help("internal commands don't have Nushell source code"),
108                    )
109                })?
110            }
111            // Block by ID - often shows up in IR
112            Value::Int { val, .. } => {
113                val.try_into()
114                    .map(BlockId::new)
115                    .map_err(|_| ShellError::IncorrectValue {
116                        msg: "not a valid block id".into(),
117                        val_span: target.span(),
118                        call_span: call.head,
119                    })?
120            }
121            // Pass through errors
122            Value::Error { error, .. } => return Err(*error),
123            _ => {
124                return Err(ShellError::TypeMismatch {
125                    err_message: "expected closure, string, or int".into(),
126                    span: call.head,
127                });
128            }
129        };
130
131        let Some(block) = engine_state.try_get_block(block_id) else {
132            return Err(ShellError::Generic(GenericError::new(
133                format!("Unknown block ID: {}", block_id.get()),
134                "ensure the block ID is correct and try again",
135                target.span(),
136            )));
137        };
138
139        let ir_block = block.ir_block.as_ref().ok_or_else(|| {
140            ShellError::Generic(
141                GenericError::new(
142                    "Can't view IR for this block",
143                    "block is missing compiled representation",
144                    block.span.unwrap_or(call.head),
145                )
146                .with_help("the IrBlock is probably missing due to a compilation error"),
147            )
148        })?;
149
150        let formatted = if json {
151            let formatted_instructions = ir_block
152                .instructions
153                .iter()
154                .map(|instruction| {
155                    instruction
156                        .display(engine_state, &ir_block.data)
157                        .to_string()
158                })
159                .collect::<Vec<_>>();
160
161            serde_json::to_string_pretty(&serde_json::json!({
162                "block_id": block_id,
163                "span": block.span,
164                "ir_block": ir_block,
165                "formatted_instructions": formatted_instructions,
166            }))
167            .map_err(|err| {
168                ShellError::Generic(GenericError::new(
169                    "JSON serialization failed",
170                    err.to_string(),
171                    call.head,
172                ))
173            })?
174        } else {
175            format!("{}", ir_block.display(engine_state))
176        };
177
178        Ok(Value::string(formatted, call.head).into_pipeline_data())
179    }
180}