From caaa2472ecedb592eb59f82ef93d317ea2f453c1 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sat, 23 Nov 2024 17:49:26 -0800 Subject: [PATCH] Edit operator type errors --- crates/nu-engine/src/compile/expression.rs | 4 +- crates/nu-parser/src/type_check.rs | 1656 ++++++++---------- crates/nu-protocol/src/ast/operator.rs | 14 +- crates/nu-protocol/src/errors/parse_error.rs | 51 +- 4 files changed, 746 insertions(+), 979 deletions(-) diff --git a/crates/nu-engine/src/compile/expression.rs b/crates/nu-engine/src/compile/expression.rs index 0badb76b45..0228f75820 100644 --- a/crates/nu-engine/src/compile/expression.rs +++ b/crates/nu-engine/src/compile/expression.rs @@ -156,13 +156,13 @@ pub(crate) fn compile_expression( Ok(()) } Expr::BinaryOp(lhs, op, rhs) => { - if let Expr::Operator(ref operator) = op.expr { + if let Expr::Operator(operator) = op.expr { drop_input(builder)?; compile_binary_op( working_set, builder, lhs, - operator.clone().into_spanned(op.span), + operator.into_spanned(op.span), rhs, expr.span, out_reg, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index e5f2a22921..7d1054134b 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -7,6 +7,42 @@ use nu_protocol::{ ParseError, Type, }; +fn type_error( + name: &'static str, + op: &Expression, + 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::OperatorTypeMismatch { + op: name, + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: op.span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + }, + (true, false) => ParseError::OperatorUnsupportedType { + op: name, + unsupported: rhs.ty.clone(), + op_span: op.span, + unsupported_span: rhs.span, + help: None, + }, + (false, _) => ParseError::OperatorUnsupportedType { + op: name, + unsupported: lhs.ty.clone(), + op_span: 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)]| { @@ -68,895 +104,660 @@ 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::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), + // 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("addition", op, 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::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), + _ => { + *op = Expression::garbage(working_set, op.span); + type_error("subtraction", op, 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("multiplication", op, lhs, rhs, |ty| { + matches!( + ty, + Type::Int + | Type::Float + | Type::Number + | Type::Date + | Type::Duration + | Type::Filesize, + ) + }) + } + }, + 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("exponentiation", op, lhs, rhs, |ty| { + matches!(ty, Type::Int | Type::Float | Type::Number) + }) + } + }, + 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("division", op, 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("modulo", op, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Filesize | Type::Duration + ) + }) + } + }, + 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::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("floor division", op, lhs, rhs, |ty| { + matches!( + ty, + Type::Int | Type::Float | Type::Number | Type::Filesize | Type::Duration + ) + }) + } + }, + Operator::Math(Math::Concat) => 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); + type_error("concatenation", op, lhs, rhs, |ty| { + matches!( + ty, + Type::List(_) | Type::Table(_) | Type::String | Type::Binary + ) + }) + } + }, + Operator::Boolean(operator) => 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); + let name = match operator { + Boolean::And => "boolean and", + Boolean::Or => "boolean or", + Boolean::Xor => "boolean xor", + }; + type_error(name, op, 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("less than comparison", op, 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("less than or equal to comparison", op, 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("greater than comparison", op, 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("greater than or equal to comparison", op, 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) => 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::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("regex match", op, lhs, rhs, |ty| matches!(ty, Type::String)) + } + }, + Operator::Comparison(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("negative regex match", op, lhs, rhs, |ty| { + matches!(ty, Type::String) + }) + } + }, + Operator::Comparison(Comparison::StartsWith) => 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("starts-with comparison", op, lhs, rhs, |ty| { + matches!(ty, Type::String) + }) + } + }, + Operator::Comparison(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("ends-with comparison", op, lhs, rhs, |ty| { + matches!(ty, Type::String) + }) + } + }, + Operator::Comparison(Comparison::In) => 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), + _ => { + *op = Expression::garbage(working_set, op.span); + let err = if matches!( + &rhs.ty, + Type::List(_) + | Type::Range + | Type::String + | Type::Record(_) + | Type::Custom(_) + | Type::Any + ) { + ParseError::OperatorTypeMismatch { + op: "'in' comparison", + 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: "'in' comparison", + unsupported: rhs.ty.clone(), + op_span: op.span, + unsupported_span: rhs.span, + help: None, + } + }; + (Type::Any, Some(err)) + } + }, + Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) { + (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), + (Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None), + (Type::String, Type::String) => (Type::Bool, None), + (Type::String, Type::Record(_)) => (Type::Bool, None), + (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!( + &rhs.ty, + Type::List(_) + | Type::Range + | Type::String + | Type::Record(_) + | Type::Custom(_) + | Type::Any + ) { + ParseError::OperatorTypeMismatch { + op: "'not-in' comparison", + 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: "'not-in' comparison", + unsupported: rhs.ty.clone(), + op_span: op.span, + unsupported_span: rhs.span, + help: None, + } + }; + (Type::Any, Some(err)) + } + }, + Operator::Bits(operator) => 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); + let name = match operator { + Bits::BitOr => "bitwise or", + Bits::BitXor => "bitwise xor", + Bits::BitAnd => "bitwise and", + Bits::ShiftLeft => "bit left shift", + Bits::ShiftRight => "bit right shift", + }; + type_error(name, op, lhs, rhs, |ty| matches!(ty, Type::Int)) + } + }, + // TODO: fix this + Operator::Assignment(operator) => 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 name = match operator { + Assignment::Assign => "variable assignment", + Assignment::PlusAssign => "addition assignment", + Assignment::MinusAssign => "subtraction assignment", + Assignment::MultiplyAssign => "multiplication assignment", + Assignment::DivideAssign => "division assignment", + Assignment::ConcatAssign => "concatenation assignment", + }; + let err = ParseError::OperatorTypeMismatch { + op: name, + 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)) } - 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) => 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::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::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)), - ) - } } } @@ -1085,54 +886,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() { @@ -1144,36 +897,39 @@ 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: "range creation", + 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(), - )); + working_set.error(ParseError::OperatorTypeMismatch { + op: "range creation", + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: next_op_span, + lhs_span: lhs.span, + rhs_span: rhs.span, + help: None, + }); *rhs = Expression::garbage(working_set, rhs.span); } (Some(lhs), Some(rhs), _) | (Some(lhs), None, Some(rhs)) | (None, Some(lhs), 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::OperatorTypeMismatch { + op: "range creation", + lhs: lhs.ty.clone(), + rhs: rhs.ty.clone(), + op_span: range.operator.span, + lhs_span: lhs.span, + rhs_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) => { diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index bde866498c..7ab20e2399 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -5,7 +5,7 @@ use std::fmt::Display; use super::{Expr, Expression}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Comparison { Equal, NotEqual, @@ -21,7 +21,7 @@ pub enum Comparison { EndsWith, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Math { Plus, Concat, @@ -33,14 +33,14 @@ pub enum Math { Pow, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Boolean { And, Or, Xor, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Bits { BitOr, BitXor, @@ -49,7 +49,7 @@ pub enum Bits { ShiftRight, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Assignment { Assign, PlusAssign, @@ -59,7 +59,7 @@ pub enum Assignment { DivideAssign, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Operator { Comparison(Comparison), Math(Math), @@ -170,7 +170,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 ce8522daa1..5c51a54203 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -111,25 +111,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("{op} is not supported on values of type {unsupported}.")] + #[diagnostic(code(nu::parser::operator_unsupported_type))] + OperatorUnsupportedType { + op: &'static str, + unsupported: Type, + #[label = "does support this type"] + 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, - ), + /// The operator supports the types of both values, but not the specific combination of their types. + #[error("{op} is not supported between types {lhs} and {rhs}.")] + #[diagnostic(code(nu::parser::operator_type_mismatch))] + OperatorTypeMismatch { + op: &'static str, + lhs: Type, + rhs: Type, + #[label = "does not operate between these two types"] + op_span: Span, + #[label("{lhs}")] + lhs_span: Span, + #[label("{rhs}")] + rhs_span: Span, + #[help] + help: Option<&'static str>, + }, #[error("{0} is not supported between {3}, {5}, and {7}.")] #[diagnostic(code(nu::parser::unsupported_operation))] @@ -528,8 +539,8 @@ impl ParseError { ParseError::Expected(_, s) => *s, ParseError::ExpectedWithStringMsg(_, s) => *s, ParseError::Mismatch(_, _, s) => *s, - ParseError::UnsupportedOperationLHS(_, _, s, _) => *s, - ParseError::UnsupportedOperationRHS(_, _, _, _, s, _) => *s, + ParseError::OperatorUnsupportedType { op_span, .. } => *op_span, + ParseError::OperatorTypeMismatch { op_span, .. } => *op_span, ParseError::UnsupportedOperationTernary(_, _, _, _, _, _, s, _) => *s, ParseError::ExpectedKeyword(_, s) => *s, ParseError::UnexpectedKeyword(_, s) => *s,