diff --git a/core/object/call_error_info.cpp b/core/object/call_error_info.cpp new file mode 100644 index 000000000000..cab2b643e320 --- /dev/null +++ b/core/object/call_error_info.cpp @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* call_error_info.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "call_error_info.h" + +CallErrorInfo::CallError CallErrorInfo::get_call_error() { + return call_error; +} + +int CallErrorInfo::get_expected_arguments() { + ERR_FAIL_COND_V_MSG(call_error != CALL_ERROR_TOO_MANY_ARGUMENTS && call_error != CALL_ERROR_TOO_FEW_ARGUMENTS, -1, "Error is not about expected argument count"); + return expected; +} + +Variant::Type CallErrorInfo::get_invalid_argument_type() { + ERR_FAIL_COND_V_MSG(call_error != CALL_ERROR_INVALID_ARGUMENT, Variant::NIL, "Error is not about an invalid argument"); + return Variant::Type(expected); +} +int CallErrorInfo::get_invalid_argument_index() { + ERR_FAIL_COND_V_MSG(call_error != CALL_ERROR_INVALID_ARGUMENT, -1, "Error is not about an invalid argument"); + return argument; +} + +void CallErrorInfo::set_call_error(CallError p_error, int p_argument, int p_expected) { + ERR_FAIL_COND_MSG(p_error == CALL_ERROR_INVALID_ARGUMENT && (p_expected < 0 || expected >= Variant::VARIANT_MAX), "Invalid value for expected argument, must be a valid Variant type"); + call_error = p_error; + argument = p_argument; + expected = p_error; +} + +void CallErrorInfo::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_call_error"), &CallErrorInfo::get_call_error); + ClassDB::bind_method(D_METHOD("get_expected_arguments"), &CallErrorInfo::get_expected_arguments); + ClassDB::bind_method(D_METHOD("get_invalid_argument_type"), &CallErrorInfo::get_invalid_argument_type); + ClassDB::bind_method(D_METHOD("get_invalid_argument_index"), &CallErrorInfo::get_invalid_argument_index); + + ClassDB::bind_method(D_METHOD("set_call_error", "error", "argument", "expected"), &CallErrorInfo::set_call_error); + + BIND_ENUM_CONSTANT(CALL_OK); + BIND_ENUM_CONSTANT(CALL_ERROR_INVALID_METHOD); + BIND_ENUM_CONSTANT(CALL_ERROR_INVALID_ARGUMENT); + BIND_ENUM_CONSTANT(CALL_ERROR_TOO_MANY_ARGUMENTS); + BIND_ENUM_CONSTANT(CALL_ERROR_TOO_FEW_ARGUMENTS); + BIND_ENUM_CONSTANT(CALL_ERROR_INSTANCE_IS_NULL); + BIND_ENUM_CONSTANT(CALL_ERROR_METHOD_NOT_CONST); + BIND_ENUM_CONSTANT(CALL_ERROR_SCRIPT_ERROR); +} diff --git a/core/object/call_error_info.h b/core/object/call_error_info.h new file mode 100644 index 000000000000..c1ca65c6094c --- /dev/null +++ b/core/object/call_error_info.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* call_error_info.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 CALL_ERROR_INFO_H +#define CALL_ERROR_INFO_H + +#include "core/object/ref_counted.h" +#include "core/variant/callable.h" + +class CallErrorInfo : public RefCounted { + GDCLASS(CallErrorInfo, RefCounted) +public: + enum CallError { + CALL_OK, + CALL_ERROR_INVALID_METHOD, + CALL_ERROR_INVALID_ARGUMENT, // expected is variant type + CALL_ERROR_TOO_MANY_ARGUMENTS, // expected is number of arguments + CALL_ERROR_TOO_FEW_ARGUMENTS, // expected is number of arguments + CALL_ERROR_INSTANCE_IS_NULL, + CALL_ERROR_METHOD_NOT_CONST, + CALL_ERROR_SCRIPT_ERROR, + }; + +private: + CallError call_error = CALL_OK; + int argument = 0; + int expected = 0; + +protected: + static void _bind_methods(); + +public: + CallError get_call_error(); + int get_expected_arguments(); + Variant::Type get_invalid_argument_type(); + int get_invalid_argument_index(); + + void set_call_error(CallError p_error, int p_argument, int p_expected); +}; + +VARIANT_ENUM_CAST(CallErrorInfo::CallError); + +#endif // CALL_ERROR_INFO_H diff --git a/core/object/object.cpp b/core/object/object.cpp index 8b3ab4027102..7e2be63dec9f 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -33,6 +33,7 @@ #include "core/extension/gdextension_manager.h" #include "core/io/resource.h" +#include "core/object/call_error_info.h" #include "core/object/class_db.h" #include "core/object/message_queue.h" #include "core/object/script_language.h" @@ -598,6 +599,40 @@ void Object::get_method_list(List *p_list) const { } } +Variant Object::_call_with_error_test_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 2) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.expected = 2; + return Variant(); + } + + Ref err_info = *p_args[0]; + + if (err_info.is_null()) { + ERR_PRINT("First argument to function must be a CallErrorInfo object."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + return Variant(); + } + + if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + StringName method = *p_args[1]; + + Variant ret = callp(method, &p_args[2], p_argcount - 2, r_error); + err_info->set_call_error(CallErrorInfo::CallError(r_error.error), r_error.argument, r_error.expected); + + r_error.error = Callable::CallError::CALL_OK; // This call validates, so the call should not fail. + + return ret; +} + Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -799,6 +834,9 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: return ret; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { + } break; + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: { + return ret; } } } @@ -843,6 +881,9 @@ Variant Object::call_const(const StringName &p_method, const Variant **p_args, i case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: return ret; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { + } break; + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: { + return ret; } } } @@ -1679,6 +1720,15 @@ void Object::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call", &Object::_call_bind, mi); } + { + MethodInfo mi; + mi.name = "call_with_error_test"; + mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "call_error_info", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "ClassErrorInfo")); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_with_error_test", &Object::_call_with_error_test_bind, mi); + } + { MethodInfo mi; mi.name = "call_deferred"; diff --git a/core/object/object.h b/core/object/object.h index bc3f663baf97..bb6684376a40 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -735,6 +735,8 @@ class Object { static void get_valid_parents_static(List *p_parents); static void _get_valid_parents_static(List *p_parents); + Variant _call_with_error_test_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Variant _call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Variant _call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index c866ff041525..4bb99ae417b9 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -71,6 +71,7 @@ #include "core/math/geometry_3d.h" #include "core/math/random_number_generator.h" #include "core/math/triangle_mesh.h" +#include "core/object/call_error_info.h" #include "core/object/class_db.h" #include "core/object/script_language_extension.h" #include "core/object/undo_redo.h" @@ -157,6 +158,8 @@ void register_core_types() { GDREGISTER_CLASS(Object); + GDREGISTER_CLASS(CallErrorInfo); + GDREGISTER_ABSTRACT_CLASS(Script); GDREGISTER_ABSTRACT_CLASS(ScriptLanguage); diff --git a/core/variant/callable.h b/core/variant/callable.h index 63757d9d6e40..cd969499ff7b 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -63,6 +63,7 @@ class Callable { CALL_ERROR_TOO_FEW_ARGUMENTS, // expected is number of arguments CALL_ERROR_INSTANCE_IS_NULL, CALL_ERROR_METHOD_NOT_CONST, + CALL_ERROR_SCRIPT_ERROR, }; Error error = Error::CALL_OK; int argument = 0; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 186643b024ac..47f214c692b8 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3652,6 +3652,8 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method, err_text = "Instance is null"; } else if (ce.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { err_text = "Method not const in const instance"; + } else if (ce.error == Callable::CallError::CALL_ERROR_SCRIPT_ERROR) { + err_text = "Script error"; } else if (ce.error == Callable::CallError::CALL_OK) { return "Call OK"; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7e29a9c0fe7e..77e5a304343f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3117,6 +3117,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: case Callable::CallError::CALL_ERROR_INVALID_METHOD: { String signature = Variant::get_type_name(builtin_type) + "("; for (int i = 0; i < p_call->arguments.size(); i++) { @@ -3287,6 +3288,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: p_call->is_constant = true; @@ -3327,6 +3329,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a p_call->arguments[err.argument]); } break; + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index ddb0cf95029a..3f804953528d 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -162,6 +162,8 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant ** return "Attempt to call " + p_where + " on a null instance."; case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: return "Attempt to call " + p_where + " on a const instance."; + case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: + return "Function called resulted in a script error."; } return "Bug: Invalid call error code " + itos(p_err.error) + "."; } @@ -3669,6 +3671,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a retvalue = _get_default_variant_for_data_type(return_type); #endif + r_err.error = Callable::CallError::CALL_ERROR_SCRIPT_ERROR; + OPCODE_OUT; }