Encode pure in CallableFunction variant.

This commit is contained in:
Stephen Chung 2023-04-11 10:26:23 +08:00
parent 8662ffec62
commit 407d376a61
5 changed files with 47 additions and 60 deletions

View File

@ -90,15 +90,15 @@ impl Engine {
let param_type_names: Option<&[&str]> = None; let param_type_names: Option<&[&str]> = None;
let fn_name = name.as_ref(); let fn_name = name.as_ref();
let no_const = false; let is_pure = true;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[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"))] #[cfg(not(feature = "no_object"))]
let no_const = let is_pure =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); 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( self.global_namespace_mut().set_fn(
name, name,

View File

@ -276,6 +276,7 @@ impl Engine {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
is_pure: false,
}, },
source: None, source: None,
}) })
@ -285,6 +286,7 @@ impl Engine {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
is_pure: true,
}, },
source: None, source: None,
}), }),
@ -372,8 +374,8 @@ impl Engine {
let backup = &mut ArgBackup::new(); let backup = &mut ArgBackup::new();
// Calling pure function but the first argument is a reference? // Calling non-method function but the first argument is a reference?
let swap = is_ref_mut && func.is_pure() && !args.is_empty(); let swap = is_ref_mut && !func.is_method() && !args.is_empty();
if swap { if swap {
// Clone the first argument // Clone the first argument
@ -400,12 +402,11 @@ impl Engine {
.has_context() .has_context()
.then(|| (self, name, src, &*global, pos).into()); .then(|| (self, name, src, &*global, pos).into());
let mut _result = if let Some(f) = func.get_plugin_fn() { let mut _result = if !func.is_pure() && !args.is_empty() && args[0].is_read_only() {
if !f.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()) Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else { } else if let Some(f) = func.get_plugin_fn() {
f.call(context, args) f.call(context, args)
}
} else if let Some(f) = func.get_native_fn() { } else if let Some(f) = func.get_native_fn() {
f(context, args) f(context, args)
} else { } else {
@ -1493,18 +1494,19 @@ impl Engine {
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) 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() => { Some(f) if f.is_plugin_fn() => {
let f = f.get_plugin_fn().expect("plugin function"); let f = f.get_plugin_fn().expect("plugin function");
let context = f let context = f
.has_context() .has_context()
.then(|| (self, fn_name, module.id(), &*global, pos).into()); .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) f.call(context, args)
.and_then(|r| self.check_data_size(r, pos)) .and_then(|r| self.check_data_size(r, pos))
} }
}
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function"); let func = f.get_native_fn().expect("native function");

View File

@ -39,6 +39,8 @@ pub enum CallableFunction {
func: Shared<FnAny>, func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool, 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, /// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value. /// and the rest passed by value.
@ -47,6 +49,8 @@ pub enum CallableFunction {
func: Shared<FnAny>, func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool, has_context: bool,
/// Allow operating on constants?
is_pure: bool,
}, },
/// An iterator function. /// An iterator function.
Iterator { Iterator {
@ -105,9 +109,10 @@ impl CallableFunction {
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::Pure { .. } => true, 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"))] #[cfg(not(feature = "no_function"))]
Self::Script { .. } => false, Self::Script { .. } => false,

View File

@ -84,7 +84,7 @@ pub trait RegisterNativeFunction<
{ {
/// Convert this function into a [`CallableFunction`]. /// Convert this function into a [`CallableFunction`].
#[must_use] #[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. /// Get the type ID's of this function's parameters.
#[must_use] #[must_use]
fn param_types() -> [TypeId; N]; 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 { macro_rules! def_register {
() => { () => {
def_register!(imp Pure : 0;); def_register!(imp Pure : 0;);
@ -160,11 +147,9 @@ macro_rules! def_register {
> RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN { > RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[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 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| { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // 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 drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
@ -173,7 +158,7 @@ macro_rules! def_register {
// Map the result // Map the result
Ok(Dynamic::from(r)) 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 { > RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[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 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<NativeCallContext>, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap(); let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types! // 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 drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
@ -199,7 +182,7 @@ macro_rules! def_register {
// Map the result // Map the result
Ok(Dynamic::from(r)) 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>()),*] } #[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 param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[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| { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // 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 drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self($($arg),*).map(Dynamic::from) 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>()),*] } #[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 param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[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<NativeCallContext>, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap(); let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types! // 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 drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from) self(ctx, $($arg),*).map(Dynamic::from)
}), has_context: true } }), has_context: true, is_pure }
} }
} }

View File

@ -1266,6 +1266,7 @@ impl Module {
CallableFunction::Method { CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context: true, has_context: true,
is_pure: false,
}, },
) )
} }
@ -1303,15 +1304,15 @@ impl Module {
F: RegisterNativeFunction<A, N, C, T, true>, F: RegisterNativeFunction<A, N, C, T, true>,
{ {
let fn_name = name.into(); let fn_name = name.into();
let no_const = false; let is_pure = true;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[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"))] #[cfg(not(feature = "no_object"))]
let no_const = let is_pure =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); 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( self.set_fn(
fn_name, fn_name,
@ -1350,7 +1351,7 @@ impl Module {
F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_getter(name.as_ref()); 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( self.set_fn(
fn_name, fn_name,
@ -1394,7 +1395,7 @@ impl Module {
F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_setter(name.as_ref()); 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( self.set_fn(
fn_name, fn_name,
@ -1510,7 +1511,7 @@ impl Module {
FnAccess::Public, FnAccess::Public,
None, None,
F::param_types(), 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, FnAccess::Public,
None, None,
F::param_types(), 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),
) )
} }