Skip to content

Commit

Permalink
Merge pull request #79249 from raulsntos/dotnet/dont-ignore-call-error
Browse files Browse the repository at this point in the history
C#: Print error when MethodBind/Callable call fails
  • Loading branch information
akien-mga committed Aug 2, 2023
2 parents 2331eab + 77e5e19 commit ed301a4
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 2 deletions.
17 changes: 16 additions & 1 deletion modules/mono/editor/bindings_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,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 @@ -2501,6 +2506,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 @@ -2566,7 +2576,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.";
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

0 comments on commit ed301a4

Please sign in to comment.