Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Print error when MethodBind/Callable call fails #79249

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion modules/mono/editor/bindings_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2087,6 +2087,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}

// Collect caller name for MethodBind
if (p_imethod.is_vararg) {
icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
}

// Generate method
{
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
Expand Down Expand Up @@ -2460,6 +2465,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
i++;
}

// Collect caller name for MethodBind
if (p_icall.is_vararg) {
c_func_sig << ", godot_string_name caller";
}

String icall_method = p_icall.name;

// Generate icall function
Expand Down Expand Up @@ -2525,7 +2535,12 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("
<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
<< ", total_length, out _);\n";
<< ", total_length, out godot_variant_call_error vcall_error);\n";

r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"
<< ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
<< ", total_length, vcall_error);\n";

if (!ret_void) {
if (return_type->cname != name_cache.type_Variant) {
Expand Down
3 changes: 2 additions & 1 deletion modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public unsafe Variant Call(params Variant[] args)
}

godot_variant ret = NativeFuncs.godotsharp_callable_call(callable,
(godot_variant**)argsPtr, argc, out _);
(godot_variant**)argsPtr, argc, out godot_variant_call_error vcall_error);
ExceptionUtils.DebugCheckCallError(callable, (godot_variant**)argsPtr, argc, vcall_error);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,109 @@ public static void LogUnhandledException(Exception e)
OnExceptionLoggerException(unexpected, e);
}
}

[Conditional("DEBUG")]
public unsafe static void DebugCheckCallError(godot_string_name method, IntPtr instance, godot_variant** args, int argCount, godot_variant_call_error error)
{
if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
{
using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance);
string where = GetCallErrorWhere(method, &instanceVariant, args, argCount);
string errorText = GetCallErrorMessage(error, where, args);
GD.PushError(errorText);
}
}

[Conditional("DEBUG")]
public unsafe static void DebugCheckCallError(in godot_callable callable, godot_variant** args, int argCount, godot_variant_call_error error)
{
if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
{
using godot_variant callableVariant = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(callable);
string where = $"callable '{VariantUtils.ConvertToString(callableVariant)}'";
string errorText = GetCallErrorMessage(error, where, args);
GD.PushError(errorText);
}
}

private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount)
{
string? methodstr = null;
string basestr = GetVariantTypeName(instance);

if (method == GodotObject.MethodName.Call || (basestr == "Godot.TreeItem" && method == TreeItem.MethodName.CallRecursive))
{
if (argCount >= 1)
{
methodstr = VariantUtils.ConvertToString(*args[0]);
}
}

if (string.IsNullOrEmpty(methodstr))
{
methodstr = StringName.CreateTakingOwnershipOfDisposableValue(method);
}

return $"function '{methodstr}' in base '{basestr}'";
}

private unsafe static string GetCallErrorMessage(godot_variant_call_error error, string where, godot_variant** args)
{
switch (error.Error)
{
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT:
{
int errorarg = error.Argument;
// Handle the Object to Object case separately as we don't have further class details.
#if DEBUG
if (error.Expected == Variant.Type.Object && args[errorarg]->Type == error.Expected)
{
return $"Invalid type in {where}. The Object-derived class of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") is not a subclass of the expected argument class.";
}
else if (error.Expected == Variant.Type.Array && args[errorarg]->Type == error.Expected)
{
return $"Invalid type in {where}. The array of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") does not have the same element type as the expected typed array argument.";
}
else
#endif
{
return $"Invalid type in {where}. Cannot convert argument {errorarg + 1} from {args[errorarg]->Type} to {error.Expected}.";
}
}
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS:
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS:
return $"Invalid call to {where}. Expected {error.Argument} arguments.";
RedworkDE marked this conversation as resolved.
Show resolved Hide resolved
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD:
return $"Invalid call. Nonexistent {where}.";
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL:
return $"Attempt to call {where} on a null instance.";
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST:
return $"Attempt to call {where} on a const instance.";
default:
return $"Bug, call error: #{error.Error}";
}
}

private unsafe static string GetVariantTypeName(godot_variant* variant)
{
if (variant->Type == Variant.Type.Object)
{
GodotObject obj = VariantUtils.ConvertToGodotObject(*variant);
if (obj == null)
{
return "null instance";
}
else if (!GodotObject.IsInstanceValid(obj))
{
return "previously freed";
}
else
{
return obj.GetType().ToString();
}
}

return variant->Type.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public enum godot_variant_call_error_error
GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL,
GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST,
}

[StructLayout(LayoutKind.Sequential)]
Expand Down
Loading