diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index dad2d3f306..c0bf9c6e49 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -122,7 +122,9 @@ impl Command for For { Ok(pipeline) => { let exit_code = pipeline.print(&engine_state, stack, false, false)?; if exit_code != 0 { - break; + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_code, + )); } } } @@ -164,7 +166,9 @@ impl Command for For { Ok(pipeline) => { let exit_code = pipeline.drain_with_exit_code()?; if exit_code != 0 { - break; + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_code, + )); } } } diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index e146082a8b..89bd0e88e9 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -69,7 +69,9 @@ impl Command for Loop { Ok(pipeline) => { let exit_code = pipeline.drain_with_exit_code()?; if exit_code != 0 { - break; + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_code, + )); } } } diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index a99dc85ace..168b2b146f 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -79,7 +79,11 @@ impl Command for While { Ok(pipeline) => { let exit_code = pipeline.drain_with_exit_code()?; if exit_code != 0 { - break; + return Ok( + PipelineData::new_external_stream_with_only_exit_code( + exit_code, + ), + ); } } } diff --git a/crates/nu-command/tests/commands/for_.rs b/crates/nu-command/tests/commands/for_.rs index 9d07555db0..17962ff8f9 100644 --- a/crates/nu-command/tests/commands/for_.rs +++ b/crates/nu-command/tests/commands/for_.rs @@ -28,3 +28,27 @@ fn for_break_on_external_failed() { // so our output will be `1` assert_eq!(actual.out, "1"); } + +#[test] +fn failed_for_should_break_running() { + let actual = nu!( + cwd: ".", + r#" + for i in 1..2 { + nu --testbin fail + } + print 3"# + ); + assert!(!actual.out.contains('3')); + + let actual = nu!( + cwd: ".", + r#" + let x = [1 2] + for i in $x { + nu --testbin fail + } + print 3"# + ); + assert!(!actual.out.contains('3')); +} diff --git a/crates/nu-command/tests/commands/loop_.rs b/crates/nu-command/tests/commands/loop_.rs index f0cfc61397..4d7cd8aba9 100644 --- a/crates/nu-command/tests/commands/loop_.rs +++ b/crates/nu-command/tests/commands/loop_.rs @@ -40,3 +40,22 @@ fn loop_break_on_external_failed() { // so our output will be `1`. assert_eq!(actual.out, "1"); } + +#[test] +fn failed_loop_should_break_running() { + let actual = nu!( + cwd: ".", + r#" + mut total = 0; + loop { + if $total == 3 { + break; + } else { + $total += 1; + } + nu --testbin fail; + } + print 3"# + ); + assert!(!actual.out.contains('3')); +} diff --git a/crates/nu-command/tests/commands/while_.rs b/crates/nu-command/tests/commands/while_.rs index d1cdc37975..8933997774 100644 --- a/crates/nu-command/tests/commands/while_.rs +++ b/crates/nu-command/tests/commands/while_.rs @@ -31,3 +31,12 @@ fn while_break_on_external_failed() { // so our output will be `1` assert_eq!(actual.out, "1"); } + +#[test] +fn failed_while_should_break_running() { + let actual = nu!( + cwd: ".", + "mut total = 0; while $total < 2 { $total = $total + 1; nu --testbin fail }; print 3" + ); + assert!(!actual.out.contains('3')); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index c1da1b1553..3e47d2efca 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -72,6 +72,23 @@ impl PipelineData { PipelineData::Value(Value::Nothing { span }, metadata) } + /// create a `PipelineData::ExternalStream` with proper exit_code + /// + /// It's useful to break running without raising error at user level. + pub fn new_external_stream_with_only_exit_code(exit_code: i64) -> PipelineData { + PipelineData::ExternalStream { + stdout: None, + stderr: None, + exit_code: Some(ListStream::from_stream( + [Value::int(exit_code, Span::unknown())].into_iter(), + None, + )), + span: Span::unknown(), + metadata: None, + trim_end_newline: false, + } + } + pub fn empty() -> PipelineData { PipelineData::Empty }