From 34c7dabe4481613b2722c3b40bff43d4510c6e29 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Mar 2023 16:43:15 +0800 Subject: [PATCH] Add is_def_fn with 3 parameters. --- CHANGELOG.md | 1 + src/api/definitions/builtin-functions.d.rhai | 15 +++++ src/func/call.rs | 64 ++++++++++++++++---- tests/method_call.rs | 9 +++ 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 532c29e3..8598a79c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ 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. +* `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)` Version 1.13.0 diff --git a/src/api/definitions/builtin-functions.d.rhai b/src/api/definitions/builtin-functions.d.rhai index 93d07df3..0ccab0d0 100644 --- a/src/api/definitions/builtin-functions.d.rhai +++ b/src/api/definitions/builtin-functions.d.rhai @@ -112,6 +112,21 @@ fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr; /// ``` fn is_def_fn(fn_name: String, num_params: int) -> bool; +/// Return `true` if a script-defined function exists with a specified name and +/// number of parameters, bound to a specified type for `this`. +/// +/// # Example +/// +/// ```rhai +/// // A method that requires `this` to be `MyType` +/// fn MyType.foo(x) { } +/// +/// print(is_def_fn("MyType", "foo", 1)); // prints true +/// print(is_def_fn("foo", 1)); // prints false +/// print(is_def_fn("MyType", "foo", 2)); // prints false +/// ``` +fn is_def_fn(this_type: String, fn_name: String, num_params: int) -> bool; + /// Return `true` if a variable matching a specified name is defined. /// /// # Example diff --git a/src/func/call.rs b/src/func/call.rs index 38eb7cca..5afb6e7a 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1,6 +1,9 @@ //! Implement function-calling mechanism for [`Engine`]. -use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn, CallableFunction}; +use super::{ + calc_typed_method_hash, get_builtin_binary_op_fn, get_builtin_op_assignment_fn, + CallableFunction, +}; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; use crate::ast::{Expr, FnCallExpr, FnCallHashes}; use crate::engine::{ @@ -1099,7 +1102,7 @@ impl Engine { FnCallHashes::from_hash(calc_fn_hash(None, name, args_len)) }; } - // Handle Fn() + // Handle Fn(fn_name) KEYWORD_FN_PTR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = @@ -1114,7 +1117,7 @@ impl Engine { .map_err(|err| err.fill_position(arg_pos)); } - // Handle curry() + // Handle curry(x, ...) KEYWORD_FN_PTR_CURRY if total_args > 1 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = @@ -1137,7 +1140,7 @@ impl Engine { return Ok(fn_ptr.into()); } - // Handle is_shared() + // Handle is_shared(var) #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let arg = first_arg.unwrap(); @@ -1146,7 +1149,7 @@ impl Engine { return Ok(arg_value.is_shared().into()); } - // Handle is_def_fn() + // Handle is_def_fn(fn_name, arity) #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let first = first_arg.unwrap(); @@ -1164,17 +1167,54 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; - return Ok(if (0..=crate::MAX_USIZE_INT).contains(&num_params) { + return Ok(if num_params >= 0 { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); - self.has_script_fn(global, caches, hash_script) + self.has_script_fn(global, caches, hash_script).into() } else { - false - } - .into()); + Dynamic::FALSE + }); } - // Handle is_def_var() + // Handle is_def_fn(this_type, fn_name, arity) + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_object"))] + crate::engine::KEYWORD_IS_DEF_FN if total_args == 3 => { + let first = first_arg.unwrap(); + let (arg_value, arg_pos) = + self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; + + let this_type = arg_value + .into_immutable_string() + .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; + + let (arg_value, arg_pos) = + self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &a_expr[0])?; + + let fn_name = arg_value + .into_immutable_string() + .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; + + let (arg_value, arg_pos) = + self.get_arg_value(global, caches, scope, this_ptr, &a_expr[1])?; + + let num_params = arg_value + .as_int() + .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; + + return Ok(if num_params >= 0 { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let hash_script = calc_typed_method_hash( + calc_fn_hash(None, &fn_name, num_params as usize), + &this_type, + ); + self.has_script_fn(global, caches, hash_script).into() + } else { + Dynamic::FALSE + }); + } + + // Handle is_def_var(fn_name) KEYWORD_IS_DEF_VAR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = @@ -1185,7 +1225,7 @@ impl Engine { return Ok(scope.contains(&var_name).into()); } - // Handle eval() + // Handle eval(script) KEYWORD_EVAL if total_args == 1 => { // eval - only in function call style let orig_scope_len = scope.len(); diff --git a/tests/method_call.rs b/tests/method_call.rs index 824f1c72..577447e7 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -108,6 +108,15 @@ fn test_method_call_typed() -> Result<(), Box> { TestStruct { x: 1002 } ); + assert!(engine.eval::( + r#" + fn "Test-Struct#ABC".foo(x) { + this.update(x); + } + is_def_fn("Test-Struct#ABC", "foo", 1) + "# + )?); + assert!(matches!( *engine .run(