diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 1a44d6fffb..86acdc628d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -190,6 +190,7 @@ pub fn eval_expression( Operator::GreaterThanOrEqual => lhs.gte(op_span, &rhs), Operator::Equal => lhs.eq(op_span, &rhs), Operator::NotEqual => lhs.ne(op_span, &rhs), + Operator::In => lhs.r#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 03ce14c6e6..f41e6787d6 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -273,6 +273,27 @@ pub fn math_result_type( ) } }, + Operator::In => 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::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 334790ab27..4ac2d563eb 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -13,7 +13,7 @@ pub use unit::*; use std::fmt::Debug; -use crate::ast::{CellPath, PathMember}; +use crate::ast::{CellPath, PathMember, RangeInclusion}; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -1014,6 +1014,44 @@ impl Value { }), } } + + pub fn r#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: lhs + .gte(Span::unknown(), &rhs.from) + .map_or(false, |v| v.is_true()) + && match rhs.inclusion { + RangeInclusion::Inclusive => lhs + .lte(Span::unknown(), &rhs.to) + .map_or(false, |v| v.is_true()), + RangeInclusion::RightExclusive => lhs + .lt(Span::unknown(), &rhs.to) + .map_or(false, |v| v.is_true()), + }, + 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 + .iter() + .any(|x| lhs.eq(Span::unknown(), x).map_or(false, |v| v.is_true())), + 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 4c2c96191d..5d628b22b0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -569,3 +569,37 @@ fn split_column() -> TestResult { fn for_loops() -> TestResult { run_test(r#"(for x in [1, 2, 3] { $x + 10 }).1"#, "12") } + +fn type_in_list_of_this_type() -> TestResult { + run_test(r#"42 in [41 42 43]"#, "true") +} + +#[test] +fn type_in_list_of_non_this_type() -> TestResult { + fail_test(r#"'hello' in [41 42 43]"#, "mismatched for operation") +} + +#[test] +fn string_in_string() -> TestResult { + run_test(r#"'z' in 'abc'"#, "false") +} + +#[test] +fn non_string_in_string() -> TestResult { + fail_test(r#"42 in 'abc'"#, "mismatched for operation") +} + +#[test] +fn int_in_range() -> TestResult { + run_test(r#"1 in -4..9.42"#, "true") +} + +#[test] +fn int_in_exclusive_range() -> TestResult { + run_test(r#"3 in 0..<3"#, "false") +} + +#[test] +fn non_number_in_range() -> TestResult { + fail_test(r#"'a' in 1..3"#, "mismatched for operation") +}