nu_command/debug/
view_ir.rs1use 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 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 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 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 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}