Add new operators has and not-has (#14841)

# Description
This PR add 2 new operators, `has` and `not-has`. They are basically
`in` and `not-in` with the order of operands swapped.

Motivation for this was the awkward way of searching for rows that
contain an item using `where`

```nushell
[[name, children]; [foo, [a, b, c]], [bar [d, e, f]]]
| where ("e" in $it.children)
```
vs
```nushell
[[name, children]; [foo, [a, b, c]], [bar [d, e, f]]]
| where children has "e"
``` 

# User-Facing Changes
Added `has` and `not-has` operators, mirroring `in` and `not-in`.

# Tests + Formatting

- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
This commit is contained in:
Bahex
2025-01-17 15:20:00 +03:00
committed by GitHub
parent 0587308684
commit 089c5221cc
9 changed files with 97 additions and 41 deletions

View File

@ -5147,6 +5147,8 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
b"//" => Operator::Math(Math::FloorDivision),
b"in" => Operator::Comparison(Comparison::In),
b"not-in" => Operator::Comparison(Comparison::NotIn),
b"has" => Operator::Comparison(Comparison::Has),
b"not-has" => Operator::Comparison(Comparison::NotHas),
b"mod" => Operator::Math(Math::Modulo),
b"bit-or" => Operator::Bits(Bits::BitOr),
b"bit-xor" => Operator::Bits(Bits::BitXor),
@ -5187,7 +5189,7 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
b"contains" => {
working_set.error(ParseError::UnknownOperator(
"contains",
"Did you mean '$string =~ $pattern' or '$element in $container'?",
"Did you mean 'has'?",
span,
));
return garbage(working_set, span);

View File

@ -821,7 +821,7 @@ pub fn math_result_type(
)
}
},
Operator::Comparison(Comparison::In) => match (&lhs.ty, &rhs.ty) {
Operator::Comparison(Comparison::In | Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
@ -859,44 +859,48 @@ pub fn math_result_type(
)
}
},
Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
(Type::String, Type::Record(_)) => (Type::Bool, None),
Operator::Comparison(Comparison::Has | Comparison::NotHas) => {
let container = lhs;
let element = rhs;
match (&element.ty, &container.ty) {
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
(Type::String, Type::String) => (Type::Bool, None),
(Type::String, Type::Record(_)) => (Type::Bool, None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None),
(Type::Custom(a), _) => (Type::Custom(a.clone()), None),
(Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None),
(Type::Custom(a), _) => (Type::Custom(a.clone()), None),
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
(Type::Int | Type::Float | Type::String, _) => {
*op = Expression::garbage(working_set, op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperationRHS(
"subset comparison".into(),
op.span,
lhs.span,
lhs.ty.clone(),
rhs.span,
rhs.ty.clone(),
)),
)
(Type::Any, _) => (Type::Bool, None),
(_, Type::Any) => (Type::Bool, None),
(Type::Int | Type::Float | Type::String, _) => {
*op = Expression::garbage(working_set, op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperationLHS(
"subset comparison".into(),
op.span,
element.span,
element.ty.clone(),
)),
)
}
_ => {
*op = Expression::garbage(working_set, op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperationRHS(
"subset comparison".into(),
op.span,
element.span,
element.ty.clone(),
container.span,
container.ty.clone(),
)),
)
}
}
_ => {
*op = Expression::garbage(working_set, op.span);
(
Type::Any,
Some(ParseError::UnsupportedOperationLHS(
"subset comparison".into(),
op.span,
lhs.span,
lhs.ty.clone(),
)),
)
}
},
}
Operator::Bits(Bits::ShiftLeft)
| Operator::Bits(Bits::ShiftRight)
| Operator::Bits(Bits::BitOr)