Browse Source

Merge pull request #79249 from raulsntos/dotnet/dont-ignore-call-error

C#: Print error when MethodBind/Callable call fails
Rémi Verschelde 2 years ago
parent
commit
ed301a4078

+ 16 - 1
modules/mono/editor/bindings_generator.cpp

@@ -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) {
@@ -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
@@ -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) {

+ 2 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs

@@ -109,7 +109,8 @@ namespace Godot
                 }
 
                 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);
             }
         }

+ 104 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs

@@ -135,5 +135,109 @@ namespace Godot.NativeInterop
                 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();
+        }
     }
 }

+ 1 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs

@@ -71,6 +71,7 @@ namespace Godot.NativeInterop
         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)]