Skip to content

Commit 601b4ac

Browse files
committed
Ability to call with error test.
Adds a new function Object.call_with_error_test Allows to tell if the call of a function fails and the reasons behind the failure. Should help better implement unit tets and other situations.
1 parent cae3d72 commit 601b4ac

13 files changed

+322
-0
lines changed

core/object/call_error_info.cpp

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**************************************************************************/
2+
/* call_error_info.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "call_error_info.h"
32+
33+
CallErrorInfo::CallError CallErrorInfo::get_call_error() {
34+
return call_error;
35+
}
36+
37+
int CallErrorInfo::get_expected_arguments() {
38+
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");
39+
return expected;
40+
}
41+
42+
Variant::Type CallErrorInfo::get_invalid_argument_type() {
43+
ERR_FAIL_COND_V_MSG(call_error != CALL_ERROR_INVALID_ARGUMENT, Variant::NIL, "Error is not about an invalid argument");
44+
return Variant::Type(expected);
45+
}
46+
int CallErrorInfo::get_invalid_argument_index() {
47+
ERR_FAIL_COND_V_MSG(call_error != CALL_ERROR_INVALID_ARGUMENT, -1, "Error is not about an invalid argument");
48+
return argument;
49+
}
50+
51+
void CallErrorInfo::set_call_error(CallError p_error, int p_argument, int p_expected) {
52+
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");
53+
call_error = p_error;
54+
argument = p_argument;
55+
expected = p_error;
56+
}
57+
58+
void CallErrorInfo::_bind_methods() {
59+
ClassDB::bind_method(D_METHOD("get_call_error"), &CallErrorInfo::get_call_error);
60+
ClassDB::bind_method(D_METHOD("get_expected_arguments"), &CallErrorInfo::get_expected_arguments);
61+
ClassDB::bind_method(D_METHOD("get_invalid_argument_type"), &CallErrorInfo::get_invalid_argument_type);
62+
ClassDB::bind_method(D_METHOD("get_invalid_argument_index"), &CallErrorInfo::get_invalid_argument_index);
63+
64+
ClassDB::bind_method(D_METHOD("set_call_error", "error", "argument", "expected"), &CallErrorInfo::set_call_error);
65+
66+
BIND_ENUM_CONSTANT(CALL_OK);
67+
BIND_ENUM_CONSTANT(CALL_ERROR_INVALID_METHOD);
68+
BIND_ENUM_CONSTANT(CALL_ERROR_INVALID_ARGUMENT);
69+
BIND_ENUM_CONSTANT(CALL_ERROR_TOO_MANY_ARGUMENTS);
70+
BIND_ENUM_CONSTANT(CALL_ERROR_TOO_FEW_ARGUMENTS);
71+
BIND_ENUM_CONSTANT(CALL_ERROR_INSTANCE_IS_NULL);
72+
BIND_ENUM_CONSTANT(CALL_ERROR_METHOD_NOT_CONST);
73+
BIND_ENUM_CONSTANT(CALL_ERROR_SCRIPT_ERROR);
74+
}

core/object/call_error_info.h

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**************************************************************************/
2+
/* call_error_info.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef CALL_ERROR_INFO_H
32+
#define CALL_ERROR_INFO_H
33+
34+
#include "core/object/ref_counted.h"
35+
#include "core/variant/callable.h"
36+
37+
class CallErrorInfo : public RefCounted {
38+
GDCLASS(CallErrorInfo, RefCounted)
39+
public:
40+
enum CallError {
41+
CALL_OK,
42+
CALL_ERROR_INVALID_METHOD,
43+
CALL_ERROR_INVALID_ARGUMENT, // expected is variant type
44+
CALL_ERROR_TOO_MANY_ARGUMENTS, // expected is number of arguments
45+
CALL_ERROR_TOO_FEW_ARGUMENTS, // expected is number of arguments
46+
CALL_ERROR_INSTANCE_IS_NULL,
47+
CALL_ERROR_METHOD_NOT_CONST,
48+
CALL_ERROR_SCRIPT_ERROR,
49+
};
50+
51+
private:
52+
CallError call_error = CALL_OK;
53+
int argument = 0;
54+
int expected = 0;
55+
56+
protected:
57+
static void _bind_methods();
58+
59+
public:
60+
CallError get_call_error();
61+
int get_expected_arguments();
62+
Variant::Type get_invalid_argument_type();
63+
int get_invalid_argument_index();
64+
65+
void set_call_error(CallError p_error, int p_argument, int p_expected);
66+
};
67+
68+
VARIANT_ENUM_CAST(CallErrorInfo::CallError);
69+
70+
#endif // CALL_ERROR_INFO_H

core/object/object.cpp

+50
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "core/extension/gdextension_manager.h"
3434
#include "core/io/resource.h"
35+
#include "core/object/call_error_info.h"
3536
#include "core/object/class_db.h"
3637
#include "core/object/message_queue.h"
3738
#include "core/object/script_language.h"
@@ -636,6 +637,40 @@ void Object::get_method_list(List<MethodInfo> *p_list) const {
636637
}
637638
}
638639

640+
Variant Object::_call_with_error_test_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
641+
if (p_argcount < 2) {
642+
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
643+
r_error.expected = 2;
644+
return Variant();
645+
}
646+
647+
Ref<CallErrorInfo> err_info = *p_args[0];
648+
649+
if (err_info.is_null()) {
650+
ERR_PRINT("First argument to function must be a CallErrorInfo object.");
651+
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
652+
r_error.argument = 0;
653+
r_error.expected = Variant::OBJECT;
654+
return Variant();
655+
}
656+
657+
if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) {
658+
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
659+
r_error.argument = 1;
660+
r_error.expected = Variant::STRING_NAME;
661+
return Variant();
662+
}
663+
664+
StringName method = *p_args[1];
665+
666+
Variant ret = callp(method, &p_args[2], p_argcount - 2, r_error);
667+
err_info->set_call_error(CallErrorInfo::CallError(r_error.error), r_error.argument, r_error.expected);
668+
669+
r_error.error = Callable::CallError::CALL_OK; // This call validates, so the call should not fail.
670+
671+
return ret;
672+
}
673+
639674
Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
640675
if (p_argcount < 1) {
641676
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -837,6 +872,9 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
837872
case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST:
838873
return ret;
839874
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: {
875+
} break;
876+
case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: {
877+
return ret;
840878
}
841879
}
842880
}
@@ -881,6 +919,9 @@ Variant Object::call_const(const StringName &p_method, const Variant **p_args, i
881919
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
882920
return ret;
883921
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: {
922+
} break;
923+
case Callable::CallError::CALL_ERROR_SCRIPT_ERROR: {
924+
return ret;
884925
}
885926
}
886927
}
@@ -1730,6 +1771,15 @@ void Object::_bind_methods() {
17301771
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call", &Object::_call_bind, mi);
17311772
}
17321773

1774+
{
1775+
MethodInfo mi;
1776+
mi.name = "call_with_error_test";
1777+
mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "call_error_info", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "CallErrorInfo"));
1778+
mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method"));
1779+
1780+
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_with_error_test", &Object::_call_with_error_test_bind, mi);
1781+
}
1782+
17331783
{
17341784
MethodInfo mi;
17351785
mi.name = "call_deferred";

core/object/object.h

+2
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,8 @@ class Object {
745745
static void get_valid_parents_static(List<String> *p_parents);
746746
static void _get_valid_parents_static(List<String> *p_parents);
747747

748+
Variant _call_with_error_test_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
749+
748750
Variant _call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
749751
Variant _call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
750752

core/register_core_types.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include "core/math/expression.h"
7070
#include "core/math/random_number_generator.h"
7171
#include "core/math/triangle_mesh.h"
72+
#include "core/object/call_error_info.h"
7273
#include "core/object/class_db.h"
7374
#include "core/object/script_language_extension.h"
7475
#include "core/object/undo_redo.h"
@@ -155,6 +156,8 @@ void register_core_types() {
155156

156157
GDREGISTER_CLASS(Object);
157158

159+
GDREGISTER_CLASS(CallErrorInfo);
160+
158161
GDREGISTER_ABSTRACT_CLASS(Script);
159162
GDREGISTER_ABSTRACT_CLASS(ScriptLanguage);
160163

core/variant/callable.h

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Callable {
6161
CALL_ERROR_TOO_FEW_ARGUMENTS, // expected is number of arguments
6262
CALL_ERROR_INSTANCE_IS_NULL,
6363
CALL_ERROR_METHOD_NOT_CONST,
64+
CALL_ERROR_SCRIPT_ERROR,
6465
};
6566
Error error = Error::CALL_OK;
6667
int argument = 0;

core/variant/variant.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -3534,6 +3534,8 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method,
35343534
err_text = "Instance is null";
35353535
} else if (ce.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) {
35363536
err_text = "Method not const in const instance";
3537+
} else if (ce.error == Callable::CallError::CALL_ERROR_SCRIPT_ERROR) {
3538+
err_text = "Script error";
35373539
} else if (ce.error == Callable::CallError::CALL_OK) {
35383540
return "Call OK";
35393541
}

core/variant/variant_call.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "core/debugger/engine_debugger.h"
3535
#include "core/io/compression.h"
3636
#include "core/io/marshalls.h"
37+
#include "core/object/call_error_info.h"
3738
#include "core/object/class_db.h"
3839
#include "core/os/os.h"
3940
#include "core/templates/local_vector.h"
@@ -1057,6 +1058,30 @@ struct _VariantCall {
10571058
callable->callp(p_args, p_argcount, r_ret, r_error);
10581059
}
10591060

1061+
static void func_Callable_call_with_error(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
1062+
Callable *callable = VariantGetInternalPtr<Callable>::get_ptr(v);
1063+
if (p_argcount == 0) {
1064+
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
1065+
r_error.expected = 1;
1066+
return;
1067+
}
1068+
1069+
Ref<CallErrorInfo> err_info = *p_args[0];
1070+
1071+
if (err_info.is_null()) {
1072+
ERR_PRINT("First argument to function must be a CallErrorInfo object.");
1073+
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
1074+
r_error.argument = 0;
1075+
r_error.expected = Variant::OBJECT;
1076+
return;
1077+
}
1078+
1079+
callable->callp(p_args + 1, p_argcount - 1, r_ret, r_error);
1080+
1081+
err_info->set_call_error(CallErrorInfo::CallError(r_error.error), r_error.argument, r_error.expected);
1082+
r_error.error = Callable::CallError::CALL_OK; // This call validates, so the call should not fail.
1083+
}
1084+
10601085
static void func_Callable_call_deferred(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
10611086
Callable *callable = VariantGetInternalPtr<Callable>::get_ptr(v);
10621087
callable->call_deferredp(p_args, p_argcount);
@@ -2171,6 +2196,7 @@ static void _register_variant_builtin_methods_misc() {
21712196

21722197
bind_custom(Callable, call, _VariantCall::func_Callable_call, true, Variant);
21732198
bind_custom(Callable, call_deferred, _VariantCall::func_Callable_call_deferred, false, Variant);
2199+
bind_custom1(Callable, call_with_error, _VariantCall::func_Callable_call_with_error, Variant::OBJECT, "call_error_info");
21742200
bind_custom(Callable, rpc, _VariantCall::func_Callable_rpc, false, Variant);
21752201
bind_custom1(Callable, rpc_id, _VariantCall::func_Callable_rpc_id, Variant::INT, "peer_id");
21762202
bind_custom(Callable, bind, _VariantCall::func_Callable_bind, true, Callable);

doc/classes/CallErrorInfo.xml

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="CallErrorInfo" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
3+
<brief_description>
4+
Information pertaining to error reporting on called functions.
5+
</brief_description>
6+
<description>
7+
Information pertaining to error reporting on called functions. This object is used in conjunction with [method Object.call_with_error_test].
8+
</description>
9+
<tutorials>
10+
</tutorials>
11+
<methods>
12+
<method name="get_call_error">
13+
<return type="int" enum="CallErrorInfo.CallError" />
14+
<description>
15+
Return the error type.
16+
</description>
17+
</method>
18+
<method name="get_expected_arguments">
19+
<return type="int" />
20+
<description>
21+
Return the amount of arguments expected (when the error is too many or too few arguments).
22+
</description>
23+
</method>
24+
<method name="get_invalid_argument_index">
25+
<return type="int" />
26+
<description>
27+
Return the index of the invalid argument (when the error is an invalid argument).
28+
</description>
29+
</method>
30+
<method name="get_invalid_argument_type">
31+
<return type="int" enum="Variant.Type" />
32+
<description>
33+
Return the expected type for the invalid argument (when the error is an invalid argument).
34+
</description>
35+
</method>
36+
<method name="set_call_error">
37+
<return type="void" />
38+
<param index="0" name="error" type="int" enum="CallErrorInfo.CallError" />
39+
<param index="1" name="argument" type="int" />
40+
<param index="2" name="expected" type="int" />
41+
<description>
42+
Setup the error. For invalid arguments [param argument] is the argument index and [param expected] is the expected variant type. For too few or too many arguments, [param argument] is the expected argument count.
43+
</description>
44+
</method>
45+
</methods>
46+
<constants>
47+
<constant name="CALL_OK" value="0" enum="CallError">
48+
Call happened with no errors.
49+
</constant>
50+
<constant name="CALL_ERROR_INVALID_METHOD" value="1" enum="CallError">
51+
The method was invalid (non existing).
52+
</constant>
53+
<constant name="CALL_ERROR_INVALID_ARGUMENT" value="2" enum="CallError">
54+
At least one argument of the call was invalid (the type was not the right one).
55+
</constant>
56+
<constant name="CALL_ERROR_TOO_MANY_ARGUMENTS" value="3" enum="CallError">
57+
More arguments were passed to the call than expected.
58+
</constant>
59+
<constant name="CALL_ERROR_TOO_FEW_ARGUMENTS" value="4" enum="CallError">
60+
Fewer arguments were passed to the call than expected.
61+
</constant>
62+
<constant name="CALL_ERROR_INSTANCE_IS_NULL" value="5" enum="CallError">
63+
A call happened on a null object instance.
64+
</constant>
65+
<constant name="CALL_ERROR_METHOD_NOT_CONST" value="6" enum="CallError">
66+
A constant call happened in a non-const function.
67+
</constant>
68+
<constant name="CALL_ERROR_SCRIPT_ERROR" value="7" enum="CallError">
69+
The function called was provided by a script, which failed during the call.
70+
</constant>
71+
</constants>
72+
</class>

0 commit comments

Comments
 (0)