diff --git a/crates/rune-macros/src/context.rs b/crates/rune-macros/src/context.rs index 973b1aa3f..9f213ad43 100644 --- a/crates/rune-macros/src/context.rs +++ b/crates/rune-macros/src/context.rs @@ -306,6 +306,51 @@ impl Context { }; } + macro_rules! generate { + ($proto:ident, $op:tt) => { + |g| { + let Generate { + ty, + target, + field, + protocol, + .. + } = g; + + let protocol_field = g.tokens.protocol(&Protocol::$proto); + + match target { + GenerateTarget::Named { field_ident, field_name } => { + if let Some(custom) = &protocol.custom { + quote_spanned! { field.span() => + module.field_function(&#protocol_field, #field_name, #custom)?; + } + } else { + quote_spanned! { field.span() => + module.field_function(&#protocol_field, #field_name, |s: &mut Self, value: #ty| { + s.#field_ident $op value + })?; + } + } + } + GenerateTarget::Numbered { field_index } => { + if let Some(custom) = &protocol.custom { + quote_spanned! { field.span() => + module.index_function(&#protocol_field, #field_index, #custom)?; + } + } else { + quote_spanned! { field.span() => + module.index_function(&#protocol_field, #field_index, |s: &mut Self, value: #ty| { + s.#field_index $op value + })?; + } + } + } + } + } + }; + } + let mut attr = FieldAttrs::default(); for a in input { @@ -314,6 +359,35 @@ impl Context { } let result = a.parse_nested_meta(|meta| { + macro_rules! field_functions { + ( + $( + $assign:literal, $assign_proto:ident, [$($assign_op:tt)*], + $op:literal, $op_proto:ident, [$($op_op:tt)*], + )* + ) => {{ + $( + if meta.path.is_ident($assign) { + attr.protocols.push(FieldProtocol { + custom: self.parse_field_custom(meta.input)?, + generate: generate_assign!($assign_proto, $($assign_op)*), + }); + + return Ok(()); + } + + if meta.path.is_ident($op) { + attr.protocols.push(FieldProtocol { + custom: self.parse_field_custom(meta.input)?, + generate: generate!($op_proto, $($op_op)*), + }); + + return Ok(()); + } + )* + }}; + } + if meta.path.is_ident("id") { attr.id = Some(meta.path.span()); return Ok(()); @@ -451,94 +525,17 @@ impl Context { return Ok(()); } - if meta.path.is_ident("add_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(ADD_ASSIGN, +=), - }); - - return Ok(()); - } - - if meta.path.is_ident("sub_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(SUB_ASSIGN, -=), - }); - - return Ok(()); - } - - if meta.path.is_ident("div_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(DIV_ASSIGN, /=), - }); - - return Ok(()); - } - - if meta.path.is_ident("mul_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(MUL_ASSIGN, *=), - }); - - return Ok(()); - } - - if meta.path.is_ident("bit_and_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(BIT_AND_ASSIGN, &=), - }); - - return Ok(()); - } - - if meta.path.is_ident("bit_or_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(BIT_OR_ASSIGN, |=), - }); - - return Ok(()); - } - - if meta.path.is_ident("bit_xor_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(BIT_XOR_ASSIGN, ^=), - }); - - return Ok(()); - } - - if meta.path.is_ident("shl_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(SHL_ASSIGN, <<=), - }); - - return Ok(()); - } - - if meta.path.is_ident("shr_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(SHR_ASSIGN, >>=), - }); - - return Ok(()); - } - - if meta.path.is_ident("rem_assign") { - attr.protocols.push(FieldProtocol { - custom: self.parse_field_custom(meta.input)?, - generate: generate_assign!(REM_ASSIGN, %=), - }); - - return Ok(()); + field_functions! { + "add_assign", ADD_ASSIGN, [+=], "add", ADD, [+], + "sub_assign", SUB_ASSIGN, [-=], "sub", SUB, [-], + "div_assign", DIV_ASSIGN, [/=], "div", DIV, [/], + "mul_assign", MUL_ASSIGN, [*=], "mul", MUL, [*], + "rem_assign", REM_ASSIGN, [%=], "rem", REM, [%], + "bit_and_assign", BIT_AND_ASSIGN, [&=], "bit_and", BIT_AND, [&], + "bit_or_assign", BIT_OR_ASSIGN, [|=], "bit_or", BIT_OR, [|], + "bit_xor_assign", BIT_XOR_ASSIGN, [^=], "bit_xor", BIT_XOR, [^], + "shl_assign", SHL_ASSIGN, [<<=], "shl", SHL, [<<], + "shr_assign", SHR_ASSIGN, [>>=], "shr", SHR, [>>], } Err(syn::Error::new_spanned(&meta.path, "Unsupported attribute")) diff --git a/crates/rune/src/runtime/static_string.rs b/crates/rune/src/runtime/static_string.rs index 0e35e853d..286982ade 100644 --- a/crates/rune/src/runtime/static_string.rs +++ b/crates/rune/src/runtime/static_string.rs @@ -72,6 +72,13 @@ impl AsRef for StaticString { } } +impl fmt::Display for StaticString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + impl fmt::Debug for StaticString { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/rune/src/runtime/vm.rs b/crates/rune/src/runtime/vm.rs index a6e6c4f64..cd8f8ac21 100644 --- a/crates/rune/src/runtime/vm.rs +++ b/crates/rune/src/runtime/vm.rs @@ -1212,7 +1212,7 @@ impl Vm { VmResult::Ok(()) } - TargetFallback::Field(lhs, hash, rhs) => { + TargetFallback::Field(lhs, hash, slot, rhs) => { let mut args = DynGuardedArgs::new((rhs,)); if let CallResult::Unsupported(lhs) = vm_try!(self.call_field_fn( @@ -1222,8 +1222,13 @@ impl Vm { &mut args, Output::discard() )) { + let Some(field) = self.unit.lookup_string(slot) else { + return err(VmErrorKind::MissingStaticString { slot }); + }; + return err(VmErrorKind::UnsupportedObjectSlotIndexGet { target: lhs.type_info(), + field: field.clone(), }); } @@ -2213,8 +2218,13 @@ impl Vm { vm_try!(self.call_field_fn(&Protocol::SET, target, hash, &mut args, Output::discard())); if let CallResult::Unsupported(target) = result { + let Some(field) = self.unit.lookup_string(slot) else { + return err(VmErrorKind::MissingStaticString { slot }); + }; + return err(VmErrorKind::UnsupportedObjectSlotIndexSet { target: target.type_info(), + field: field.clone(), }); }; @@ -2260,6 +2270,7 @@ impl Vm { target => { return err(VmErrorKind::UnsupportedObjectSlotIndexGet { target: target.type_info(), + field: index.clone(), }); } } @@ -2269,8 +2280,13 @@ impl Vm { if let CallResult::Unsupported(target) = vm_try!(self.call_field_fn(&Protocol::GET, target, index.hash(), &mut (), out)) { + let Some(field) = self.unit.lookup_string(slot) else { + return err(VmErrorKind::MissingStaticString { slot }); + }; + return err(VmErrorKind::UnsupportedObjectSlotIndexGet { target: target.type_info(), + field: field.clone(), }); } @@ -3414,7 +3430,7 @@ fn check_args(args: usize, expected: usize) -> Result<(), VmErrorKind> { enum TargetFallback { Value(Value, Value), - Field(Value, Hash, Value), + Field(Value, Hash, usize, Value), Index(Value, usize, Value), } @@ -3468,6 +3484,7 @@ fn target_value<'a>( Ok(TargetValue::Fallback(TargetFallback::Field( lhs.clone(), field.hash(), + slot, rhs.clone(), ))) } diff --git a/crates/rune/src/runtime/vm_error.rs b/crates/rune/src/runtime/vm_error.rs index 2c265932a..8e7a6b2b1 100644 --- a/crates/rune/src/runtime/vm_error.rs +++ b/crates/rune/src/runtime/vm_error.rs @@ -9,12 +9,13 @@ use crate::alloc::prelude::*; use crate::alloc::{self, String}; use crate::compile::meta; use crate::runtime::unit::{BadInstruction, BadJump}; -use crate::runtime::{ +use crate::{Any, Hash, ItemBuf}; + +use super::{ AccessError, AccessErrorKind, AnyObjError, AnyObjErrorKind, AnyTypeInfo, BoxedPanic, CallFrame, DynArgsUsed, DynamicTakeError, ExecutionState, MaybeTypeOf, Panic, Protocol, SliceError, - StackError, TypeInfo, TypeOf, Unit, Vm, VmHaltInfo, + StackError, StaticString, TypeInfo, TypeOf, Unit, Vm, VmHaltInfo, }; -use crate::{Any, Hash, ItemBuf}; /// A virtual machine error which includes tracing information. pub struct VmError { @@ -741,9 +742,11 @@ pub(crate) enum VmErrorKind { }, UnsupportedObjectSlotIndexGet { target: TypeInfo, + field: Arc, }, UnsupportedObjectSlotIndexSet { target: TypeInfo, + field: Arc, }, UnsupportedIs { value: TypeInfo, @@ -962,11 +965,11 @@ impl fmt::Display for VmErrorKind { f, "The tuple index set operation is not supported on `{target}`", ), - VmErrorKind::UnsupportedObjectSlotIndexGet { target } => { - write!(f, "Field not available to get on `{target}`") + VmErrorKind::UnsupportedObjectSlotIndexGet { target, field } => { + write!(f, "Field `{field}` not available on `{target}`") } - VmErrorKind::UnsupportedObjectSlotIndexSet { target } => { - write!(f, "Field not available to set on `{target}`") + VmErrorKind::UnsupportedObjectSlotIndexSet { target, field } => { + write!(f, "Field `{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 a008ea3ab..9ceadfd7f 100644 --- a/crates/rune/src/tests/external_ops.rs +++ b/crates/rune/src/tests/external_ops.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use std::sync::Arc; #[test] -fn assign_ops_struct() -> Result<()> { +fn strut_assign() -> Result<()> { macro_rules! test_case { ([$($op:tt)*], $protocol:ident, $derived:tt, $initial:literal, $arg:literal, $expected:literal) => {{ #[derive(Debug, Default, Any)] @@ -37,7 +37,7 @@ fn assign_ops_struct() -> Result<()> { module.associated_function(&Protocol::$protocol, External::value)?; module.field_function(&Protocol::$protocol, "field", External::field)?; - let mut context = Context::with_default_modules()?; + let mut context = Context::default(); context.install(module)?; let mut sources = Sources::new(); @@ -83,17 +83,17 @@ fn assign_ops_struct() -> Result<()> { test_case!([-=], SUB_ASSIGN, sub_assign, 4, 3, 1); test_case!([*=], MUL_ASSIGN, mul_assign, 8, 2, 16); test_case!([/=], DIV_ASSIGN, div_assign, 8, 3, 2); + test_case!([%=], REM_ASSIGN, rem_assign, 25, 10, 5); test_case!([&=], BIT_AND_ASSIGN, bit_and_assign, 0b1001, 0b0011, 0b0001); test_case!([|=], BIT_OR_ASSIGN, bit_or_assign, 0b1001, 0b0011, 0b1011); test_case!([^=], BIT_XOR_ASSIGN, bit_xor_assign, 0b1001, 0b0011, 0b1010); test_case!([<<=], SHL_ASSIGN, shl_assign, 0b1001, 0b0001, 0b10010); test_case!([>>=], SHR_ASSIGN, shr_assign, 0b1001, 0b0001, 0b100); - test_case!([%=], REM_ASSIGN, rem_assign, 25, 10, 5); Ok(()) } #[test] -fn assign_ops_tuple() -> Result<()> { +fn tuple_assign() -> Result<()> { macro_rules! test_case { ([$($op:tt)*], $protocol:ident, $derived:tt, $initial:literal, $arg:literal, $expected:literal) => {{ #[derive(Debug, Default, Any)] @@ -119,7 +119,7 @@ fn assign_ops_tuple() -> Result<()> { module.associated_function(&Protocol::$protocol, External::value)?; module.index_function(&Protocol::$protocol, 1, External::field)?; - let mut context = Context::with_default_modules()?; + let mut context = Context::default(); context.install(module)?; let mut sources = Sources::new(); @@ -165,12 +165,102 @@ fn assign_ops_tuple() -> Result<()> { test_case!([-=], SUB_ASSIGN, sub_assign, 4, 3, 1); test_case!([*=], MUL_ASSIGN, mul_assign, 8, 2, 16); test_case!([/=], DIV_ASSIGN, div_assign, 8, 3, 2); + test_case!([%=], REM_ASSIGN, rem_assign, 25, 10, 5); test_case!([&=], BIT_AND_ASSIGN, bit_and_assign, 0b1001, 0b0011, 0b0001); test_case!([|=], BIT_OR_ASSIGN, bit_or_assign, 0b1001, 0b0011, 0b1011); test_case!([^=], BIT_XOR_ASSIGN, bit_xor_assign, 0b1001, 0b0011, 0b1010); test_case!([<<=], SHL_ASSIGN, shl_assign, 0b1001, 0b0001, 0b10010); test_case!([>>=], SHR_ASSIGN, shr_assign, 0b1001, 0b0001, 0b100); - test_case!([%=], REM_ASSIGN, rem_assign, 25, 10, 5); + Ok(()) +} + +#[test] +#[ignore = "assembly currently doesn't know how to handle this"] +fn struct_ops() -> Result<()> { + macro_rules! test_case { + ([$($op:tt)*], $protocol:ident, $derived:tt, $initial:literal, $arg:literal, $expected:literal) => {{ + #[derive(Debug, Default, Any)] + struct External { + value: i64, + field: i64, + #[rune($derived)] + derived: i64, + #[rune($derived = External::custom)] + custom: i64, + } + + impl External { + fn value(&self, value: i64) -> i64 { + self.value $($op)* value + } + + fn field(&self, value: i64) -> i64 { + self.field $($op)* value + } + + fn custom(&self, value: i64) -> i64 { + self.custom $($op)* value + } + } + + let mut module = Module::new(); + module.ty::()?; + + module.associated_function(&Protocol::$protocol, External::value)?; + module.field_function(&Protocol::$protocol, "field", External::field)?; + + let mut context = Context::default(); + context.install(module)?; + + let source = format!(r#" + pub fn type(number) {{ + let a = number {op} {arg}; + let b = number.field {op} {arg}; + let c = number.derived {op} {arg}; + let d = number.custom {op} {arg}; + (a, b, c, d) + }} + "#, op = stringify!($($op)*), arg = stringify!($arg)); + + let mut sources = Sources::new(); + sources.insert(Source::memory(source)?)?; + + let unit = prepare(&mut sources) + .with_context(&context) + .build()?; + + let unit = Arc::new(unit); + + let mut vm = Vm::new(Arc::new(context.runtime()?), unit); + + let mut foo = External::default(); + foo.value = $initial; + foo.field = $initial; + foo.derived = $initial; + foo.custom = $initial; + + let output = vm.call(["type"], (&mut foo,))?; + let (a, b, c, d) = crate::from_value::<(i64, i64, i64, i64)>(output)?; + + let expected: i64 = $expected; + + assert_eq!(a, expected, "{a} != {expected} (value)"); + assert_eq!(b, expected, "{b} != {expected} (field)"); + assert_eq!(c, expected, "{c} != {expected} (derived)"); + assert_eq!(d, expected, "{d} != {expected} (custom)"); + }}; + } + + test_case!([+], ADD, add, 0, 3, 3); + test_case!([-], SUB, sub, 4, 3, 1); + test_case!([*], MUL, mul, 8, 2, 16); + test_case!([/], DIV, div, 8, 3, 2); + test_case!([%], REM, rem, 25, 10, 5); + test_case!([&], BIT_AND, bit_and, 0b1001, 0b0011, 0b0001); + test_case!([|], BIT_OR, bit_or, 0b1001, 0b0011, 0b1011); + test_case!([^], BIT_XOR, bit_xor, 0b1001, 0b0011, 0b1010); + test_case!([<<], SHL, shl, 0b1001, 0b0001, 0b10010); + test_case!([>>], SHR, shr, 0b1001, 0b0001, 0b100); Ok(()) } @@ -194,7 +284,7 @@ fn ordering_struct() -> Result<()> { module.associated_function(&Protocol::$protocol, External::value)?; - let mut context = Context::with_default_modules()?; + let mut context = Context::default(); context.install(module)?; let mut sources = Sources::new(); @@ -263,16 +353,14 @@ fn eq_struct() -> Result<()> { module.associated_function(&Protocol::$protocol, External::value)?; - let mut context = Context::with_default_modules()?; + let mut context = Context::default(); context.install(module)?; let mut sources = Sources::new(); sources.insert(Source::new( "test", format!(r#" - pub fn type(number) {{ - number {op} {arg} - }} + pub fn type(number) {{ number {op} {arg} }} "#, op = stringify!($($op)*), arg = stringify!($arg)), )?)?;