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