From b1eb737719b88702ec3ca3eed211582e9188e38e Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Tue, 12 Sep 2023 21:55:55 +0300 Subject: [PATCH] GDScript: Fix some lambda bugs --- .editorconfig | 10 -- modules/gdscript/.editorconfig | 8 + modules/gdscript/gdscript_compiler.cpp | 37 ++++- modules/gdscript/gdscript_compiler.h | 2 +- modules/gdscript/gdscript_lambda_callable.cpp | 74 +++++++++- modules/gdscript/gdscript_vm.cpp | 9 +- .../scripts/runtime/features/member_info.gd | 65 +-------- .../scripts/runtime/features/member_info.out | 6 +- .../scripts/runtime/features/metatypes.gd | 36 +++++ .../runtime/features/metatypes.notest.gd | 1 + .../scripts/runtime/features/metatypes.out | 13 ++ .../gdscript/tests/scripts/utils.notest.gd | 137 ++++++++++++++++++ 12 files changed, 314 insertions(+), 84 deletions(-) create mode 100644 modules/gdscript/.editorconfig create mode 100644 modules/gdscript/tests/scripts/runtime/features/metatypes.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/metatypes.out create mode 100644 modules/gdscript/tests/scripts/utils.notest.gd diff --git a/.editorconfig b/.editorconfig index 4bb7553b167..92ee947a82b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,13 +21,3 @@ indent_size = 4 [*.{yml,yaml}] indent_style = space indent_size = 2 - -# GDScript unit test files -[*.gd] -indent_style = tab -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.out] -insert_final_newline = true diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig new file mode 100644 index 00000000000..640c2050937 --- /dev/null +++ b/modules/gdscript/.editorconfig @@ -0,0 +1,8 @@ +[*.gd] +indent_style = tab +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.out] +insert_final_newline = true diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fae7861539a..a087e8cdf9e 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -84,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype) { if (!p_datatype.is_set() || !p_datatype.is_hard_type() || p_datatype.is_coroutine) { return GDScriptDataType(); } @@ -101,11 +101,25 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = p_datatype.builtin_type; } break; case GDScriptParser::DataType::NATIVE: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = GDScriptNativeClass::get_class_static(); + break; + } + result.kind = GDScriptDataType::NATIVE; result.native_type = p_datatype.native_type; result.builtin_type = p_datatype.builtin_type; } break; case GDScriptParser::DataType::SCRIPT: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = p_datatype.script_type.is_valid() ? p_datatype.script_type->get_class() : Script::get_class_static(); + break; + } + result.kind = GDScriptDataType::SCRIPT; result.builtin_type = p_datatype.builtin_type; result.script_type_ref = p_datatype.script_type; @@ -113,6 +127,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = GDScript::get_class_static(); + break; + } + result.kind = GDScriptDataType::GDSCRIPT; result.builtin_type = p_datatype.builtin_type; result.native_type = p_datatype.native_type; @@ -148,6 +169,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::DICTIONARY; + break; + } + result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; break; @@ -159,7 +186,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner, false)); } return result; @@ -533,7 +560,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast(p_expression); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script, false); GDScriptCodeGenerator::Address result; if (cast_type.has_type) { @@ -911,7 +938,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand); - GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script); + GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script, false); if (r_error) { return GDScriptCodeGenerator::Address(); } @@ -2587,7 +2614,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP } } - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script, false); int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 18824713318..099bd00a2ea 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -124,7 +124,7 @@ class GDScriptCompiler { Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 3b89f077bdd..9d0fce0928b 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -79,13 +79,48 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V args.resize(p_argcount + captures_amount); for (int i = 0; i < captures_amount; i++) { args.write[i] = &captures[i]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } for (int i = 0; i < p_argcount; i++) { args.write[i + captures_amount] = p_arguments[i]; } r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); - r_call_error.argument -= captures_amount; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } else { r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error); } @@ -148,13 +183,48 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun args.resize(p_argcount + captures_amount); for (int i = 0; i < captures_amount; i++) { args.write[i] = &captures[i]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } for (int i = 0; i < p_argcount; i++) { args.write[i + captures_amount] = p_arguments[i]; } r_return_value = function->call(static_cast(object->get_script_instance()), args.ptrw(), args.size(), r_call_error); - r_call_error.argument -= captures_amount; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } else { r_return_value = function->call(static_cast(object->get_script_instance()), p_arguments, p_argcount, r_call_error); } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index a7dc0b6d595..c0644e089cd 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -116,6 +116,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { int errorarg = p_err.argument; + ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer."); // Handle the Object to Object case separately as we don't have further class details. #ifdef DEBUG_ENABLED if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { @@ -128,9 +129,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; } } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { err_text = "Invalid call. Nonexistent " + p_where + "."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { @@ -511,13 +512,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (p_argcount != _argument_count) { if (p_argcount > _argument_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; + r_err.expected = _argument_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; + r_err.expected = _argument_count - _default_arg_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else { diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 50f840cef3d..805ea42455c 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -5,6 +5,8 @@ class MyClass: enum MyEnum {} +const Utils = preload("../../utils.notest.gd") + static var test_static_var_untyped static var test_static_var_weak_null = null static var test_static_var_weak_int = 1 @@ -58,68 +60,13 @@ func test(): var script: Script = get_script() for property in script.get_property_list(): if str(property.name).begins_with("test_"): - if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): - print("Error: Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.") - print("static var ", property.name, ": ", get_type(property)) + print(Utils.get_property_signature(property, true)) for property in get_property_list(): if str(property.name).begins_with("test_"): - if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): - print("Error: Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.") - print("var ", property.name, ": ", get_type(property)) + print(Utils.get_property_signature(property)) for method in get_method_list(): if str(method.name).begins_with("test_"): - print(get_signature(method)) + print(Utils.get_method_signature(method)) for method in get_signal_list(): if str(method.name).begins_with("test_"): - print(get_signature(method, true)) - -func get_type(property: Dictionary, is_return: bool = false) -> String: - match property.type: - TYPE_NIL: - if property.usage & PROPERTY_USAGE_NIL_IS_VARIANT: - return "Variant" - return "void" if is_return else "null" - TYPE_BOOL: - return "bool" - TYPE_INT: - if property.usage & PROPERTY_USAGE_CLASS_IS_ENUM: - return property.class_name - return "int" - TYPE_STRING: - return "String" - TYPE_DICTIONARY: - return "Dictionary" - TYPE_ARRAY: - if property.hint == PROPERTY_HINT_ARRAY_TYPE: - return "Array[%s]" % property.hint_string - return "Array" - TYPE_OBJECT: - if not str(property.class_name).is_empty(): - return property.class_name - return "Object" - return "" - -func get_signature(method: Dictionary, is_signal: bool = false) -> String: - var result: String = "" - if method.flags & METHOD_FLAG_STATIC: - result += "static " - result += ("signal " if is_signal else "func ") + method.name + "(" - - var args: Array[Dictionary] = method.args - var default_args: Array = method.default_args - var mandatory_argc: int = args.size() - default_args.size() - for i in args.size(): - if i > 0: - result += ", " - var arg: Dictionary = args[i] - result += arg.name + ": " + get_type(arg) - if i >= mandatory_argc: - result += " = " + var_to_str(default_args[i - mandatory_argc]) - - result += ")" - if is_signal: - if get_type(method.return, true) != "void": - print("Error: Signal return type must be `void`.") - else: - result += " -> " + get_type(method.return, true) - return result + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out index 7c826ac05a8..3a91507da91 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.out +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -6,13 +6,13 @@ static var test_static_var_hard_int: int var test_var_untyped: Variant var test_var_weak_null: Variant var test_var_weak_int: Variant -var test_var_weak_int_exported: int +@export var test_var_weak_int_exported: int var test_var_weak_variant_type: Variant -var test_var_weak_variant_type_exported: Variant.Type +@export var test_var_weak_variant_type_exported: Variant.Type var test_var_hard_variant: Variant var test_var_hard_int: int var test_var_hard_variant_type: Variant.Type -var test_var_hard_variant_type_exported: Variant.Type +@export var test_var_hard_variant_type_exported: Variant.Type var test_var_hard_node_process_mode: Node.ProcessMode var test_var_hard_my_enum: TestMemberInfo.MyEnum var test_var_hard_array: Array diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd new file mode 100644 index 00000000000..6c5df32ffe1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -0,0 +1,36 @@ +class MyClass: + const TEST = 10 + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") +const Other = preload("./metatypes.notest.gd") + +var test_native := JSON +var test_script := Other +var test_class := MyClass +var test_enum := MyEnum + +func check_gdscript_native_class(value: Variant) -> void: + print(var_to_str(value).get_slice(",", 0).trim_prefix("Object(")) + +func check_gdscript(value: GDScript) -> void: + print(value.get_class()) + +func check_enum(value: Dictionary) -> void: + print(value) + +func test(): + for property in get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + + check_gdscript_native_class(test_native) + check_gdscript(test_script) + check_gdscript(test_class) + check_enum(test_enum) + + print(test_native.stringify([])) + print(test_script.TEST) + print(test_class.TEST) + print(test_enum.keys()) diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd new file mode 100644 index 00000000000..e6a591b9278 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd @@ -0,0 +1 @@ +const TEST = 100 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.out b/modules/gdscript/tests/scripts/runtime/features/metatypes.out new file mode 100644 index 00000000000..352d1caa596 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.out @@ -0,0 +1,13 @@ +GDTEST_OK +var test_native: GDScriptNativeClass +var test_script: GDScript +var test_class: GDScript +var test_enum: Dictionary +GDScriptNativeClass +GDScript +GDScript +{ "A": 0, "B": 1, "C": 2 } +[] +100 +10 +["A", "B", "C"] diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd new file mode 100644 index 00000000000..50444e62a1a --- /dev/null +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -0,0 +1,137 @@ +static func get_type(property: Dictionary, is_return: bool = false) -> String: + match property.type: + TYPE_NIL: + if property.usage & PROPERTY_USAGE_NIL_IS_VARIANT: + return "Variant" + return "void" if is_return else "null" + TYPE_INT: + if property.usage & PROPERTY_USAGE_CLASS_IS_ENUM: + if property.class_name == &"": + return "" + return property.class_name + TYPE_ARRAY: + if property.hint == PROPERTY_HINT_ARRAY_TYPE: + if str(property.hint_string).is_empty(): + return "Array[]" + return "Array[%s]" % property.hint_string + TYPE_OBJECT: + if not str(property.class_name).is_empty(): + return property.class_name + return variant_get_type_name(property.type) + +static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: + var result: String = "" + if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): + printerr("Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.") + if property.usage & PROPERTY_USAGE_DEFAULT: + result += "@export " + if is_static: + result += "static " + result += "var " + property.name + ": " + get_type(property) + return result + +static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String: + var result: String = "" + if method.flags & METHOD_FLAG_STATIC: + result += "static " + result += ("signal " if is_signal else "func ") + method.name + "(" + + var args: Array[Dictionary] = method.args + var default_args: Array = method.default_args + var mandatory_argc: int = args.size() - default_args.size() + for i in args.size(): + if i > 0: + result += ", " + var arg: Dictionary = args[i] + result += arg.name + ": " + get_type(arg) + if i >= mandatory_argc: + result += " = " + var_to_str(default_args[i - mandatory_argc]) + + result += ")" + if is_signal: + if get_type(method.return, true) != "void": + printerr("Signal return type must be `void`.") + else: + result += " -> " + get_type(method.return, true) + return result + +static func variant_get_type_name(type: Variant.Type) -> String: + match type: + TYPE_NIL: + return "Nil" # `Nil` in core, `null` in GDScript. + TYPE_BOOL: + return "bool" + TYPE_INT: + return "int" + TYPE_FLOAT: + return "float" + TYPE_STRING: + return "String" + TYPE_VECTOR2: + return "Vector2" + TYPE_VECTOR2I: + return "Vector2i" + TYPE_RECT2: + return "Rect2" + TYPE_RECT2I: + return "Rect2i" + TYPE_VECTOR3: + return "Vector3" + TYPE_VECTOR3I: + return "Vector3i" + TYPE_TRANSFORM2D: + return "Transform2D" + TYPE_VECTOR4: + return "Vector4" + TYPE_VECTOR4I: + return "Vector4i" + TYPE_PLANE: + return "Plane" + TYPE_QUATERNION: + return "Quaternion" + TYPE_AABB: + return "AABB" + TYPE_BASIS: + return "Basis" + TYPE_TRANSFORM3D: + return "Transform3D" + TYPE_PROJECTION: + return "Projection" + TYPE_COLOR: + return "Color" + TYPE_STRING_NAME: + return "StringName" + TYPE_NODE_PATH: + return "NodePath" + TYPE_RID: + return "RID" + TYPE_OBJECT: + return "Object" + TYPE_CALLABLE: + return "Callable" + TYPE_SIGNAL: + return "Signal" + TYPE_DICTIONARY: + return "Dictionary" + TYPE_ARRAY: + return "Array" + TYPE_PACKED_BYTE_ARRAY: + return "PackedByteArray" + TYPE_PACKED_INT32_ARRAY: + return "PackedInt32Array" + TYPE_PACKED_INT64_ARRAY: + return "PackedInt64Array" + TYPE_PACKED_FLOAT32_ARRAY: + return "PackedFloat32Array" + TYPE_PACKED_FLOAT64_ARRAY: + return "PackedFloat64Array" + TYPE_PACKED_STRING_ARRAY: + return "PackedStringArray" + TYPE_PACKED_VECTOR2_ARRAY: + return "PackedVector2Array" + TYPE_PACKED_VECTOR3_ARRAY: + return "PackedVector3Array" + TYPE_PACKED_COLOR_ARRAY: + return "PackedColorArray" + push_error("Argument `type` is invalid. Use `TYPE_*` constants.") + return ""