Add typed methods definition.

This commit is contained in:
Stephen Chung 2023-03-22 16:05:25 +08:00
parent 3d4a278f2e
commit e60d0fc0bc
14 changed files with 275 additions and 71 deletions

View File

@ -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
============== ==============

View File

@ -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(),
} }

View File

@ -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]?

View File

@ -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.
// //

View File

@ -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
}

View File

@ -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)]

View File

@ -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,

View File

@ -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};

View File

@ -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());
} }
} }

View File

@ -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(),
}) })

View File

@ -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

View File

@ -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"))]

View File

@ -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

View File

@ -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(())
}