From ed1f0eb231a8a6da4de60f7df391614c20ddea64 Mon Sep 17 00:00:00 2001 From: Reilly Wood <26268125+rgwood@users.noreply.github.com> Date: Thu, 24 Nov 2022 10:02:20 -0800 Subject: [PATCH] Make catch block a closure w/ access to error (#7228) A small follow-up to #7221. This changes the `catch` block from a block to a closure, so that it can access the error returned from the `try` block. This helps with a common scenario: "the `try` block failed, and I want to log why it failed." ### Example ![image](https://user-images.githubusercontent.com/26268125/203841966-f1f8f102-fd73-41e6-83bc-bf69ed436fa8.png) ### Future Work Nu's closure syntax is a little awkward here; it might be nicer to allow something like `catch err { print $err }`. We discussed this on Discord and it will require special parser code similar to what's already done for `for`. I'm not feeling confident enough in my parser knowledge to make that change; I will spend some more time looking at the `for` code but I doubt I will be able to implement anything in the next few days. Volunteers welcome. --- crates/nu-command/src/core_commands/try_.rs | 19 +++++++++++++++---- crates/nu-command/tests/commands/try_.rs | 12 ++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/core_commands/try_.rs b/crates/nu-command/src/core_commands/try_.rs index 49aade1c31..8503c6ea96 100644 --- a/crates/nu-command/src/core_commands/try_.rs +++ b/crates/nu-command/src/core_commands/try_.rs @@ -1,6 +1,6 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Block, Command, EngineState, Stack}; +use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack}; use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type, Value}; #[derive(Clone)] @@ -21,7 +21,10 @@ impl Command for Try { .required("try_block", SyntaxShape::Block, "block to run") .optional( "else_expression", - SyntaxShape::Keyword(b"catch".to_vec(), Box::new(SyntaxShape::Block)), + SyntaxShape::Keyword( + b"catch".to_vec(), + Box::new(SyntaxShape::Closure(Some(vec![SyntaxShape::Any]))), + ), "block to run if try block fails", ) .category(Category::Core) @@ -44,16 +47,24 @@ impl Command for Try { input: PipelineData, ) -> Result { let try_block: Block = call.req(engine_state, stack, 0)?; - let catch_block: Option = call.opt(engine_state, stack, 1)?; + let catch_block: Option = call.opt(engine_state, stack, 1)?; let try_block = engine_state.get_block(try_block.block_id); let result = eval_block(engine_state, stack, try_block, input, false, false); match result { - Err(_) | Ok(PipelineData::Value(Value::Error { .. }, ..)) => { + Err(error) | Ok(PipelineData::Value(Value::Error { error }, ..)) => { if let Some(catch_block) = catch_block { let catch_block = engine_state.get_block(catch_block.block_id); + + if let Some(var) = catch_block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + let err_value = Value::Error { error }; + stack.add_var(*var_id, err_value); + } + } + eval_block( engine_state, stack, diff --git a/crates/nu-command/tests/commands/try_.rs b/crates/nu-command/tests/commands/try_.rs index 03da540082..514e2e7a9c 100644 --- a/crates/nu-command/tests/commands/try_.rs +++ b/crates/nu-command/tests/commands/try_.rs @@ -24,3 +24,15 @@ fn try_catch() { assert!(output.out.contains("hello")); }) } + +#[test] +fn catch_can_access_error() { + Playground::setup("try_catch_test", |dirs, _sandbox| { + let output = nu!( + cwd: dirs.test(), + "try { foobarbaz } catch { |err| $err }" + ); + + assert!(output.err.contains("External command failed")); + }) +}