Skip to content

Commit

Permalink
Ability to call with error test.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
reduz committed Sep 3, 2024
1 parent 8120e03 commit d104c4f
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 0 deletions.
74 changes: 74 additions & 0 deletions core/object/call_error_info.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
70 changes: 70 additions & 0 deletions core/object/call_error_info.h
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions core/object/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -598,6 +599,40 @@ void Object::get_method_list(List<MethodInfo> *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<CallErrorInfo> 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;
Expand Down Expand Up @@ -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;
}
}
}
Expand Down Expand Up @@ -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;
}
}
}
Expand Down Expand Up @@ -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, "CallErrorInfo"));
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";
Expand Down
2 changes: 2 additions & 0 deletions core/object/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,8 @@ class Object {
static void get_valid_parents_static(List<String> *p_parents);
static void _get_valid_parents_static(List<String> *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);

Expand Down
3 changes: 3 additions & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -157,6 +158,8 @@ void register_core_types() {

GDREGISTER_CLASS(Object);

GDREGISTER_CLASS(CallErrorInfo);

GDREGISTER_ABSTRACT_CLASS(Script);
GDREGISTER_ABSTRACT_CLASS(ScriptLanguage);

Expand Down
1 change: 1 addition & 0 deletions core/variant/callable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions core/variant/variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
26 changes: 26 additions & 0 deletions core/variant/variant_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "core/debugger/engine_debugger.h"
#include "core/io/compression.h"
#include "core/io/marshalls.h"
#include "core/object/call_error_info.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "core/templates/local_vector.h"
Expand Down Expand Up @@ -1023,6 +1024,30 @@ struct _VariantCall {
callable->callp(p_args, p_argcount, r_ret, r_error);
}

static void func_Callable_call_with_error(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
Callable *callable = VariantGetInternalPtr<Callable>::get_ptr(v);
if (p_argcount == 0) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.expected = 1;
return;
}

Ref<CallErrorInfo> 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;
}

callable->callp(p_args + 1, p_argcount - 1, r_ret, 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.
}

static void func_Callable_call_deferred(Variant *v, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
Callable *callable = VariantGetInternalPtr<Callable>::get_ptr(v);
callable->call_deferredp(p_args, p_argcount);
Expand Down Expand Up @@ -2106,6 +2131,7 @@ static void _register_variant_builtin_methods_misc() {

bind_custom(Callable, call, _VariantCall::func_Callable_call, true, Variant);
bind_custom(Callable, call_deferred, _VariantCall::func_Callable_call_deferred, false, Variant);
bind_custom1(Callable, call_with_error, _VariantCall::func_Callable_call_with_error, Variant::OBJECT, "call_error_info");
bind_custom(Callable, rpc, _VariantCall::func_Callable_rpc, false, Variant);
bind_custom1(Callable, rpc_id, _VariantCall::func_Callable_rpc_id, Variant::INT, "peer_id");
bind_custom(Callable, bind, _VariantCall::func_Callable_bind, true, Callable);
Expand Down
72 changes: 72 additions & 0 deletions doc/classes/CallErrorInfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CallErrorInfo" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Information pertaining to error reporting on called functions.
</brief_description>
<description>
Information pertaining to error reporting on called functions. This object is used in conjunction with [method Object.call_with_error_test].
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_call_error">
<return type="int" enum="CallErrorInfo.CallError" />
<description>
Return the error type.
</description>
</method>
<method name="get_expected_arguments">
<return type="int" />
<description>
Return the amount of arguments expected (when the error is too many or too few arguments).
</description>
</method>
<method name="get_invalid_argument_index">
<return type="int" />
<description>
Return the index of the invalid argument (when the error is an invalid argument).
</description>
</method>
<method name="get_invalid_argument_type">
<return type="int" enum="Variant.Type" />
<description>
Return the expected type for the invalid argument (when the error is an invalid argument).
</description>
</method>
<method name="set_call_error">
<return type="void" />
<param index="0" name="error" type="int" enum="CallErrorInfo.CallError" />
<param index="1" name="argument" type="int" />
<param index="2" name="expected" type="int" />
<description>
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.
</description>
</method>
</methods>
<constants>
<constant name="CALL_OK" value="0" enum="CallError">
Call happened with no errors.
</constant>
<constant name="CALL_ERROR_INVALID_METHOD" value="1" enum="CallError">
The method was invalid (non existing).
</constant>
<constant name="CALL_ERROR_INVALID_ARGUMENT" value="2" enum="CallError">
At least one argument of the call was invalid (the type was not the right one).
</constant>
<constant name="CALL_ERROR_TOO_MANY_ARGUMENTS" value="3" enum="CallError">
More arguments were passed to the call than expected.
</constant>
<constant name="CALL_ERROR_TOO_FEW_ARGUMENTS" value="4" enum="CallError">
Fewer arguments were passed to the call than expected.
</constant>
<constant name="CALL_ERROR_INSTANCE_IS_NULL" value="5" enum="CallError">
A call happened on a null object instance.
</constant>
<constant name="CALL_ERROR_METHOD_NOT_CONST" value="6" enum="CallError">
A constant call happened in a non-const function.
</constant>
<constant name="CALL_ERROR_SCRIPT_ERROR" value="7" enum="CallError">
The function called was provided by a script, which failed during the call.
</constant>
</constants>
</class>
Loading

0 comments on commit d104c4f

Please sign in to comment.