Add typed methods definition.
This commit is contained in:
parent
3d4a278f2e
commit
e60d0fc0bc
@ -6,6 +6,11 @@ Version 1.14.0
|
|||||||
|
|
||||||
The code hacks that attempt to optimize branch prediction performance are removed because benchmarks do not show any material speed improvements.
|
The code hacks that attempt to optimize branch prediction performance are removed because benchmarks do not show any material speed improvements.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type.
|
||||||
|
|
||||||
|
|
||||||
Version 1.13.0
|
Version 1.13.0
|
||||||
==============
|
==============
|
||||||
|
@ -17,6 +17,10 @@ pub struct ScriptFnDef {
|
|||||||
pub name: ImmutableString,
|
pub name: ImmutableString,
|
||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
/// Type of `this` pointer, if any.
|
||||||
|
/// Not available under `no_object`.
|
||||||
|
pub this_type: Option<ImmutableString>,
|
||||||
/// Names of function parameters.
|
/// Names of function parameters.
|
||||||
pub params: FnArgsVec<ImmutableString>,
|
pub params: FnArgsVec<ImmutableString>,
|
||||||
/// _(metadata)_ Function doc-comments (if any).
|
/// _(metadata)_ Function doc-comments (if any).
|
||||||
@ -39,13 +43,23 @@ pub struct ScriptFnDef {
|
|||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
let this_type = self
|
||||||
|
.this_type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |s| format!("{:?}.", s));
|
||||||
|
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
let this_type = "";
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{}({})",
|
"{}{}{}({})",
|
||||||
match self.access {
|
match self.access {
|
||||||
FnAccess::Public => "",
|
FnAccess::Public => "",
|
||||||
FnAccess::Private => "private ",
|
FnAccess::Private => "private ",
|
||||||
},
|
},
|
||||||
|
this_type,
|
||||||
self.name,
|
self.name,
|
||||||
self.params
|
self.params
|
||||||
.iter()
|
.iter()
|
||||||
@ -70,6 +84,10 @@ pub struct ScriptFnMetadata<'a> {
|
|||||||
pub params: Vec<&'a str>,
|
pub params: Vec<&'a str>,
|
||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
/// Type of `this` pointer, if any.
|
||||||
|
/// Not available under `no_object`.
|
||||||
|
pub this_type: Option<&'a str>,
|
||||||
/// _(metadata)_ Function doc-comments (if any).
|
/// _(metadata)_ Function doc-comments (if any).
|
||||||
/// Exported under the `metadata` feature only.
|
/// Exported under the `metadata` feature only.
|
||||||
///
|
///
|
||||||
@ -90,13 +108,23 @@ pub struct ScriptFnMetadata<'a> {
|
|||||||
|
|
||||||
impl fmt::Display for ScriptFnMetadata<'_> {
|
impl fmt::Display for ScriptFnMetadata<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
let this_type = self
|
||||||
|
.this_type
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |s| format!("{:?}.", s));
|
||||||
|
|
||||||
|
#[cfg(feature = "no_object")]
|
||||||
|
let this_type = "";
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{}({})",
|
"{}{}{}({})",
|
||||||
match self.access {
|
match self.access {
|
||||||
FnAccess::Public => "",
|
FnAccess::Public => "",
|
||||||
FnAccess::Private => "private ",
|
FnAccess::Private => "private ",
|
||||||
},
|
},
|
||||||
|
this_type,
|
||||||
self.name,
|
self.name,
|
||||||
self.params
|
self.params
|
||||||
.iter()
|
.iter()
|
||||||
@ -114,6 +142,8 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
|
|||||||
name: &value.name,
|
name: &value.name,
|
||||||
params: value.params.iter().map(|s| s.as_str()).collect(),
|
params: value.params.iter().map(|s| s.as_str()).collect(),
|
||||||
access: value.access,
|
access: value.access,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: value.this_type.as_ref().map(|s| s.as_str()),
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: value.comments.iter().map(<_>::as_ref).collect(),
|
comments: value.comments.iter().map(<_>::as_ref).collect(),
|
||||||
}
|
}
|
||||||
|
@ -251,12 +251,22 @@ impl GlobalRuntimeState {
|
|||||||
pub fn get_qualified_fn(
|
pub fn get_qualified_fn(
|
||||||
&self,
|
&self,
|
||||||
hash: u64,
|
hash: u64,
|
||||||
|
global_namespace_only: bool,
|
||||||
) -> Option<(&crate::func::CallableFunction, Option<&ImmutableString>)> {
|
) -> Option<(&crate::func::CallableFunction, Option<&ImmutableString>)> {
|
||||||
self.modules.as_ref().and_then(|m| {
|
if global_namespace_only {
|
||||||
m.iter()
|
self.modules.as_ref().and_then(|m| {
|
||||||
.rev()
|
m.iter()
|
||||||
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
|
.rev()
|
||||||
})
|
.filter(|m| m.contains_indexed_global_functions())
|
||||||
|
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.modules.as_ref().and_then(|m| {
|
||||||
|
m.iter()
|
||||||
|
.rev()
|
||||||
|
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of
|
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of
|
||||||
/// globally-imported [modules][crate::Module]?
|
/// globally-imported [modules][crate::Module]?
|
||||||
|
@ -202,20 +202,18 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let func = if args.is_none() {
|
let func = func
|
||||||
// Scripted functions are not exposed globally
|
.or_else(|| _global.get_qualified_fn(hash, true))
|
||||||
func
|
.or_else(|| {
|
||||||
} else {
|
|
||||||
func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| {
|
|
||||||
self.global_sub_modules
|
self.global_sub_modules
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.filter(|(_, m)| m.contains_indexed_global_functions())
|
||||||
.find_map(|(_, m)| {
|
.find_map(|(_, m)| {
|
||||||
m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))
|
m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((f, s)) = func {
|
if let Some((f, s)) = func {
|
||||||
// Specific version found
|
// Specific version found
|
||||||
@ -335,8 +333,7 @@ impl Engine {
|
|||||||
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
||||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||||
///
|
///
|
||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument -
|
/// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
/// all others are silently replaced by `()`!
|
|
||||||
pub(crate) fn exec_native_fn_call(
|
pub(crate) fn exec_native_fn_call(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -562,8 +559,7 @@ impl Engine {
|
|||||||
/// Function call arguments may be _consumed_ when the function requires them to be passed by
|
/// Function call arguments may be _consumed_ when the function requires them to be passed by
|
||||||
/// value. All function arguments not in the first position are always passed by value and thus consumed.
|
/// value. All function arguments not in the first position are always passed by value and thus consumed.
|
||||||
///
|
///
|
||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument -
|
/// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
/// all others are silently replaced by `()`!
|
|
||||||
pub(crate) fn exec_fn_call(
|
pub(crate) fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -572,14 +568,14 @@ impl Engine {
|
|||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
op_token: Option<&Token>,
|
op_token: Option<&Token>,
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
mut _args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
_is_method_call: bool,
|
_is_method_call: bool,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||||
// Check for data race.
|
// Check for data race.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
ensure_no_data_race(fn_name, _args, is_ref_mut)?;
|
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||||
|
|
||||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||||
|
|
||||||
@ -587,18 +583,18 @@ impl Engine {
|
|||||||
if hashes.is_native_only() {
|
if hashes.is_native_only() {
|
||||||
match fn_name {
|
match fn_name {
|
||||||
// Handle type_of()
|
// Handle type_of()
|
||||||
KEYWORD_TYPE_OF if _args.len() == 1 => {
|
KEYWORD_TYPE_OF if args.len() == 1 => {
|
||||||
let typ = self.map_type_name(_args[0].type_name()).to_string().into();
|
let typ = self.get_interned_string(self.map_type_name(args[0].type_name()));
|
||||||
return Ok((typ, false));
|
return Ok((typ.into(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle is_def_fn()
|
// Handle is_def_fn()
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
crate::engine::KEYWORD_IS_DEF_FN
|
crate::engine::KEYWORD_IS_DEF_FN
|
||||||
if _args.len() == 2 && _args[0].is_fnptr() && _args[1].is_int() =>
|
if args.len() == 2 && args[0].is_fnptr() && args[1].is_int() =>
|
||||||
{
|
{
|
||||||
let fn_name = _args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
|
let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
|
||||||
let num_params = _args[1].as_int().expect("`INT`");
|
let num_params = args[1].as_int().expect("`INT`");
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
if (0..=crate::MAX_USIZE_INT).contains(&num_params) {
|
if (0..=crate::MAX_USIZE_INT).contains(&num_params) {
|
||||||
@ -629,16 +625,27 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Script-defined function call?
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !hashes.is_native_only() {
|
if !hashes.is_native_only() {
|
||||||
// Script-defined function call?
|
|
||||||
let hash = hashes.script();
|
let hash = hashes.script();
|
||||||
let local_entry = &mut None;
|
let local_entry = &mut None;
|
||||||
|
|
||||||
if let Some(FnResolutionCacheEntry { func, ref source }) = self
|
let resolved = if _is_method_call && !args.is_empty() {
|
||||||
.resolve_fn(global, caches, local_entry, None, hash, None, false)
|
let typed_hash =
|
||||||
.cloned()
|
crate::calc_typed_method_hash(hash, self.map_type_name(args[0].type_name()));
|
||||||
{
|
self.resolve_fn(global, caches, local_entry, None, typed_hash, None, false)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolved = if resolved.is_none() {
|
||||||
|
self.resolve_fn(global, caches, local_entry, None, hash, None, false)
|
||||||
|
} else {
|
||||||
|
resolved
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(FnResolutionCacheEntry { func, ref source }) = resolved.cloned() {
|
||||||
// Script function call
|
// Script function call
|
||||||
debug_assert!(func.is_script());
|
debug_assert!(func.is_script());
|
||||||
|
|
||||||
@ -662,7 +669,7 @@ impl Engine {
|
|||||||
|
|
||||||
return if _is_method_call {
|
return if _is_method_call {
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first_arg, rest_args) = _args.split_first_mut().unwrap();
|
let (first_arg, rest_args) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
self.call_script_fn(
|
self.call_script_fn(
|
||||||
global,
|
global,
|
||||||
@ -680,13 +687,13 @@ impl Engine {
|
|||||||
let backup = &mut ArgBackup::new();
|
let backup = &mut ArgBackup::new();
|
||||||
|
|
||||||
// The first argument is a reference?
|
// The first argument is a reference?
|
||||||
let swap = is_ref_mut && !_args.is_empty();
|
let swap = is_ref_mut && !args.is_empty();
|
||||||
|
|
||||||
if swap {
|
if swap {
|
||||||
backup.change_first_arg_to_copy(_args);
|
backup.change_first_arg_to_copy(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_restore! { args = (_args) if swap => move |a| backup.restore_first_arg(a) }
|
auto_restore! { args = (args) if swap => move |a| backup.restore_first_arg(a) }
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -698,7 +705,7 @@ impl Engine {
|
|||||||
let hash = hashes.native();
|
let hash = hashes.native();
|
||||||
|
|
||||||
self.exec_native_fn_call(
|
self.exec_native_fn_call(
|
||||||
global, caches, fn_name, op_token, hash, _args, is_ref_mut, pos,
|
global, caches, fn_name, op_token, hash, args, is_ref_mut, pos,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1392,15 +1399,11 @@ impl Engine {
|
|||||||
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?;
|
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?;
|
||||||
|
|
||||||
// First search script-defined functions in namespace (can override built-in)
|
// First search script-defined functions in namespace (can override built-in)
|
||||||
let mut func = match module.get_qualified_fn(hash) {
|
let mut func = module.get_qualified_fn(hash).or_else(|| {
|
||||||
// Then search native Rust functions
|
// Then search native Rust functions
|
||||||
None => {
|
let hash_qualified_fn = calc_fn_hash_full(hash, args.iter().map(|a| a.type_id()));
|
||||||
self.track_operation(global, pos)?;
|
module.get_qualified_fn(hash_qualified_fn)
|
||||||
let hash_qualified_fn = calc_fn_hash_full(hash, args.iter().map(|a| a.type_id()));
|
});
|
||||||
module.get_qualified_fn(hash_qualified_fn)
|
|
||||||
}
|
|
||||||
r => r,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check for `Dynamic` parameters.
|
// Check for `Dynamic` parameters.
|
||||||
//
|
//
|
||||||
|
@ -76,6 +76,9 @@ pub fn get_hasher() -> ahash::AHasher {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn calc_var_hash<'a>(namespace: impl IntoIterator<Item = &'a str>, var_name: &str) -> u64 {
|
pub fn calc_var_hash<'a>(namespace: impl IntoIterator<Item = &'a str>, var_name: &str) -> u64 {
|
||||||
let s = &mut get_hasher();
|
let s = &mut get_hasher();
|
||||||
|
|
||||||
|
s.write_u8(b'V'); // hash a discriminant
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
// We always skip the first module
|
// We always skip the first module
|
||||||
@ -111,6 +114,9 @@ pub fn calc_fn_hash<'a>(
|
|||||||
num: usize,
|
num: usize,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
let s = &mut get_hasher();
|
let s = &mut get_hasher();
|
||||||
|
|
||||||
|
s.write_u8(b'F'); // hash a discriminant
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
namespace.into_iter().for_each(|m| {
|
namespace.into_iter().for_each(|m| {
|
||||||
@ -134,6 +140,9 @@ pub fn calc_fn_hash<'a>(
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) -> u64 {
|
pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) -> u64 {
|
||||||
let s = &mut get_hasher();
|
let s = &mut get_hasher();
|
||||||
|
|
||||||
|
s.write_u8(b'A'); // hash a discriminant
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
params.into_iter().for_each(|t| {
|
params.into_iter().for_each(|t| {
|
||||||
t.hash(s);
|
t.hash(s);
|
||||||
@ -143,3 +152,17 @@ pub fn calc_fn_hash_full(base: u64, params: impl IntoIterator<Item = TypeId>) ->
|
|||||||
|
|
||||||
s.finish() ^ base
|
s.finish() ^ base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate a [`u64`] hash key from a base [`u64`] hash key and the type of the `this` pointer.
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn calc_typed_method_hash(base: u64, this_type: &str) -> u64 {
|
||||||
|
let s = &mut get_hasher();
|
||||||
|
|
||||||
|
s.write_u8(b'T'); // hash a discriminant
|
||||||
|
this_type.hash(s);
|
||||||
|
|
||||||
|
s.finish() ^ base
|
||||||
|
}
|
||||||
|
@ -21,6 +21,9 @@ pub use call::FnCallArgs;
|
|||||||
pub use callable_function::{CallableFunction, EncapsulatedEnviron};
|
pub use callable_function::{CallableFunction, EncapsulatedEnviron};
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use func::Func;
|
pub use func::Func;
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub use hashing::calc_typed_method_hash;
|
||||||
pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap};
|
pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap};
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
|
@ -22,7 +22,7 @@ impl Engine {
|
|||||||
/// Function call arguments may be _consumed_ when the function requires them to be passed by value.
|
/// Function call arguments may be _consumed_ when the function requires them to be passed by value.
|
||||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||||
///
|
///
|
||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values except for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_script_fn(
|
pub(crate) fn call_script_fn(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
|
@ -227,6 +227,8 @@ pub use api::{eval::eval, events::VarDefInfo, run::run};
|
|||||||
pub use ast::{FnAccess, AST};
|
pub use ast::{FnAccess, AST};
|
||||||
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
|
||||||
pub use eval::EvalContext;
|
pub use eval::EvalContext;
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
use func::calc_typed_method_hash;
|
||||||
use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash};
|
use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash};
|
||||||
pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction};
|
pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction};
|
||||||
pub use module::{FnNamespace, Module};
|
pub use module::{FnNamespace, Module};
|
||||||
|
@ -73,6 +73,9 @@ pub struct FuncInfoMetadata {
|
|||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
/// Function name.
|
/// Function name.
|
||||||
pub name: Identifier,
|
pub name: Identifier,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
/// Type of `this` pointer, if any.
|
||||||
|
pub this_type: Option<ImmutableString>,
|
||||||
/// Number of parameters.
|
/// Number of parameters.
|
||||||
pub num_params: usize,
|
pub num_params: usize,
|
||||||
/// Parameter types (if applicable).
|
/// Parameter types (if applicable).
|
||||||
@ -728,8 +731,16 @@ impl Module {
|
|||||||
let fn_def = fn_def.into();
|
let fn_def = fn_def.into();
|
||||||
|
|
||||||
// None + function name + number of arguments.
|
// None + function name + number of arguments.
|
||||||
|
let namespace = FnNamespace::Internal;
|
||||||
let num_params = fn_def.params.len();
|
let num_params = fn_def.params.len();
|
||||||
let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params);
|
let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
let (hash_script, namespace) = if let Some(ref this_type) = fn_def.this_type {
|
||||||
|
let hash = crate::calc_typed_method_hash(hash_script, this_type);
|
||||||
|
(hash, FnNamespace::Global)
|
||||||
|
} else {
|
||||||
|
(hash_script, namespace)
|
||||||
|
};
|
||||||
|
|
||||||
// Catch hash collisions in testing environment only.
|
// Catch hash collisions in testing environment only.
|
||||||
#[cfg(feature = "testing-environ")]
|
#[cfg(feature = "testing-environ")]
|
||||||
@ -750,8 +761,10 @@ impl Module {
|
|||||||
FuncInfo {
|
FuncInfo {
|
||||||
metadata: FuncInfoMetadata {
|
metadata: FuncInfoMetadata {
|
||||||
name: fn_def.name.as_str().into(),
|
name: fn_def.name.as_str().into(),
|
||||||
namespace: FnNamespace::Internal,
|
namespace,
|
||||||
access: fn_def.access,
|
access: fn_def.access,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: fn_def.this_type.clone(),
|
||||||
num_params,
|
num_params,
|
||||||
param_types: FnArgsVec::new_const(),
|
param_types: FnArgsVec::new_const(),
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
@ -765,8 +778,10 @@ impl Module {
|
|||||||
func: fn_def.into(),
|
func: fn_def.into(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
self.flags
|
self.flags
|
||||||
.remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS);
|
.remove(ModuleFlags::INDEXED | ModuleFlags::INDEXED_GLOBAL_FUNCTIONS);
|
||||||
|
|
||||||
hash_script
|
hash_script
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,7 +1024,7 @@ impl Module {
|
|||||||
type_id
|
type_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function into the [`Module`], returning a [`u64`] hash key.
|
/// Set a native Rust function into the [`Module`], returning a [`u64`] hash key.
|
||||||
///
|
///
|
||||||
/// If there is an existing Rust function of the same hash, it is replaced.
|
/// If there is an existing Rust function of the same hash, it is replaced.
|
||||||
///
|
///
|
||||||
@ -1067,22 +1082,22 @@ impl Module {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let hash_script = calc_fn_hash(None, name, param_types.len());
|
let hash_base = calc_fn_hash(None, name, param_types.len());
|
||||||
let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied());
|
let hash_fn = calc_fn_hash_full(hash_base, param_types.iter().copied());
|
||||||
|
|
||||||
// Catch hash collisions in testing environment only.
|
// Catch hash collisions in testing environment only.
|
||||||
#[cfg(feature = "testing-environ")]
|
#[cfg(feature = "testing-environ")]
|
||||||
if let Some(f) = self.functions.as_ref().and_then(|f| f.get(&hash_script)) {
|
if let Some(f) = self.functions.as_ref().and_then(|f| f.get(&hash_base)) {
|
||||||
panic!(
|
panic!(
|
||||||
"Hash {} already exists when registering function {}:\n{:#?}",
|
"Hash {} already exists when registering function {}:\n{:#?}",
|
||||||
hash_script, name, f
|
hash_base, name, f
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_dynamic {
|
if is_dynamic {
|
||||||
self.dynamic_functions_filter
|
self.dynamic_functions_filter
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
.mark(hash_script);
|
.mark(hash_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.functions
|
self.functions
|
||||||
@ -1095,6 +1110,8 @@ impl Module {
|
|||||||
name: name.into(),
|
name: name.into(),
|
||||||
namespace,
|
namespace,
|
||||||
access,
|
access,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: None,
|
||||||
num_params: param_types.len(),
|
num_params: param_types.len(),
|
||||||
param_types,
|
param_types,
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
@ -1114,7 +1131,7 @@ impl Module {
|
|||||||
hash_fn
|
hash_fn
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(metadata)_ Set a Rust function into the [`Module`], returning a [`u64`] hash key.
|
/// _(metadata)_ Set a native Rust function into the [`Module`], returning a [`u64`] hash key.
|
||||||
/// Exported under the `metadata` feature only.
|
/// Exported under the `metadata` feature only.
|
||||||
///
|
///
|
||||||
/// If there is an existing Rust function of the same hash, it is replaced.
|
/// If there is an existing Rust function of the same hash, it is replaced.
|
||||||
@ -1167,13 +1184,7 @@ impl Module {
|
|||||||
hash
|
hash
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine],
|
/// Set a native Rust function into the [`Module`], returning a [`u64`] hash key.
|
||||||
/// the current set of functions, plus a list of mutable [`Dynamic`] references
|
|
||||||
/// into the [`Module`], returning a [`u64`] hash key.
|
|
||||||
///
|
|
||||||
/// Use this to register a built-in function which must reference settings on the scripting
|
|
||||||
/// [`Engine`][crate::Engine] (e.g. to prevent growing an array beyond the allowed maximum size),
|
|
||||||
/// or to call a script-defined function in the current evaluation context.
|
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
@ -1259,7 +1270,7 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function into the [`Module`], returning a [`u64`] hash key.
|
/// Set a native Rust function into the [`Module`], returning a [`u64`] hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
@ -1618,7 +1629,7 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a Rust function by hash.
|
/// Look up a native Rust function by hash.
|
||||||
///
|
///
|
||||||
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
|
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -2298,14 +2309,14 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index type iterators
|
// Index all type iterators
|
||||||
if let Some(ref t) = module.type_iterators {
|
if let Some(ref t) = module.type_iterators {
|
||||||
for (&type_id, func) in t.iter() {
|
for (&type_id, func) in t.iter() {
|
||||||
type_iterators.insert(type_id, func.clone());
|
type_iterators.insert(type_id, func.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index all Rust functions
|
// Index all functions
|
||||||
for (&hash, f) in module.functions.iter().flatten() {
|
for (&hash, f) in module.functions.iter().flatten() {
|
||||||
match f.metadata.namespace {
|
match f.metadata.namespace {
|
||||||
FnNamespace::Global => {
|
FnNamespace::Global => {
|
||||||
@ -2347,22 +2358,27 @@ impl Module {
|
|||||||
|
|
||||||
functions.insert(hash_qualified_fn, f.func.clone());
|
functions.insert(hash_qualified_fn, f.func.clone());
|
||||||
} else if cfg!(not(feature = "no_function")) {
|
} else if cfg!(not(feature = "no_function")) {
|
||||||
let hash_qualified_script = crate::calc_fn_hash(
|
let mut _hash_qualified_script = crate::calc_fn_hash(
|
||||||
path.iter().copied(),
|
path.iter().copied(),
|
||||||
&f.metadata.name,
|
&f.metadata.name,
|
||||||
f.metadata.num_params,
|
f.metadata.num_params,
|
||||||
);
|
);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
if let Some(ref this_type) = f.metadata.this_type {
|
||||||
|
_hash_qualified_script =
|
||||||
|
crate::calc_typed_method_hash(_hash_qualified_script, this_type);
|
||||||
|
}
|
||||||
|
|
||||||
// Catch hash collisions in testing environment only.
|
// Catch hash collisions in testing environment only.
|
||||||
#[cfg(feature = "testing-environ")]
|
#[cfg(feature = "testing-environ")]
|
||||||
if let Some(fx) = functions.get(&hash_qualified_script) {
|
if let Some(fx) = functions.get(&_hash_qualified_script) {
|
||||||
panic!(
|
panic!(
|
||||||
"Hash {} already exists when indexing function {:#?}:\n{:#?}",
|
"Hash {} already exists when indexing function {:#?}:\n{:#?}",
|
||||||
hash_qualified_script, f.func, fx
|
_hash_qualified_script, f.func, fx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
functions.insert(hash_qualified_script, f.func.clone());
|
functions.insert(_hash_qualified_script, f.func.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1348,8 +1348,9 @@ impl Engine {
|
|||||||
name: fn_def.name.clone(),
|
name: fn_def.name.clone(),
|
||||||
access: fn_def.access,
|
access: fn_def.access,
|
||||||
body: crate::ast::StmtBlock::NONE,
|
body: crate::ast::StmtBlock::NONE,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: fn_def.this_type.clone(),
|
||||||
params: fn_def.params.clone(),
|
params: fn_def.params.clone(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
comments: Box::default(),
|
comments: Box::default(),
|
||||||
})
|
})
|
||||||
|
@ -208,6 +208,10 @@ fn collect_fn_metadata(
|
|||||||
"is_anonymous".into(),
|
"is_anonymous".into(),
|
||||||
func.name.starts_with(FN_ANONYMOUS).into(),
|
func.name.starts_with(FN_ANONYMOUS).into(),
|
||||||
);
|
);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
if let Some(ref this_type) = func.this_type {
|
||||||
|
map.insert("this_type".into(), this_type.into());
|
||||||
|
}
|
||||||
map.insert(
|
map.insert(
|
||||||
"params".into(),
|
"params".into(),
|
||||||
func.params
|
func.params
|
||||||
|
@ -3392,10 +3392,15 @@ impl Engine {
|
|||||||
comments,
|
comments,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Restore parse state
|
|
||||||
|
|
||||||
let hash = calc_fn_hash(None, &f.name, f.params.len());
|
let hash = calc_fn_hash(None, &f.name, f.params.len());
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
let hash = if let Some(ref this_type) = f.this_type {
|
||||||
|
crate::calc_typed_method_hash(hash, this_type)
|
||||||
|
} else {
|
||||||
|
hash
|
||||||
|
};
|
||||||
|
|
||||||
if !lib.is_empty() && lib.contains_key(&hash) {
|
if !lib.is_empty() && lib.contains_key(&hash) {
|
||||||
return Err(PERR::FnDuplicatedDefinition(
|
return Err(PERR::FnDuplicatedDefinition(
|
||||||
f.name.to_string(),
|
f.name.to_string(),
|
||||||
@ -3605,6 +3610,35 @@ impl Engine {
|
|||||||
|
|
||||||
let (token, pos) = input.next().expect(NEVER_ENDS);
|
let (token, pos) = input.next().expect(NEVER_ENDS);
|
||||||
|
|
||||||
|
// Parse type for `this` pointer
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
let ((token, pos), this_type) = match token {
|
||||||
|
Token::StringConstant(s) if input.peek().expect(NEVER_ENDS).0 == Token::Period => {
|
||||||
|
eat_token(input, Token::Period);
|
||||||
|
let s = match s.as_str() {
|
||||||
|
"int" => state.get_interned_string(std::any::type_name::<crate::INT>()),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
"float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()),
|
||||||
|
_ => state.get_interned_string(*s),
|
||||||
|
};
|
||||||
|
(input.next().expect(NEVER_ENDS), Some(s))
|
||||||
|
}
|
||||||
|
Token::StringConstant(..) => {
|
||||||
|
return Err(PERR::MissingSymbol(".".to_string()).into_err(pos))
|
||||||
|
}
|
||||||
|
Token::Identifier(s) if input.peek().expect(NEVER_ENDS).0 == Token::Period => {
|
||||||
|
eat_token(input, Token::Period);
|
||||||
|
let s = match s.as_str() {
|
||||||
|
"int" => state.get_interned_string(std::any::type_name::<crate::INT>()),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
"float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()),
|
||||||
|
_ => state.get_interned_string(*s),
|
||||||
|
};
|
||||||
|
(input.next().expect(NEVER_ENDS), Some(s))
|
||||||
|
}
|
||||||
|
_ => ((token, pos), None),
|
||||||
|
};
|
||||||
|
|
||||||
let name = match token.into_function_name_for_override() {
|
let name = match token.into_function_name_for_override() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(Token::Reserved(s)) => return Err(PERR::Reserved(s.to_string()).into_err(pos)),
|
Err(Token::Reserved(s)) => return Err(PERR::Reserved(s.to_string()).into_err(pos)),
|
||||||
@ -3679,6 +3713,8 @@ impl Engine {
|
|||||||
Ok(ScriptFnDef {
|
Ok(ScriptFnDef {
|
||||||
name: state.get_interned_string(name),
|
name: state.get_interned_string(name),
|
||||||
access,
|
access,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type,
|
||||||
params,
|
params,
|
||||||
body,
|
body,
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
@ -3839,6 +3875,8 @@ impl Engine {
|
|||||||
let script = Shared::new(ScriptFnDef {
|
let script = Shared::new(ScriptFnDef {
|
||||||
name: fn_name.clone(),
|
name: fn_name.clone(),
|
||||||
access: crate::FnAccess::Public,
|
access: crate::FnAccess::Public,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: None,
|
||||||
params,
|
params,
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
@ -38,6 +38,9 @@ struct FnMetadata<'a> {
|
|||||||
pub is_anonymous: bool,
|
pub is_anonymous: bool,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub typ: FnType,
|
pub typ: FnType,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub this_type: Option<&'a str>,
|
||||||
pub num_params: usize,
|
pub num_params: usize,
|
||||||
#[serde(default, skip_serializing_if = "StaticVec::is_empty")]
|
#[serde(default, skip_serializing_if = "StaticVec::is_empty")]
|
||||||
pub params: StaticVec<FnParam<'a>>,
|
pub params: StaticVec<FnParam<'a>>,
|
||||||
@ -88,6 +91,8 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
is_anonymous: crate::parser::is_anonymous_fn(&info.metadata.name),
|
is_anonymous: crate::parser::is_anonymous_fn(&info.metadata.name),
|
||||||
typ,
|
typ,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
this_type: info.metadata.this_type.as_ref().map(|s| s.as_str()),
|
||||||
num_params: info.metadata.num_params,
|
num_params: info.metadata.num_params,
|
||||||
params: info
|
params: info
|
||||||
.metadata
|
.metadata
|
||||||
|
@ -75,3 +75,67 @@ fn test_method_call_with_full_optimization() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[test]
|
||||||
|
fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<TestStruct>("Test-Struct#ABC")
|
||||||
|
.register_fn("update", TestStruct::update)
|
||||||
|
.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<TestStruct>(
|
||||||
|
r#"
|
||||||
|
fn "Test-Struct#ABC".foo(x) {
|
||||||
|
this.update(x);
|
||||||
|
}
|
||||||
|
fn foo(x) {
|
||||||
|
this += x;
|
||||||
|
}
|
||||||
|
|
||||||
|
let z = 1000;
|
||||||
|
z.foo(1);
|
||||||
|
|
||||||
|
let x = new_ts();
|
||||||
|
x.foo(z);
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
TestStruct { x: 1002 }
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.run(
|
||||||
|
r#"
|
||||||
|
fn "Test-Struct#ABC".foo(x) {
|
||||||
|
this.update(x);
|
||||||
|
}
|
||||||
|
foo(1000);
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.run(
|
||||||
|
r#"
|
||||||
|
fn "Test-Struct#ABC".foo(x) {
|
||||||
|
this.update(x);
|
||||||
|
}
|
||||||
|
let x = 42;
|
||||||
|
x.foo(1000);
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user