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 87f79c1
Show file tree
Hide file tree
Showing 9 changed files with 209 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, "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";
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
3 changes: 3 additions & 0 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions modules/gdscript/gdscript_vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) + ".";
}
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit 87f79c1

Please sign in to comment.