diff --git a/src/api/register.rs b/src/api/register.rs index 8f4d4833..285f959a 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -90,15 +90,15 @@ impl Engine { let param_type_names: Option<&[&str]> = None; let fn_name = name.as_ref(); - let no_const = false; + let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); + let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] - let no_const = - no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); + let is_pure = + is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET)); - let func = func.into_callable_function(fn_name.into(), no_const); + let func = func.into_callable_function(fn_name.into(), is_pure); self.global_namespace_mut().set_fn( name, diff --git a/src/func/call.rs b/src/func/call.rs index dac38df3..6c8413d0 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -276,6 +276,7 @@ impl Engine { func: CallableFunction::Method { func: Shared::new(f), has_context, + is_pure: false, }, source: None, }) @@ -285,6 +286,7 @@ impl Engine { func: CallableFunction::Method { func: Shared::new(f), has_context, + is_pure: true, }, source: None, }), @@ -372,8 +374,8 @@ impl Engine { let backup = &mut ArgBackup::new(); - // Calling pure function but the first argument is a reference? - let swap = is_ref_mut && func.is_pure() && !args.is_empty(); + // Calling non-method function but the first argument is a reference? + let swap = is_ref_mut && !func.is_method() && !args.is_empty(); if swap { // Clone the first argument @@ -400,12 +402,11 @@ impl Engine { .has_context() .then(|| (self, name, src, &*global, pos).into()); - let mut _result = if let Some(f) = func.get_plugin_fn() { - if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { - Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) - } else { - f.call(context, args) - } + let mut _result = if !func.is_pure() && !args.is_empty() && args[0].is_read_only() { + // If function is not pure, there must be at least one argument + Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) + } else if let Some(f) = func.get_plugin_fn() { + f.call(context, args) } else if let Some(f) = func.get_native_fn() { f(context, args) } else { @@ -1493,17 +1494,18 @@ impl Engine { self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) } + Some(f) if !f.is_pure() && args[0].is_read_only() => { + // If function is not pure, there must be at least one argument + Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) + } + Some(f) if f.is_plugin_fn() => { let f = f.get_plugin_fn().expect("plugin function"); let context = f .has_context() .then(|| (self, fn_name, module.id(), &*global, pos).into()); - if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { - Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) - } else { - f.call(context, args) - .and_then(|r| self.check_data_size(r, pos)) - } + f.call(context, args) + .and_then(|r| self.check_data_size(r, pos)) } Some(f) if f.is_native() => { diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index 21a7ae4d..3b66d9a1 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -39,6 +39,8 @@ pub enum CallableFunction { func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, + /// This is a dummy field and is not used. + is_pure: bool, }, /// A native Rust object method with the first argument passed by reference, /// and the rest passed by value. @@ -47,6 +49,8 @@ pub enum CallableFunction { func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, + /// Allow operating on constants? + is_pure: bool, }, /// An iterator function. Iterator { @@ -105,9 +109,10 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure { .. } => true, - Self::Method { .. } | Self::Iterator { .. } => false, + Self::Method { is_pure, .. } => *is_pure, + Self::Iterator { .. } => true, - Self::Plugin { func, .. } => !func.is_method_call(), + Self::Plugin { func, .. } => func.is_pure(), #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, diff --git a/src/func/register.rs b/src/func/register.rs index de5d835c..ea9ef6d5 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -84,7 +84,7 @@ pub trait RegisterNativeFunction< { /// Convert this function into a [`CallableFunction`]. #[must_use] - fn into_callable_function(self, name: Identifier, no_const: bool) -> CallableFunction; + fn into_callable_function(self, name: Identifier, is_pure: bool) -> CallableFunction; /// Get the type ID's of this function's parameters. #[must_use] fn param_types() -> [TypeId; N]; @@ -127,19 +127,6 @@ pub trait RegisterNativeFunction< } } -macro_rules! check_constant { - ($abi:ident, $n:expr, $fn_name:ident, $no_const:ident, $args:ident) => { - #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] - if stringify!($abi) == "Method" && $no_const && $args[0].is_read_only() { - return Err(crate::ERR::ErrorNonPureMethodCallOnConstant( - $fn_name.to_string(), - crate::Position::NONE, - ) - .into()); - } - }; -} - macro_rules! def_register { () => { def_register!(imp Pure : 0;); @@ -160,11 +147,9 @@ macro_rules! def_register { > RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* @@ -173,7 +158,7 @@ macro_rules! def_register { // Map the result Ok(Dynamic::from(r)) - }), has_context: false } + }), has_context: false, is_pure } } } @@ -184,13 +169,11 @@ macro_rules! def_register { > RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* @@ -199,7 +182,7 @@ macro_rules! def_register { // Map the result Ok(Dynamic::from(r)) - }), has_context: true } + }), has_context: true, is_pure } } } @@ -211,17 +194,15 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self($($arg),*).map(Dynamic::from) - }), has_context: false } + }), has_context: false, is_pure } } } @@ -233,19 +214,17 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self(ctx, $($arg),*).map(Dynamic::from) - }), has_context: true } + }), has_context: true, is_pure } } } diff --git a/src/module/mod.rs b/src/module/mod.rs index 6e7fe262..eeb6874e 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1266,6 +1266,7 @@ impl Module { CallableFunction::Method { func: Shared::new(f), has_context: true, + is_pure: false, }, ) } @@ -1303,15 +1304,15 @@ impl Module { F: RegisterNativeFunction, { let fn_name = name.into(); - let no_const = false; + let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); + let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] - let no_const = - no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); + let is_pure = + is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET)); - let func = func.into_callable_function(fn_name.clone(), no_const); + let func = func.into_callable_function(fn_name.clone(), is_pure); self.set_fn( fn_name, @@ -1350,7 +1351,7 @@ impl Module { F: RegisterNativeFunction<(Mut,), 1, C, T, true> + SendSync + 'static, { let fn_name = crate::engine::make_getter(name.as_ref()); - let func = func.into_callable_function(fn_name.clone(), false); + let func = func.into_callable_function(fn_name.clone(), true); self.set_fn( fn_name, @@ -1394,7 +1395,7 @@ impl Module { F: RegisterNativeFunction<(Mut, T), 2, C, (), true> + SendSync + 'static, { let fn_name = crate::engine::make_setter(name.as_ref()); - let func = func.into_callable_function(fn_name.clone(), true); + let func = func.into_callable_function(fn_name.clone(), false); self.set_fn( fn_name, @@ -1510,7 +1511,7 @@ impl Module { FnAccess::Public, None, F::param_types(), - func.into_callable_function(crate::engine::FN_IDX_GET.into(), false), + func.into_callable_function(crate::engine::FN_IDX_GET.into(), true), ) } @@ -1571,7 +1572,7 @@ impl Module { FnAccess::Public, None, F::param_types(), - func.into_callable_function(crate::engine::FN_IDX_SET.into(), true), + func.into_callable_function(crate::engine::FN_IDX_SET.into(), false), ) }