mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 20:17:44 +02:00
Fix incorrect handling of boolean flags for builtin commands (#11492)
# Description
Possible fix of #11456
This PR fixes a bug where builtin commands did not respect the logic of
dynamically passed boolean flags. The reason is
[has_flag](6f59abaf43/crates/nu-protocol/src/ast/call.rs (L204C5-L212C6)
)
method did not evaluate and take into consideration expression used with
flag.
To address this issue a solution is proposed:
1. `has_flag` method is moved to `CallExt` and new logic to evaluate
expression and check if it is a boolean value is added
2. `has_flag_const` method is added to `CallExt` which is a constant
version of `has_flag`
3. `has_named` method is added to `Call` which is basically the old
logic of `has_flag`
4. All usages of `has_flag` in code are updated, mostly to pass
`engine_state` and `stack` to new `has_flag`. In `run_const` commands it
is replaced with `has_flag_const`. And in a few select places: parser,
`to nuon` and `into string` old logic via `has_named` is used.
# User-Facing Changes
Explicit values of boolean flags are now respected in builtin commands.
Before:

After:

Another example:
Before:

After:

# Tests + Formatting
Added test reproducing some variants of original issue.
This commit is contained in:
@ -1,13 +1,20 @@
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expression},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
eval_const::eval_constant,
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack},
|
||||
FromValue, ShellError, Value,
|
||||
};
|
||||
|
||||
use crate::eval_expression;
|
||||
|
||||
pub trait CallExt {
|
||||
/// Check if a boolean flag is set (i.e. `--bool` or `--bool=true`)
|
||||
fn has_flag(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
flag_name: &str,
|
||||
) -> Result<bool, ShellError>;
|
||||
|
||||
fn get_flag<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -15,12 +22,6 @@ pub trait CallExt {
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ShellError>;
|
||||
|
||||
fn get_flag_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ShellError>;
|
||||
|
||||
fn rest<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -28,16 +29,6 @@ pub trait CallExt {
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError>;
|
||||
|
||||
fn rest_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError>;
|
||||
|
||||
fn rest_iter_flattened<F>(&self, start: usize, eval: F) -> Result<Vec<Value>, ShellError>
|
||||
where
|
||||
F: FnMut(&Expression) -> Result<Value, ShellError>;
|
||||
|
||||
fn opt<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -52,12 +43,6 @@ pub trait CallExt {
|
||||
pos: usize,
|
||||
) -> Result<T, ShellError>;
|
||||
|
||||
fn req_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
pos: usize,
|
||||
) -> Result<T, ShellError>;
|
||||
|
||||
fn req_parser_info<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -67,6 +52,35 @@ pub trait CallExt {
|
||||
}
|
||||
|
||||
impl CallExt for Call {
|
||||
fn has_flag(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
flag_name: &str,
|
||||
) -> Result<bool, ShellError> {
|
||||
for name in self.named_iter() {
|
||||
if flag_name == name.0.item {
|
||||
return if let Some(expr) = &name.2 {
|
||||
// Check --flag=false
|
||||
let result = eval_expression(engine_state, stack, expr)?;
|
||||
match result {
|
||||
Value::Bool { val, .. } => Ok(val),
|
||||
_ => Err(ShellError::CantConvert {
|
||||
to_type: "bool".into(),
|
||||
from_type: result.get_type().to_string(),
|
||||
span: result.span(),
|
||||
help: Some("".into()),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Ok(true)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_flag<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -81,19 +95,6 @@ impl CallExt for Call {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_flag_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
name: &str,
|
||||
) -> Result<Option<T>, ShellError> {
|
||||
if let Some(expr) = self.get_flag_expr(name) {
|
||||
let result = eval_constant(working_set, expr)?;
|
||||
FromValue::from_value(result).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn rest<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -111,43 +112,6 @@ impl CallExt for Call {
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn rest_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
starting_pos: usize,
|
||||
) -> Result<Vec<T>, ShellError> {
|
||||
let mut output = vec![];
|
||||
|
||||
for result in
|
||||
self.rest_iter_flattened(starting_pos, |expr| eval_constant(working_set, expr))?
|
||||
{
|
||||
output.push(FromValue::from_value(result)?);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn rest_iter_flattened<F>(&self, start: usize, mut eval: F) -> Result<Vec<Value>, ShellError>
|
||||
where
|
||||
F: FnMut(&Expression) -> Result<Value, ShellError>,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
for (expr, spread) in self.rest_iter(start) {
|
||||
let result = eval(expr)?;
|
||||
if spread {
|
||||
match result {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
}
|
||||
} else {
|
||||
output.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn opt<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -181,24 +145,6 @@ impl CallExt for Call {
|
||||
}
|
||||
}
|
||||
|
||||
fn req_const<T: FromValue>(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
pos: usize,
|
||||
) -> Result<T, ShellError> {
|
||||
if let Some(expr) = self.positional_nth(pos) {
|
||||
let result = eval_constant(working_set, expr)?;
|
||||
FromValue::from_value(result)
|
||||
} else if self.positional_len() == 0 {
|
||||
Err(ShellError::AccessEmptyContent { span: self.head })
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: self.positional_len() - 1,
|
||||
span: self.head,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn req_parser_info<T: FromValue>(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{call_ext::CallExt, current_dir_str, get_full_help};
|
||||
use crate::{current_dir_str, get_full_help};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
|
Reference in New Issue
Block a user