Rework operator type errors (#14429)

# Description

This PR adds two new `ParseError` and `ShellError` cases for type errors
relating to operators.
- `OperatorUnsupportedType` is used when a type is not supported by an
operator in any way, shape, or form. E.g., `+` does not support `bool`.
- `OperatorIncompatibleTypes` is used when a operator is used with types
it supports, but the combination of types provided cannot be used
together. E.g., `filesize + duration` is not a valid combination.

The other preexisting error cases related to operators have been removed
and replaced with the new ones above. Namely:

- `ShellError::OperatorMismatch`
- `ShellError::UnsupportedOperator`
- `ParseError::UnsupportedOperationLHS`
- `ParseError::UnsupportedOperationRHS`
- `ParseError::UnsupportedOperationTernary`

# User-Facing Changes

- `help operators` now lists the precedence of `not` as 55 instead of 0
(above the other boolean operators). Fixes #13675.
- `math median` and `math mode` now ignore NaN values so that `[NaN NaN]
| math median` and `[NaN NaN] | math mode` no longer trigger a type
error. Instead, it's now an empty input error. Fixing this in earnest
can be left for a future PR.
- Comparisons with `nan` now return false instead of causing an error.
E.g., `1 == nan` is now `false`.
- All the operator type errors have been standardized and reworked. In
particular, they can now have a help message, which is currently used
for types errors relating to `++`.

```nu
[1] ++ 2
```
```
Error: nu::parser::operator_unsupported_type

  × The '++' operator does not work on values of type 'int'.
   ╭─[entry #1:1:5]
 1 │ [1] ++ 2
   ·     ─┬ ┬
   ·      │ ╰── int
   ·      ╰── does not support 'int'
   ╰────
  help: if you meant to append a value to a list or a record to a table, use the `append` command or wrap the value in a list. For example: `$list ++ $value` should be
        `$list ++ [$value]` or `$list | append $value`.
```
This commit is contained in:
Ian Manske 2025-02-13 04:03:40 +00:00 committed by GitHub
parent 2e1b6acc0e
commit 62e56d3581
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1684 additions and 1772 deletions

View File

@ -240,7 +240,9 @@ fn test_computable_style_closure_errors() {
];
let actual_repl = nu!(nu_repl_code(&inp));
// Check that the error was printed
assert!(actual_repl.err.contains("type mismatch for operator"));
assert!(actual_repl
.err
.contains("nu::shell::operator_incompatible_types"));
// Check that the value was printed
assert!(actual_repl.out.contains("bell"));
}

View File

@ -30,11 +30,11 @@ impl Command for HelpOperators {
let head = call.head;
let mut operators = [
Operator::Assignment(Assignment::Assign),
Operator::Assignment(Assignment::PlusAssign),
Operator::Assignment(Assignment::ConcatAssign),
Operator::Assignment(Assignment::MinusAssign),
Operator::Assignment(Assignment::AddAssign),
Operator::Assignment(Assignment::SubtractAssign),
Operator::Assignment(Assignment::MultiplyAssign),
Operator::Assignment(Assignment::DivideAssign),
Operator::Assignment(Assignment::ConcatenateAssign),
Operator::Comparison(Comparison::Equal),
Operator::Comparison(Comparison::NotEqual),
Operator::Comparison(Comparison::LessThan),
@ -49,22 +49,22 @@ impl Command for HelpOperators {
Operator::Comparison(Comparison::NotHas),
Operator::Comparison(Comparison::StartsWith),
Operator::Comparison(Comparison::EndsWith),
Operator::Math(Math::Plus),
Operator::Math(Math::Concat),
Operator::Math(Math::Minus),
Operator::Math(Math::Add),
Operator::Math(Math::Subtract),
Operator::Math(Math::Multiply),
Operator::Math(Math::Divide),
Operator::Math(Math::FloorDivide),
Operator::Math(Math::Modulo),
Operator::Math(Math::FloorDivision),
Operator::Math(Math::Pow),
Operator::Math(Math::Concatenate),
Operator::Bits(Bits::BitOr),
Operator::Bits(Bits::BitXor),
Operator::Bits(Bits::BitAnd),
Operator::Bits(Bits::ShiftLeft),
Operator::Bits(Bits::ShiftRight),
Operator::Boolean(Boolean::And),
Operator::Boolean(Boolean::Or),
Operator::Boolean(Boolean::Xor),
Operator::Boolean(Boolean::And),
]
.into_iter()
.map(|op| {
@ -87,7 +87,7 @@ impl Command for HelpOperators {
"operator" => Value::string("not", head),
"name" => Value::string("Not", head),
"description" => Value::string("Negates a value or expression.", head),
"precedence" => Value::int(0, head),
"precedence" => Value::int(55, head),
},
head,
));
@ -151,32 +151,32 @@ fn description(operator: &Operator) -> &'static str {
}
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
Operator::Math(Math::Plus) => "Adds two values.",
Operator::Math(Math::Concat) => {
"Concatenates two lists, two strings, or two binary values."
}
Operator::Math(Math::Minus) => "Subtracts two values.",
Operator::Math(Math::Add) => "Adds two values.",
Operator::Math(Math::Subtract) => "Subtracts two values.",
Operator::Math(Math::Multiply) => "Multiplies two values.",
Operator::Math(Math::Divide) => "Divides two values.",
Operator::Math(Math::FloorDivide) => "Divides two values and floors the result.",
Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.",
Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.",
Operator::Math(Math::Pow) => "Raises one value to the power of another.",
Operator::Boolean(Boolean::And) => "Checks if both values are true.",
Operator::Math(Math::Concatenate) => {
"Concatenates two lists, two strings, or two binary values."
}
Operator::Boolean(Boolean::Or) => "Checks if either value is true.",
Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.",
Operator::Boolean(Boolean::And) => "Checks if both values are true.",
Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.",
Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.",
Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.",
Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.",
Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.",
Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.",
Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.",
Operator::Assignment(Assignment::ConcatAssign) => {
"Concatenates two lists, two strings, or two binary values."
}
Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.",
Operator::Assignment(Assignment::AddAssign) => "Adds a value to a variable.",
Operator::Assignment(Assignment::SubtractAssign) => "Subtracts a value from a variable.",
Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.",
Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.",
Operator::Assignment(Assignment::ConcatenateAssign) => {
"Concatenates a list, a string, or a binary value to a variable of the same type."
}
}
}

View File

@ -91,45 +91,26 @@ pub fn median(values: &[Value], span: Span, head: Span) -> Result<Value, ShellEr
Pick::Median
};
let mut sorted = vec![];
for item in values {
sorted.push(item.clone());
}
if let Some(Err(values)) = values
.windows(2)
.map(|elem| {
if elem[0].partial_cmp(&elem[1]).is_none() {
return Err(ShellError::OperatorMismatch {
op_span: head,
lhs_ty: elem[0].get_type().to_string(),
lhs_span: elem[0].span(),
rhs_ty: elem[1].get_type().to_string(),
rhs_span: elem[1].span(),
});
}
Ok(elem[0].partial_cmp(&elem[1]).unwrap_or(Ordering::Equal))
})
.find(|elem| elem.is_err())
{
return Err(values);
}
let mut sorted = values
.iter()
.filter(|x| !x.as_float().is_ok_and(f64::is_nan))
.collect::<Vec<_>>();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
match take {
Pick::Median => {
let idx = (values.len() as f64 / 2.0).floor() as usize;
let out = sorted
Ok(sorted
.get(idx)
.ok_or_else(|| ShellError::UnsupportedInput {
msg: "Empty input".to_string(),
input: "value originates from here".into(),
msg_span: head,
input_span: span,
})?;
Ok(out.clone())
})?
.to_owned()
.to_owned())
}
Pick::MedianAverage => {
let idx_end = values.len() / 2;
@ -143,7 +124,8 @@ pub fn median(values: &[Value], span: Span, head: Span) -> Result<Value, ShellEr
msg_span: head,
input_span: span,
})?
.clone();
.to_owned()
.to_owned();
let right = sorted
.get(idx_end)
@ -153,7 +135,8 @@ pub fn median(values: &[Value], span: Span, head: Span) -> Result<Value, ShellEr
msg_span: head,
input_span: span,
})?
.clone();
.to_owned()
.to_owned();
average(&[left, right], span, head)
}

View File

@ -111,29 +111,12 @@ impl Command for SubCommand {
}
pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellError> {
if let Some(Err(values)) = values
.windows(2)
.map(|elem| {
if elem[0].partial_cmp(&elem[1]).is_none() {
return Err(ShellError::OperatorMismatch {
op_span: head,
lhs_ty: elem[0].get_type().to_string(),
lhs_span: elem[0].span(),
rhs_ty: elem[1].get_type().to_string(),
rhs_span: elem[1].span(),
});
}
Ok(elem[0].partial_cmp(&elem[1]).unwrap_or(Ordering::Equal))
})
.find(|elem| elem.is_err())
{
return Err(values);
}
//In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside
// But f64 doesn't implement Hash, so we get the binary representation to use as
// key in the HashMap
let hashable_values = values
.iter()
.filter(|x| !x.as_float().is_ok_and(f64::is_nan))
.map(|val| match val {
Value::Int { val, .. } => Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Int)),
Value::Duration { val, .. } => {

View File

@ -32,18 +32,8 @@ pub fn max(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
.clone();
for value in &data {
if let Some(result) = value.partial_cmp(&biggest) {
if result == Ordering::Greater {
biggest = value.clone();
}
} else {
return Err(ShellError::OperatorMismatch {
op_span: head,
lhs_ty: biggest.get_type().to_string(),
lhs_span: biggest.span(),
rhs_ty: value.get_type().to_string(),
rhs_span: value.span(),
});
if value.partial_cmp(&biggest) == Some(Ordering::Greater) {
biggest = value.clone();
}
}
Ok(biggest)
@ -61,18 +51,8 @@ pub fn min(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError
.clone();
for value in &data {
if let Some(result) = value.partial_cmp(&smallest) {
if result == Ordering::Less {
smallest = value.clone();
}
} else {
return Err(ShellError::OperatorMismatch {
op_span: head,
lhs_ty: smallest.get_type().to_string(),
lhs_span: smallest.span(),
rhs_ty: value.get_type().to_string(),
rhs_span: value.span(),
});
if value.partial_cmp(&smallest) == Some(Ordering::Less) {
smallest = value.clone();
}
}
Ok(smallest)

View File

@ -62,7 +62,9 @@ fn concat_assign_type_mismatch() {
$a ++= 'str'
"#);
assert!(actual.err.contains("nu::parser::unsupported_operation"));
assert!(actual
.err
.contains("nu::parser::operator_incompatible_types"));
}
#[test]
@ -72,5 +74,7 @@ fn concat_assign_runtime_type_mismatch() {
$a ++= if true { 'str' }
"#);
assert!(actual.err.contains("nu::shell::type_mismatch"));
assert!(actual
.err
.contains("nu::shell::operator_incompatible_types"));
}

View File

@ -107,7 +107,9 @@ fn error_reduce_fold_type_mismatch() {
"echo a b c | reduce --fold 0 { |it, acc| $acc + $it }"
));
assert!(actual.err.contains("mismatch"));
assert!(actual
.err
.contains("nu::shell::operator_incompatible_types"));
}
#[test]

View File

@ -164,13 +164,13 @@ pub(crate) fn compile_expression(
Ok(())
}
Expr::BinaryOp(lhs, op, rhs) => {
if let Expr::Operator(ref operator) = op.expr {
if let Expr::Operator(operator) = op.expr {
drop_input(builder)?;
compile_binary_op(
working_set,
builder,
lhs,
operator.clone().into_spanned(op.span),
operator.into_spanned(op.span),
rhs,
expr.span,
out_reg,

View File

@ -150,11 +150,11 @@ pub(crate) fn compile_binary_op(
pub(crate) fn decompose_assignment(assignment: Assignment) -> Option<Operator> {
match assignment {
Assignment::Assign => None,
Assignment::PlusAssign => Some(Operator::Math(Math::Plus)),
Assignment::ConcatAssign => Some(Operator::Math(Math::Concat)),
Assignment::MinusAssign => Some(Operator::Math(Math::Minus)),
Assignment::AddAssign => Some(Operator::Math(Math::Add)),
Assignment::SubtractAssign => Some(Operator::Math(Math::Subtract)),
Assignment::MultiplyAssign => Some(Operator::Math(Math::Multiply)),
Assignment::DivideAssign => Some(Operator::Math(Math::Divide)),
Assignment::ConcatenateAssign => Some(Operator::Math(Math::Concatenate)),
}
}

View File

@ -520,11 +520,11 @@ impl Eval for EvalRuntime {
let rhs = match assignment {
Assignment::Assign => rhs,
Assignment::PlusAssign => {
Assignment::AddAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.add(op_span, &rhs, op_span)?
}
Assignment::MinusAssign => {
Assignment::SubtractAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.sub(op_span, &rhs, op_span)?
}
@ -536,7 +536,7 @@ impl Eval for EvalRuntime {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.div(op_span, &rhs, op_span)?
}
Assignment::ConcatAssign => {
Assignment::ConcatenateAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.concat(op_span, &rhs, op_span)?
}

View File

@ -970,19 +970,19 @@ fn binary_op(
Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
},
Operator::Math(mat) => match mat {
Math::Plus => lhs_val.add(op_span, &rhs_val, span)?,
Math::Concat => lhs_val.concat(op_span, &rhs_val, span)?,
Math::Minus => lhs_val.sub(op_span, &rhs_val, span)?,
Math::Add => lhs_val.add(op_span, &rhs_val, span)?,
Math::Subtract => lhs_val.sub(op_span, &rhs_val, span)?,
Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?,
Math::Divide => lhs_val.div(op_span, &rhs_val, span)?,
Math::FloorDivide => lhs_val.floor_div(op_span, &rhs_val, span)?,
Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?,
Math::FloorDivision => lhs_val.floor_div(op_span, &rhs_val, span)?,
Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?,
Math::Concatenate => lhs_val.concat(op_span, &rhs_val, span)?,
},
Operator::Boolean(bl) => match bl {
Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?,
Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?,
Boolean::And => lhs_val.and(op_span, &rhs_val, span)?,
},
Operator::Bits(bit) => match bit {
Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?,

View File

@ -5136,11 +5136,11 @@ pub fn parse_assignment_operator(working_set: &mut StateWorkingSet, span: Span)
let operator = match contents {
b"=" => Operator::Assignment(Assignment::Assign),
b"+=" => Operator::Assignment(Assignment::PlusAssign),
b"++=" => Operator::Assignment(Assignment::ConcatAssign),
b"-=" => Operator::Assignment(Assignment::MinusAssign),
b"+=" => Operator::Assignment(Assignment::AddAssign),
b"-=" => Operator::Assignment(Assignment::SubtractAssign),
b"*=" => Operator::Assignment(Assignment::MultiplyAssign),
b"/=" => Operator::Assignment(Assignment::DivideAssign),
b"++=" => Operator::Assignment(Assignment::ConcatenateAssign),
_ => {
working_set.error(ParseError::Expected("assignment operator", span));
return garbage(working_set, span);
@ -5276,28 +5276,28 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
b"=~" | b"like" => Operator::Comparison(Comparison::RegexMatch),
b"!~" | b"not-like" => Operator::Comparison(Comparison::NotRegexMatch),
b"+" => Operator::Math(Math::Plus),
b"++" => Operator::Math(Math::Concat),
b"-" => Operator::Math(Math::Minus),
b"*" => Operator::Math(Math::Multiply),
b"/" => Operator::Math(Math::Divide),
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"starts-with" => Operator::Comparison(Comparison::StartsWith),
b"ends-with" => Operator::Comparison(Comparison::EndsWith),
b"+" => Operator::Math(Math::Add),
b"-" => Operator::Math(Math::Subtract),
b"*" => Operator::Math(Math::Multiply),
b"/" => Operator::Math(Math::Divide),
b"//" => Operator::Math(Math::FloorDivide),
b"mod" => Operator::Math(Math::Modulo),
b"**" => Operator::Math(Math::Pow),
b"++" => Operator::Math(Math::Concatenate),
b"bit-or" => Operator::Bits(Bits::BitOr),
b"bit-xor" => Operator::Bits(Bits::BitXor),
b"bit-and" => Operator::Bits(Bits::BitAnd),
b"bit-shl" => Operator::Bits(Bits::ShiftLeft),
b"bit-shr" => Operator::Bits(Bits::ShiftRight),
b"starts-with" => Operator::Comparison(Comparison::StartsWith),
b"ends-with" => Operator::Comparison(Comparison::EndsWith),
b"and" => Operator::Boolean(Boolean::And),
b"or" => Operator::Boolean(Boolean::Or),
b"xor" => Operator::Boolean(Boolean::Xor),
b"**" => Operator::Math(Math::Pow),
b"and" => Operator::Boolean(Boolean::And),
// WARNING: not actual operators below! Error handling only
pow @ (b"^" | b"pow") => {
working_set.error(ParseError::UnknownOperator(

File diff suppressed because it is too large Load Diff

View File

@ -1934,17 +1934,10 @@ mod range {
let _ = parse(&mut working_set, None, code.as_bytes(), true);
assert!(
working_set.parse_errors.len() == 1,
"Errors: {:?}",
working_set.parse_errors
);
let err = &working_set.parse_errors[0].to_string();
assert!(
err.contains("range is not supported"),
"Expected unsupported operation error, got {}",
err
);
assert!(matches!(
&working_set.parse_errors[..],
[ParseError::OperatorUnsupportedType { .. }]
),);
}
#[test]

View File

@ -1489,7 +1489,7 @@ fn prepare_plugin_call_custom_value_op() {
span,
},
CustomValueOp::Operation(
Operator::Math(Math::Concat).into_spanned(span),
Operator::Math(Math::Concatenate).into_spanned(span),
cv_ok_val.clone(),
),
),
@ -1502,7 +1502,7 @@ fn prepare_plugin_call_custom_value_op() {
span,
},
CustomValueOp::Operation(
Operator::Math(Math::Concat).into_spanned(span),
Operator::Math(Math::Concatenate).into_spanned(span),
cv_bad_val.clone(),
),
),

View File

@ -1,11 +1,9 @@
use crate::{ShellError, Span};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use super::{Expr, Expression};
use crate::{ShellError, Span};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Comparison {
Equal,
NotEqual,
@ -23,26 +21,90 @@ pub enum Comparison {
EndsWith,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
impl Comparison {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Equal => "==",
Self::NotEqual => "!=",
Self::LessThan => "<",
Self::GreaterThan => ">",
Self::LessThanOrEqual => "<=",
Self::GreaterThanOrEqual => ">=",
Self::RegexMatch => "=~",
Self::NotRegexMatch => "!~",
Self::In => "in",
Self::NotIn => "not-in",
Self::Has => "has",
Self::NotHas => "not-has",
Self::StartsWith => "starts-with",
Self::EndsWith => "ends-with",
}
}
}
impl fmt::Display for Comparison {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Math {
Plus,
Concat,
Minus,
Add,
Subtract,
Multiply,
Divide,
FloorDivide,
Modulo,
FloorDivision,
Pow,
Concatenate,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
impl Math {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Add => "+",
Self::Subtract => "-",
Self::Multiply => "*",
Self::Divide => "/",
Self::FloorDivide => "//",
Self::Modulo => "mod",
Self::Pow => "**",
Self::Concatenate => "++",
}
}
}
impl fmt::Display for Math {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Boolean {
And,
Or,
Xor,
And,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
impl Boolean {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Or => "or",
Self::Xor => "xor",
Self::And => "and",
}
}
}
impl fmt::Display for Boolean {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Bits {
BitOr,
BitXor,
@ -51,17 +113,54 @@ pub enum Bits {
ShiftRight,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Assignment {
Assign,
PlusAssign,
ConcatAssign,
MinusAssign,
MultiplyAssign,
DivideAssign,
impl Bits {
pub const fn as_str(&self) -> &'static str {
match self {
Self::BitOr => "bit-or",
Self::BitXor => "bit-xor",
Self::BitAnd => "bit-and",
Self::ShiftLeft => "bit-shl",
Self::ShiftRight => "bit-shr",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
impl fmt::Display for Bits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Assignment {
Assign,
AddAssign,
SubtractAssign,
MultiplyAssign,
DivideAssign,
ConcatenateAssign,
}
impl Assignment {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Assign => "=",
Self::AddAssign => "+=",
Self::SubtractAssign => "-=",
Self::MultiplyAssign => "*=",
Self::DivideAssign => "/=",
Self::ConcatenateAssign => "++=",
}
}
}
impl fmt::Display for Assignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Operator {
Comparison(Comparison),
Math(Math),
@ -71,14 +170,24 @@ pub enum Operator {
}
impl Operator {
pub fn precedence(&self) -> u8 {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Comparison(comparison) => comparison.as_str(),
Self::Math(math) => math.as_str(),
Self::Boolean(boolean) => boolean.as_str(),
Self::Bits(bits) => bits.as_str(),
Self::Assignment(assignment) => assignment.as_str(),
}
}
pub const fn precedence(&self) -> u8 {
match self {
Self::Math(Math::Pow) => 100,
Self::Math(Math::Multiply)
| Self::Math(Math::Divide)
| Self::Math(Math::Modulo)
| Self::Math(Math::FloorDivision) => 95,
Self::Math(Math::Plus) | Self::Math(Math::Minus) => 90,
| Self::Math(Math::FloorDivide) => 95,
Self::Math(Math::Add) | Self::Math(Math::Subtract) => 90,
Self::Bits(Bits::ShiftLeft) | Self::Bits(Bits::ShiftRight) => 85,
Self::Comparison(Comparison::NotRegexMatch)
| Self::Comparison(Comparison::RegexMatch)
@ -94,7 +203,7 @@ impl Operator {
| Self::Comparison(Comparison::NotIn)
| Self::Comparison(Comparison::Has)
| Self::Comparison(Comparison::NotHas)
| Self::Math(Math::Concat) => 80,
| Self::Math(Math::Concatenate) => 80,
Self::Bits(Bits::BitAnd) => 75,
Self::Bits(Bits::BitXor) => 70,
Self::Bits(Bits::BitOr) => 60,
@ -106,46 +215,9 @@ impl Operator {
}
}
impl Display for Operator {
impl fmt::Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Operator::Assignment(Assignment::Assign) => write!(f, "="),
Operator::Assignment(Assignment::PlusAssign) => write!(f, "+="),
Operator::Assignment(Assignment::ConcatAssign) => write!(f, "++="),
Operator::Assignment(Assignment::MinusAssign) => write!(f, "-="),
Operator::Assignment(Assignment::MultiplyAssign) => write!(f, "*="),
Operator::Assignment(Assignment::DivideAssign) => write!(f, "/="),
Operator::Comparison(Comparison::Equal) => write!(f, "=="),
Operator::Comparison(Comparison::NotEqual) => write!(f, "!="),
Operator::Comparison(Comparison::LessThan) => write!(f, "<"),
Operator::Comparison(Comparison::GreaterThan) => write!(f, ">"),
Operator::Comparison(Comparison::RegexMatch) => write!(f, "=~ or like"),
Operator::Comparison(Comparison::NotRegexMatch) => write!(f, "!~ or not-like"),
Operator::Comparison(Comparison::LessThanOrEqual) => write!(f, "<="),
Operator::Comparison(Comparison::GreaterThanOrEqual) => write!(f, ">="),
Operator::Comparison(Comparison::StartsWith) => write!(f, "starts-with"),
Operator::Comparison(Comparison::EndsWith) => write!(f, "ends-with"),
Operator::Comparison(Comparison::In) => write!(f, "in"),
Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"),
Operator::Comparison(Comparison::Has) => write!(f, "has"),
Operator::Comparison(Comparison::NotHas) => write!(f, "not-has"),
Operator::Math(Math::Plus) => write!(f, "+"),
Operator::Math(Math::Concat) => write!(f, "++"),
Operator::Math(Math::Minus) => write!(f, "-"),
Operator::Math(Math::Multiply) => write!(f, "*"),
Operator::Math(Math::Divide) => write!(f, "/"),
Operator::Math(Math::Modulo) => write!(f, "mod"),
Operator::Math(Math::FloorDivision) => write!(f, "//"),
Operator::Math(Math::Pow) => write!(f, "**"),
Operator::Boolean(Boolean::And) => write!(f, "and"),
Operator::Boolean(Boolean::Or) => write!(f, "or"),
Operator::Boolean(Boolean::Xor) => write!(f, "xor"),
Operator::Bits(Bits::BitOr) => write!(f, "bit-or"),
Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"),
Operator::Bits(Bits::BitAnd) => write!(f, "bit-and"),
Operator::Bits(Bits::ShiftLeft) => write!(f, "bit-shl"),
Operator::Bits(Bits::ShiftRight) => write!(f, "bit-shr"),
}
f.write_str(self.as_str())
}
}
@ -162,7 +234,7 @@ pub struct RangeOperator {
pub next_op_span: Span,
}
impl Display for RangeOperator {
impl fmt::Display for RangeOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inclusion {
RangeInclusion::Inclusive => write!(f, ".."),
@ -176,7 +248,7 @@ pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
Expression {
expr: Expr::Operator(operator),
..
} => Ok(operator.clone()),
} => Ok(*operator),
Expression { span, expr, .. } => Err(ShellError::UnknownOperator {
op_token: format!("{expr:?}"),
span: *span,

View File

@ -1,11 +1,10 @@
use crate::{ast::RedirectionSource, did_you_mean, Span, Type};
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use std::{
fmt::Display,
str::{from_utf8, Utf8Error},
};
use crate::{ast::RedirectionSource, did_you_mean, Span, Type};
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)]
@ -115,38 +114,36 @@ pub enum ParseError {
span: Span,
},
#[error("{0} is not supported on values of type {3}")]
#[diagnostic(code(nu::parser::unsupported_operation))]
UnsupportedOperationLHS(
String,
#[label = "doesn't support this value"] Span,
#[label("{3}")] Span,
Type,
),
/// One or more of the values have types not supported by the operator.
#[error("The '{op}' operator does not work on values of type '{unsupported}'.")]
#[diagnostic(code(nu::parser::operator_unsupported_type))]
OperatorUnsupportedType {
op: &'static str,
unsupported: Type,
#[label = "does not support '{unsupported}'"]
op_span: Span,
#[label("{unsupported}")]
unsupported_span: Span,
#[help]
help: Option<&'static str>,
},
#[error("{0} is not supported between {3} and {5}.")]
#[diagnostic(code(nu::parser::unsupported_operation))]
UnsupportedOperationRHS(
String,
#[label = "doesn't support these values"] Span,
#[label("{3}")] Span,
Type,
#[label("{5}")] Span,
Type,
),
#[error("{0} is not supported between {3}, {5}, and {7}.")]
#[diagnostic(code(nu::parser::unsupported_operation))]
UnsupportedOperationTernary(
String,
#[label = "doesn't support these values"] Span,
#[label("{3}")] Span,
Type,
#[label("{5}")] Span,
Type,
#[label("{7}")] Span,
Type,
),
/// The operator supports the types of both values, but not the specific combination of their types.
#[error("Types '{lhs}' and '{rhs}' are not compatible for the '{op}' operator.")]
#[diagnostic(code(nu::parser::operator_incompatible_types))]
OperatorIncompatibleTypes {
op: &'static str,
lhs: Type,
rhs: Type,
#[label = "does not operate between '{lhs}' and '{rhs}'"]
op_span: Span,
#[label("{lhs}")]
lhs_span: Span,
#[label("{rhs}")]
rhs_span: Span,
#[help]
help: Option<&'static str>,
},
#[error("Capture of mutable variable.")]
#[diagnostic(code(nu::parser::expected_keyword))]
@ -564,9 +561,8 @@ impl ParseError {
ParseError::ExpectedWithStringMsg(_, s) => *s,
ParseError::ExpectedWithDidYouMean(_, _, s) => *s,
ParseError::Mismatch(_, _, s) => *s,
ParseError::UnsupportedOperationLHS(_, _, s, _) => *s,
ParseError::UnsupportedOperationRHS(_, _, _, _, s, _) => *s,
ParseError::UnsupportedOperationTernary(_, _, _, _, _, _, s, _) => *s,
ParseError::OperatorUnsupportedType { op_span, .. } => *op_span,
ParseError::OperatorIncompatibleTypes { op_span, .. } => *op_span,
ParseError::ExpectedKeyword(_, s) => *s,
ParseError::UnexpectedKeyword(_, s) => *s,
ParseError::CantAliasKeyword(_, s) => *s,

View File

@ -17,22 +17,35 @@ pub mod location;
/// and pass it into an error viewer to display to the user.
#[derive(Debug, Clone, Error, Diagnostic, PartialEq)]
pub enum ShellError {
/// An operator received two arguments of incompatible types.
///
/// ## Resolution
///
/// Check each argument's type and convert one or both as needed.
#[error("Type mismatch during operation.")]
#[diagnostic(code(nu::shell::type_mismatch))]
OperatorMismatch {
#[label = "type mismatch for operator"]
/// One or more of the values have types not supported by the operator.
#[error("The '{op}' operator does not work on values of type '{unsupported}'.")]
#[diagnostic(code(nu::shell::operator_unsupported_type))]
OperatorUnsupportedType {
op: Operator,
unsupported: Type,
#[label = "does not support '{unsupported}'"]
op_span: Span,
lhs_ty: String,
#[label("{lhs_ty}")]
#[label("{unsupported}")]
unsupported_span: Span,
#[help]
help: Option<&'static str>,
},
/// The operator supports the types of both values, but not the specific combination of their types.
#[error("Types '{lhs}' and '{rhs}' are not compatible for the '{op}' operator.")]
#[diagnostic(code(nu::shell::operator_incompatible_types))]
OperatorIncompatibleTypes {
op: Operator,
lhs: Type,
rhs: Type,
#[label = "does not operate between '{lhs}' and '{rhs}'"]
op_span: Span,
#[label("{lhs}")]
lhs_span: Span,
rhs_ty: String,
#[label("{rhs_ty}")]
#[label("{rhs}")]
rhs_span: Span,
#[help]
help: Option<&'static str>,
},
/// An arithmetic operation's resulting value overflowed its possible size.
@ -156,20 +169,6 @@ pub enum ShellError {
call_span: Span,
},
/// This value cannot be used with this operator.
///
/// ## Resolution
///
/// Not all values, for example custom values, can be used with all operators. Either
/// implement support for the operator on this type, or convert the type to a supported one.
#[error("Unsupported operator: {operator}.")]
#[diagnostic(code(nu::shell::unsupported_operator))]
UnsupportedOperator {
operator: Operator,
#[label = "unsupported operator"]
span: Span,
},
/// Invalid assignment left-hand side
///
/// ## Resolution

View File

@ -235,14 +235,14 @@ pub trait Eval {
let rhs = Self::eval::<D>(state, mut_state, rhs)?;
match math {
Math::Plus => lhs.add(op_span, &rhs, expr_span),
Math::Minus => lhs.sub(op_span, &rhs, expr_span),
Math::Add => lhs.add(op_span, &rhs, expr_span),
Math::Subtract => lhs.sub(op_span, &rhs, expr_span),
Math::Multiply => lhs.mul(op_span, &rhs, expr_span),
Math::Divide => lhs.div(op_span, &rhs, expr_span),
Math::Concat => lhs.concat(op_span, &rhs, expr_span),
Math::FloorDivide => lhs.floor_div(op_span, &rhs, expr_span),
Math::Modulo => lhs.modulo(op_span, &rhs, expr_span),
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr_span),
Math::Pow => lhs.pow(op_span, &rhs, expr_span),
Math::Concatenate => lhs.concat(op_span, &rhs, expr_span),
}
}
Operator::Comparison(comparison) => {

View File

@ -1,6 +1,6 @@
use std::{cmp::Ordering, fmt};
use crate::{ast::Operator, ShellError, Span, Value};
use crate::{ast::Operator, ShellError, Span, Type, Value};
/// Trait definition for a custom [`Value`](crate::Value) type
#[typetag::serde(tag = "type")]
@ -68,7 +68,7 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
///
/// The Operator enum is used to indicate the expected operation.
///
/// Default impl raises [`ShellError::UnsupportedOperator`].
/// Default impl raises [`ShellError::OperatorUnsupportedType`].
fn operation(
&self,
lhs_span: Span,
@ -77,7 +77,13 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
right: &Value,
) -> Result<Value, ShellError> {
let _ = (lhs_span, right);
Err(ShellError::UnsupportedOperator { operator, span: op })
Err(ShellError::OperatorUnsupportedType {
op: operator,
unsupported: Type::Custom(self.type_name().into()),
op_span: op,
unsupported_span: lhs_span,
help: None,
})
}
/// For custom values in plugins: return `true` here if you would like to be notified when all

File diff suppressed because it is too large Load Diff

View File

@ -32,45 +32,48 @@ def command-not-found-error [span: record] {
throw-error "std::help::command_not_found" "command not found" $span
}
def get-all-operators [] { return [
[type, operator, name, description, precedence];
[Assignment, =, Assign, "Assigns a value to a variable.", 10]
[Assignment, +=, PlusAssign, "Adds a value to a variable.", 10]
[Assignment, ++=, ConcatAssign, "Concatenate two lists, two strings, or two binary values.", 10]
[Assignment, -=, MinusAssign, "Subtracts a value from a variable.", 10]
[Assignment, *=, MultiplyAssign, "Multiplies a variable by a value.", 10]
[Assignment, /=, DivideAssign, "Divides a variable by a value.", 10]
[Comparison, ==, Equal, "Checks if two values are equal.", 80]
[Comparison, !=, NotEqual, "Checks if two values are not equal.", 80]
[Comparison, <, LessThan, "Checks if a value is less than another.", 80]
[Comparison, <=, LessThanOrEqual, "Checks if a value is less than or equal to another.", 80]
[Comparison, >, GreaterThan, "Checks if a value is greater than another.", 80]
[Comparison, >=, GreaterThanOrEqual, "Checks if a value is greater than or equal to another.", 80]
[Comparison, '=~ or like', RegexMatch, "Checks if a value matches a regular expression.", 80]
[Comparison, '!~ or not-like', NotRegexMatch, "Checks if a value does not match a regular expression.", 80]
[Comparison, in, In, "Checks if a value is in a list or string.", 80]
[Comparison, not-in, NotIn, "Checks if a value is not in a list or string.", 80]
[Comparison, starts-with, StartsWith, "Checks if a string starts with another.", 80]
[Comparison, ends-with, EndsWith, "Checks if a string ends with another.", 80]
[Comparison, not, UnaryNot, "Negates a value or expression.", 0]
[Math, +, Plus, "Adds two values.", 90]
[Math, ++, Concat, "Concatenate two lists, two strings, or two binary values.", 80]
[Math, -, Minus, "Subtracts two values.", 90]
[Math, *, Multiply, "Multiplies two values.", 95]
[Math, /, Divide, "Divides two values.", 95]
[Math, //, FloorDivision, "Divides two values and floors the result.", 95]
[Math, mod, Modulo, "Divides two values and returns the remainder.", 95]
[Math, **, "Pow ", "Raises one value to the power of another.", 100]
[Bitwise, bit-or, BitOr, "Performs a bitwise OR on two values.", 60]
[Bitwise, bit-xor, BitXor, "Performs a bitwise XOR on two values.", 70]
[Bitwise, bit-and, BitAnd, "Performs a bitwise AND on two values.", 75]
[Bitwise, bit-shl, ShiftLeft, "Shifts a value left by another.", 85]
[Bitwise, bit-shr, ShiftRight, "Shifts a value right by another.", 85]
[Boolean, and, And, "Checks if two values are true.", 50]
[Boolean, or, Or, "Checks if either value is true.", 40]
[Boolean, xor, Xor, "Checks if one value is true and the other is false.", 45]
]}
def get-all-operators [] {
[
[type, operator, name, description, precedence];
[Assignment, =, Assign, 'Assigns a value to a variable.', 10]
[Assignment, +=, AddAssign, 'Adds a value to a variable.', 10]
[Assignment, -=, SubtractAssign, 'Subtracts a value from a variable.', 10]
[Assignment, *=, MultiplyAssign, 'Multiplies a variable by a value.', 10]
[Assignment, /=, DivideAssign, 'Divides a variable by a value.', 10]
[Assignment, ++=, ConcatenateAssign, 'Concatenates a list, a string, or a binary value to a variable of the same type.', 10]
[Comparison, ==, Equal, 'Checks if two values are equal.', 80]
[Comparison, !=, NotEqual, 'Checks if two values are not equal.', 80]
[Comparison, <, LessThan, 'Checks if a value is less than another.', 80]
[Comparison, >, GreaterThan, 'Checks if a value is greater than another.', 80]
[Comparison, <=, LessThanOrEqual, 'Checks if a value is less than or equal to another.', 80]
[Comparison, >=, GreaterThanOrEqual, 'Checks if a value is greater than or equal to another.', 80]
[Comparison, '=~ or like', RegexMatch, 'Checks if a value matches a regular expression.', 80]
[Comparison, '!~ or not-like', NotRegexMatch, 'Checks if a value does not match a regular expression.', 80]
[Comparison, in, In, 'Checks if a value is in a list, is part of a string, or is a key in a record.', 80]
[Comparison, not-in, NotIn, 'Checks if a value is not in a list, is not part of a string, or is not a key in a record.', 80]
[Comparison, has, Has, 'Checks if a list contains a value, a string contains another, or if a record has a key.', 80]
[Comparison, not-has, NotHas, 'Checks if a list does not contains a value, a string does not contains another, or if a record does not have a key.', 80]
[Comparison, starts-with, StartsWith, 'Checks if a string starts with another.', 80]
[Comparison, ends-with, EndsWith, 'Checks if a string ends with another.', 80]
[Math, +, Add, 'Adds two values.', 90]
[Math, -, Subtract, 'Subtracts two values.', 90]
[Math, *, Multiply, 'Multiplies two values.', 95]
[Math, /, Divide, 'Divides two values.', 95]
[Math, //, FloorDivide, 'Divides two values and floors the result.', 95]
[Math, mod, Modulo, 'Divides two values and returns the remainder.', 95]
[Math, **, Pow, 'Raises one value to the power of another.', 100]
[Math, ++, Concatenate, 'Concatenates two lists, two strings, or two binary values.', 80]
[Bitwise, bit-or, BitOr, 'Performs a bitwise OR on two values.', 60]
[Bitwise, bit-xor, BitXor, 'Performs a bitwise XOR on two values.', 70]
[Bitwise, bit-and, BitAnd, 'Performs a bitwise AND on two values.', 75]
[Bitwise, bit-shl, ShiftLeft, 'Bitwise shifts a value left by another.', 85]
[Bitwise, bit-shr, ShiftRight, 'Bitwise shifts a value right by another.', 85]
[Boolean, or, Or, 'Checks if either value is true.', 40]
[Boolean, xor, Xor, 'Checks if one value is true and the other is false.', 45]
[Boolean, and, And, 'Checks if both values are true.', 50]
[Boolean, not, Not, 'Negates a value or expression.', 55]
]
}
def "nu-complete list-aliases" [] {
scope aliases | select name description | rename value description

View File

@ -1,4 +1,7 @@
use nu_protocol::{ast, CustomValue, ShellError, Span, Value};
use nu_protocol::{
ast::{self, Math, Operator},
CustomValue, ShellError, Span, Type, Value,
};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
@ -112,7 +115,7 @@ impl CustomValue for CoolCustomValue {
) -> Result<Value, ShellError> {
match operator {
// Append the string inside `cool`
ast::Operator::Math(ast::Math::Concat) => {
Operator::Math(Math::Concatenate) => {
if let Some(right) = right
.as_custom_value()
.ok()
@ -125,18 +128,21 @@ impl CustomValue for CoolCustomValue {
op_span,
))
} else {
Err(ShellError::OperatorMismatch {
Err(ShellError::OperatorUnsupportedType {
op: Operator::Math(Math::Concatenate),
unsupported: right.get_type(),
op_span,
lhs_ty: self.typetag_name().into(),
lhs_span,
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
})
}
}
_ => Err(ShellError::UnsupportedOperator {
operator,
span: op_span,
_ => Err(ShellError::OperatorUnsupportedType {
op: Operator::Math(Math::Concatenate),
unsupported: Type::Custom(self.type_name().into()),
op_span,
unsupported_span: lhs_span,
help: None,
}),
}
}

View File

@ -7,20 +7,21 @@ mod nu_schema;
mod nu_when;
pub mod utils;
use crate::{Cacheable, PolarsPlugin};
use nu_plugin::EngineInterface;
use nu_protocol::{
ast::Operator, CustomValue, PipelineData, ShellError, Span, Spanned, Type, Value,
};
use std::{cmp::Ordering, fmt};
use uuid::Uuid;
pub use file_type::PolarsFileType;
pub use nu_dataframe::{Axis, Column, NuDataFrame, NuDataFrameCustomValue};
pub use nu_expression::{NuExpression, NuExpressionCustomValue};
pub use nu_lazyframe::{NuLazyFrame, NuLazyFrameCustomValue};
pub use nu_lazygroupby::{NuLazyGroupBy, NuLazyGroupByCustomValue};
use nu_plugin::EngineInterface;
use nu_protocol::{ast::Operator, CustomValue, PipelineData, ShellError, Span, Spanned, Value};
pub use nu_schema::{str_to_dtype, NuSchema};
pub use nu_when::{NuWhen, NuWhenCustomValue, NuWhenType};
use uuid::Uuid;
use crate::{Cacheable, PolarsPlugin};
#[derive(Debug, Clone)]
pub enum PolarsPluginType {
@ -217,13 +218,16 @@ pub trait PolarsPluginCustomValue: CustomValue {
&self,
_plugin: &PolarsPlugin,
_engine: &EngineInterface,
_lhs_span: Span,
lhs_span: Span,
operator: Spanned<Operator>,
_right: Value,
) -> Result<Value, ShellError> {
Err(ShellError::UnsupportedOperator {
operator: operator.item,
span: operator.span,
Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: Type::Custom(self.type_name().into()),
op_span: operator.span,
unsupported_span: lhs_span,
help: None,
})
}

View File

@ -18,15 +18,15 @@ pub(super) fn between_dataframes(
rhs: &NuDataFrame,
) -> Result<NuDataFrame, ShellError> {
match operator.item {
Operator::Math(Math::Plus) => {
Operator::Math(Math::Add) => {
lhs.append_df(rhs, Axis::Row, Span::merge(left.span(), right.span()))
}
_ => Err(ShellError::OperatorMismatch {
op => Err(ShellError::OperatorUnsupportedType {
op,
unsupported: left.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: left.span(),
help: None,
}),
}
}
@ -40,7 +40,7 @@ pub(super) fn compute_between_series(
) -> Result<NuDataFrame, ShellError> {
let operation_span = Span::merge(left.span(), right.span());
match operator.item {
Operator::Math(Math::Plus) => {
Operator::Math(Math::Add) => {
let mut res = (lhs + rhs).map_err(|e| ShellError::GenericError {
error: format!("Addition error: {e}"),
msg: "".into(),
@ -52,7 +52,7 @@ pub(super) fn compute_between_series(
res.rename(name.into());
NuDataFrame::try_from_series(res, operation_span)
}
Operator::Math(Math::Minus) => {
Operator::Math(Math::Subtract) => {
let mut res = (lhs - rhs).map_err(|e| ShellError::GenericError {
error: format!("Subtraction error: {e}"),
msg: "".into(),
@ -181,12 +181,12 @@ pub(super) fn compute_between_series(
span: operation_span,
}),
},
_ => Err(ShellError::OperatorMismatch {
op => Err(ShellError::OperatorUnsupportedType {
op,
unsupported: left.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: left.span(),
help: None,
}),
}
}
@ -222,12 +222,12 @@ pub(super) fn compute_series_single_value(
right: &Value,
) -> Result<NuDataFrame, ShellError> {
if !lhs.is_series() {
return Err(ShellError::OperatorMismatch {
return Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: left.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: left.span(),
help: None,
});
}
@ -235,7 +235,7 @@ pub(super) fn compute_series_single_value(
let lhs = lhs.as_series(lhs_span)?;
match operator.item {
Operator::Math(Math::Plus) => match &right {
Operator::Math(Math::Add) => match &right {
Value::Int { val, .. } => {
compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::add, lhs_span)
}
@ -243,27 +243,27 @@ pub(super) fn compute_series_single_value(
compute_series_float(&lhs, *val, <ChunkedArray<Float64Type>>::add, lhs_span)
}
Value::String { val, .. } => add_string_to_series(&lhs, val, lhs_span),
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Math(Math::Minus) => match &right {
Operator::Math(Math::Subtract) => match &right {
Value::Int { val, .. } => {
compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::sub, lhs_span)
}
Value::Float { val, .. } => {
compute_series_float(&lhs, *val, <ChunkedArray<Float64Type>>::sub, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Math(Math::Multiply) => match &right {
@ -273,12 +273,12 @@ pub(super) fn compute_series_single_value(
Value::Float { val, .. } => {
compute_series_float(&lhs, *val, <ChunkedArray<Float64Type>>::mul, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Math(Math::Divide) => {
@ -298,12 +298,12 @@ pub(super) fn compute_series_single_value(
compute_series_float(&lhs, *val, <ChunkedArray<Float64Type>>::div, lhs_span)
}
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
}
}
@ -319,12 +319,12 @@ pub(super) fn compute_series_single_value(
Value::Date { val, .. } => {
compare_series_i64(&lhs, val.timestamp_millis(), ChunkedArray::equal, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::NotEqual) => match &right {
@ -344,12 +344,12 @@ pub(super) fn compute_series_single_value(
ChunkedArray::not_equal,
lhs_span,
),
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::LessThan) => match &right {
@ -360,12 +360,12 @@ pub(super) fn compute_series_single_value(
Value::Date { val, .. } => {
compare_series_i64(&lhs, val.timestamp_millis(), ChunkedArray::lt, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::LessThanOrEqual) => match &right {
@ -376,12 +376,12 @@ pub(super) fn compute_series_single_value(
Value::Date { val, .. } => {
compare_series_i64(&lhs, val.timestamp_millis(), ChunkedArray::lt_eq, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::GreaterThan) => match &right {
@ -392,12 +392,12 @@ pub(super) fn compute_series_single_value(
Value::Date { val, .. } => {
compare_series_i64(&lhs, val.timestamp_millis(), ChunkedArray::gt, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::GreaterThanOrEqual) => match &right {
@ -408,23 +408,23 @@ pub(super) fn compute_series_single_value(
Value::Date { val, .. } => {
compare_series_i64(&lhs, val.timestamp_millis(), ChunkedArray::gt_eq, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
// TODO: update this to do a regex match instead of a simple contains?
Operator::Comparison(Comparison::RegexMatch) => match &right {
Value::String { val, .. } => contains_series_pat(&lhs, val, lhs_span),
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::StartsWith) => match &right {
@ -432,12 +432,12 @@ pub(super) fn compute_series_single_value(
let starts_with_pattern = format!("^{}", fancy_regex::escape(val));
contains_series_pat(&lhs, &starts_with_pattern, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
Operator::Comparison(Comparison::EndsWith) => match &right {
@ -445,20 +445,20 @@ pub(super) fn compute_series_single_value(
let ends_with_pattern = format!("{}$", fancy_regex::escape(val));
contains_series_pat(&lhs, &ends_with_pattern, lhs_span)
}
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: right.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: right.span(),
help: None,
}),
},
_ => Err(ShellError::OperatorMismatch {
_ => Err(ShellError::OperatorUnsupportedType {
op: operator.item,
unsupported: left.get_type(),
op_span: operator.span,
lhs_ty: left.get_type().to_string(),
lhs_span: left.span(),
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
unsupported_span: left.span(),
help: None,
}),
}
}

View File

@ -81,14 +81,14 @@ fn with_operator(
left: &NuExpression,
right: &NuExpression,
lhs_span: Span,
rhs_span: Span,
_rhs_span: Span,
op_span: Span,
) -> Result<Value, ShellError> {
match operator {
Operator::Math(Math::Plus) => {
Operator::Math(Math::Add) => {
apply_arithmetic(plugin, engine, left, right, lhs_span, Add::add)
}
Operator::Math(Math::Minus) => {
Operator::Math(Math::Subtract) => {
apply_arithmetic(plugin, engine, left, right, lhs_span, Sub::sub)
}
Operator::Math(Math::Multiply) => {
@ -100,7 +100,7 @@ fn with_operator(
Operator::Math(Math::Modulo) => {
apply_arithmetic(plugin, engine, left, right, lhs_span, Rem::rem)
}
Operator::Math(Math::FloorDivision) => {
Operator::Math(Math::FloorDivide) => {
apply_arithmetic(plugin, engine, left, right, lhs_span, Div::div)
}
Operator::Comparison(Comparison::Equal) => Ok(left
@ -133,12 +133,12 @@ fn with_operator(
.apply_with_expr(right.clone(), Expr::lt_eq)
.cache(plugin, engine, lhs_span)?
.into_value(lhs_span)),
_ => Err(ShellError::OperatorMismatch {
op => Err(ShellError::OperatorUnsupportedType {
op,
unsupported: Type::Custom(TYPE_NAME.into()),
op_span,
lhs_ty: Type::Custom(TYPE_NAME.into()).to_string(),
lhs_span,
rhs_ty: Type::Custom(TYPE_NAME.into()).to_string(),
rhs_span,
unsupported_span: lhs_span,
help: None,
}),
}
}

View File

@ -167,7 +167,7 @@ fn const_binary_operator(#[case] inp: &[&str], #[case] expect: &str) {
#[case(&["const x = 1 / 0", "$x"], "division by zero")]
#[case(&["const x = 10 ** 10000000", "$x"], "pow operation overflowed")]
#[case(&["const x = 2 ** 62 * 2", "$x"], "multiply operation overflowed")]
#[case(&["const x = 1 ++ 0", "$x"], "doesn't support this value")]
#[case(&["const x = 1 ++ 0", "$x"], "nu::parser::operator_unsupported_type")]
fn const_operator_error(#[case] inp: &[&str], #[case] expect: &str) {
let actual = nu!(&inp.join("; "));
assert!(actual.err.contains(expect));

View File

@ -27,7 +27,7 @@ fn float_in_dec_range() -> TestResult {
#[test]
fn non_number_in_range() -> TestResult {
fail_test(r#"'a' in 1..3"#, "subset comparison is not supported")
fail_test(r#"'a' in 1..3"#, "nu::parser::operator_incompatible_types")
}
#[test]

View File

@ -73,10 +73,10 @@ fn invalid_not_regex_fails() -> TestResult {
#[test]
fn regex_on_int_fails() -> TestResult {
fail_test(r#"33 =~ foo"#, "is not supported")
fail_test(r#"33 =~ foo"#, "nu::parser::operator_unsupported_type")
}
#[test]
fn not_regex_on_int_fails() -> TestResult {
fail_test(r#"33 !~ foo"#, "is not supported")
fail_test(r#"33 !~ foo"#, "nu::parser::operator_unsupported_type")
}

View File

@ -20,7 +20,7 @@ fn string_in_string() -> TestResult {
#[test]
fn non_string_in_string() -> TestResult {
fail_test(r#"42 in 'abc'"#, "is not supported")
fail_test(r#"42 in 'abc'"#, "nu::parser::operator_incompatible_types")
}
#[test]
@ -32,7 +32,7 @@ fn string_in_record() -> TestResult {
fn non_string_in_record() -> TestResult {
fail_test(
r#"4 in ('{ "a": 13, "b": 14 }' | from json)"#,
"mismatch during operation",
"nu::shell::operator_incompatible_types",
)
}

View File

@ -13,7 +13,10 @@ fn type_in_list_of_this_type() -> TestResult {
#[test]
fn type_in_list_of_non_this_type() -> TestResult {
fail_test(r#"'hello' in [41 42 43]"#, "is not supported")
fail_test(
r#"'hello' in [41 42 43]"#,
"nu::parser::operator_incompatible_types",
)
}
#[test]
@ -40,7 +43,10 @@ fn date_minus_duration() -> TestResult {
#[test]
fn duration_minus_date_not_supported() -> TestResult {
fail_test("2day - 2023-04-22", "doesn't support these values")
fail_test(
"2day - 2023-04-22",
"nu::parser::operator_incompatible_types",
)
}
#[test]

View File

@ -770,8 +770,10 @@ fn filesize_math() {
#[test]
fn filesize_math2() {
let actual = nu!("100 / 10kb");
assert!(actual.err.contains("doesn't support"));
let actual = nu!("100 / 10kB");
assert!(actual
.err
.contains("nu::parser::operator_incompatible_types"));
}
#[test]