Add more argument types to view ir (#13343)

# Description

Add a few more options to `view ir` for finding blocks, which I found
myself wanting while trying to trace through the generated code.

If we end up adding support for plugins to call commands that are in
scope by name, this will also make it possible for
`nu_plugin_explore_ir` to just step through IR automatically (by passing
the block/decl ids) without exposing too many internals. With that I
could potentially add keys that allow you to step in to closures or
decls with the press of a button, just by calling `view ir --json`
appropriately.

# User-Facing Changes

- `view ir` can now take names of custom commands that are in scope.
- integer arguments are treated as block IDs, which sometimes show up in
IR (closure, block, row condition literals).
- `--decl-id` provided to treat the argument as a decl ID instead, which
is also sometimes necessary to access something that isn't in scope.
This commit is contained in:
Devyn Cairns 2024-07-11 04:05:06 -07:00 committed by GitHub
parent d97512df8e
commit 9de7f931c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,5 +1,4 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::Closure;
#[derive(Clone)] #[derive(Clone)]
pub struct ViewIr; pub struct ViewIr;
@ -12,15 +11,20 @@ impl Command for ViewIr {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.required( .required(
"closure", "target",
SyntaxShape::Closure(None), SyntaxShape::Any,
"The closure to see compiled code for.", "The name or block to view compiled code for.",
) )
.switch( .switch(
"json", "json",
"Dump the raw block data as JSON (unstable).", "Dump the raw block data as JSON (unstable).",
Some('j'), Some('j'),
) )
.switch(
"decl-id",
"Integer is a declaration ID rather than a block ID.",
Some('d'),
)
.input_output_type(Type::Nothing, Type::String) .input_output_type(Type::Nothing, Type::String)
.category(Category::Debug) .category(Category::Debug)
} }
@ -29,6 +33,20 @@ impl Command for ViewIr {
"View the compiled IR code for a block of code." "View the compiled IR code for a block of code."
} }
fn extra_usage(&self) -> &str {
"
The target can be a closure, the name of a custom command, or an internal block
ID. Closure literals within IR dumps often reference the block by ID (e.g.
`closure(3231)`), so this provides an easy way to read the IR of any embedded
closures.
The --decl-id option is provided to use a declaration ID instead, which can be
found on `call` instructions. This is sometimes better than using the name, as
the declaration may not be in scope.
"
.trim()
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -36,10 +54,79 @@ impl Command for ViewIr {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let closure: Closure = call.req(engine_state, stack, 0)?; let target: Value = call.req(engine_state, stack, 0)?;
let json = call.has_flag(engine_state, stack, "json")?; let json = call.has_flag(engine_state, stack, "json")?;
let is_decl_id = call.has_flag(engine_state, stack, "decl-id")?;
let block_id = match target {
Value::Closure { ref val, .. } => val.block_id,
// Decl by name
Value::String { ref val, .. } => {
if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
let decl = engine_state.get_decl(decl_id);
decl.block_id().ok_or_else(|| ShellError::GenericError {
error: format!("Can't view IR for `{val}`"),
msg: "not a custom command".into(),
span: Some(target.span()),
help: Some("internal commands don't have Nushell source code".into()),
inner: vec![],
})?
} else {
return Err(ShellError::GenericError {
error: format!("Can't view IR for `{val}`"),
msg: "can't find a command with this name".into(),
span: Some(target.span()),
help: None,
inner: vec![],
});
}
}
// Decl by ID - IR dump always shows name of decl, but sometimes it isn't in scope
Value::Int { val, .. } if is_decl_id => {
let decl_id = val
.try_into()
.ok()
.filter(|id| *id < engine_state.num_decls())
.ok_or_else(|| ShellError::IncorrectValue {
msg: "not a valid decl id".into(),
val_span: target.span(),
call_span: call.head,
})?;
let decl = engine_state.get_decl(decl_id);
decl.block_id().ok_or_else(|| ShellError::GenericError {
error: format!("Can't view IR for `{}`", decl.name()),
msg: "not a custom command".into(),
span: Some(target.span()),
help: Some("internal commands don't have Nushell source code".into()),
inner: vec![],
})?
}
// Block by ID - often shows up in IR
Value::Int { val, .. } => val.try_into().map_err(|_| ShellError::IncorrectValue {
msg: "not a valid block id".into(),
val_span: target.span(),
call_span: call.head,
})?,
// Pass through errors
Value::Error { error, .. } => return Err(*error),
_ => {
return Err(ShellError::TypeMismatch {
err_message: "expected closure, string, or int".into(),
span: call.head,
})
}
};
let Some(block) = engine_state.try_get_block(block_id) else {
return Err(ShellError::GenericError {
error: format!("Unknown block ID: {block_id}"),
msg: "ensure the block ID is correct and try again".into(),
span: Some(target.span()),
help: None,
inner: vec![],
});
};
let block = engine_state.get_block(closure.block_id);
let ir_block = block let ir_block = block
.ir_block .ir_block
.as_ref() .as_ref()
@ -63,7 +150,7 @@ impl Command for ViewIr {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
serde_json::to_string_pretty(&serde_json::json!({ serde_json::to_string_pretty(&serde_json::json!({
"block_id": closure.block_id, "block_id": block_id,
"span": block.span, "span": block.span,
"ir_block": ir_block, "ir_block": ir_block,
"formatted_instructions": formatted_instructions, "formatted_instructions": formatted_instructions,