nu_command/debug/
view_ir.rs

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