From ceb177feec6b1292b0663223b7035900bed996dd Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sun, 30 Jul 2023 18:19:48 +0200 Subject: [PATCH] Add new protocol and native support for Ordering (#591) --- crates/rune-core/src/protocol.rs | 8 ++ crates/rune-macros/src/any.rs | 2 +- crates/rune-macros/src/context.rs | 22 ++-- crates/rune/src/any.rs | 1 - crates/rune/src/compile/named.rs | 8 ++ crates/rune/src/modules/cmp.rs | 14 +-- crates/rune/src/runtime.rs | 2 +- crates/rune/src/runtime/from_value.rs | 45 ++++---- crates/rune/src/runtime/static_type.rs | 15 ++- crates/rune/src/runtime/to_value.rs | 9 ++ crates/rune/src/runtime/value.rs | 61 +++++++--- crates/rune/src/runtime/vm.rs | 76 +++++++++++-- crates/rune/src/runtime/vm_error.rs | 13 ++- crates/rune/src/tests/external_ops.rs | 149 +++++++++++++++++++++++-- 14 files changed, 331 insertions(+), 94 deletions(-) diff --git a/crates/rune-core/src/protocol.rs b/crates/rune-core/src/protocol.rs index 06e7a5ae2..cf6db01da 100644 --- a/crates/rune-core/src/protocol.rs +++ b/crates/rune-core/src/protocol.rs @@ -153,6 +153,14 @@ impl Protocol { doc: ["Allows an equality operation to work."], }; + /// Perform an ordered comparison between two values. + pub const CMP: Protocol = Protocol { + name: "cmp", + hash: 0x240f1b75466cd1a3, + repr: Some("if $value < b { }"), + doc: ["Allows for ordering operations to work."], + }; + /// The function to implement for the addition operation. pub const ADD: Protocol = Protocol { name: "add", diff --git a/crates/rune-macros/src/any.rs b/crates/rune-macros/src/any.rs index 4f2bb86d5..8d33790f1 100644 --- a/crates/rune-macros/src/any.rs +++ b/crates/rune-macros/src/any.rs @@ -409,7 +409,7 @@ fn expand_enum_install_with( module.index_function(#protocol::GET, #index, |this: &Self| { match this { #(#matches,)* - _ => return #vm_result::__rune_macros__unsupported_tuple_index_get(::type_info()), + _ => return #vm_result::__rune_macros__unsupported_tuple_index_get(::type_info(), #index), } })?; }); diff --git a/crates/rune-macros/src/context.rs b/crates/rune-macros/src/context.rs index b41dc8733..f0c54a574 100644 --- a/crates/rune-macros/src/context.rs +++ b/crates/rune-macros/src/context.rs @@ -160,7 +160,7 @@ impl Context { /// Parse field attributes. pub(crate) fn field_attrs(&self, input: &[syn::Attribute]) -> Result { - macro_rules! generate_op { + macro_rules! generate_assign { ($proto:ident, $op:tt) => { |g| { let Generate { @@ -323,52 +323,52 @@ impl Context { } else if meta.path == ADD_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_ADD_ASSIGN, +=), + generate: generate_assign!(PROTOCOL_ADD_ASSIGN, +=), }); } else if meta.path == SUB_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_SUB_ASSIGN, -=), + generate: generate_assign!(PROTOCOL_SUB_ASSIGN, -=), }); } else if meta.path == DIV_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_DIV_ASSIGN, /=), + generate: generate_assign!(PROTOCOL_DIV_ASSIGN, /=), }); } else if meta.path == MUL_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_MUL_ASSIGN, *=), + generate: generate_assign!(PROTOCOL_MUL_ASSIGN, *=), }); } else if meta.path == BIT_AND_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_BIT_AND_ASSIGN, &=), + generate: generate_assign!(PROTOCOL_BIT_AND_ASSIGN, &=), }); } else if meta.path == BIT_OR_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_BIT_OR_ASSIGN, |=), + generate: generate_assign!(PROTOCOL_BIT_OR_ASSIGN, |=), }); } else if meta.path == BIT_XOR_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_BIT_XOR_ASSIGN, ^=), + generate: generate_assign!(PROTOCOL_BIT_XOR_ASSIGN, ^=), }); } else if meta.path == SHL_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_SHL_ASSIGN, <<=), + generate: generate_assign!(PROTOCOL_SHL_ASSIGN, <<=), }); } else if meta.path == SHR_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_SHR_ASSIGN, >>=), + generate: generate_assign!(PROTOCOL_SHR_ASSIGN, >>=), }); } else if meta.path == REM_ASSIGN { attr.protocols.push(FieldProtocol { custom: self.parse_field_custom(meta.input)?, - generate: generate_op!(PROTOCOL_REM_ASSIGN, %=), + generate: generate_assign!(PROTOCOL_REM_ASSIGN, %=), }); } else { return Err(syn::Error::new_spanned(&meta.path, "Unsupported attribute")); diff --git a/crates/rune/src/any.rs b/crates/rune/src/any.rs index f09d9f0db..3ec26a4cc 100644 --- a/crates/rune/src/any.rs +++ b/crates/rune/src/any.rs @@ -84,4 +84,3 @@ pub trait Any: Named + any::Any { crate::__internal_impl_any!(::std::fmt, crate::no_std::fmt::Error); crate::__internal_impl_any!(::std::io, crate::no_std::io::Error); crate::__internal_impl_any!(::std::error, crate::no_std::Error); -crate::__internal_impl_any!(::std::cmp, core::cmp::Ordering); diff --git a/crates/rune/src/compile/named.rs b/crates/rune/src/compile/named.rs index e84e80d18..3beda941b 100644 --- a/crates/rune/src/compile/named.rs +++ b/crates/rune/src/compile/named.rs @@ -1,3 +1,5 @@ +use core::cmp::Ordering; + use crate::no_std::prelude::*; use crate::module::InstallWith; @@ -53,3 +55,9 @@ impl Named for bool { } impl InstallWith for bool {} + +impl Named for Ordering { + const BASE_NAME: RawStr = RawStr::from_str("Ordering"); +} + +impl InstallWith for Ordering {} diff --git a/crates/rune/src/modules/cmp.rs b/crates/rune/src/modules/cmp.rs index 79a670a57..d251dbe34 100644 --- a/crates/rune/src/modules/cmp.rs +++ b/crates/rune/src/modules/cmp.rs @@ -1,6 +1,6 @@ //! The `std::cmp` module. -use core::cmp; +use core::cmp::Ordering; use crate::runtime::Protocol; use crate::{ContextError, Module}; @@ -9,7 +9,7 @@ use crate::{ContextError, Module}; pub fn module() -> Result { let mut module = Module::with_crate_item("std", ["cmp"]); - let ty = module.ty::()?.docs([ + let ty = module.ty::()?.docs([ "An `Ordering` is the result of a comparison between two values.", "", "# Examples", @@ -32,21 +32,19 @@ pub fn module() -> Result { ty.variant_mut(0)? .make_empty()? - .constructor(|| cmp::Ordering::Less)? + .constructor(|| Ordering::Less)? .docs(["An ordering where a compared value is less than another."]); ty.variant_mut(1)? .make_empty()? - .constructor(|| cmp::Ordering::Equal)? + .constructor(|| Ordering::Equal)? .docs(["An ordering where a compared value is equal to another."]); ty.variant_mut(2)? .make_empty()? - .constructor(|| cmp::Ordering::Greater)? + .constructor(|| Ordering::Greater)? .docs(["An ordering where a compared value is greater than another."]); - module.associated_function(Protocol::EQ, |lhs: cmp::Ordering, rhs: cmp::Ordering| { - lhs == rhs - })?; + module.associated_function(Protocol::EQ, |lhs: Ordering, rhs: Ordering| lhs == rhs)?; Ok(module) } diff --git a/crates/rune/src/runtime.rs b/crates/rune/src/runtime.rs index 5972567ff..cfc629243 100644 --- a/crates/rune/src/runtime.rs +++ b/crates/rune/src/runtime.rs @@ -110,7 +110,7 @@ mod static_type; pub use self::static_type::{ StaticType, BOOL_TYPE, BYTES_TYPE, BYTE_TYPE, BYTE_TYPE_HASH, CHAR_TYPE, FLOAT_TYPE, FLOAT_TYPE_HASH, FORMAT_TYPE, FUNCTION_TYPE, FUTURE_TYPE, GENERATOR_STATE_TYPE, GENERATOR_TYPE, - INTEGER_TYPE, INTEGER_TYPE_HASH, ITERATOR_TYPE, OBJECT_TYPE, OPTION_TYPE, RANGE_TYPE, + INTEGER_TYPE, INTEGER_TYPE_HASH, ITERATOR_TYPE, OBJECT_TYPE, OPTION_TYPE, ORDERING, RANGE_TYPE, RESULT_TYPE, STREAM_TYPE, STRING_TYPE, TUPLE_TYPE, TYPE, UNIT_TYPE, VEC_TYPE, }; diff --git a/crates/rune/src/runtime/from_value.rs b/crates/rune/src/runtime/from_value.rs index de83d27d9..cccc0f7c4 100644 --- a/crates/rune/src/runtime/from_value.rs +++ b/crates/rune/src/runtime/from_value.rs @@ -1,4 +1,5 @@ use core::any; +use core::cmp::Ordering; use crate::no_std::collections::HashMap; use crate::no_std::prelude::*; @@ -252,23 +253,6 @@ impl UnsafeFromValue for &mut Option { } } -impl UnsafeFromValue for &mut Result { - type Output = *mut Result; - type Guard = RawMut; - - fn from_value(value: Value) -> VmResult<(Self::Output, Self::Guard)> { - let result = vm_try!(value.into_result()); - let result = vm_try!(result.into_mut()); - VmResult::Ok(Mut::into_raw(result)) - } - - unsafe fn unsafe_coerce(output: Self::Output) -> Self { - &mut *output - } -} - -// String impls - impl FromValue for String { fn from_value(value: Value) -> VmResult { match value { @@ -309,6 +293,7 @@ impl FromValue for Box { /// /// Note that we need to hold onto an instance of the static string to prevent /// the reference to it from being deallocated (the `StaticString` variant). +#[non_exhaustive] pub enum StrGuard { RawRef(RawRef), StaticString(Arc), @@ -408,8 +393,6 @@ impl UnsafeFromValue for &mut String { } } -// Result impls - impl FromValue for Result where T: FromValue, @@ -438,7 +421,20 @@ impl UnsafeFromValue for &Result { } } -// number impls +impl UnsafeFromValue for &mut Result { + type Output = *mut Result; + type Guard = RawMut; + + fn from_value(value: Value) -> VmResult<(Self::Output, Self::Guard)> { + let result = vm_try!(value.into_result()); + let result = vm_try!(result.into_mut()); + VmResult::Ok(Mut::into_raw(result)) + } + + unsafe fn unsafe_coerce(output: Self::Output) -> Self { + &mut *output + } +} impl FromValue for () { fn from_value(value: Value) -> VmResult { @@ -517,8 +513,6 @@ impl FromValue for f32 { } } -// map impls - macro_rules! impl_map { ($ty:ty) => { impl FromValue for $ty @@ -542,3 +536,10 @@ macro_rules! impl_map { } impl_map!(HashMap); + +impl FromValue for Ordering { + #[inline] + fn from_value(value: Value) -> VmResult { + value.into_ordering() + } +} diff --git a/crates/rune/src/runtime/static_type.rs b/crates/rune/src/runtime/static_type.rs index be0c72c8d..23ad1866e 100644 --- a/crates/rune/src/runtime/static_type.rs +++ b/crates/rune/src/runtime/static_type.rs @@ -1,4 +1,4 @@ -use core::cmp; +use core::cmp::{Eq, Ordering, PartialEq}; use core::hash; use crate::no_std::collections::HashMap; @@ -26,13 +26,13 @@ impl StaticType { } } -impl cmp::PartialEq for &'static StaticType { +impl PartialEq for &'static StaticType { fn eq(&self, other: &Self) -> bool { self.hash == other.hash } } -impl cmp::Eq for &'static StaticType {} +impl Eq for &'static StaticType {} impl hash::Hash for &'static StaticType { fn hash(&self, state: &mut H) { @@ -263,3 +263,12 @@ pub static TYPE: &StaticType = &StaticType { }; impl_static_type!(rt::Type => TYPE); + +/// The specialized type information for type objects. +pub static ORDERING: &StaticType = &StaticType { + name: RawStr::from_str("Ordering"), + // hash for ::std::cmp::Ordering + hash: Hash::new(0x30d24cc3fa13e4b7), +}; + +impl_static_type!(Ordering => ORDERING); diff --git a/crates/rune/src/runtime/to_value.rs b/crates/rune/src/runtime/to_value.rs index 2d18961fb..a37457a42 100644 --- a/crates/rune/src/runtime/to_value.rs +++ b/crates/rune/src/runtime/to_value.rs @@ -1,4 +1,5 @@ use core::any; +use core::cmp::Ordering; use crate::no_std::collections::HashMap; use crate::no_std::prelude::*; @@ -247,6 +248,7 @@ number_value_trait!(i128); number_value_trait!(isize); impl ToValue for f32 { + #[inline] fn to_value(self) -> VmResult { VmResult::Ok(Value::Float(self as f64)) } @@ -274,3 +276,10 @@ macro_rules! impl_map { } impl_map!(HashMap); + +impl ToValue for Ordering { + #[inline] + fn to_value(self) -> VmResult { + VmResult::Ok(Value::Ordering(self)) + } +} diff --git a/crates/rune/src/runtime/value.rs b/crates/rune/src/runtime/value.rs index 494ae8a5c..489bbf619 100644 --- a/crates/rune/src/runtime/value.rs +++ b/crates/rune/src/runtime/value.rs @@ -1,6 +1,5 @@ use core::borrow::Borrow; -use core::cmp; -use core::cmp::Ord; +use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; use core::fmt; use core::fmt::Write; use core::hash; @@ -172,13 +171,13 @@ pub struct VariantRtti { pub item: ItemBuf, } -impl cmp::PartialEq for VariantRtti { +impl PartialEq for VariantRtti { fn eq(&self, other: &Self) -> bool { self.hash == other.hash } } -impl cmp::Eq for VariantRtti {} +impl Eq for VariantRtti {} impl hash::Hash for VariantRtti { fn hash(&self, state: &mut H) { @@ -186,14 +185,14 @@ impl hash::Hash for VariantRtti { } } -impl cmp::PartialOrd for VariantRtti { - fn partial_cmp(&self, other: &Self) -> Option { +impl PartialOrd for VariantRtti { + fn partial_cmp(&self, other: &Self) -> Option { self.hash.partial_cmp(&other.hash) } } -impl cmp::Ord for VariantRtti { - fn cmp(&self, other: &Self) -> cmp::Ordering { +impl Ord for VariantRtti { + fn cmp(&self, other: &Self) -> Ordering { self.hash.cmp(&other.hash) } } @@ -208,13 +207,13 @@ pub struct Rtti { pub item: ItemBuf, } -impl cmp::PartialEq for Rtti { +impl PartialEq for Rtti { fn eq(&self, other: &Self) -> bool { self.hash == other.hash } } -impl cmp::Eq for Rtti {} +impl Eq for Rtti {} impl hash::Hash for Rtti { fn hash(&self, state: &mut H) { @@ -222,14 +221,14 @@ impl hash::Hash for Rtti { } } -impl cmp::PartialOrd for Rtti { - fn partial_cmp(&self, other: &Self) -> Option { +impl PartialOrd for Rtti { + fn partial_cmp(&self, other: &Self) -> Option { self.hash.partial_cmp(&other.hash) } } -impl cmp::Ord for Rtti { - fn cmp(&self, other: &Self) -> cmp::Ordering { +impl Ord for Rtti { + fn cmp(&self, other: &Self) -> Ordering { self.hash.cmp(&other.hash) } } @@ -251,6 +250,8 @@ pub enum Value { Float(f64), /// A type hash. Describes a type in the virtual machine. Type(Type), + /// Ordering. + Ordering(Ordering), /// A static string. /// /// While `Rc` would've been enough to store an unsized `str`, either @@ -631,6 +632,7 @@ impl Value { Self::Integer(value) => Self::Integer(value), Self::Float(value) => Self::Float(value), Self::Type(value) => Self::Type(value), + Self::Ordering(value) => Self::Ordering(value), Self::StaticString(value) => Self::StaticString(value), Self::String(value) => Self::String(Shared::new(vm_try!(value.take()))), Self::Bytes(value) => Self::Bytes(Shared::new(vm_try!(value.take()))), @@ -772,6 +774,28 @@ impl Value { } } + /// Try to coerce value into an [Ordering]. + #[inline] + pub fn as_ordering(&self) -> VmResult { + match self { + Self::Ordering(ty) => VmResult::Ok(*ty), + actual => err(VmErrorKind::expected::(vm_try!( + actual.type_info() + ))), + } + } + + /// Try to coerce value into an [Ordering]. + #[inline] + pub fn into_ordering(self) -> VmResult { + match self { + Self::Ordering(ty) => VmResult::Ok(ty), + actual => err(VmErrorKind::expected::(vm_try!( + actual.type_info() + ))), + } + } + /// Try to coerce value into a result. #[inline] pub fn into_result(self) -> VmResult>> { @@ -992,6 +1016,8 @@ impl Value { Self::Char(..) => crate::runtime::CHAR_TYPE.hash, Self::Integer(..) => crate::runtime::INTEGER_TYPE.hash, Self::Float(..) => crate::runtime::FLOAT_TYPE.hash, + Self::Type(..) => crate::runtime::TYPE.hash, + Self::Ordering(..) => crate::runtime::ORDERING.hash, Self::StaticString(..) => crate::runtime::STRING_TYPE.hash, Self::String(..) => crate::runtime::STRING_TYPE.hash, Self::Bytes(..) => crate::runtime::BYTES_TYPE.hash, @@ -1008,7 +1034,6 @@ impl Value { Self::Function(..) => crate::runtime::FUNCTION_TYPE.hash, Self::Format(..) => crate::runtime::FORMAT_TYPE.hash, Self::Iterator(..) => crate::runtime::ITERATOR_TYPE.hash, - Self::Type(..) => crate::runtime::TYPE.hash, Self::UnitStruct(empty) => empty.borrow_ref()?.rtti.hash, Self::TupleStruct(tuple) => tuple.borrow_ref()?.rtti.hash, Self::Struct(object) => object.borrow_ref()?.rtti.hash, @@ -1026,6 +1051,8 @@ impl Value { Self::Char(..) => TypeInfo::StaticType(crate::runtime::CHAR_TYPE), Self::Integer(..) => TypeInfo::StaticType(crate::runtime::INTEGER_TYPE), Self::Float(..) => TypeInfo::StaticType(crate::runtime::FLOAT_TYPE), + Self::Type(..) => TypeInfo::StaticType(crate::runtime::TYPE), + Self::Ordering(..) => TypeInfo::StaticType(crate::runtime::ORDERING), Self::StaticString(..) => TypeInfo::StaticType(crate::runtime::STRING_TYPE), Self::String(..) => TypeInfo::StaticType(crate::runtime::STRING_TYPE), Self::Bytes(..) => TypeInfo::StaticType(crate::runtime::BYTES_TYPE), @@ -1042,7 +1069,6 @@ impl Value { Self::Function(..) => TypeInfo::StaticType(crate::runtime::FUNCTION_TYPE), Self::Format(..) => TypeInfo::StaticType(crate::runtime::FORMAT_TYPE), Self::Iterator(..) => TypeInfo::StaticType(crate::runtime::ITERATOR_TYPE), - Self::Type(..) => TypeInfo::StaticType(crate::runtime::TYPE), Self::UnitStruct(empty) => vm_try!(empty.borrow_ref()).type_info(), Self::TupleStruct(tuple) => vm_try!(tuple.borrow_ref()).type_info(), Self::Struct(object) => vm_try!(object.borrow_ref()).type_info(), @@ -1400,6 +1426,8 @@ impl ser::Serialize for Value { Value::Byte(c) => serializer.serialize_u8(*c), Value::Integer(integer) => serializer.serialize_i64(*integer), Value::Float(float) => serializer.serialize_f64(*float), + Value::Type(..) => Err(ser::Error::custom("cannot serialize types")), + Value::Ordering(..) => Err(ser::Error::custom("cannot serialize orderings")), Value::StaticString(string) => serializer.serialize_str(string.as_ref()), Value::String(string) => { let string = string.borrow_ref().map_err(ser::Error::custom)?; @@ -1448,7 +1476,6 @@ impl ser::Serialize for Value { Value::Struct(..) => Err(ser::Error::custom("cannot serialize objects structs")), Value::Variant(..) => Err(ser::Error::custom("cannot serialize variants")), Value::Result(..) => Err(ser::Error::custom("cannot serialize results")), - Value::Type(..) => Err(ser::Error::custom("cannot serialize types")), Value::Future(..) => Err(ser::Error::custom("cannot serialize futures")), Value::Stream(..) => Err(ser::Error::custom("cannot serialize streams")), Value::Generator(..) => Err(ser::Error::custom("cannot serialize generators")), diff --git a/crates/rune/src/runtime/vm.rs b/crates/rune/src/runtime/vm.rs index c1307b453..9c1e55bed 100644 --- a/crates/rune/src/runtime/vm.rs +++ b/crates/rune/src/runtime/vm.rs @@ -1,3 +1,4 @@ +use core::cmp::Ordering; use core::fmt; use core::mem; use core::ops; @@ -568,6 +569,7 @@ impl Vm { &mut self, int_op: fn(i64, i64) -> bool, float_op: fn(f64, f64) -> bool, + match_ordering: fn(Ordering) -> bool, op: &'static str, lhs: InstAddress, rhs: InstAddress, @@ -579,11 +581,20 @@ impl Vm { (Value::Integer(lhs), Value::Integer(rhs)) => int_op(lhs, rhs), (Value::Float(lhs), Value::Float(rhs)) => float_op(lhs, rhs), (lhs, rhs) => { - return err(VmErrorKind::UnsupportedBinaryOperation { - op, - lhs: vm_try!(lhs.type_info()), - rhs: vm_try!(rhs.type_info()), - }); + let ordering = match vm_try!(self.call_instance_fn(lhs, Protocol::CMP, (&rhs,))) { + CallResult::Ok(()) => { + vm_try!(::from_value(vm_try!(self.stack.pop()))) + } + CallResult::Unsupported(lhs) => { + return err(VmErrorKind::UnsupportedBinaryOperation { + op, + lhs: vm_try!(lhs.type_info()), + rhs: vm_try!(rhs.type_info()), + }); + } + }; + + match_ordering(ordering) } }; @@ -1244,9 +1255,20 @@ impl Vm { vm_try!(<()>::from_value(value)); VmResult::Ok(()) } - TargetFallback::Index(lhs, ..) => err(VmErrorKind::UnsupportedTupleIndexGet { - target: vm_try!(lhs.type_info()), - }), + TargetFallback::Index(lhs, index, rhs) => { + if let CallResult::Unsupported(lhs) = + vm_try!(self.call_index_fn(protocol, lhs.clone(), index, (rhs,))) + { + return err(VmErrorKind::UnsupportedTupleIndexGet { + target: vm_try!(lhs.type_info()), + index, + }); + } + + let value = vm_try!(self.stack.pop()); + vm_try!(<()>::from_value(value)); + VmResult::Ok(()) + } } } @@ -1769,16 +1791,44 @@ impl Vm { vm_try!(self.internal_infallible_bitwise(Protocol::SHR, ops::Shr::shr, lhs, rhs)); } InstOp::Gt => { - vm_try!(self.internal_boolean_ops(|a, b| a > b, |a, b| a > b, ">", lhs, rhs)); + vm_try!(self.internal_boolean_ops( + |a, b| a > b, + |a, b| a > b, + |o| matches!(o, Ordering::Greater), + ">", + lhs, + rhs + )); } InstOp::Gte => { - vm_try!(self.internal_boolean_ops(|a, b| a >= b, |a, b| a >= b, ">=", lhs, rhs)); + vm_try!(self.internal_boolean_ops( + |a, b| a >= b, + |a, b| a >= b, + |o| matches!(o, Ordering::Greater | Ordering::Equal), + ">=", + lhs, + rhs + )); } InstOp::Lt => { - vm_try!(self.internal_boolean_ops(|a, b| a < b, |a, b| a < b, "<", lhs, rhs)); + vm_try!(self.internal_boolean_ops( + |a, b| a < b, + |a, b| a < b, + |o| matches!(o, Ordering::Less), + "<", + lhs, + rhs + )); } InstOp::Lte => { - vm_try!(self.internal_boolean_ops(|a, b| a <= b, |a, b| a <= b, "<=", lhs, rhs)); + vm_try!(self.internal_boolean_ops( + |a, b| a <= b, + |a, b| a <= b, + |o| matches!(o, Ordering::Less | Ordering::Equal), + "<=", + lhs, + rhs + )); } InstOp::Eq => { let rhs = vm_try!(self.stack.address(rhs)); @@ -2148,6 +2198,7 @@ impl Vm { { return err(VmErrorKind::UnsupportedTupleIndexGet { target: vm_try!(value.type_info()), + index, }); } @@ -2187,6 +2238,7 @@ impl Vm { { return err(VmErrorKind::UnsupportedTupleIndexGet { target: vm_try!(value.type_info()), + index, }); } diff --git a/crates/rune/src/runtime/vm_error.rs b/crates/rune/src/runtime/vm_error.rs index 48a53d89f..97b0a18a6 100644 --- a/crates/rune/src/runtime/vm_error.rs +++ b/crates/rune/src/runtime/vm_error.rs @@ -331,8 +331,8 @@ impl VmResult { #[doc(hidden)] #[inline] - pub fn __rune_macros__unsupported_tuple_index_get(target: TypeInfo) -> Self { - Self::err(VmErrorKind::UnsupportedTupleIndexGet { target }) + pub fn __rune_macros__unsupported_tuple_index_get(target: TypeInfo, index: usize) -> Self { + Self::err(VmErrorKind::UnsupportedTupleIndexGet { target, index }) } } @@ -475,6 +475,7 @@ pub(crate) enum VmErrorKind { }, UnsupportedTupleIndexGet { target: TypeInfo, + index: usize, }, UnsupportedTupleIndexSet { target: TypeInfo, @@ -658,19 +659,19 @@ impl fmt::Display for VmErrorKind { f, "The index get operation `{target}[{index}]` is not supported", ), - VmErrorKind::UnsupportedTupleIndexGet { target } => write!( + VmErrorKind::UnsupportedTupleIndexGet { target, index } => write!( f, - "The tuple index get operation is not supported on `{target}`", + "The tuple index get {index} operation is not supported on `{target}`", ), VmErrorKind::UnsupportedTupleIndexSet { target } => write!( f, "The tuple index set operation is not supported on `{target}`", ), VmErrorKind::UnsupportedObjectSlotIndexGet { target } => { - write!(f, "Field not available on `{target}`",) + write!(f, "Field not available to get on `{target}`",) } VmErrorKind::UnsupportedObjectSlotIndexSet { target } => { - write!(f, "Field not available on `{target}`",) + write!(f, "Field not available to set on `{target}`",) } VmErrorKind::UnsupportedIs { value, test_type } => { write!(f, "Operation `{value} is {test_type}` is not supported",) diff --git a/crates/rune/src/tests/external_ops.rs b/crates/rune/src/tests/external_ops.rs index 4d3b7d9ba..1328c73c8 100644 --- a/crates/rune/src/tests/external_ops.rs +++ b/crates/rune/src/tests/external_ops.rs @@ -1,10 +1,10 @@ prelude!(); +use std::cmp::{Ord, Ordering}; use std::sync::Arc; #[test] -fn test_external_ops_struct() -> Result<()> { - /// Test case for a single operation. +fn assign_ops_struct() -> Result<()> { macro_rules! test_case { ([$($op:tt)*], $protocol:ident, $derived:tt, $initial:literal, $arg:literal, $expected:literal) => {{ #[derive(Debug, Default, Any)] @@ -71,9 +71,9 @@ fn test_external_ops_struct() -> Result<()> { let output = vm.clone().call(["type"], (&mut foo,))?; assert_eq!(foo.value, $expected, "{} != {} (value)", foo.value, $expected); - assert_eq!(foo.field, $expected, "{} != {} (field)", foo.value, $expected); - assert_eq!(foo.derived, $expected, "{} != {} (derived)", foo.value, $expected); - assert_eq!(foo.custom, $expected, "{} != {} (custom)", foo.value, $expected); + assert_eq!(foo.field, $expected, "{} != {} (field)", foo.field, $expected); + assert_eq!(foo.derived, $expected, "{} != {} (derived)", foo.derived, $expected); + assert_eq!(foo.custom, $expected, "{} != {} (custom)", foo.custom, $expected); assert!(matches!(output, Value::Unit)); } }}; @@ -93,9 +93,7 @@ fn test_external_ops_struct() -> Result<()> { } #[test] -#[ignore = "Currently does not work, but should!"] -fn test_external_ops_tuple() -> Result<()> { - /// Test case for a single operation. +fn assign_ops_tuple() -> Result<()> { macro_rules! test_case { ([$($op:tt)*], $protocol:ident, $derived:tt, $initial:literal, $arg:literal, $expected:literal) => {{ #[derive(Debug, Default, Any)] @@ -154,10 +152,10 @@ fn test_external_ops_tuple() -> Result<()> { let output = vm.clone().call(["type"], (&mut foo,))?; - assert_eq!(foo.0, $expected, "{} != {} (value)", foo.0, $expected); - assert_eq!(foo.1, $expected, "{} != {} (field)", foo.0, $expected); - assert_eq!(foo.2, $expected, "{} != {} (derived)", foo.0, $expected); - assert_eq!(foo.3, $expected, "{} != {} (custom)", foo.0, $expected); + assert_eq!(foo.0, $expected, "{} != {} (value .0)", foo.0, $expected); + assert_eq!(foo.1, $expected, "{} != {} (field .1)", foo.1, $expected); + assert_eq!(foo.2, $expected, "{} != {} (derived .2)", foo.2, $expected); + assert_eq!(foo.3, $expected, "{} != {} (custom .3)", foo.3, $expected); assert!(matches!(output, Value::Unit)); } }}; @@ -175,3 +173,130 @@ fn test_external_ops_tuple() -> Result<()> { test_case!([%=], REM_ASSIGN, rem_assign, 25, 10, 5); Ok(()) } + +#[test] +fn ordering_struct() -> Result<()> { + macro_rules! test_case { + ([$($op:tt)*], $protocol:ident, $initial:literal, $arg:literal, $expected:literal) => {{ + #[derive(Debug, Default, Any)] + struct External { + value: i64, + } + + impl External { + fn value(&self, value: i64) -> Ordering { + Ord::cmp(&self.value, &value) + } + } + + let mut module = Module::new(); + module.ty::()?; + + module.associated_function(Protocol::$protocol, External::value)?; + + let mut context = Context::with_default_modules()?; + context.install(module)?; + + let mut sources = Sources::new(); + sources.insert(Source::new( + "test", + format!(r#" + pub fn type(number) {{ + number {op} {arg} + }} + "#, op = stringify!($($op)*), arg = stringify!($arg)), + )); + + let unit = prepare(&mut sources) + .with_context(&context) + .build()?; + + let unit = Arc::new(unit); + + let vm = Vm::new(Arc::new(context.runtime()), unit); + + { + let mut foo = External::default(); + foo.value = $initial; + + let output = vm.clone().call(["type"], (&mut foo,))?; + let a = ::from_value(output).into_result()?; + + assert_eq!(a, $expected, "{} != {} (value)", foo.value, $expected); + } + }}; + } + + test_case!([<], CMP, 1, 2, true); + test_case!([<], CMP, 2, 1, false); + + test_case!([>], CMP, 2, 1, true); + test_case!([>], CMP, 1, 2, false); + + test_case!([>=], CMP, 3, 2, true); + test_case!([>=], CMP, 2, 2, true); + test_case!([>=], CMP, 1, 2, false); + + test_case!([<=], CMP, 2, 3, true); + test_case!([<=], CMP, 2, 2, true); + test_case!([<=], CMP, 2, 1, false); + Ok(()) +} + +#[test] +fn eq_struct() -> Result<()> { + macro_rules! test_case { + ([$($op:tt)*], $protocol:ident, $initial:literal, $arg:literal, $expected:literal) => {{ + #[derive(Debug, Default, Any)] + struct External { + value: i64, + } + + impl External { + fn value(&self, value: i64) -> bool { + self.value $($op)* value + } + } + + let mut module = Module::new(); + module.ty::()?; + + module.associated_function(Protocol::$protocol, External::value)?; + + let mut context = Context::with_default_modules()?; + context.install(module)?; + + let mut sources = Sources::new(); + sources.insert(Source::new( + "test", + format!(r#" + pub fn type(number) {{ + number {op} {arg} + }} + "#, op = stringify!($($op)*), arg = stringify!($arg)), + )); + + let unit = prepare(&mut sources) + .with_context(&context) + .build()?; + + let unit = Arc::new(unit); + + let vm = Vm::new(Arc::new(context.runtime()), unit); + + { + let mut foo = External::default(); + foo.value = $initial; + + let output = vm.clone().call(["type"], (&mut foo,))?; + let a = ::from_value(output).into_result()?; + + assert_eq!(a, $expected, "{} != {} (value)", foo.value, $expected); + } + }}; + } + + test_case!([==], EQ, 2, 2, true); + test_case!([==], EQ, 2, 1, false); + Ok(()) +}