Bläddra i källkod

Add methods to get argument count of methods

Added to:
* `Callable`s
* `Object`s
* `ClassDB`
* `Script(Instance)`s
A Thousand Ships 1 år sedan
förälder
incheckning
59bcc2888c
50 ändrade filer med 821 tillägg och 3 borttagningar
  1. 6 0
      core/core_bind.cpp
  2. 2 0
      core/core_bind.h
  3. 56 0
      core/extension/gdextension_interface.cpp
  4. 50 1
      core/extension/gdextension_interface.h
  5. 25 0
      core/object/callable_method_pointer.h
  6. 25 0
      core/object/class_db.cpp
  7. 1 0
      core/object/class_db.h
  8. 55 0
      core/object/object.cpp
  9. 2 0
      core/object/object.h
  10. 22 0
      core/object/script_instance.cpp
  11. 2 0
      core/object/script_instance.h
  12. 16 0
      core/object/script_language.cpp
  13. 9 0
      core/object/script_language.h
  14. 3 0
      core/object/script_language_extension.cpp
  15. 18 0
      core/object/script_language_extension.h
  16. 19 0
      core/variant/callable.cpp
  17. 2 0
      core/variant/callable.h
  18. 16 0
      core/variant/callable_bind.cpp
  19. 2 0
      core/variant/callable_bind.h
  20. 5 0
      core/variant/variant_call.cpp
  21. 9 0
      core/variant/variant_callable.cpp
  22. 1 0
      core/variant/variant_callable.h
  23. 6 0
      doc/classes/Callable.xml
  24. 9 0
      doc/classes/ClassDB.xml
  25. 8 0
      doc/classes/Object.xml
  26. 6 0
      doc/classes/ScriptExtension.xml
  27. 34 0
      modules/gdscript/gdscript.cpp
  28. 6 0
      modules/gdscript/gdscript.h
  29. 1 0
      modules/gdscript/gdscript_function.h
  30. 18 0
      modules/gdscript/gdscript_lambda_callable.cpp
  31. 2 0
      modules/gdscript/gdscript_lambda_callable.h
  32. 4 0
      modules/gdscript/gdscript_rpc_callable.cpp
  33. 1 0
      modules/gdscript/gdscript_rpc_callable.h
  34. 15 0
      modules/gdscript/gdscript_utility_callable.cpp
  35. 1 0
      modules/gdscript/gdscript_utility_callable.h
  36. 1 1
      modules/gdscript/gdscript_utility_functions.cpp
  37. 1 1
      modules/gdscript/gdscript_utility_functions.h
  38. 102 0
      modules/gdscript/tests/scripts/runtime/features/argument_count.gd
  39. 27 0
      modules/gdscript/tests/scripts/runtime/features/argument_count.out
  40. 51 0
      modules/mono/csharp_script.cpp
  41. 2 0
      modules/mono/csharp_script.h
  42. 2 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
  43. 23 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
  44. 4 0
      modules/mono/managed_callable.cpp
  45. 1 0
      modules/mono/managed_callable.h
  46. 1 0
      modules/mono/mono_gd/gd_mono_cache.cpp
  47. 2 0
      modules/mono/mono_gd/gd_mono_cache.h
  48. 6 0
      tests/core/object/test_object.h
  49. 140 0
      tests/core/variant/test_callable.h
  50. 1 0
      tests/test_main.cpp

+ 6 - 0
core/core_bind.cpp

@@ -1434,6 +1434,10 @@ bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_me
 	return ::ClassDB::has_method(p_class, p_method, p_no_inheritance);
 }
 
+int ClassDB::class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const {
+	return ::ClassDB::get_method_argument_count(p_class, p_method, nullptr, p_no_inheritance);
+}
+
 TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class, bool p_no_inheritance) const {
 	List<MethodInfo> methods;
 	::ClassDB::get_method_list(p_class, &methods, p_no_inheritance);
@@ -1562,6 +1566,8 @@ void ClassDB::_bind_methods() {
 
 	::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false));
 
+	::ClassDB::bind_method(D_METHOD("class_get_method_argument_count", "class", "method", "no_inheritance"), &ClassDB::class_get_method_argument_count, DEFVAL(false));
+
 	::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
 
 	::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));

+ 2 - 0
core/core_bind.h

@@ -447,6 +447,8 @@ public:
 
 	bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
 
+	int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
+
 	TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
 
 	PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;

+ 56 - 0
core/extension/gdextension_interface.cpp

@@ -59,6 +59,8 @@ class CallableCustomExtension : public CallableCustom {
 
 	GDExtensionCallableCustomToString to_string_func;
 
+	GDExtensionCallableCustomGetArgumentCount get_argument_count_func;
+
 	uint32_t _hash;
 
 	static bool default_compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
@@ -143,6 +145,21 @@ public:
 		return object;
 	}
 
+	int get_argument_count(bool &r_is_valid) const override {
+		if (get_argument_count_func != nullptr) {
+			GDExtensionBool is_valid = false;
+
+			GDExtensionInt ret = get_argument_count_func(userdata, &is_valid);
+
+			if (is_valid) {
+				r_is_valid = true;
+				return ret;
+			}
+		}
+		r_is_valid = false;
+		return 0;
+	}
+
 	void *get_userdata(void *p_token) const {
 		return (p_token == token) ? userdata : nullptr;
 	}
@@ -157,6 +174,7 @@ public:
 		r_call_error.expected = error.expected;
 	}
 
+#ifndef DISABLE_DEPRECATED
 	CallableCustomExtension(GDExtensionCallableCustomInfo *p_info) {
 		userdata = p_info->callable_userdata;
 		token = p_info->token;
@@ -172,6 +190,35 @@ public:
 
 		to_string_func = p_info->to_string_func;
 
+		get_argument_count_func = nullptr;
+
+		// Pre-calculate the hash.
+		if (p_info->hash_func != nullptr) {
+			_hash = p_info->hash_func(userdata);
+		} else {
+			_hash = hash_murmur3_one_64((uint64_t)call_func);
+			_hash = hash_murmur3_one_64((uint64_t)userdata, _hash);
+		}
+	}
+#endif
+
+	CallableCustomExtension(GDExtensionCallableCustomInfo2 *p_info) {
+		userdata = p_info->callable_userdata;
+		token = p_info->token;
+
+		object = p_info->object_id;
+
+		call_func = p_info->call_func;
+		is_valid_func = p_info->is_valid_func;
+		free_func = p_info->free_func;
+
+		equal_func = p_info->equal_func;
+		less_than_func = p_info->less_than_func;
+
+		to_string_func = p_info->to_string_func;
+
+		get_argument_count_func = p_info->get_argument_count_func;
+
 		// Pre-calculate the hash.
 		if (p_info->hash_func != nullptr) {
 			_hash = p_info->hash_func(userdata);
@@ -1378,9 +1425,15 @@ static GDExtensionScriptInstancePtr gdextension_object_get_script_instance(GDExt
 	return script_instance_extension->instance;
 }
 
+#ifndef DISABLE_DEPRECATED
 static void gdextension_callable_custom_create(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_custom_callable_info) {
 	memnew_placement(r_callable, Callable(memnew(CallableCustomExtension(p_custom_callable_info))));
 }
+#endif
+
+static void gdextension_callable_custom_create2(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo2 *p_custom_callable_info) {
+	memnew_placement(r_callable, Callable(memnew(CallableCustomExtension(p_custom_callable_info))));
+}
 
 static void *gdextension_callable_custom_get_userdata(GDExtensionTypePtr p_callable, void *p_token) {
 	const Callable &callable = *reinterpret_cast<const Callable *>(p_callable);
@@ -1595,7 +1648,10 @@ void gdextension_setup_interface() {
 	REGISTER_INTERFACE_FUNC(placeholder_script_instance_create);
 	REGISTER_INTERFACE_FUNC(placeholder_script_instance_update);
 	REGISTER_INTERFACE_FUNC(object_get_script_instance);
+#ifndef DISABLE_DEPRECATED
 	REGISTER_INTERFACE_FUNC(callable_custom_create);
+#endif // DISABLE_DEPRECATED
+	REGISTER_INTERFACE_FUNC(callable_custom_create2);
 	REGISTER_INTERFACE_FUNC(callable_custom_get_userdata);
 	REGISTER_INTERFACE_FUNC(classdb_construct_object);
 	REGISTER_INTERFACE_FUNC(classdb_get_method_bind);

+ 50 - 1
core/extension/gdextension_interface.h

@@ -442,6 +442,8 @@ typedef GDExtensionBool (*GDExtensionCallableCustomLessThan)(void *callable_user
 
 typedef void (*GDExtensionCallableCustomToString)(void *callable_userdata, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out);
 
+typedef GDExtensionInt (*GDExtensionCallableCustomGetArgumentCount)(void *callable_userdata, GDExtensionBool *r_is_valid);
+
 typedef struct {
 	/* Only `call_func` and `token` are strictly required, however, `object_id` should be passed if its not a static method.
 	 *
@@ -471,7 +473,40 @@ typedef struct {
 	GDExtensionCallableCustomLessThan less_than_func;
 
 	GDExtensionCallableCustomToString to_string_func;
-} GDExtensionCallableCustomInfo;
+} GDExtensionCallableCustomInfo; // Deprecated. Use GDExtensionCallableCustomInfo2 instead.
+
+typedef struct {
+	/* Only `call_func` and `token` are strictly required, however, `object_id` should be passed if its not a static method.
+	 *
+	 * `token` should point to an address that uniquely identifies the GDExtension (for example, the
+	 * `GDExtensionClassLibraryPtr` passed to the entry symbol function.
+	 *
+	 * `hash_func`, `equal_func`, and `less_than_func` are optional. If not provided both `call_func` and
+	 * `callable_userdata` together are used as the identity of the callable for hashing and comparison purposes.
+	 *
+	 * The hash returned by `hash_func` is cached, `hash_func` will not be called more than once per callable.
+	 *
+	 * `is_valid_func` is necessary if the validity of the callable can change before destruction.
+	 *
+	 * `free_func` is necessary if `callable_userdata` needs to be cleaned up when the callable is freed.
+	 */
+	void *callable_userdata;
+	void *token;
+
+	GDObjectInstanceID object_id;
+
+	GDExtensionCallableCustomCall call_func;
+	GDExtensionCallableCustomIsValid is_valid_func;
+	GDExtensionCallableCustomFree free_func;
+
+	GDExtensionCallableCustomHash hash_func;
+	GDExtensionCallableCustomEqual equal_func;
+	GDExtensionCallableCustomLessThan less_than_func;
+
+	GDExtensionCallableCustomToString to_string_func;
+
+	GDExtensionCallableCustomGetArgumentCount get_argument_count_func;
+} GDExtensionCallableCustomInfo2;
 
 /* SCRIPT INSTANCE EXTENSION */
 
@@ -2510,6 +2545,7 @@ typedef GDExtensionScriptInstanceDataPtr (*GDExtensionInterfaceObjectGetScriptIn
 /**
  * @name callable_custom_create
  * @since 4.2
+ * @deprecated in Godot 4.3. Use `callable_custom_create2` instead.
  *
  * Creates a custom Callable object from a function pointer.
  *
@@ -2520,6 +2556,19 @@ typedef GDExtensionScriptInstanceDataPtr (*GDExtensionInterfaceObjectGetScriptIn
  */
 typedef void (*GDExtensionInterfaceCallableCustomCreate)(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo *p_callable_custom_info);
 
+/**
+ * @name callable_custom_create2
+ * @since 4.3
+ *
+ * Creates a custom Callable object from a function pointer.
+ *
+ * Provided struct can be safely freed once the function returns.
+ *
+ * @param r_callable A pointer that will receive the new Callable.
+ * @param p_callable_custom_info The info required to construct a Callable.
+ */
+typedef void (*GDExtensionInterfaceCallableCustomCreate2)(GDExtensionUninitializedTypePtr r_callable, GDExtensionCallableCustomInfo2 *p_callable_custom_info);
+
 /**
  * @name callable_custom_get_userdata
  * @since 4.2

+ 25 - 0
core/object/callable_method_pointer.h

@@ -93,6 +93,11 @@ public:
 		return data.instance->get_instance_id();
 	}
 
+	virtual int get_argument_count(bool &r_is_valid) const {
+		r_is_valid = true;
+		return sizeof...(P);
+	}
+
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 		ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
 		call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error);
@@ -140,6 +145,11 @@ public:
 		return data.instance->get_instance_id();
 	}
 
+	virtual int get_argument_count(bool &r_is_valid) const {
+		r_is_valid = true;
+		return sizeof...(P);
+	}
+
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 		ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
 		call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
@@ -187,6 +197,11 @@ public:
 		return data.instance->get_instance_id();
 	}
 
+	virtual int get_argument_count(bool &r_is_valid) const override {
+		r_is_valid = true;
+		return sizeof...(P);
+	}
+
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
 		ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
 		call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
@@ -238,6 +253,11 @@ public:
 		return ObjectID();
 	}
 
+	virtual int get_argument_count(bool &r_is_valid) const override {
+		r_is_valid = true;
+		return sizeof...(P);
+	}
+
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
 		call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error);
 		r_return_value = Variant();
@@ -280,6 +300,11 @@ public:
 		return ObjectID();
 	}
 
+	virtual int get_argument_count(bool &r_is_valid) const override {
+		r_is_valid = true;
+		return sizeof...(P);
+	}
+
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
 		call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error);
 	}

+ 25 - 0
core/object/class_db.cpp

@@ -1666,6 +1666,31 @@ bool ClassDB::has_method(const StringName &p_class, const StringName &p_method,
 	return false;
 }
 
+int ClassDB::get_method_argument_count(const StringName &p_class, const StringName &p_method, bool *r_is_valid, bool p_no_inheritance) {
+	OBJTYPE_RLOCK;
+
+	ClassInfo *type = classes.getptr(p_class);
+
+	while (type) {
+		MethodBind **method = type->method_map.getptr(p_method);
+		if (method && *method) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return (*method)->get_argument_count();
+		}
+		if (p_no_inheritance) {
+			break;
+		}
+		type = type->inherits_ptr;
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method) {
 	_bind_method_custom(p_class, p_method, false);
 }

+ 1 - 0
core/object/class_db.h

@@ -430,6 +430,7 @@ public:
 	static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
 	static void get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods_with_hash, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
 	static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
+	static int get_method_argument_count(const StringName &p_class, const StringName &p_method, bool *r_is_valid = nullptr, bool p_no_inheritance = false);
 	static MethodBind *get_method(const StringName &p_class, const StringName &p_name);
 	static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr);
 	static Vector<uint32_t> get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);

+ 55 - 0
core/object/object.cpp

@@ -688,6 +688,59 @@ bool Object::has_method(const StringName &p_method) const {
 	return false;
 }
 
+int Object::_get_method_argument_count_bind(const StringName &p_method) const {
+	return get_method_argument_count(p_method);
+}
+
+int Object::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	if (p_method == CoreStringNames::get_singleton()->_free) {
+		if (r_is_valid) {
+			*r_is_valid = true;
+		}
+		return 0;
+	}
+
+	if (script_instance) {
+		bool valid = false;
+		int ret = script_instance->get_method_argument_count(p_method, &valid);
+		if (valid) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return ret;
+		}
+	}
+
+	{
+		bool valid = false;
+		int ret = ClassDB::get_method_argument_count(get_class_name(), p_method, &valid);
+		if (valid) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return ret;
+		}
+	}
+
+	const Script *scr = Object::cast_to<Script>(this);
+	while (scr != nullptr) {
+		bool valid = false;
+		int ret = scr->get_script_method_argument_count(p_method, &valid);
+		if (valid) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return ret;
+		}
+		scr = scr->get_base_script().ptr();
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 Variant Object::getvar(const Variant &p_key, bool *r_valid) const {
 	if (r_valid) {
 		*r_valid = false;
@@ -1644,6 +1697,8 @@ void Object::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("has_method", "method"), &Object::has_method);
 
+	ClassDB::bind_method(D_METHOD("get_method_argument_count", "method"), &Object::_get_method_argument_count_bind);
+
 	ClassDB::bind_method(D_METHOD("has_signal", "signal"), &Object::has_signal);
 	ClassDB::bind_method(D_METHOD("get_signal_list"), &Object::_get_signal_list);
 	ClassDB::bind_method(D_METHOD("get_signal_connection_list", "signal"), &Object::_get_signal_connection_list);

+ 2 - 0
core/object/object.h

@@ -654,6 +654,7 @@ private:
 	Variant _get_bind(const StringName &p_name) const;
 	void _set_indexed_bind(const NodePath &p_name, const Variant &p_value);
 	Variant _get_indexed_bind(const NodePath &p_name) const;
+	int _get_method_argument_count_bind(const StringName &p_name) const;
 
 	_FORCE_INLINE_ void _construct_object(bool p_reference);
 
@@ -865,6 +866,7 @@ public:
 	Variant property_get_revert(const StringName &p_name) const;
 
 	bool has_method(const StringName &p_method) const;
+	int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
 	void get_method_list(List<MethodInfo> *p_list) const;
 	Variant callv(const StringName &p_method, const Array &p_args);
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);

+ 22 - 0
core/object/script_instance.cpp

@@ -32,6 +32,28 @@
 
 #include "core/object/script_language.h"
 
+int ScriptInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	// Default implementation simply traverses hierarchy.
+	Ref<Script> script = get_script();
+	while (script.is_valid()) {
+		bool valid = false;
+		int ret = script->get_script_method_argument_count(p_method, &valid);
+		if (valid) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return ret;
+		}
+
+		script = script->get_base_script();
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
 	return callp(p_method, p_args, p_argcount, r_error);
 }

+ 2 - 0
core/object/script_instance.h

@@ -53,6 +53,8 @@ public:
 	virtual void get_method_list(List<MethodInfo> *p_list) const = 0;
 	virtual bool has_method(const StringName &p_method) const = 0;
 
+	virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
+
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0;
 
 	template <typename... VarArgs>

+ 16 - 0
core/object/script_language.cpp

@@ -102,6 +102,22 @@ Dictionary Script::_get_script_constant_map() {
 	return ret;
 }
 
+int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	MethodInfo mi = get_method_info(p_method);
+
+	if (mi == MethodInfo()) {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = true;
+	}
+	return mi.arguments.size();
+}
+
 #ifdef TOOLS_ENABLED
 
 PropertyInfo Script::get_class_category() const {

+ 9 - 0
core/object/script_language.h

@@ -151,6 +151,8 @@ public:
 	virtual bool has_method(const StringName &p_method) const = 0;
 	virtual bool has_static_method(const StringName &p_method) const { return false; }
 
+	virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
+
 	virtual MethodInfo get_method_info(const StringName &p_method) const = 0;
 
 	virtual bool is_tool() const = 0;
@@ -442,6 +444,13 @@ public:
 	virtual void get_method_list(List<MethodInfo> *p_list) const override;
 	virtual bool has_method(const StringName &p_method) const override;
 
+	virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
 		r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
 		return Variant();

+ 3 - 0
core/object/script_language_extension.cpp

@@ -56,6 +56,9 @@ void ScriptExtension::_bind_methods() {
 
 	GDVIRTUAL_BIND(_has_method, "method");
 	GDVIRTUAL_BIND(_has_static_method, "method");
+
+	GDVIRTUAL_BIND(_get_script_method_argument_count, "method");
+
 	GDVIRTUAL_BIND(_get_method_info, "method");
 
 	GDVIRTUAL_BIND(_is_tool);

+ 18 - 0
core/object/script_language_extension.h

@@ -101,6 +101,19 @@ public:
 	EXBIND1RC(bool, has_method, const StringName &)
 	EXBIND1RC(bool, has_static_method, const StringName &)
 
+	GDVIRTUAL1RC(Variant, _get_script_method_argument_count, const StringName &)
+	virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
+		Variant ret;
+		if (GDVIRTUAL_CALL(_get_script_method_argument_count, p_method, ret) && ret.get_type() == Variant::INT) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return ret.operator int();
+		}
+		// Fallback to default.
+		return Script::get_script_method_argument_count(p_method, r_is_valid);
+	}
+
 	GDVIRTUAL1RC(Dictionary, _get_method_info, const StringName &)
 	virtual MethodInfo get_method_info(const StringName &p_method) const override {
 		Dictionary mi;
@@ -807,6 +820,11 @@ public:
 		return false;
 	}
 
+	virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
+		// Fallback to default.
+		return ScriptInstance::get_method_argument_count(p_method, r_is_valid);
+	}
+
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
 		Variant ret;
 		if (native_info->call_func) {

+ 19 - 0
core/variant/callable.cpp

@@ -163,6 +163,20 @@ StringName Callable::get_method() const {
 	return method;
 }
 
+int Callable::get_argument_count(bool *r_is_valid) const {
+	if (is_custom()) {
+		bool valid = false;
+		return custom->get_argument_count(r_is_valid ? *r_is_valid : valid);
+	} else if (!is_null()) {
+		return get_object()->get_method_argument_count(method, r_is_valid);
+	} else {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+}
+
 int Callable::get_bound_arguments_count() const {
 	if (!is_null() && is_custom()) {
 		return custom->get_bound_arguments_count();
@@ -417,6 +431,11 @@ const Callable *CallableCustom::get_base_comparator() const {
 	return nullptr;
 }
 
+int CallableCustom::get_argument_count(bool &r_is_valid) const {
+	r_is_valid = false;
+	return 0;
+}
+
 int CallableCustom::get_bound_arguments_count() const {
 	return 0;
 }

+ 2 - 0
core/variant/callable.h

@@ -109,6 +109,7 @@ public:
 	ObjectID get_object_id() const;
 	StringName get_method() const;
 	CallableCustom *get_custom() const;
+	int get_argument_count(bool *r_is_valid = nullptr) const;
 	int get_bound_arguments_count() const;
 	void get_bound_arguments_ref(Vector<Variant> &r_arguments, int &r_argcount) const; // Internal engine use, the exposed one is below.
 	Array get_bound_arguments() const;
@@ -155,6 +156,7 @@ public:
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const = 0;
 	virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const;
 	virtual const Callable *get_base_comparator() const;
+	virtual int get_argument_count(bool &r_is_valid) const;
 	virtual int get_bound_arguments_count() const;
 	virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const;
 

+ 16 - 0
core/variant/callable_bind.cpp

@@ -91,6 +91,14 @@ const Callable *CallableCustomBind::get_base_comparator() const {
 	return callable.get_base_comparator();
 }
 
+int CallableCustomBind::get_argument_count(bool &r_is_valid) const {
+	int ret = callable.get_argument_count(&r_is_valid);
+	if (r_is_valid) {
+		return ret - binds.size();
+	}
+	return 0;
+}
+
 int CallableCustomBind::get_bound_arguments_count() const {
 	return callable.get_bound_arguments_count() + binds.size();
 }
@@ -225,6 +233,14 @@ const Callable *CallableCustomUnbind::get_base_comparator() const {
 	return callable.get_base_comparator();
 }
 
+int CallableCustomUnbind::get_argument_count(bool &r_is_valid) const {
+	int ret = callable.get_argument_count(&r_is_valid);
+	if (r_is_valid) {
+		return ret + argcount;
+	}
+	return 0;
+}
+
 int CallableCustomUnbind::get_bound_arguments_count() const {
 	return callable.get_bound_arguments_count() - argcount;
 }

+ 2 - 0
core/variant/callable_bind.h

@@ -53,6 +53,7 @@ public:
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 	virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
 	virtual const Callable *get_base_comparator() const override;
+	virtual int get_argument_count(bool &r_is_valid) const override;
 	virtual int get_bound_arguments_count() const override;
 	virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override;
 	Callable get_callable() { return callable; }
@@ -81,6 +82,7 @@ public:
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 	virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
 	virtual const Callable *get_base_comparator() const override;
+	virtual int get_argument_count(bool &r_is_valid) const override;
 	virtual int get_bound_arguments_count() const override;
 	virtual void get_bound_arguments(Vector<Variant> &r_arguments, int &r_argcount) const override;
 

+ 5 - 0
core/variant/variant_call.cpp

@@ -1053,6 +1053,10 @@ struct _VariantCall {
 		r_ret = callable->bindp(p_args, p_argcount);
 	}
 
+	static int func_Callable_get_argument_count(Callable *p_callable) {
+		return p_callable->get_argument_count();
+	}
+
 	static void func_Signal_emit(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
 		Signal *signal = VariantGetInternalPtr<Signal>::get_ptr(v);
 		signal->emit(p_args, p_argcount);
@@ -2048,6 +2052,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Callable, get_object, sarray(), varray());
 	bind_method(Callable, get_object_id, sarray(), varray());
 	bind_method(Callable, get_method, sarray(), varray());
+	bind_function(Callable, get_argument_count, _VariantCall::func_Callable_get_argument_count, sarray(), varray());
 	bind_method(Callable, get_bound_arguments_count, sarray(), varray());
 	bind_method(Callable, get_bound_arguments, sarray(), varray());
 	bind_method(Callable, hash, sarray(), varray());

+ 9 - 0
core/variant/variant_callable.cpp

@@ -68,6 +68,15 @@ ObjectID VariantCallable::get_object() const {
 	return ObjectID();
 }
 
+int VariantCallable::get_argument_count(bool &r_is_valid) const {
+	if (!Variant::has_builtin_method(variant.get_type(), method)) {
+		r_is_valid = false;
+		return 0;
+	}
+	r_is_valid = true;
+	return Variant::get_builtin_method_argument_count(variant.get_type(), method);
+}
+
 void VariantCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 	Variant v = variant;
 	v.callp(method, p_arguments, p_argcount, r_return_value, r_call_error);

+ 1 - 0
core/variant/variant_callable.h

@@ -50,6 +50,7 @@ public:
 	bool is_valid() const override;
 	StringName get_method() const override;
 	ObjectID get_object() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	VariantCallable(const Variant &p_variant, const StringName &p_method);

+ 6 - 0
doc/classes/Callable.xml

@@ -147,6 +147,12 @@
 				[b]Note:[/b] This method is always necessary for the [Dictionary] type, as property syntax is used to access its entries. You may also use this method when [param variant]'s type is not known in advance (for polymorphism).
 			</description>
 		</method>
+		<method name="get_argument_count" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns the total number of arguments this [Callable] should take, including optional arguments. This means that any arguments bound with [method bind] are [i]subtracted[/i] from the result, and any arguments unbound with [method unbind] are [i]added[/i] to the result.
+			</description>
+		</method>
 		<method name="get_bound_arguments" qualifiers="const">
 			<return type="Array" />
 			<description>

+ 9 - 0
doc/classes/ClassDB.xml

@@ -65,6 +65,15 @@
 				Returns an array with the names all the integer constants of [param class] or its ancestry.
 			</description>
 		</method>
+		<method name="class_get_method_argument_count" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="class" type="StringName" />
+			<param index="1" name="method" type="StringName" />
+			<param index="2" name="no_inheritance" type="bool" default="false" />
+			<description>
+				Returns the number of arguments of the method [param method] of [param class] or its ancestry if [param no_inheritance] is [code]false[/code].
+			</description>
+		</method>
 		<method name="class_get_method_list" qualifiers="const">
 			<return type="Dictionary[]" />
 			<param index="0" name="class" type="StringName" />

+ 8 - 0
doc/classes/Object.xml

@@ -711,6 +711,14 @@
 				Returns the object's metadata entry names as a [PackedStringArray].
 			</description>
 		</method>
+		<method name="get_method_argument_count" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="method" type="StringName" />
+			<description>
+				Returns the number of arguments of the given [param method] by name.
+				[b]Note:[/b] In C#, [param method] must be in snake_case when referring to built-in Godot methods. Prefer using the names exposed in the [code]MethodName[/code] class to avoid allocating a new [StringName] on each call.
+			</description>
+		</method>
 		<method name="get_method_list" qualifiers="const">
 			<return type="Dictionary[]" />
 			<description>

+ 6 - 0
doc/classes/ScriptExtension.xml

@@ -80,6 +80,12 @@
 			<description>
 			</description>
 		</method>
+		<method name="_get_script_method_argument_count" qualifiers="virtual const">
+			<return type="Variant" />
+			<param index="0" name="method" type="StringName" />
+			<description>
+			</description>
+		</method>
 		<method name="_get_script_method_list" qualifiers="virtual const">
 			<return type="Dictionary[]" />
 			<description>

+ 34 - 0
modules/gdscript/gdscript.cpp

@@ -354,6 +354,21 @@ bool GDScript::has_static_method(const StringName &p_method) const {
 	return member_functions.has(p_method) && member_functions[p_method]->is_static();
 }
 
+int GDScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method);
+	if (!E) {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = true;
+	}
+	return E->value->get_argument_count();
+}
+
 MethodInfo GDScript::get_method_info(const StringName &p_method) const {
 	HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method);
 	if (!E) {
@@ -1916,6 +1931,25 @@ bool GDScriptInstance::has_method(const StringName &p_method) const {
 	return false;
 }
 
+int GDScriptInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	const GDScript *sptr = script.ptr();
+	while (sptr) {
+		HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_method);
+		if (E) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return E->value->get_argument_count();
+		}
+		sptr = sptr->_base;
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
 	GDScript *sptr = script.ptr();
 	if (unlikely(p_method == SNAME("_ready"))) {

+ 6 - 0
modules/gdscript/gdscript.h

@@ -312,6 +312,9 @@ public:
 	virtual void get_script_method_list(List<MethodInfo> *p_list) const override;
 	virtual bool has_method(const StringName &p_method) const override;
 	virtual bool has_static_method(const StringName &p_method) const override;
+
+	virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override;
+
 	virtual MethodInfo get_method_info(const StringName &p_method) const override;
 
 	virtual void get_script_property_list(List<PropertyInfo> *p_list) const override;
@@ -376,6 +379,9 @@ public:
 
 	virtual void get_method_list(List<MethodInfo> *p_list) const;
 	virtual bool has_method(const StringName &p_method) const;
+
+	virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
+
 	virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
 
 	Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }

+ 1 - 0
modules/gdscript/gdscript_function.h

@@ -511,6 +511,7 @@ public:
 	_FORCE_INLINE_ GDScript *get_script() const { return _script; }
 	_FORCE_INLINE_ bool is_static() const { return _static; }
 	_FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; }
+	_FORCE_INLINE_ int get_argument_count() const { return _argument_count; }
 	_FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; }
 	_FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; }
 

+ 18 - 0
modules/gdscript/gdscript_lambda_callable.cpp

@@ -78,6 +78,15 @@ StringName GDScriptLambdaCallable::get_method() const {
 	return function->get_name();
 }
 
+int GDScriptLambdaCallable::get_argument_count(bool &r_is_valid) const {
+	if (function == nullptr) {
+		r_is_valid = false;
+		return 0;
+	}
+	r_is_valid = true;
+	return function->get_argument_count();
+}
+
 void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 	int captures_amount = captures.size();
 
@@ -189,6 +198,15 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const {
 	return object->get_instance_id();
 }
 
+int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const {
+	if (function == nullptr) {
+		r_is_valid = false;
+		return 0;
+	}
+	r_is_valid = true;
+	return function->get_argument_count();
+}
+
 void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 #ifdef DEBUG_ENABLED
 	if (object->get_script_instance() == nullptr || object->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {

+ 2 - 0
modules/gdscript/gdscript_lambda_callable.h

@@ -59,6 +59,7 @@ public:
 	CompareLessFunc get_compare_less_func() const override;
 	ObjectID get_object() const override;
 	StringName get_method() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	GDScriptLambdaCallable(GDScriptLambdaCallable &) = delete;
@@ -86,6 +87,7 @@ public:
 	CompareEqualFunc get_compare_equal_func() const override;
 	CompareLessFunc get_compare_less_func() const override;
 	ObjectID get_object() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	GDScriptLambdaSelfCallable(GDScriptLambdaSelfCallable &) = delete;

+ 4 - 0
modules/gdscript/gdscript_rpc_callable.cpp

@@ -68,6 +68,10 @@ StringName GDScriptRPCCallable::get_method() const {
 	return method;
 }
 
+int GDScriptRPCCallable::get_argument_count(bool &r_is_valid) const {
+	return object->get_method_argument_count(method, &r_is_valid);
+}
+
 void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 	r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error);
 }

+ 1 - 0
modules/gdscript/gdscript_rpc_callable.h

@@ -52,6 +52,7 @@ public:
 	CompareLessFunc get_compare_less_func() const override;
 	ObjectID get_object() const override;
 	StringName get_method() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 	Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
 

+ 15 - 0
modules/gdscript/gdscript_utility_callable.cpp

@@ -80,6 +80,21 @@ ObjectID GDScriptUtilityCallable::get_object() const {
 	return ObjectID();
 }
 
+int GDScriptUtilityCallable::get_argument_count(bool &r_is_valid) const {
+	switch (type) {
+		case TYPE_INVALID:
+			r_is_valid = false;
+			return 0;
+		case TYPE_GLOBAL:
+			r_is_valid = true;
+			return Variant::get_utility_function_argument_count(function_name);
+		case TYPE_GDSCRIPT:
+			r_is_valid = true;
+			return GDScriptUtilityFunctions::get_function_argument_count(function_name);
+	}
+	ERR_FAIL_V_MSG(0, "Invalid type.");
+}
+
 void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 	switch (type) {
 		case TYPE_INVALID:

+ 1 - 0
modules/gdscript/gdscript_utility_callable.h

@@ -57,6 +57,7 @@ public:
 	bool is_valid() const override;
 	StringName get_method() const override;
 	ObjectID get_object() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	GDScriptUtilityCallable(const StringName &p_function_name);

+ 1 - 1
modules/gdscript/gdscript_utility_functions.cpp

@@ -759,7 +759,7 @@ Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringN
 	return info->info.arguments[p_arg].type;
 }
 
-int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) {
+int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function) {
 	GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
 	ERR_FAIL_NULL_V(info, 0);
 	return info->info.arguments.size();

+ 1 - 1
modules/gdscript/gdscript_utility_functions.h

@@ -46,7 +46,7 @@ public:
 	static Variant::Type get_function_return_type(const StringName &p_function);
 	static StringName get_function_return_class(const StringName &p_function);
 	static Variant::Type get_function_argument_type(const StringName &p_function, int p_arg);
-	static int get_function_argument_count(const StringName &p_function, int p_arg);
+	static int get_function_argument_count(const StringName &p_function);
 	static bool is_function_vararg(const StringName &p_function);
 	static bool is_function_constant(const StringName &p_function);
 

+ 102 - 0
modules/gdscript/tests/scripts/runtime/features/argument_count.gd

@@ -0,0 +1,102 @@
+extends Node
+
+func my_func_1(_foo, _bar):
+    pass
+
+func my_func_2(_foo, _bar, _baz):
+    pass
+
+static func my_static_func_1(_foo, _bar):
+    pass
+
+static func my_static_func_2(_foo, _bar, _baz):
+    pass
+
+@rpc
+func my_rpc_func_1(_foo, _bar):
+    pass
+
+@rpc
+func my_rpc_func_2(_foo, _bar, _baz):
+    pass
+
+func test():
+    # Test built-in methods.
+    var builtin_callable_1 : Callable = add_to_group
+    print(builtin_callable_1.get_argument_count()) # Should print 2.
+    var builtin_callable_2 : Callable = find_child
+    print(builtin_callable_2.get_argument_count()) # Should print 3.
+
+    # Test built-in vararg methods.
+    var builtin_vararg_callable_1 : Callable = call_thread_safe
+    print(builtin_vararg_callable_1.get_argument_count()) # Should print 1.
+    var builtin_vararg_callable_2 : Callable = rpc_id
+    print(builtin_vararg_callable_2.get_argument_count()) # Should print 2.
+
+    # Test plain methods.
+    var callable_1 : Callable = my_func_1
+    print(callable_1.get_argument_count()) # Should print 2.
+    var callable_2 : Callable = my_func_2
+    print(callable_2.get_argument_count()) # Should print 3.
+
+    # Test static methods.
+    var static_callable_1 : Callable = my_static_func_1
+    print(static_callable_1.get_argument_count()) # Should print 2.
+    var static_callable_2 : Callable = my_static_func_2
+    print(static_callable_2.get_argument_count()) # Should print 3.
+
+    # Test rpc methods.
+    var rpc_callable_1 : Callable = my_rpc_func_1
+    print(rpc_callable_1.get_argument_count()) # Should print 2.
+    var rpc_callable_2 : Callable = my_rpc_func_2
+    print(rpc_callable_2.get_argument_count()) # Should print 3.
+
+    # Test lambdas.
+    var lambda_callable_1 : Callable = func(_foo, _bar): pass
+    print(lambda_callable_1.get_argument_count()) # Should print 2.
+    var lambda_callable_2 : Callable = func(_foo, _bar, _baz): pass
+    print(lambda_callable_2.get_argument_count()) # Should print 3.
+
+    # Test lambas with self.
+    var lambda_self_callable_1 : Callable = func(_foo, _bar): return self
+    print(lambda_self_callable_1.get_argument_count()) # Should print 2.
+    var lambda_self_callable_2 : Callable = func(_foo, _bar, _baz): return self
+    print(lambda_self_callable_2.get_argument_count()) # Should print 3.
+
+    # Test bind.
+    var bind_callable_1 : Callable = my_func_2.bind(1)
+    print(bind_callable_1.get_argument_count()) # Should print 2.
+    var bind_callable_2 : Callable = my_func_2.bind(1, 2)
+    print(bind_callable_2.get_argument_count()) # Should print 1.
+
+    # Test unbind.
+    var unbind_callable_1 : Callable = my_func_2.unbind(1)
+    print(unbind_callable_1.get_argument_count()) # Should print 4.
+    var unbind_callable_2 : Callable = my_func_2.unbind(2)
+    print(unbind_callable_2.get_argument_count()) # Should print 5.
+
+    # Test variant callables.
+    var string_tmp := String()
+    var variant_callable_1 : Callable = string_tmp.replace
+    print(variant_callable_1.get_argument_count()) # Should print 2.
+    var variant_callable_2 : Callable = string_tmp.rsplit
+    print(variant_callable_2.get_argument_count()) # Should print 3.
+
+    # Test variant vararg callables.
+    var callable_tmp := Callable()
+    var variant_vararg_callable_1 : Callable = callable_tmp.call
+    print(variant_vararg_callable_1.get_argument_count()) # Should print 0.
+    var variant_vararg_callable_2 : Callable = callable_tmp.rpc_id
+    print(variant_vararg_callable_2.get_argument_count()) # Should print 1.
+
+    # Test global methods.
+    var global_callable_1 = is_equal_approx
+    print(global_callable_1.get_argument_count()) # Should print 2.
+    var global_callable_2 = inverse_lerp
+    print(global_callable_2.get_argument_count()) # Should print 3.
+
+    # Test GDScript methods.
+    var gdscript_callable_1 = char
+    print(gdscript_callable_1.get_argument_count()) # Should print 1.
+    var gdscript_callable_2 = is_instance_of
+    print(gdscript_callable_2.get_argument_count()) # Should print 2.

+ 27 - 0
modules/gdscript/tests/scripts/runtime/features/argument_count.out

@@ -0,0 +1,27 @@
+GDTEST_OK
+2
+3
+1
+2
+2
+3
+2
+3
+2
+3
+2
+3
+2
+3
+2
+1
+4
+5
+2
+3
+0
+1
+2
+3
+1
+2

+ 51 - 0
modules/mono/csharp_script.cpp

@@ -1735,6 +1735,34 @@ bool CSharpInstance::has_method(const StringName &p_method) const {
 			gchandle.get_intptr(), &p_method);
 }
 
+int CSharpInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	if (!script->is_valid() || !script->valid) {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+
+	const CSharpScript *top = script.ptr();
+	while (top != nullptr) {
+		for (const CSharpScript::CSharpMethodInfo &E : top->methods) {
+			if (E.name == p_method) {
+				if (r_is_valid) {
+					*r_is_valid = true;
+				}
+				return E.method_info.arguments.size();
+			}
+		}
+
+		top = top->base_script.ptr();
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
 	ERR_FAIL_COND_V(!script.is_valid(), Variant());
 
@@ -2579,6 +2607,29 @@ bool CSharpScript::has_method(const StringName &p_method) const {
 	return false;
 }
 
+int CSharpScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
+	if (!valid) {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
+
+	for (const CSharpMethodInfo &E : methods) {
+		if (E.name == p_method) {
+			if (r_is_valid) {
+				*r_is_valid = true;
+			}
+			return E.method_info.arguments.size();
+		}
+	}
+
+	if (r_is_valid) {
+		*r_is_valid = false;
+	}
+	return 0;
+}
+
 MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
 	if (!valid) {
 		return MethodInfo();

+ 2 - 0
modules/mono/csharp_script.h

@@ -278,6 +278,7 @@ public:
 
 	void get_script_method_list(List<MethodInfo> *p_list) const override;
 	bool has_method(const StringName &p_method) const override;
+	virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override;
 	MethodInfo get_method_info(const StringName &p_method) const override;
 	Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
 
@@ -346,6 +347,7 @@ public:
 
 	void get_method_list(List<MethodInfo> *p_list) const override;
 	bool has_method(const StringName &p_method) const override;
+	virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override;
 	Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
 
 	void mono_object_disposed(GCHandleIntPtr p_gchandle_to_free);

+ 2 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs

@@ -12,6 +12,7 @@ namespace Godot.Bridge
         public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
         public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
         public delegate* unmanaged<IntPtr, int> DelegateUtils_DelegateHash;
+        public delegate* unmanaged<IntPtr, godot_bool*, int> DelegateUtils_GetArgumentCount;
         public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
         public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;
         public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback;
@@ -55,6 +56,7 @@ namespace Godot.Bridge
                 DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
                 DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
                 DelegateUtils_DelegateHash = &DelegateUtils.DelegateHash,
+                DelegateUtils_GetArgumentCount = &DelegateUtils.GetArgumentCount,
                 DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle,
                 DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle,
                 ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,

+ 23 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs

@@ -45,6 +45,29 @@ namespace Godot
             }
         }
 
+        [UnmanagedCallersOnly]
+        internal static unsafe int GetArgumentCount(IntPtr delegateGCHandle, godot_bool* outIsValid)
+        {
+            try
+            {
+                var @delegate = (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target;
+                int? argCount = @delegate?.Method?.GetParameters().Length;
+                if (argCount is null)
+                {
+                    *outIsValid = godot_bool.False;
+                    return 0;
+                }
+                *outIsValid = godot_bool.True;
+                return argCount.Value;
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
+                *outIsValid = godot_bool.False;
+                return 0;
+            }
+        }
+
         [UnmanagedCallersOnly]
         internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline,
             godot_variant** args, int argc, godot_variant* outRet)

+ 4 - 0
modules/mono/managed_callable.cpp

@@ -85,6 +85,10 @@ ObjectID ManagedCallable::get_object() const {
 	return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
 }
 
+int ManagedCallable::get_argument_count(bool &r_is_valid) const {
+	return GDMonoCache::managed_callbacks.DelegateUtils_GetArgumentCount(delegate_handle, &r_is_valid);
+}
+
 void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
 	r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
 	r_return_value = Variant();

+ 1 - 0
modules/mono/managed_callable.h

@@ -56,6 +56,7 @@ public:
 	CompareEqualFunc get_compare_equal_func() const override;
 	CompareLessFunc get_compare_less_func() const override;
 	ObjectID get_object() const override;
+	int get_argument_count(bool &r_is_valid) const override;
 	void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
 
 	_FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; }

+ 1 - 0
modules/mono/mono_gd/gd_mono_cache.cpp

@@ -53,6 +53,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateHash);
+	CHECK_CALLBACK_NOT_NULL(DelegateUtils, GetArgumentCount);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback);

+ 2 - 0
modules/mono/mono_gd/gd_mono_cache.h

@@ -78,6 +78,7 @@ struct ManagedCallbacks {
 	using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, void *, const Variant **, int32_t, const Variant *);
 	using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
 	using FuncDelegateUtils_DelegateHash = int32_t(GD_CLR_STDCALL *)(GCHandleIntPtr);
+	using FuncDelegateUtils_GetArgumentCount = int32_t(GD_CLR_STDCALL *)(GCHandleIntPtr, bool *);
 	using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *);
 	using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *);
 	using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)();
@@ -115,6 +116,7 @@ struct ManagedCallbacks {
 	FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs;
 	FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals;
 	FuncDelegateUtils_DelegateHash DelegateUtils_DelegateHash;
+	FuncDelegateUtils_GetArgumentCount DelegateUtils_GetArgumentCount;
 	FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle;
 	FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle;
 	FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback;

+ 6 - 0
tests/core/object/test_object.h

@@ -95,6 +95,12 @@ public:
 	bool has_method(const StringName &p_method) const override {
 		return false;
 	}
+	int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
+		if (r_is_valid) {
+			*r_is_valid = false;
+		}
+		return 0;
+	}
 	Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
 		return Variant();
 	}

+ 140 - 0
tests/core/variant/test_callable.h

@@ -0,0 +1,140 @@
+/**************************************************************************/
+/*  test_callable.h                                                       */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef TEST_CALLABLE_H
+#define TEST_CALLABLE_H
+
+#include "core/object/class_db.h"
+#include "core/object/object.h"
+
+#include "tests/test_macros.h"
+
+namespace TestCallable {
+
+class TestClass : public Object {
+	GDCLASS(TestClass, Object);
+
+protected:
+	static void _bind_methods() {
+		ClassDB::bind_method(D_METHOD("test_func_1", "foo", "bar"), &TestClass::test_func_1);
+		ClassDB::bind_method(D_METHOD("test_func_2", "foo", "bar", "baz"), &TestClass::test_func_2);
+		ClassDB::bind_static_method("TestClass", D_METHOD("test_func_5", "foo", "bar"), &TestClass::test_func_5);
+		ClassDB::bind_static_method("TestClass", D_METHOD("test_func_6", "foo", "bar", "baz"), &TestClass::test_func_6);
+
+		{
+			MethodInfo mi;
+			mi.name = "test_func_7";
+			mi.arguments.push_back(PropertyInfo(Variant::INT, "foo"));
+			mi.arguments.push_back(PropertyInfo(Variant::INT, "bar"));
+
+			ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_7", &TestClass::test_func_7, mi, varray(), false);
+		}
+
+		{
+			MethodInfo mi;
+			mi.name = "test_func_8";
+			mi.arguments.push_back(PropertyInfo(Variant::INT, "foo"));
+			mi.arguments.push_back(PropertyInfo(Variant::INT, "bar"));
+			mi.arguments.push_back(PropertyInfo(Variant::INT, "baz"));
+
+			ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func_8", &TestClass::test_func_8, mi, varray(), false);
+		}
+	}
+
+public:
+	void test_func_1(int p_foo, int p_bar) {}
+	void test_func_2(int p_foo, int p_bar, int p_baz) {}
+
+	int test_func_3(int p_foo, int p_bar) const { return 0; }
+	int test_func_4(int p_foo, int p_bar, int p_baz) const { return 0; }
+
+	static void test_func_5(int p_foo, int p_bar) {}
+	static void test_func_6(int p_foo, int p_bar, int p_baz) {}
+
+	void test_func_7(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {}
+	void test_func_8(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {}
+};
+
+TEST_CASE("[Callable] Argument count") {
+	TestClass *my_test = memnew(TestClass);
+
+	// Bound methods tests.
+
+	// Test simple methods.
+	Callable callable_1 = Callable(my_test, "test_func_1");
+	CHECK_EQ(callable_1.get_argument_count(), 2);
+	Callable callable_2 = Callable(my_test, "test_func_2");
+	CHECK_EQ(callable_2.get_argument_count(), 3);
+	Callable callable_3 = Callable(my_test, "test_func_5");
+	CHECK_EQ(callable_3.get_argument_count(), 2);
+	Callable callable_4 = Callable(my_test, "test_func_6");
+	CHECK_EQ(callable_4.get_argument_count(), 3);
+
+	// Test vararg methods.
+	Callable callable_vararg_1 = Callable(my_test, "test_func_7");
+	CHECK_MESSAGE(callable_vararg_1.get_argument_count() == 2, "vararg Callable should return the number of declared arguments");
+	Callable callable_vararg_2 = Callable(my_test, "test_func_8");
+	CHECK_MESSAGE(callable_vararg_2.get_argument_count() == 3, "vararg Callable should return the number of declared arguments");
+
+	// Callable MP tests.
+
+	// Test simple methods.
+	Callable callable_mp_1 = callable_mp(my_test, &TestClass::test_func_1);
+	CHECK_EQ(callable_mp_1.get_argument_count(), 2);
+	Callable callable_mp_2 = callable_mp(my_test, &TestClass::test_func_2);
+	CHECK_EQ(callable_mp_2.get_argument_count(), 3);
+	Callable callable_mp_3 = callable_mp(my_test, &TestClass::test_func_3);
+	CHECK_EQ(callable_mp_3.get_argument_count(), 2);
+	Callable callable_mp_4 = callable_mp(my_test, &TestClass::test_func_4);
+	CHECK_EQ(callable_mp_4.get_argument_count(), 3);
+
+	// Test static methods.
+	Callable callable_mp_static_1 = callable_mp_static(&TestClass::test_func_5);
+	CHECK_EQ(callable_mp_static_1.get_argument_count(), 2);
+	Callable callable_mp_static_2 = callable_mp_static(&TestClass::test_func_6);
+	CHECK_EQ(callable_mp_static_2.get_argument_count(), 3);
+
+	// Test bind.
+	Callable callable_mp_bind_1 = callable_mp_2.bind(1);
+	CHECK_MESSAGE(callable_mp_bind_1.get_argument_count() == 2, "bind should subtract from the argument count");
+	Callable callable_mp_bind_2 = callable_mp_2.bind(1, 2);
+	CHECK_MESSAGE(callable_mp_bind_2.get_argument_count() == 1, "bind should subtract from the argument count");
+
+	// Test unbind.
+	Callable callable_mp_unbind_1 = callable_mp_2.unbind(1);
+	CHECK_MESSAGE(callable_mp_unbind_1.get_argument_count() == 4, "unbind should add to the argument count");
+	Callable callable_mp_unbind_2 = callable_mp_2.unbind(2);
+	CHECK_MESSAGE(callable_mp_unbind_2.get_argument_count() == 5, "unbind should add to the argument count");
+
+	memdelete(my_test);
+}
+} // namespace TestCallable
+
+#endif // TEST_CALLABLE_H

+ 1 - 0
tests/test_main.cpp

@@ -93,6 +93,7 @@
 #include "tests/core/test_time.h"
 #include "tests/core/threads/test_worker_thread_pool.h"
 #include "tests/core/variant/test_array.h"
+#include "tests/core/variant/test_callable.h"
 #include "tests/core/variant/test_dictionary.h"
 #include "tests/core/variant/test_variant.h"
 #include "tests/core/variant/test_variant_utility.h"