diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 86acdc628d..16572d8ad8 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -191,6 +191,7 @@ pub fn eval_expression( Operator::Equal => lhs.eq(op_span, &rhs), Operator::NotEqual => lhs.ne(op_span, &rhs), Operator::In => lhs.r#in(op_span, &rhs), + Operator::NotIn => lhs.not_in(op_span, &rhs), x => Err(ShellError::UnsupportedOperator(x, op_span)), } } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index d2f972cabf..ca087d0a87 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -295,6 +295,28 @@ pub fn math_result_type( ) } }, + Operator::NotIn => match (&lhs.ty, &rhs.ty) { + (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), + (Type::Int | Type::Float, Type::Range) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::String, Type::Record(_, _)) => (Type::Bool, None), + + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, _ => { *op = Expression::garbage(op.span); diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 0ad12a9864..4b8e6b7948 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -812,7 +812,7 @@ impl Value { match self.partial_cmp(rhs) { Some(ordering) => Ok(Value::Bool { - val: !matches!(ordering, Ordering::Less), + val: !matches!(ordering, Ordering::Equal), span, }), None => Err(ShellError::OperatorMismatch { @@ -846,7 +846,7 @@ impl Value { span, }), (lhs, Value::Stream { stream: rhs, .. }) => Ok(Value::Bool { - // TODO(@arthur-targaryen): Not sure about this clone. + // TODO(@arthur-targaryen): Not sure about this clone (see also `Value::not_in`). val: rhs.clone().any(|x| lhs == &x), span, }), @@ -859,6 +859,41 @@ impl Value { }), } } + + pub fn not_in(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (lhs, Value::Range { val: rhs, .. }) => Ok(Value::Bool { + val: !rhs.contains(lhs), + span, + }), + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool { + val: !rhs.contains(lhs), + span, + }), + (lhs, Value::List { vals: rhs, .. }) => Ok(Value::Bool { + val: !rhs.contains(lhs), + span, + }), + (Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool { + val: !rhs.contains(lhs), + span, + }), + (lhs, Value::Stream { stream: rhs, .. }) => Ok(Value::Bool { + // TODO(@arthur-targaryen): Not sure about this clone (see also `Value::r#in`). + val: rhs.clone().all(|x| lhs != &x), + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } } /// Format a duration in nanoseconds into a string diff --git a/src/tests.rs b/src/tests.rs index 2de6466fbe..7d9ca9707e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -631,3 +631,13 @@ fn string_in_valuestream() -> TestResult { "true", ) } + +#[test] +fn string_not_in_string() -> TestResult { + run_test(r#"'d' not-in 'abc'"#, "true") +} + +#[test] +fn float_not_in_inc_range() -> TestResult { + run_test(r#"1.4 not-in 2..9.42"#, "true") +}