Edit operator type errors

This commit is contained in:
Ian Manske 2024-11-23 20:59:39 -08:00
parent 9fb54a8f46
commit ca6db77a63
7 changed files with 435 additions and 397 deletions

View file

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

View file

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

View file

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

View file

@ -215,12 +215,7 @@ pub fn math_result_type(
type_error("multiplication", op, lhs, rhs, |ty| { type_error("multiplication", op, lhs, rhs, |ty| {
matches!( matches!(
ty, ty,
Type::Int Type::Int | Type::Float | Type::Number | Type::Duration | Type::Filesize,
| Type::Float
| Type::Number
| Type::Date
| Type::Duration
| Type::Filesize,
) )
}) })
} }

View file

@ -17,6 +17,8 @@ pub enum ShellError {
/// ## Resolution /// ## Resolution
/// ///
/// Check each argument's type and convert one or both as needed. /// Check each argument's type and convert one or both as needed.
///
#[deprecated = "use `OperatorUnsupportedType` or `OperatorTypeMismatch` instead"]
#[error("Type mismatch during operation.")] #[error("Type mismatch during operation.")]
#[diagnostic(code(nu::shell::type_mismatch))] #[diagnostic(code(nu::shell::type_mismatch))]
OperatorMismatch { OperatorMismatch {
@ -30,6 +32,37 @@ pub enum ShellError {
rhs_span: Span, rhs_span: Span,
}, },
/// 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::shell::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>,
},
/// 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::shell::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>,
},
/// An arithmetic operation's resulting value overflowed its possible size. /// An arithmetic operation's resulting value overflowed its possible size.
/// ///
/// ## Resolution /// ## Resolution

View file

@ -2443,7 +2443,6 @@ impl Value {
(Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => {
Ok(Value::string(lhs.to_string() + rhs, span)) Ok(Value::string(lhs.to_string() + rhs, span))
} }
(Value::Duration { val: lhs, .. }, Value::Date { val: rhs, .. }) => { (Value::Duration { val: lhs, .. }, Value::Date { val: rhs, .. }) => {
if let Some(val) = rhs.checked_add_signed(chrono::Duration::nanoseconds(*lhs)) { if let Some(val) = rhs.checked_add_signed(chrono::Duration::nanoseconds(*lhs)) {
Ok(Value::date(val, span)) Ok(Value::date(val, span))
@ -2488,43 +2487,20 @@ impl Value {
}) })
} }
} }
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Plus), op, rhs) lhs.operation(self.span(), Operator::Math(Math::Plus), op, rhs)
} }
_ => Err(operator_type_error("addition", op, self, rhs, |val| {
_ => Err(ShellError::OperatorMismatch { matches!(
op_span: op, val,
lhs_ty: self.get_type().to_string(), Value::Int { .. }
lhs_span: self.span(), | Value::Float { .. }
rhs_ty: rhs.get_type().to_string(), | Value::String { .. }
rhs_span: rhs.span(), | Value::Date { .. }
}), | Value::Duration { .. }
} | Value::Filesize { .. },
} )
})),
pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
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::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(),
}),
} }
} }
@ -2552,7 +2528,6 @@ impl Value {
} }
(Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => { (Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => {
let result = lhs.signed_duration_since(*rhs); let result = lhs.signed_duration_since(*rhs);
if let Some(v) = result.num_nanoseconds() { if let Some(v) = result.num_nanoseconds() {
Ok(Value::duration(v, span)) Ok(Value::duration(v, span))
} else { } else {
@ -2595,18 +2570,19 @@ impl Value {
}) })
} }
} }
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Minus), op, rhs) lhs.operation(self.span(), Operator::Math(Math::Minus), op, rhs)
} }
_ => Err(operator_type_error("subtraction", op, self, rhs, |val| {
_ => Err(ShellError::OperatorMismatch { matches!(
op_span: op, val,
lhs_ty: self.get_type().to_string(), Value::Int { .. }
lhs_span: self.span(), | Value::Float { .. }
rhs_ty: rhs.get_type().to_string(), | Value::Date { .. }
rhs_span: rhs.span(), | Value::Duration { .. }
}), | Value::Filesize { .. },
)
})),
} }
} }
@ -2659,13 +2635,21 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Multiply), op, rhs) lhs.operation(self.span(), Operator::Math(Math::Multiply), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "multiplication",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| {
}), matches!(
val,
Value::Int { .. }
| Value::Float { .. }
| Value::Duration { .. }
| Value::Filesize { .. },
)
},
)),
} }
} }
@ -2774,14 +2758,15 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs) lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs)
} }
_ => Err(operator_type_error("division", op, self, rhs, |val| {
_ => Err(ShellError::OperatorMismatch { matches!(
op_span: op, val,
lhs_ty: self.get_type().to_string(), Value::Int { .. }
lhs_span: self.span(), | Value::Float { .. }
rhs_ty: rhs.get_type().to_string(), | Value::Duration { .. }
rhs_span: rhs.span(), | Value::Filesize { .. },
}), )
})),
} }
} }
@ -2931,14 +2916,15 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Math(Math::Modulo), op, rhs) lhs.operation(span, Operator::Math(Math::Modulo), op, rhs)
} }
_ => Err(operator_type_error("modulo", op, self, rhs, |val| {
_ => Err(ShellError::OperatorMismatch { matches!(
op_span: op, val,
lhs_ty: self.get_type().to_string(), Value::Int { .. }
lhs_span: self.span(), | Value::Float { .. }
rhs_ty: rhs.get_type().to_string(), | Value::Duration { .. }
rhs_span: rhs.span(), | Value::Filesize { .. },
}), )
})),
} }
} }
@ -3087,13 +3073,51 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs) lhs.operation(self.span(), Operator::Math(Math::Divide), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "floor division",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| {
}), matches!(
val,
Value::Int { .. }
| Value::Float { .. }
| Value::Duration { .. }
| Value::Filesize { .. },
)
},
)),
}
}
pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
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::Concat), op, rhs)
}
_ => Err(operator_type_error(
"concatentation",
op,
self,
rhs,
|val| {
matches!(
val,
Value::List { .. } | Value::String { .. } | Value::Binary { .. },
)
},
)),
} }
} }
@ -3111,30 +3135,32 @@ impl Value {
return Ok(Value::nothing(span)); return Ok(Value::nothing(span));
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type()) {
&& (self.get_type() != Type::Any) return Err(operator_type_error(
&& (rhs.get_type() != Type::Any) "less than comparison",
{ op,
return Err(ShellError::OperatorMismatch { self,
op_span: op, rhs,
lhs_ty: self.get_type().to_string(), |val| {
lhs_span: self.span(), matches!(
rhs_ty: rhs.get_type().to_string(), val,
rhs_span: rhs.span(), 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(
Ok(Value::bool(matches!(ordering, Ordering::Less), span)) matches!(self.partial_cmp(rhs), Some(Ordering::Less)),
} else { 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(),
})
}
} }
pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn lte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3151,28 +3177,35 @@ impl Value {
return Ok(Value::nothing(span)); return Ok(Value::nothing(span));
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type()) {
&& (self.get_type() != Type::Any) return Err(operator_type_error(
&& (rhs.get_type() != Type::Any) "less than or equal to comparison",
{ op,
return Err(ShellError::OperatorMismatch { self,
op_span: op, rhs,
lhs_ty: self.get_type().to_string(), |val| {
lhs_span: self.span(), matches!(
rhs_ty: rhs.get_type().to_string(), val,
rhs_span: rhs.span(), Value::Int { .. }
}); | Value::Float { .. }
| Value::String { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Bool { .. }
| Value::Nothing { .. }
)
},
));
} }
self.partial_cmp(rhs) Ok(Value::bool(
.map(|ordering| Value::bool(matches!(ordering, Ordering::Less | Ordering::Equal), span)) matches!(
.ok_or(ShellError::OperatorMismatch { self.partial_cmp(rhs),
op_span: op, Some(Ordering::Less | Ordering::Equal)
lhs_ty: self.get_type().to_string(), ),
lhs_span: self.span(), span,
rhs_ty: rhs.get_type().to_string(), ))
rhs_span: rhs.span(),
})
} }
pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn gt(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3189,28 +3222,32 @@ impl Value {
return Ok(Value::nothing(span)); return Ok(Value::nothing(span));
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type()) {
&& (self.get_type() != Type::Any) return Err(operator_type_error(
&& (rhs.get_type() != Type::Any) "greater than comparison",
{ op,
return Err(ShellError::OperatorMismatch { self,
op_span: op, rhs,
lhs_ty: self.get_type().to_string(), |val| {
lhs_span: self.span(), matches!(
rhs_ty: rhs.get_type().to_string(), val,
rhs_span: rhs.span(), Value::Int { .. }
}); | Value::Float { .. }
| Value::String { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Bool { .. }
| Value::Nothing { .. }
)
},
));
} }
self.partial_cmp(rhs) Ok(Value::bool(
.map(|ordering| Value::bool(matches!(ordering, Ordering::Greater), span)) matches!(self.partial_cmp(rhs), Some(Ordering::Greater)),
.ok_or(ShellError::OperatorMismatch { span,
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 gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn gte(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3227,32 +3264,35 @@ impl Value {
return Ok(Value::nothing(span)); return Ok(Value::nothing(span));
} }
if !type_compatible(self.get_type(), rhs.get_type()) if !type_compatible(self.get_type(), rhs.get_type()) {
&& (self.get_type() != Type::Any) return Err(operator_type_error(
&& (rhs.get_type() != Type::Any) "greater than or equal to comparison",
{ op,
return Err(ShellError::OperatorMismatch { self,
op_span: op, rhs,
lhs_ty: self.get_type().to_string(), |val| {
lhs_span: self.span(), matches!(
rhs_ty: rhs.get_type().to_string(), val,
rhs_span: rhs.span(), Value::Int { .. }
}); | Value::Float { .. }
| Value::String { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Bool { .. }
| Value::Nothing { .. }
)
},
));
} }
match self.partial_cmp(rhs) { Ok(Value::bool(
Some(ordering) => Ok(Value::bool( matches!(
matches!(ordering, Ordering::Greater | Ordering::Equal), self.partial_cmp(rhs),
span, Some(Ordering::Greater | Ordering::Equal)
)), ),
None => Err(ShellError::OperatorMismatch { span,
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 eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn eq(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3265,22 +3305,10 @@ impl Value {
); );
} }
if let Some(ordering) = self.partial_cmp(rhs) { Ok(Value::bool(
Ok(Value::bool(matches!(ordering, Ordering::Equal), span)) matches!(self.partial_cmp(rhs), Some(Ordering::Equal)),
} else { span,
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(),
}),
}
}
} }
pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn ne(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3293,22 +3321,10 @@ impl Value {
); );
} }
if let Some(ordering) = self.partial_cmp(rhs) { Ok(Value::bool(
Ok(Value::bool(!matches!(ordering, Ordering::Equal), span)) !matches!(self.partial_cmp(rhs), Some(Ordering::Equal)),
} else { span,
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(),
}),
}
}
} }
pub fn r#in(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> { pub fn r#in(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
@ -3349,13 +3365,34 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(self.span(), Operator::Comparison(Comparison::In), op, rhs) lhs.operation(self.span(), Operator::Comparison(Comparison::In), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { (lhs, rhs) => Err(
op_span: op, if matches!(
lhs_ty: self.get_type().to_string(), rhs,
lhs_span: self.span(), Value::List { .. }
rhs_ty: rhs.get_type().to_string(), | Value::Range { .. }
rhs_span: rhs.span(), | Value::String { .. }
}), | Value::Record { .. }
| Value::Custom { .. }
) {
ShellError::OperatorTypeMismatch {
op: "'in' comparison",
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: "'in' comparison",
unsupported: rhs.get_type(),
op_span: op,
unsupported_span: rhs.span(),
help: None,
}
},
),
} }
} }
@ -3400,13 +3437,34 @@ impl Value {
op, op,
rhs, rhs,
), ),
_ => Err(ShellError::OperatorMismatch { (lhs, rhs) => Err(
op_span: op, if matches!(
lhs_ty: self.get_type().to_string(), rhs,
lhs_span: self.span(), Value::List { .. }
rhs_ty: rhs.get_type().to_string(), | Value::Range { .. }
rhs_span: rhs.span(), | Value::String { .. }
}), | Value::Record { .. }
| Value::Custom { .. }
) {
ShellError::OperatorTypeMismatch {
op: "'not-in' comparison",
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: "'not-in' comparison",
unsupported: rhs.get_type(),
op_span: op,
unsupported_span: rhs.span(),
help: None,
}
},
),
} }
} }
@ -3468,13 +3526,9 @@ impl Value {
op, op,
rhs, rhs,
), ),
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error("regex match", op, self, rhs, |val| {
op_span: op, matches!(val, Value::String { .. })
lhs_ty: self.get_type().to_string(), })),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
} }
} }
@ -3489,13 +3543,13 @@ impl Value {
op, op,
rhs, rhs,
), ),
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "starts-with comparison",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| matches!(val, Value::String { .. }),
}), )),
} }
} }
@ -3510,13 +3564,13 @@ impl Value {
op, op,
rhs, rhs,
), ),
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "ends-with comparison",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| matches!(val, Value::String { .. }),
}), )),
} }
} }
@ -3539,13 +3593,13 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::ShiftLeft), op, rhs) lhs.operation(span, Operator::Bits(Bits::ShiftLeft), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "bit left shift",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| matches!(val, Value::Int { .. }),
}), )),
} }
} }
@ -3568,49 +3622,13 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::ShiftRight), op, rhs) lhs.operation(span, Operator::Bits(Bits::ShiftRight), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error(
op_span: op, "bit right shift",
lhs_ty: self.get_type().to_string(), op,
lhs_span: self.span(), self,
rhs_ty: rhs.get_type().to_string(), rhs,
rhs_span: rhs.span(), |val| matches!(val, Value::Int { .. }),
}), )),
}
}
pub fn bit_or(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
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<Value, ShellError> {
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(),
}),
} }
} }
@ -3622,13 +3640,37 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Bits(Bits::BitAnd), op, rhs) lhs.operation(span, Operator::Bits(Bits::BitAnd), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error("bitwise and", op, self, rhs, |val| {
op_span: op, matches!(val, Value::Int { .. })
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<Value, ShellError> {
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("bitwise or", op, self, rhs, |val| {
matches!(val, Value::Int { .. })
})),
}
}
pub fn bit_xor(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
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("bitwise xor", op, self, rhs, |val| {
matches!(val, Value::Int { .. })
})),
} }
} }
@ -3640,13 +3682,9 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::And), op, rhs) lhs.operation(span, Operator::Boolean(Boolean::And), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error("boolean and", op, self, rhs, |val| {
op_span: op, matches!(val, Value::Bool { .. })
lhs_ty: self.get_type().to_string(), })),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
} }
} }
@ -3658,13 +3696,9 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::Or), op, rhs) lhs.operation(span, Operator::Boolean(Boolean::Or), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error("boolean or", op, self, rhs, |val| {
op_span: op, matches!(val, Value::Bool { .. })
lhs_ty: self.get_type().to_string(), })),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
} }
} }
@ -3676,13 +3710,9 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Boolean(Boolean::Xor), op, rhs) lhs.operation(span, Operator::Boolean(Boolean::Xor), op, rhs)
} }
_ => Err(ShellError::OperatorMismatch { _ => Err(operator_type_error("boolean xor", op, self, rhs, |val| {
op_span: op, matches!(val, Value::Bool { .. })
lhs_ty: self.get_type().to_string(), })),
lhs_span: self.span(),
rhs_ty: rhs.get_type().to_string(),
rhs_span: rhs.span(),
}),
} }
} }
@ -3711,14 +3741,13 @@ impl Value {
(Value::Custom { val: lhs, .. }, rhs) => { (Value::Custom { val: lhs, .. }, rhs) => {
lhs.operation(span, Operator::Math(Math::Pow), op, rhs) lhs.operation(span, Operator::Math(Math::Pow), op, rhs)
} }
_ => Err(operator_type_error(
_ => Err(ShellError::OperatorMismatch { "exponentiation",
op_span: op, op,
lhs_ty: self.get_type().to_string(), self,
lhs_span: self.span(), rhs,
rhs_ty: rhs.get_type().to_string(), |val| matches!(val, Value::Int { .. } | Value::Float { .. }),
rhs_span: rhs.span(), )),
}),
} }
} }
} }
@ -3733,6 +3762,41 @@ fn type_compatible(a: Type, b: Type) -> bool {
matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int)) matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int))
} }
fn operator_type_error(
op_name: &'static str,
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::OperatorTypeMismatch {
op: op_name,
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: op_name,
unsupported: rhs.get_type(),
op_span,
unsupported_span: rhs.span(),
help: None,
},
(false, _) => ShellError::OperatorUnsupportedType {
op: op_name,
unsupported: lhs.get_type(),
op_span,
unsupported_span: lhs.span(),
help: None,
},
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Record, Value}; use super::{Record, Value};

View file

@ -105,7 +105,7 @@ impl CustomValue for CoolCustomValue {
fn operation( fn operation(
&self, &self,
lhs_span: Span, _lhs_span: Span,
operator: ast::Operator, operator: ast::Operator,
op_span: Span, op_span: Span,
right: &Value, right: &Value,
@ -125,12 +125,12 @@ impl CustomValue for CoolCustomValue {
op_span, op_span,
)) ))
} else { } else {
Err(ShellError::OperatorMismatch { Err(ShellError::OperatorUnsupportedType {
op: "concatenation",
unsupported: right.get_type(),
op_span, op_span,
lhs_ty: self.typetag_name().into(), unsupported_span: right.span(),
lhs_span, help: None,
rhs_ty: right.get_type().to_string(),
rhs_span: right.span(),
}) })
} }
} }