From 62e56d35812335b41d2d7975ca90ea02068708e3 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Thu, 13 Feb 2025 04:03:40 +0000 Subject: [PATCH] Rework operator type errors (#14429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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`. ``` --- crates/nu-color-config/src/style_computer.rs | 4 +- crates/nu-command/src/help/help_operators.rs | 42 +- crates/nu-command/src/math/median.rs | 41 +- crates/nu-command/src/math/mode.rs | 19 +- crates/nu-command/src/math/reducers.rs | 28 +- .../tests/commands/assignment/concat.rs | 8 +- crates/nu-command/tests/commands/reduce.rs | 4 +- crates/nu-engine/src/compile/expression.rs | 4 +- crates/nu-engine/src/compile/operator.rs | 6 +- crates/nu-engine/src/eval.rs | 6 +- crates/nu-engine/src/eval_ir.rs | 10 +- crates/nu-parser/src/parser.rs | 26 +- crates/nu-parser/src/type_check.rs | 1666 +++++++---------- crates/nu-parser/tests/test_parser.rs | 15 +- .../nu-plugin-engine/src/interface/tests.rs | 4 +- crates/nu-protocol/src/ast/operator.rs | 208 +- crates/nu-protocol/src/errors/parse_error.rs | 72 +- .../nu-protocol/src/errors/shell_error/mod.rs | 53 +- crates/nu-protocol/src/eval_base.rs | 8 +- crates/nu-protocol/src/value/custom_value.rs | 12 +- crates/nu-protocol/src/value/mod.rs | 865 +++++---- crates/nu-std/std/help/mod.nu | 81 +- .../src/cool_custom_value.rs | 26 +- .../src/dataframe/values/mod.rs | 22 +- .../values/nu_dataframe/between_values.rs | 180 +- .../values/nu_expression/custom_value.rs | 18 +- tests/const_/mod.rs | 2 +- tests/repl/test_ranges.rs | 2 +- tests/repl/test_regex.rs | 4 +- tests/repl/test_strings.rs | 4 +- tests/repl/test_type_check.rs | 10 +- tests/shell/pipeline/commands/internal.rs | 6 +- 32 files changed, 1684 insertions(+), 1772 deletions(-) diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs index bddc1bbf94..b055a6a46c 100644 --- a/crates/nu-color-config/src/style_computer.rs +++ b/crates/nu-color-config/src/style_computer.rs @@ -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")); } diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index 73368479c2..eeea7ace67 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -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." + } } } diff --git a/crates/nu-command/src/math/median.rs b/crates/nu-command/src/math/median.rs index 653ebb59ba..7b88fab346 100644 --- a/crates/nu-command/src/math/median.rs +++ b/crates/nu-command/src/math/median.rs @@ -91,45 +91,26 @@ pub fn median(values: &[Value], span: Span, head: Span) -> Result>(); 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 Result Result { - 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, .. } => { diff --git a/crates/nu-command/src/math/reducers.rs b/crates/nu-command/src/math/reducers.rs index 6bda6fa632..b076ca7567 100644 --- a/crates/nu-command/src/math/reducers.rs +++ b/crates/nu-command/src/math/reducers.rs @@ -32,18 +32,8 @@ pub fn max(data: Vec, span: Span, head: Span) -> Result, span: Span, head: Span) -> Result { - 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, diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs index d03d81b966..6e127c8d5b 100644 --- a/crates/nu-engine/src/compile/operator.rs +++ b/crates/nu-engine/src/compile/operator.rs @@ -150,11 +150,11 @@ pub(crate) fn compile_binary_op( pub(crate) fn decompose_assignment(assignment: Assignment) -> Option { 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)), } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 3f47e4a42e..01f7b69f7a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -520,11 +520,11 @@ impl Eval for EvalRuntime { let rhs = match assignment { Assignment::Assign => rhs, - Assignment::PlusAssign => { + Assignment::AddAssign => { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.add(op_span, &rhs, op_span)? } - Assignment::MinusAssign => { + Assignment::SubtractAssign => { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.sub(op_span, &rhs, op_span)? } @@ -536,7 +536,7 @@ impl Eval for EvalRuntime { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.div(op_span, &rhs, op_span)? } - Assignment::ConcatAssign => { + Assignment::ConcatenateAssign => { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.concat(op_span, &rhs, op_span)? } diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 25b4cc1921..8252497a53 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -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)?, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 30c014718a..7c2b162c37 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -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( diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index b0197589af..c04033047d 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,12 +1,45 @@ use nu_protocol::{ - ast::{ - Assignment, Bits, Block, Boolean, Comparison, Expr, Expression, Math, Operator, Pipeline, - Range, - }, + ast::{Block, Comparison, Expr, Expression, Math, Operator, Pipeline, Range}, engine::StateWorkingSet, - ParseError, Type, + ParseError, Span, Type, }; +fn type_error( + op: Operator, + op_span: Span, + lhs: &Expression, + rhs: &Expression, + is_supported: fn(&Type) -> bool, +) -> (Type, Option) { + let is_supported = |ty| is_supported(ty) || matches!(ty, Type::Any | Type::Custom(_)); + let err = match (is_supported(&lhs.ty), is_supported(&rhs.ty)) { + (true, true) => ParseError::OperatorIncompatibleTypes { + op: op.as_str(), + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + }, + (true, false) => ParseError::OperatorUnsupportedType { + op: op.as_str(), + unsupported: rhs.ty.clone(), + op_span, + unsupported_span: rhs.span, + help: None, + }, + (false, _) => ParseError::OperatorUnsupportedType { + op: op.as_str(), + unsupported: lhs.ty.clone(), + op_span, + unsupported_span: lhs.span, + help: None, + }, + }; + (Type::Any, Some(err)) +} + pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { // Structural subtyping let is_compatible = |expected: &[(String, Type)], found: &[(String, Type)]| { @@ -65,899 +98,648 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { } } +// TODO: rework type checking for Custom values pub fn math_result_type( working_set: &mut StateWorkingSet, lhs: &mut Expression, op: &mut Expression, rhs: &mut Expression, ) -> (Type, Option) { - match &op.expr { - Expr::Operator(operator) => match operator { - Operator::Math(Math::Plus) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, None), - (Type::String, Type::String) => (Type::String, None), - (Type::Date, Type::Duration) => (Type::Date, None), - (Type::Duration, Type::Date) => (Type::Date, None), - (Type::Duration, Type::Duration) => (Type::Duration, None), - (Type::Filesize, Type::Filesize) => (Type::Filesize, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - ( - Type::Int - | Type::Float - | Type::String - | Type::Date - | Type::Duration - | Type::Filesize, - _, - ) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "addition".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), + let &Expr::Operator(operator) = &op.expr else { + *op = Expression::garbage(working_set, op.span); + return ( + Type::Any, + Some(ParseError::IncompleteMathExpression(op.span)), + ); + }; + match operator { + Operator::Math(Math::Add) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::String, Type::String) => (Type::String, None), + // TODO: should this include glob + (Type::Date, Type::Duration) => (Type::Date, None), + (Type::Duration, Type::Date) => (Type::Date, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::String + | Type::Date + | Type::Duration + | Type::Filesize, ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "addition".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::Concat) => check_concat(working_set, lhs, rhs, op), - Operator::Math(Math::Minus) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, None), - (Type::Date, Type::Date) => (Type::Duration, None), - (Type::Date, Type::Duration) => (Type::Date, None), - (Type::Duration, Type::Duration) => (Type::Duration, None), - (Type::Filesize, Type::Filesize) => (Type::Filesize, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int | Type::Float | Type::Date | Type::Duration | Type::Filesize, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "subtraction".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "subtraction".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::Multiply) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, None), - (Type::Filesize, Type::Int) => (Type::Filesize, None), - (Type::Int, Type::Filesize) => (Type::Filesize, None), - (Type::Filesize, Type::Float) => (Type::Filesize, None), - (Type::Float, Type::Filesize) => (Type::Filesize, None), - (Type::Duration, Type::Int) => (Type::Duration, None), - (Type::Int, Type::Duration) => (Type::Duration, None), - (Type::Duration, Type::Float) => (Type::Duration, None), - (Type::Float, Type::Duration) => (Type::Duration, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int, _) - | (Type::Float, _) - | (Type::String, _) - | (Type::Date, _) - | (Type::Duration, _) - | (Type::Filesize, _) - | (Type::List(_), _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "multiplication".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "multiplication".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::Pow) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int | Type::Float, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "exponentiation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "exponentiation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::Divide) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Float, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Float, None), - (Type::Number, Type::Int) => (Type::Float, None), - (Type::Int, Type::Number) => (Type::Float, None), - (Type::Number, Type::Float) => (Type::Float, None), - (Type::Float, Type::Number) => (Type::Float, None), - (Type::Filesize, Type::Filesize) => (Type::Float, None), - (Type::Filesize, Type::Int) => (Type::Filesize, None), - (Type::Filesize, Type::Float) => (Type::Filesize, None), - (Type::Duration, Type::Duration) => (Type::Float, None), - (Type::Duration, Type::Int) => (Type::Duration, None), - (Type::Duration, Type::Float) => (Type::Duration, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int | Type::Float | Type::Filesize | Type::Duration, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, None), - (Type::Filesize, Type::Filesize) => (Type::Filesize, None), - (Type::Filesize, Type::Int) => (Type::Filesize, None), - (Type::Filesize, Type::Float) => (Type::Filesize, None), - (Type::Duration, Type::Duration) => (Type::Duration, None), - (Type::Duration, Type::Int) => (Type::Duration, None), - (Type::Duration, Type::Float) => (Type::Duration, 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::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int | Type::Float | Type::Filesize | Type::Duration, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Math(Math::FloorDivision) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Number, Type::Number) => (Type::Number, None), - (Type::Number, Type::Int) => (Type::Number, None), - (Type::Int, Type::Number) => (Type::Number, None), - (Type::Number, Type::Float) => (Type::Number, None), - (Type::Float, Type::Number) => (Type::Number, None), - (Type::Filesize, Type::Filesize) => (Type::Int, None), - (Type::Filesize, Type::Int) => (Type::Filesize, None), - (Type::Filesize, Type::Float) => (Type::Filesize, None), - (Type::Duration, Type::Duration) => (Type::Int, None), - (Type::Duration, Type::Int) => (Type::Duration, None), - (Type::Duration, Type::Float) => (Type::Duration, None), - - (Type::Any, _) => (Type::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int | Type::Float | Type::Filesize | Type::Duration, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "floor division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "floor division".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Boolean(Boolean::And) - | Operator::Boolean(Boolean::Or) - | Operator::Boolean(Boolean::Xor) => { - match (&lhs.ty, &rhs.ty) { - (Type::Bool, Type::Bool) => (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::Any, _) => (Type::Any, None), - (_, Type::Any) => (Type::Any, None), - - // FIX ME. This is added because there is no type output for custom function - // definitions. As soon as that syntax is added this should be removed - (a, b) if a == b => (Type::Bool, None), - (Type::Bool, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "boolean operation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "boolean operation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - } + }) } - Operator::Comparison(Comparison::LessThan) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Number, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Number) => (Type::Bool, None), - (Type::String, Type::String) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Date, Type::Date) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), - (_, Type::Nothing) => (Type::Nothing, None), - - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (Type::Bool, None), - (Type::Int | Type::Float | Type::Duration | Type::Filesize, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "less-than comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "less-than comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::LessThanOrEqual) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Number, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Number) => (Type::Bool, None), - (Type::String, Type::String) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Date, Type::Date) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), - (_, Type::Nothing) => (Type::Nothing, None), - - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (Type::Bool, None), - (Type::Int | Type::Float | Type::Duration | Type::Filesize, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "less-than or equal comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "less-than or equal comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::GreaterThan) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Number, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Number) => (Type::Bool, None), - (Type::String, Type::String) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Date, Type::Date) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - (Type::Bool, Type::Bool) => (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::Any, _) => (Type::Bool, None), - (_, Type::Any) => (Type::Bool, None), - - (Type::Nothing, _) => (Type::Nothing, None), - (_, Type::Nothing) => (Type::Nothing, None), - (Type::Int | Type::Float | Type::Duration | Type::Filesize, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "greater-than comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "greater-than comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::GreaterThanOrEqual) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Number, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Number) => (Type::Bool, None), - (Type::Number, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Number) => (Type::Bool, None), - (Type::String, Type::String) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Date, Type::Date) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - (Type::Bool, Type::Bool) => (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::Any, _) => (Type::Bool, None), - (_, Type::Any) => (Type::Bool, None), - - (Type::Nothing, _) => (Type::Nothing, None), - (_, Type::Nothing) => (Type::Nothing, None), - (Type::Int | Type::Float | Type::Duration | Type::Filesize, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "greater-than or equal comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "greater-than or equal comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::Equal) => match (&lhs.ty, &rhs.ty) { - (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), - (Type::Custom(a), _) => (Type::Custom(a.clone()), None), - - _ => (Type::Bool, None), - }, - Operator::Comparison(Comparison::NotEqual) => match (&lhs.ty, &rhs.ty) { - (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), - (Type::Custom(a), _) => (Type::Custom(a.clone()), None), - - _ => (Type::Bool, None), - }, - Operator::Comparison(Comparison::RegexMatch) => match (&lhs.ty, &rhs.ty) { - (Type::String, Type::String) => (Type::Bool, None), - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (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::String, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "regex matching".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "regex matching".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::NotRegexMatch) => match (&lhs.ty, &rhs.ty) { - (Type::String, Type::String) => (Type::Bool, None), - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (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::String, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "regex matching".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "regex matching".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::StartsWith) => match (&lhs.ty, &rhs.ty) { - (Type::String, Type::String) => (Type::Bool, None), - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (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::String, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "starts-with comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "starts-with comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Comparison(Comparison::EndsWith) => match (&lhs.ty, &rhs.ty) { - (Type::String, Type::String) => (Type::Bool, None), - (Type::Any, _) => (Type::Bool, None), - (_, Type::Any) => (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::String, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "ends-with comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "ends-with comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - 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), - (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::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(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "subset comparison".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - 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::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(), - )), - ) - } - } - } - Operator::Bits(Bits::ShiftLeft) - | Operator::Bits(Bits::ShiftRight) - | Operator::Bits(Bits::BitOr) - | Operator::Bits(Bits::BitXor) - | Operator::Bits(Bits::BitAnd) => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - - (Type::Any, _) => (Type::Any, None), - (_, Type::Any) => (Type::Any, None), - (Type::Int, _) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "bit operations".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "bit operations".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - }, - Operator::Assignment(Assignment::ConcatAssign) => { - check_concat(working_set, lhs, rhs, op) - } - Operator::Assignment(_) => match (&lhs.ty, &rhs.ty) { - (x, y) if x == y => (Type::Nothing, None), - (Type::Any, _) => (Type::Nothing, None), - (_, Type::Any) => (Type::Nothing, None), - (Type::List(_), Type::List(_)) => (Type::Nothing, None), - (x, y) => ( - Type::Nothing, - Some(ParseError::Mismatch(x.to_string(), y.to_string(), rhs.span)), - ), - }, }, - _ => { - *op = Expression::garbage(working_set, op.span); - - ( - Type::Any, - Some(ParseError::IncompleteMathExpression(op.span)), - ) + Operator::Math(Math::Subtract) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::Date, Type::Date) => (Type::Duration, None), + (Type::Date, Type::Duration) => (Type::Date, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::Date + | Type::Duration + | Type::Filesize + ) + }) + } + }, + Operator::Math(Math::Multiply) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Int, Type::Filesize) => (Type::Filesize, None), + (Type::Filesize, Type::Float) => (Type::Filesize, None), + (Type::Float, Type::Filesize) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Int, Type::Duration) => (Type::Duration, None), + (Type::Duration, Type::Float) => (Type::Duration, None), + (Type::Float, Type::Duration) => (Type::Duration, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Duration | Type::Filesize, + ) + }) + } + }, + Operator::Math(Math::Divide) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Float, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Float, None), + (Type::Number, Type::Int) => (Type::Float, None), + (Type::Int, Type::Number) => (Type::Float, None), + (Type::Number, Type::Float) => (Type::Float, None), + (Type::Float, Type::Number) => (Type::Float, None), + (Type::Filesize, Type::Filesize) => (Type::Float, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Filesize, Type::Float) => (Type::Filesize, None), + (Type::Duration, Type::Duration) => (Type::Float, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Duration, Type::Float) => (Type::Duration, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Filesize | Type::Duration + ) + }) + } + }, + Operator::Math(Math::FloorDivide) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::Filesize, Type::Filesize) => (Type::Int, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Filesize, Type::Float) => (Type::Filesize, None), + (Type::Duration, Type::Duration) => (Type::Int, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Duration, Type::Float) => (Type::Duration, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Filesize | Type::Duration + ) + }) + } + }, + Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Filesize, Type::Float) => (Type::Filesize, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Duration, Type::Float) => (Type::Duration, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Filesize | Type::Duration + ) + }) + } + }, + Operator::Math(Math::Pow) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, 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::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!(ty, Type::Int | Type::Float | Type::Number) + }) + } + }, + Operator::Math(Math::Concatenate) => match (&lhs.ty, &rhs.ty) { + (Type::List(a), Type::List(b)) => { + if a == b { + (Type::list(a.as_ref().clone()), None) + } else { + (Type::list(Type::Any), None) + } + } + (Type::Table(a), Type::Table(_)) => (Type::Table(a.clone()), None), + (Type::Table(table), Type::List(list)) => { + if matches!(list.as_ref(), Type::Record(..)) { + (Type::Table(table.clone()), None) + } else { + (Type::list(Type::Any), None) + } + } + (Type::List(list), Type::Table(_)) => { + if matches!(list.as_ref(), Type::Record(..)) { + (Type::list(list.as_ref().clone()), None) + } else { + (Type::list(Type::Any), None) + } + } + (Type::String, Type::String) => (Type::String, None), + // TODO: should this include glob + (Type::Binary, Type::Binary) => (Type::Binary, 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::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + let is_supported = |ty: &Type| { + matches!( + ty, + Type::List(_) + | Type::Table(_) + | Type::String + | Type::Binary + | Type::Any + | Type::Custom(_) + ) + }; + let help = if matches!(lhs.ty, Type::List(_) | Type::Table(_)) + || matches!(rhs.ty, Type::List(_) | Type::Table(_)) + { + Some("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`.") + } else { + None + }; + let err = match (is_supported(&lhs.ty), is_supported(&rhs.ty)) { + (true, true) => ParseError::OperatorIncompatibleTypes { + op: operator.as_str(), + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: op.span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help, + }, + (true, false) => ParseError::OperatorUnsupportedType { + op: operator.as_str(), + unsupported: rhs.ty.clone(), + op_span: op.span, + unsupported_span: rhs.span, + help, + }, + (false, _) => ParseError::OperatorUnsupportedType { + op: operator.as_str(), + unsupported: lhs.ty.clone(), + op_span: op.span, + unsupported_span: lhs.span, + help, + }, + }; + (Type::Any, Some(err)) + } + }, + Operator::Boolean(_) => match (&lhs.ty, &rhs.ty) { + (Type::Bool, Type::Bool) => (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::Any, _) => (Type::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| matches!(ty, Type::Bool)) + } + }, + Operator::Comparison(Comparison::LessThan) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Number, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Number) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Date, Type::Date) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), // TODO: is this right + (_, Type::Nothing) => (Type::Nothing, None), // TODO: is this right + // TODO: should this include: + // - binary + // - glob + // - list + // - table + // - record + // - range + (Type::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::String + | Type::Filesize + | Type::Duration + | Type::Date + | Type::Bool + | Type::Nothing + ) + }) + } + }, + Operator::Comparison(Comparison::LessThanOrEqual) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Number, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Number) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Date, Type::Date) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), // TODO: is this right + (_, Type::Nothing) => (Type::Nothing, None), // TODO: is this right + // TODO: should this include: + // - binary + // - glob + // - list + // - table + // - record + // - range + (Type::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::String + | Type::Filesize + | Type::Duration + | Type::Date + | Type::Bool + | Type::Nothing + ) + }) + } + }, + Operator::Comparison(Comparison::GreaterThan) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Number, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Number) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Date, Type::Date) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), // TODO: is this right + (_, Type::Nothing) => (Type::Nothing, None), // TODO: is this right + // TODO: should this include: + // - binary + // - glob + // - list + // - table + // - record + // - range + (Type::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::String + | Type::Filesize + | Type::Duration + | Type::Date + | Type::Bool + | Type::Nothing + ) + }) + } + }, + Operator::Comparison(Comparison::GreaterThanOrEqual) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Number, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Number) => (Type::Bool, None), + (Type::Number, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Number) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::Duration, Type::Duration) => (Type::Bool, None), + (Type::Date, Type::Date) => (Type::Bool, None), + (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Bool, Type::Bool) => (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::Nothing, _) => (Type::Nothing, None), // TODO: is this right + (_, Type::Nothing) => (Type::Nothing, None), // TODO: is this right + // TODO: should this include: + // - binary + // - glob + // - list + // - table + // - record + // - range + (Type::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::String + | Type::Filesize + | Type::Duration + | Type::Date + | Type::Bool + | Type::Nothing + ) + }) + } + }, + Operator::Comparison(Comparison::Equal | Comparison::NotEqual) => { + match (&lhs.ty, &rhs.ty) { + (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), + (Type::Custom(a), _) => (Type::Custom(a.clone()), None), + _ => (Type::Bool, None), + } } + Operator::Comparison(Comparison::RegexMatch | Comparison::NotRegexMatch) => { + match (&lhs.ty, &rhs.ty) { + (Type::String | Type::Any, Type::String | Type::Any) => (Type::Bool, None), + // TODO: should this include glob? + (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), + (Type::Custom(a), _) => (Type::Custom(a.clone()), None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| matches!(ty, Type::String)) + } + } + } + Operator::Comparison(Comparison::StartsWith | Comparison::EndsWith) => { + match (&lhs.ty, &rhs.ty) { + (Type::String | Type::Any, Type::String | Type::Any) => (Type::Bool, None), + // TODO: should this include glob? + (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), + (Type::Custom(a), _) => (Type::Custom(a.clone()), None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| matches!(ty, Type::String)) + } + } + } + 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), + (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::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + let err = if matches!( + &rhs.ty, + Type::List(_) + | Type::Range + | Type::String + | Type::Record(_) + | Type::Custom(_) + | Type::Any + ) { + ParseError::OperatorIncompatibleTypes { + op: operator.as_str(), + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: op.span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + } + } else { + ParseError::OperatorUnsupportedType { + op: operator.as_str(), + unsupported: rhs.ty.clone(), + op_span: op.span, + unsupported_span: rhs.span, + help: None, + } + }; + *op = Expression::garbage(working_set, op.span); + (Type::Any, Some(err)) + } + }, + Operator::Comparison(Comparison::Has | Comparison::NotHas) => match (&lhs.ty, &rhs.ty) { + (Type::List(u), t) if type_compatible(u, t) => (Type::Bool, None), + (Type::Range, Type::Int | Type::Float | Type::Number) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::Record(_), Type::String) => (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::Any, _) => (Type::Bool, None), + (_, Type::Any) => (Type::Bool, None), + _ => { + *op = Expression::garbage(working_set, op.span); + let err = if matches!( + &lhs.ty, + Type::List(_) + | Type::Range + | Type::String + | Type::Record(_) + | Type::Custom(_) + | Type::Any + ) { + ParseError::OperatorIncompatibleTypes { + op: operator.as_str(), + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: op.span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + } + } else { + ParseError::OperatorUnsupportedType { + op: operator.as_str(), + unsupported: lhs.ty.clone(), + op_span: op.span, + unsupported_span: lhs.span, + help: None, + } + }; + (Type::Any, Some(err)) + } + }, + Operator::Bits(_) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Any, _) => (Type::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error(operator, op.span, lhs, rhs, |ty| matches!(ty, Type::Int)) + } + }, + // TODO: fix this + Operator::Assignment(_) => match (&lhs.ty, &rhs.ty) { + (x, y) if x == y => (Type::Nothing, None), + (Type::Any, _) => (Type::Nothing, None), + (_, Type::Any) => (Type::Nothing, None), + (Type::List(_) | Type::Table(_), Type::List(_) | Type::Table(_)) => { + (Type::Nothing, None) + } + _ => { + let err = ParseError::OperatorIncompatibleTypes { + op: operator.as_str(), + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: op.span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + }; + (Type::Nothing, Some(err)) + } + }, } } @@ -1086,54 +868,6 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> output_errors } -fn check_concat( - working_set: &mut StateWorkingSet, - lhs: &Expression, - rhs: &Expression, - op: &mut Expression, -) -> (Type, Option) { - match (&lhs.ty, &rhs.ty) { - (Type::List(a), Type::List(b)) => { - if a == b { - (Type::List(a.clone()), None) - } else { - (Type::List(Box::new(Type::Any)), None) - } - } - (Type::Table(a), Type::Table(_)) => (Type::Table(a.clone()), None), - (Type::String, Type::String) => (Type::String, None), - (Type::Binary, Type::Binary) => (Type::Binary, None), - (Type::Any, _) | (_, Type::Any) => (Type::Any, None), - (Type::Table(_) | Type::List(_) | Type::String | Type::Binary, _) - | (_, Type::Table(_) | Type::List(_) | Type::String | Type::Binary) => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationRHS( - "concatenation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(working_set, op.span); - ( - Type::Any, - Some(ParseError::UnsupportedOperationLHS( - "concatenation".into(), - op.span, - lhs.span, - lhs.ty.clone(), - )), - ) - } - } -} - /// If one of the parts of the range isn't a number, a parse error is added to the working set pub fn check_range_types(working_set: &mut StateWorkingSet, range: &mut Range) { let next_op_span = if range.next.is_some() { @@ -1145,49 +879,35 @@ pub fn check_range_types(working_set: &mut StateWorkingSet, range: &mut Range) { (Some(expr), _, _) | (None, Some(expr), Some(_)) | (None, None, Some(expr)) if !type_compatible(&Type::Number, &expr.ty) => { - working_set.error(ParseError::UnsupportedOperationLHS( - String::from("range"), - next_op_span, - expr.span, - expr.ty.clone(), - )); + working_set.error(ParseError::OperatorUnsupportedType { + op: "..", + unsupported: expr.ty.clone(), + op_span: next_op_span, + unsupported_span: expr.span, + help: None, + }); *expr = Expression::garbage(working_set, expr.span); } - (Some(lhs), Some(rhs), _) if !type_compatible(&Type::Number, &rhs.ty) => { - working_set.error(ParseError::UnsupportedOperationRHS( - String::from("range"), - next_op_span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )); - *rhs = Expression::garbage(working_set, rhs.span); - } - (Some(lhs), Some(rhs), _) | (Some(lhs), None, Some(rhs)) | (None, Some(lhs), Some(rhs)) + (Some(_), Some(rhs), _) | (Some(_), None, Some(rhs)) | (None, Some(_), Some(rhs)) if !type_compatible(&Type::Number, &rhs.ty) => { - working_set.error(ParseError::UnsupportedOperationRHS( - String::from("range"), - range.operator.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )); + working_set.error(ParseError::OperatorUnsupportedType { + op: "..", + unsupported: rhs.ty.clone(), + op_span: next_op_span, + unsupported_span: rhs.span, + help: None, + }); *rhs = Expression::garbage(working_set, rhs.span); } - (Some(from), Some(next), Some(to)) if !type_compatible(&Type::Number, &to.ty) => { - working_set.error(ParseError::UnsupportedOperationTernary( - String::from("range"), - range.operator.span, - from.span, - from.ty.clone(), - next.span, - next.ty.clone(), - to.span, - to.ty.clone(), - )); + (Some(_), Some(_), Some(to)) if !type_compatible(&Type::Number, &to.ty) => { + working_set.error(ParseError::OperatorUnsupportedType { + op: "..", + unsupported: to.ty.clone(), + op_span: next_op_span, + unsupported_span: to.span, + help: None, + }); *to = Expression::garbage(working_set, to.span); } _ => (), diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 8d3249ade2..ac9bb512ba 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -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] diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index de9f379305..f3b0e5a2b6 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -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(), ), ), diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 0f6fd0b556..e68ece0de1 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -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 { Expression { expr: Expr::Operator(operator), .. - } => Ok(operator.clone()), + } => Ok(*operator), Expression { span, expr, .. } => Err(ShellError::UnknownOperator { op_token: format!("{expr:?}"), span: *span, diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index 2f9b7b3ed4..34a481a113 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -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, diff --git a/crates/nu-protocol/src/errors/shell_error/mod.rs b/crates/nu-protocol/src/errors/shell_error/mod.rs index c7be1dfec5..cd0afba474 100644 --- a/crates/nu-protocol/src/errors/shell_error/mod.rs +++ b/crates/nu-protocol/src/errors/shell_error/mod.rs @@ -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 diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 0e7916bd3c..15fc5105d3 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -235,14 +235,14 @@ pub trait Eval { let rhs = Self::eval::(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) => { diff --git a/crates/nu-protocol/src/value/custom_value.rs b/crates/nu-protocol/src/value/custom_value.rs index 480ca0018e..fbfc4ee911 100644 --- a/crates/nu-protocol/src/value/custom_value.rs +++ b/crates/nu-protocol/src/value/custom_value.rs @@ -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 { 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 diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 6b9e90d97e..a03b1ddb4e 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -2542,7 +2542,6 @@ impl Value { (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { Ok(Value::string(lhs.to_string() + rhs, span)) } - (Value::Duration { val: lhs, .. }, Value::Date { val: rhs, .. }) => { if let Some(val) = rhs.checked_add_signed(chrono::Duration::nanoseconds(*lhs)) { Ok(Value::date(val, span)) @@ -2587,43 +2586,26 @@ impl Value { }) } } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(self.span(), Operator::Math(Math::Plus), op, rhs) + lhs.operation(self.span(), Operator::Math(Math::Add), op, rhs) } - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - - pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result { - match (self, rhs) { - (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => { - Ok(Value::list([lhs.as_slice(), rhs.as_slice()].concat(), span)) - } - (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { - Ok(Value::string([lhs.as_str(), rhs.as_str()].join(""), span)) - } - (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => Ok(Value::binary( - [lhs.as_slice(), rhs.as_slice()].concat(), - span, + _ => Err(operator_type_error( + Operator::Math(Math::Add), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::String { .. } + | Value::Date { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, )), - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(self.span(), Operator::Math(Math::Concat), op, rhs) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), } } @@ -2651,7 +2633,6 @@ impl Value { } (Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => { let result = lhs.signed_duration_since(*rhs); - if let Some(v) = result.num_nanoseconds() { Ok(Value::duration(v, span)) } else { @@ -2694,18 +2675,25 @@ impl Value { }) } } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(self.span(), Operator::Math(Math::Minus), op, rhs) + lhs.operation(self.span(), Operator::Math(Math::Subtract), op, rhs) } - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Math(Math::Subtract), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::Date { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, + )), } } @@ -2790,13 +2778,21 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(self.span(), Operator::Math(Math::Multiply), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Math(Math::Multiply), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, + )), } } @@ -2904,14 +2900,21 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs) } - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Math(Math::Divide), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, + )), } } @@ -3060,13 +3063,21 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Math(Math::FloorDivide), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, + )), } } @@ -3216,14 +3227,117 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(span, Operator::Math(Math::Modulo), op, rhs) } + _ => Err(operator_type_error( + Operator::Math(Math::Modulo), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::Duration { .. } + | Value::Filesize { .. }, + ) + }, + )), + } + } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + pub fn pow(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if let Some(val) = lhs.checked_pow(*rhs as u32) { + Ok(Value::int(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "pow operation overflowed".into(), + span, + help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()), + }) + } + } + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + Ok(Value::float((*lhs as f64).powf(*rhs), span)) + } + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::float(lhs.powf(*rhs as f64), span)) + } + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + Ok(Value::float(lhs.powf(*rhs), span)) + } + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(span, Operator::Math(Math::Pow), op, rhs) + } + _ => Err(operator_type_error( + Operator::Math(Math::Pow), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. } | Value::Float { .. }), + )), + } + } + + pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => { + Ok(Value::list([lhs.as_slice(), rhs.as_slice()].concat(), span)) + } + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { + Ok(Value::string([lhs.as_str(), rhs.as_str()].join(""), span)) + } + (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => Ok(Value::binary( + [lhs.as_slice(), rhs.as_slice()].concat(), + span, + )), + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(self.span(), Operator::Math(Math::Concatenate), op, rhs) + } + _ => { + let help = if matches!(self, Value::List { .. }) + || matches!(rhs, Value::List { .. }) + { + Some("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`.") + } else { + None + }; + let is_supported = |val: &Value| { + matches!( + val, + Value::List { .. } + | Value::String { .. } + | Value::Binary { .. } + | Value::Custom { .. } + ) + }; + Err(match (is_supported(self), is_supported(rhs)) { + (true, true) => ShellError::OperatorIncompatibleTypes { + op: Operator::Math(Math::Concatenate), + lhs: self.get_type(), + rhs: rhs.get_type(), + op_span: op, + lhs_span: self.span(), + rhs_span: rhs.span(), + help, + }, + (true, false) => ShellError::OperatorUnsupportedType { + op: Operator::Math(Math::Concatenate), + unsupported: rhs.get_type(), + op_span: op, + unsupported_span: rhs.span(), + help, + }, + (false, _) => ShellError::OperatorUnsupportedType { + op: Operator::Math(Math::Concatenate), + unsupported: self.get_type(), + op_span: op, + unsupported_span: self.span(), + help, + }, + }) + } } } @@ -3241,30 +3355,32 @@ impl Value { return Ok(Value::nothing(span)); } - if !type_compatible(self.get_type(), rhs.get_type()) - && (self.get_type() != Type::Any) - && (rhs.get_type() != Type::Any) - { - return Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }); + if !type_compatible(self.get_type(), rhs.get_type()) { + return Err(operator_type_error( + Operator::Comparison(Comparison::LessThan), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::String { .. } + | Value::Filesize { .. } + | Value::Duration { .. } + | Value::Date { .. } + | Value::Bool { .. } + | Value::Nothing { .. } + ) + }, + )); } - if let Some(ordering) = self.partial_cmp(rhs) { - Ok(Value::bool(matches!(ordering, Ordering::Less), span)) - } else { - Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }) - } + Ok(Value::bool( + matches!(self.partial_cmp(rhs), Some(Ordering::Less)), + span, + )) } pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3281,28 +3397,35 @@ impl Value { return Ok(Value::nothing(span)); } - if !type_compatible(self.get_type(), rhs.get_type()) - && (self.get_type() != Type::Any) - && (rhs.get_type() != Type::Any) - { - return Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }); + if !type_compatible(self.get_type(), rhs.get_type()) { + return Err(operator_type_error( + Operator::Comparison(Comparison::LessThanOrEqual), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::String { .. } + | Value::Filesize { .. } + | Value::Duration { .. } + | Value::Date { .. } + | Value::Bool { .. } + | Value::Nothing { .. } + ) + }, + )); } - self.partial_cmp(rhs) - .map(|ordering| Value::bool(matches!(ordering, Ordering::Less | Ordering::Equal), span)) - .ok_or(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }) + Ok(Value::bool( + matches!( + self.partial_cmp(rhs), + Some(Ordering::Less | Ordering::Equal) + ), + span, + )) } pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3319,28 +3442,32 @@ impl Value { return Ok(Value::nothing(span)); } - if !type_compatible(self.get_type(), rhs.get_type()) - && (self.get_type() != Type::Any) - && (rhs.get_type() != Type::Any) - { - return Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }); + if !type_compatible(self.get_type(), rhs.get_type()) { + return Err(operator_type_error( + Operator::Comparison(Comparison::GreaterThan), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::String { .. } + | Value::Filesize { .. } + | Value::Duration { .. } + | Value::Date { .. } + | Value::Bool { .. } + | Value::Nothing { .. } + ) + }, + )); } - self.partial_cmp(rhs) - .map(|ordering| Value::bool(matches!(ordering, Ordering::Greater), span)) - .ok_or(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }) + Ok(Value::bool( + matches!(self.partial_cmp(rhs), Some(Ordering::Greater)), + span, + )) } pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3357,32 +3484,35 @@ impl Value { return Ok(Value::nothing(span)); } - if !type_compatible(self.get_type(), rhs.get_type()) - && (self.get_type() != Type::Any) - && (rhs.get_type() != Type::Any) - { - return Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }); + if !type_compatible(self.get_type(), rhs.get_type()) { + return Err(operator_type_error( + Operator::Comparison(Comparison::GreaterThanOrEqual), + op, + self, + rhs, + |val| { + matches!( + val, + Value::Int { .. } + | Value::Float { .. } + | Value::String { .. } + | Value::Filesize { .. } + | Value::Duration { .. } + | Value::Date { .. } + | Value::Bool { .. } + | Value::Nothing { .. } + ) + }, + )); } - match self.partial_cmp(rhs) { - Some(ordering) => Ok(Value::bool( - matches!(ordering, Ordering::Greater | Ordering::Equal), - span, - )), - None => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } + Ok(Value::bool( + matches!( + self.partial_cmp(rhs), + Some(Ordering::Greater | Ordering::Equal) + ), + span, + )) } pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3395,22 +3525,10 @@ impl Value { ); } - if let Some(ordering) = self.partial_cmp(rhs) { - Ok(Value::bool(matches!(ordering, Ordering::Equal), span)) - } else { - match (self, rhs) { - (Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => { - Ok(Value::bool(false, span)) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } + Ok(Value::bool( + matches!(self.partial_cmp(rhs), Some(Ordering::Equal)), + span, + )) } pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3423,22 +3541,10 @@ impl Value { ); } - if let Some(ordering) = self.partial_cmp(rhs) { - Ok(Value::bool(!matches!(ordering, Ordering::Equal), span)) - } else { - match (self, rhs) { - (Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => { - Ok(Value::bool(true, span)) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } + Ok(Value::bool( + !matches!(self.partial_cmp(rhs), Some(Ordering::Equal)), + span, + )) } pub fn r#in(&self, op: Span, rhs: &Value, span: Span) -> Result { @@ -3479,13 +3585,34 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(self.span(), Operator::Comparison(Comparison::In), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + (lhs, rhs) => Err( + if matches!( + rhs, + Value::List { .. } + | Value::Range { .. } + | Value::String { .. } + | Value::Record { .. } + | Value::Custom { .. } + ) { + ShellError::OperatorIncompatibleTypes { + op: Operator::Comparison(Comparison::In), + lhs: lhs.get_type(), + rhs: rhs.get_type(), + op_span: op, + lhs_span: lhs.span(), + rhs_span: rhs.span(), + help: None, + } + } else { + ShellError::OperatorUnsupportedType { + op: Operator::Comparison(Comparison::In), + unsupported: rhs.get_type(), + op_span: op, + unsupported_span: rhs.span(), + help: None, + } + }, + ), } } @@ -3530,13 +3657,34 @@ impl Value { op, rhs, ), - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + (lhs, rhs) => Err( + if matches!( + rhs, + Value::List { .. } + | Value::Range { .. } + | Value::String { .. } + | Value::Record { .. } + | Value::Custom { .. } + ) { + ShellError::OperatorIncompatibleTypes { + op: Operator::Comparison(Comparison::NotIn), + lhs: lhs.get_type(), + rhs: rhs.get_type(), + op_span: op, + lhs_span: lhs.span(), + rhs_span: rhs.span(), + help: None, + } + } else { + ShellError::OperatorUnsupportedType { + op: Operator::Comparison(Comparison::NotIn), + unsupported: rhs.get_type(), + op_span: op, + unsupported_span: rhs.span(), + help: None, + } + }, + ), } } @@ -3606,13 +3754,17 @@ impl Value { op, rhs, ), - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + if invert { + Operator::Comparison(Comparison::NotRegexMatch) + } else { + Operator::Comparison(Comparison::RegexMatch) + }, + op, + self, + rhs, + |val| matches!(val, Value::String { .. }), + )), } } @@ -3627,13 +3779,13 @@ impl Value { op, rhs, ), - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Comparison(Comparison::StartsWith), + op, + self, + rhs, + |val| matches!(val, Value::String { .. }), + )), } } @@ -3648,13 +3800,67 @@ impl Value { op, rhs, ), - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Comparison(Comparison::EndsWith), + op, + self, + rhs, + |val| matches!(val, Value::String { .. }), + )), + } + } + + pub fn bit_or(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::int(*lhs | rhs, span)) + } + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(span, Operator::Bits(Bits::BitOr), op, rhs) + } + _ => Err(operator_type_error( + Operator::Bits(Bits::BitOr), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. }), + )), + } + } + + pub fn bit_xor(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::int(*lhs ^ rhs, span)) + } + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(span, Operator::Bits(Bits::BitXor), op, rhs) + } + _ => Err(operator_type_error( + Operator::Bits(Bits::BitXor), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. }), + )), + } + } + + pub fn bit_and(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::int(*lhs & rhs, span)) + } + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(span, Operator::Bits(Bits::BitAnd), op, rhs) + } + _ => Err(operator_type_error( + Operator::Bits(Bits::BitAnd), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. }), + )), } } @@ -3677,13 +3883,13 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(span, Operator::Bits(Bits::ShiftLeft), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Bits(Bits::ShiftLeft), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. }), + )), } } @@ -3706,85 +3912,13 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(span, Operator::Bits(Bits::ShiftRight), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - - pub fn bit_or(&self, op: Span, rhs: &Value, span: Span) -> Result { - match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - Ok(Value::int(*lhs | rhs, span)) - } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Bits(Bits::BitOr), op, rhs) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - - pub fn bit_xor(&self, op: Span, rhs: &Value, span: Span) -> Result { - match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - Ok(Value::int(*lhs ^ rhs, span)) - } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Bits(Bits::BitXor), op, rhs) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - - pub fn bit_and(&self, op: Span, rhs: &Value, span: Span) -> Result { - match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - Ok(Value::int(*lhs & rhs, span)) - } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Bits(Bits::BitAnd), op, rhs) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - - pub fn and(&self, op: Span, rhs: &Value, span: Span) -> Result { - match (self, rhs) { - (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => { - Ok(Value::bool(*lhs && *rhs, span)) - } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Boolean(Boolean::And), op, rhs) - } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Bits(Bits::ShiftRight), + op, + self, + rhs, + |val| matches!(val, Value::Int { .. }), + )), } } @@ -3796,13 +3930,13 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(span, Operator::Boolean(Boolean::Or), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Boolean(Boolean::Or), + op, + self, + rhs, + |val| matches!(val, Value::Bool { .. }), + )), } } @@ -3814,49 +3948,31 @@ impl Value { (Value::Custom { val: lhs, .. }, rhs) => { lhs.operation(span, Operator::Boolean(Boolean::Xor), op, rhs) } - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Boolean(Boolean::Xor), + op, + self, + rhs, + |val| matches!(val, Value::Bool { .. }), + )), } } - pub fn pow(&self, op: Span, rhs: &Value, span: Span) -> Result { + pub fn and(&self, op: Span, rhs: &Value, span: Span) -> Result { match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = lhs.checked_pow(*rhs as u32) { - Ok(Value::int(val, span)) - } else { - Err(ShellError::OperatorOverflow { - msg: "pow operation overflowed".into(), - span, - help: Some("Consider using floating point values for increased range by promoting operand with 'into float'. Note: float has reduced precision!".into()), - }) - } - } - (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - Ok(Value::float((*lhs as f64).powf(*rhs), span)) - } - (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - Ok(Value::float(lhs.powf(*rhs as f64), span)) - } - (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - Ok(Value::float(lhs.powf(*rhs), span)) + (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => { + Ok(Value::bool(*lhs && *rhs, span)) } (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Math(Math::Pow), op, rhs) + lhs.operation(span, Operator::Boolean(Boolean::And), op, rhs) } - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), + _ => Err(operator_type_error( + Operator::Boolean(Boolean::And), + op, + self, + rhs, + |val| matches!(val, Value::Bool { .. }), + )), } } } @@ -3871,6 +3987,41 @@ fn type_compatible(a: Type, b: Type) -> bool { matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int)) } +fn operator_type_error( + op: Operator, + op_span: Span, + lhs: &Value, + rhs: &Value, + is_supported: fn(&Value) -> bool, +) -> ShellError { + let is_supported = |val| is_supported(val) || matches!(val, Value::Custom { .. }); + match (is_supported(lhs), is_supported(rhs)) { + (true, true) => ShellError::OperatorIncompatibleTypes { + op, + lhs: lhs.get_type(), + rhs: rhs.get_type(), + op_span, + lhs_span: lhs.span(), + rhs_span: rhs.span(), + help: None, + }, + (true, false) => ShellError::OperatorUnsupportedType { + op, + unsupported: rhs.get_type(), + op_span, + unsupported_span: rhs.span(), + help: None, + }, + (false, _) => ShellError::OperatorUnsupportedType { + op, + unsupported: lhs.get_type(), + op_span, + unsupported_span: lhs.span(), + help: None, + }, + } +} + #[cfg(test)] mod tests { use super::{Record, Value}; diff --git a/crates/nu-std/std/help/mod.nu b/crates/nu-std/std/help/mod.nu index 85edd3790d..c57decbb15 100644 --- a/crates/nu-std/std/help/mod.nu +++ b/crates/nu-std/std/help/mod.nu @@ -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 diff --git a/crates/nu_plugin_custom_values/src/cool_custom_value.rs b/crates/nu_plugin_custom_values/src/cool_custom_value.rs index cd160e0d0c..a6ee6f4168 100644 --- a/crates/nu_plugin_custom_values/src/cool_custom_value.rs +++ b/crates/nu_plugin_custom_values/src/cool_custom_value.rs @@ -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 { 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, }), } } diff --git a/crates/nu_plugin_polars/src/dataframe/values/mod.rs b/crates/nu_plugin_polars/src/dataframe/values/mod.rs index 42fb8dace1..fec45fe1bb 100644 --- a/crates/nu_plugin_polars/src/dataframe/values/mod.rs +++ b/crates/nu_plugin_polars/src/dataframe/values/mod.rs @@ -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, _right: Value, ) -> Result { - 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, }) } diff --git a/crates/nu_plugin_polars/src/dataframe/values/nu_dataframe/between_values.rs b/crates/nu_plugin_polars/src/dataframe/values/nu_dataframe/between_values.rs index 507107c7ae..918276818c 100644 --- a/crates/nu_plugin_polars/src/dataframe/values/nu_dataframe/between_values.rs +++ b/crates/nu_plugin_polars/src/dataframe/values/nu_dataframe/between_values.rs @@ -18,15 +18,15 @@ pub(super) fn between_dataframes( rhs: &NuDataFrame, ) -> Result { 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 { 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 { 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, >::add, lhs_span) } @@ -243,27 +243,27 @@ pub(super) fn compute_series_single_value( compute_series_float(&lhs, *val, >::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, >::sub, lhs_span) } Value::Float { val, .. } => { compute_series_float(&lhs, *val, >::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, >::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, >::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, }), } } diff --git a/crates/nu_plugin_polars/src/dataframe/values/nu_expression/custom_value.rs b/crates/nu_plugin_polars/src/dataframe/values/nu_expression/custom_value.rs index a9371f03f2..e0da3012c5 100644 --- a/crates/nu_plugin_polars/src/dataframe/values/nu_expression/custom_value.rs +++ b/crates/nu_plugin_polars/src/dataframe/values/nu_expression/custom_value.rs @@ -81,14 +81,14 @@ fn with_operator( left: &NuExpression, right: &NuExpression, lhs_span: Span, - rhs_span: Span, + _rhs_span: Span, op_span: Span, ) -> Result { 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, }), } } diff --git a/tests/const_/mod.rs b/tests/const_/mod.rs index d76c18d07b..dabf3892f8 100644 --- a/tests/const_/mod.rs +++ b/tests/const_/mod.rs @@ -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)); diff --git a/tests/repl/test_ranges.rs b/tests/repl/test_ranges.rs index 8e7a557738..2f3f26b07a 100644 --- a/tests/repl/test_ranges.rs +++ b/tests/repl/test_ranges.rs @@ -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] diff --git a/tests/repl/test_regex.rs b/tests/repl/test_regex.rs index 6f9063c81a..7dff6d7710 100644 --- a/tests/repl/test_regex.rs +++ b/tests/repl/test_regex.rs @@ -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") } diff --git a/tests/repl/test_strings.rs b/tests/repl/test_strings.rs index 1c573f03ae..967086a592 100644 --- a/tests/repl/test_strings.rs +++ b/tests/repl/test_strings.rs @@ -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", ) } diff --git a/tests/repl/test_type_check.rs b/tests/repl/test_type_check.rs index 07a8d99495..86216c15a9 100644 --- a/tests/repl/test_type_check.rs +++ b/tests/repl/test_type_check.rs @@ -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] diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index aa90fbf681..e78a0947b3 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -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]