diff options
Diffstat (limited to 'modules/mono/editor/bindings_generator.cpp')
-rw-r--r-- | modules/mono/editor/bindings_generator.cpp | 402 |
1 files changed, 300 insertions, 102 deletions
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index cff41a57f3..ccfcf2a87c 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -82,6 +82,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define CS_STATIC_METHOD_GETINSTANCE "GetPtr" #define CS_METHOD_CALL "Call" #define CS_PROPERTY_SINGLETON "Singleton" +#define CS_SINGLETON_INSTANCE_SUFFIX "Instance" #define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod" #define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod" #define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal" @@ -93,6 +94,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define ICALL_PREFIX "godot_icall_" #define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility" #define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" @@ -116,7 +118,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { // Types that will be ignored by the generator and won't be available in C#. // This must be kept in sync with `ignored_types` in csharp_script.cpp -const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" }; +const Vector<String> ignored_types = {}; void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) { // C interface for enums is the same as that of 'uint32_t'. Remember to apply @@ -147,7 +149,7 @@ static String fix_doc_description(const String &p_bbcode) { .strip_edges(); } -String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) { +String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) { // Based on the version in EditorHelp if (p_bbcode.is_empty()) { @@ -304,11 +306,11 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf _append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts); } else if (link_tag == "constant") { _append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "param") { + _append_xml_param(xml_output, link_target, p_is_signal); } else if (link_tag == "theme_item") { // We do not declare theme_items in any way in C#, so there is nothing to reference _append_xml_undeclared(xml_output, link_target); - } else if (link_tag == "param") { - _append_xml_undeclared(xml_output, snake_to_camel_case(link_target, false)); } pos = brk_end + 1; @@ -366,9 +368,19 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } if (target_itype) { - xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); - xml_output.append(target_itype->proxy_name); - xml_output.append("\"/>"); + if ((!p_itype || p_itype->api_type == ClassDB::API_CORE) && target_itype->api_type == ClassDB::API_EDITOR) { + // Editor references in core documentation cannot be resolved, + // handle as standard codeblock. + _log("Cannot reference editor type '%s' in documentation for core type '%s'\n", + target_itype->proxy_name.utf8().get_data(), p_itype ? p_itype->proxy_name.utf8().get_data() : "@GlobalScope"); + xml_output.append("<c>"); + xml_output.append(target_itype->proxy_name); + xml_output.append("</c>"); + } else { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("\"/>"); + } } else { ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'."); @@ -522,7 +534,31 @@ void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const Ty p_xml_output.append(p_target_itype->proxy_name); p_xml_output.append("."); p_xml_output.append(target_imethod->proxy_name); - p_xml_output.append("\"/>"); + p_xml_output.append("("); + bool first_key = true; + for (const ArgumentInterface &iarg : target_imethod->arguments) { + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + + if (first_key) { + first_key = false; + } else { + p_xml_output.append(", "); + } + if (!arg_type) { + ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'."); + p_xml_output.append(iarg.type.cname); + continue; + } + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + p_xml_output.append("Nullable{"); + } + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + p_xml_output.append(arg_cs_type.replacen("<", "{").replacen(">", "}").replacen("params ", "")); + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + p_xml_output.append("}"); + } + } + p_xml_output.append(")\"/>"); } else { if (!p_target_itype->is_intentionally_ignored(p_link_target)) { ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); @@ -653,6 +689,11 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const _append_xml_undeclared(p_xml_output, p_link_target); } else { // Try to find the constant in the current class + if (p_target_itype->is_singleton_instance) { + // Constants and enums are declared in the static singleton class. + p_target_itype = &obj_types[p_target_itype->cname]; + } + const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants); if (target_iconst) { @@ -678,7 +719,7 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); p_xml_output.append(p_target_itype->proxy_name); p_xml_output.append("."); - p_xml_output.append(target_ienum->cname); + p_xml_output.append(target_ienum->proxy_name); p_xml_output.append("."); p_xml_output.append(target_iconst->proxy_name); p_xml_output.append("\"/>"); @@ -719,7 +760,7 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm if (target_iconst) { p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); - p_xml_output.append(target_ienum->cname); + p_xml_output.append(target_ienum->proxy_name); p_xml_output.append("."); p_xml_output.append(target_iconst->proxy_name); p_xml_output.append("\"/>"); @@ -730,6 +771,21 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm } } +void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) { + const String link_target = snake_to_camel_case(p_link_target); + + if (!p_is_signal) { + p_xml_output.append("<paramref name=\""); + p_xml_output.append(link_target); + p_xml_output.append("\"/>"); + } else { + // Documentation in C# is added to an event, not the delegate itself; + // as such, we treat these parameters as codeblocks instead. + // See: https://github.com/godotengine/godot/pull/65529 + _append_xml_undeclared(p_xml_output, link_target); + } +} + void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) { p_xml_output.append("<c>"); p_xml_output.append(p_link_target); @@ -934,9 +990,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); - p_output.append("\n#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n"); - p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); for (const ConstantInterface &iconstant : global_constants) { @@ -975,7 +1028,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { for (const EnumInterface &ienum : global_enums) { CRASH_COND(ienum.constants.is_empty()); - String enum_proxy_name = ienum.cname.operator String(); + String enum_proxy_name = ienum.proxy_name; bool enum_in_static_class = false; @@ -989,7 +1042,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { _log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); p_output.append("\npublic partial struct "); - p_output.append(pascal_to_pascal_case(enum_class_name)); + p_output.append(enum_class_name); p_output.append("\n" OPEN_BLOCK); } @@ -998,7 +1051,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { } p_output.append("\npublic enum "); - p_output.append(pascal_to_pascal_case(enum_proxy_name)); + p_output.append(enum_proxy_name); p_output.append(" : long"); p_output.append("\n" OPEN_BLOCK); @@ -1034,8 +1087,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append(CLOSE_BLOCK); } } - - p_output.append("\n#pragma warning restore CS1591\n"); } Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { @@ -1348,15 +1399,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); output.append("using System;\n"); // IntPtr + output.append("using System.ComponentModel;\n"); // EditorBrowsable output.append("using System.Diagnostics;\n"); // DebuggerBrowsable output.append("using Godot.NativeInterop;\n"); - output.append("\n" - "#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n" - "#pragma warning disable CS1573 // Disable warning: " - "'Parameter has no matching param tag in the XML comment'\n"); - output.append("\n#nullable disable\n"); const DocData::ClassDoc *class_doc = itype.class_doc; @@ -1403,8 +1449,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (is_derived_type && !itype.is_singleton) { if (obj_types.has(itype.base_name)) { + TypeInterface base_type = obj_types[itype.base_name]; output.append(" : "); - output.append(obj_types[itype.base_name].proxy_name); + output.append(base_type.proxy_name); + if (base_type.is_singleton) { + // If the type is a singleton, use the instance type. + output.append(CS_SINGLETON_INSTANCE_SUFFIX); + } } else { ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'."); return ERR_INVALID_DATA; @@ -1458,7 +1509,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output.append(MEMBER_BEGIN "public enum "); - output.append(pascal_to_pascal_case(ienum.cname.operator String())); + output.append(ienum.proxy_name); output.append(" : long"); output.append(MEMBER_BEGIN OPEN_BLOCK); @@ -1504,37 +1555,44 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "' for class '" + itype.name + "'."); } - if (itype.is_singleton) { - // Add the type name and the singleton pointer as static fields + // Add native name static field and cached type. + + if (is_derived_type && !itype.is_singleton) { + output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n"; + } - output.append(MEMBER_BEGIN "private static GodotObject singleton;\n"); + output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(itype.name); + output.append("\";\n"); - output << MEMBER_BEGIN "public static GodotObject " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n" - << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n" - << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(\"" - << itype.name - << "\");\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n"; + if (itype.is_singleton || itype.is_compat_singleton) { + // Add the Singleton static property. - output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.append(itype.name); - output.append("\";\n"); - } else { + String instance_type_name; + + if (itype.is_singleton) { + StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX; + instance_type_name = obj_types.has(instance_name) + ? obj_types[instance_name].proxy_name + : "GodotObject"; + } else { + instance_type_name = itype.proxy_name; + } + + output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n"); + + output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n" + << INDENT2 "singleton \?\?= (" + instance_type_name + ")" + << C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n"; + } + + if (!itype.is_singleton) { // IMPORTANT: We also generate the static fields for GodotObject instead of declaring // them manually in the `GodotObject.base.cs` partial class declaration, because they're // required by other static fields in this generated partial class declaration. // Static fields are initialized in order of declaration, but when they're in different // partial class declarations then it becomes harder to tell (Rider warns about this). - // Add native name static field - - if (is_derived_type) { - output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n"; - } - - output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.append(itype.name); - output.append("\";\n"); - if (itype.is_instantiable) { // Add native constructor static field @@ -1749,7 +1807,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str << INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n" << INDENT1 "/// Do not call or override this method.\n" << INDENT1 "/// </summary>\n" - << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n"; + << INDENT1 "/// <param name=\"signal\">Name of the signal to check for.</param>\n"; output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n" @@ -1809,7 +1867,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output << "\n" << INDENT1 "{\n"; + HashMap<String, StringName> method_names; for (const MethodInterface &imethod : itype.methods) { + if (method_names.has(imethod.proxy_name)) { + ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value."); + continue; + } + method_names[imethod.proxy_name] = imethod.cname; output << INDENT2 "/// <summary>\n" << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" << INDENT2 "/// </summary>\n" @@ -1837,10 +1901,6 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(CLOSE_BLOCK /* class */); - output.append("\n" - "#pragma warning restore CS1591\n" - "#pragma warning restore CS1573\n"); - return _save_file(p_output_file, output); } @@ -1894,7 +1954,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; - const TypeInterface *prop_itype = _get_type_or_null(proptype_name); + const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name); ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG, @@ -1983,7 +2043,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { - const TypeInterface *return_type = _get_type_or_null(p_imethod.return_type); + const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type); ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, @@ -2004,12 +2064,17 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String icall_params = method_bind_field; if (!p_imethod.is_static) { + String self_reference = "this"; + if (p_itype.is_singleton) { + self_reference = CS_PROPERTY_SINGLETON; + } + if (p_itype.cs_in.size()) { - cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, "this", + cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference, String(), String(), String(), INDENT2); } - icall_params += ", " + sformat(p_itype.cs_in_expr, "this"); + icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference); } StringBuilder default_args_doc; @@ -2017,7 +2082,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_imethod.arguments.front()->get(); for (const ArgumentInterface &iarg : p_imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_null(iarg.type); + const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, @@ -2057,7 +2122,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf arguments_sig += iarg.name; - if (iarg.default_argument.size()) { + if (!p_imethod.is_compat && iarg.default_argument.size()) { if (iarg.def_param_mode != ArgumentInterface::CONSTANT) { arguments_sig += " = null"; } else { @@ -2128,6 +2193,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) { @@ -2140,8 +2210,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output << "GodotObject."; } - p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." - << p_imethod.proxy_name + p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." + << p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul" << ");\n"; } @@ -2180,6 +2250,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } + if (p_imethod.is_compat) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN); p_output.append(p_imethod.is_internal ? "internal " : "public "); @@ -2270,7 +2344,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_isignal.arguments.front()->get(); for (const ArgumentInterface &iarg : p_isignal.arguments) { - const TypeInterface *arg_type = _get_type_or_null(iarg.type); + const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, @@ -2302,31 +2376,31 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Generate signal { - p_output.append(MEMBER_BEGIN "/// <summary>\n"); - p_output.append(INDENT1 "/// "); - p_output.append("Represents the method that handles the "); - p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>"); - p_output.append(" event of a "); - p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>"); - p_output.append(" class.\n"); - p_output.append(INDENT1 "/// </summary>"); - - if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); - } - - p_output.append(MEMBER_BEGIN "[Obsolete(\""); - p_output.append(p_isignal.deprecation_message); - p_output.append("\")]"); - } - bool is_parameterless = p_isignal.arguments.size() == 0; // Delegate name is [SignalName]EventHandler String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler"; if (!is_parameterless) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); + p_output.append(INDENT1 "/// "); + p_output.append("Represents the method that handles the "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>"); + p_output.append(" event of a "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>"); + p_output.append(" class.\n"); + p_output.append(INDENT1 "/// </summary>"); + + if (p_isignal.is_deprecated) { + if (p_isignal.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); + } + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_isignal.deprecation_message); + p_output.append("\")]"); + } + // Generate delegate p_output.append(MEMBER_BEGIN "public delegate void "); p_output.append(delegate_name); @@ -2362,7 +2436,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf } if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); + String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -2501,6 +2575,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 +2645,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) { @@ -2687,6 +2771,20 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con return nullptr; } +const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) { + const TypeInterface *itype = _get_type_or_null(p_typeref); + if (itype == nullptr) { + return nullptr; + } + + if (itype->is_singleton) { + StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX; + itype = &obj_types.find(instance_type_name)->value; + } + + return itype; +} + const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) { if (p_generic_type_parameters.is_empty()) { return ""; @@ -2700,8 +2798,8 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface int i = 0; String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { - const TypeInterface *param_itype = _get_type_or_null(param_type); - ERR_FAIL_NULL_V(param_itype, ""); + const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type); + ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -2872,6 +2970,12 @@ bool method_has_ptr_parameter(MethodInfo p_method_info) { return false; } +struct SortMethodWithHashes { + _FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const { + return p_a.first < p_b.first; + } +}; + bool BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -2917,17 +3021,18 @@ bool BindingsGenerator::_populate_object_type_interfaces() { itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted); itype.memory_own = itype.is_ref_counted; + if (itype.is_singleton && compat_singletons.has(itype.cname)) { + itype.is_singleton = false; + itype.is_compat_singleton = true; + } + itype.c_out = "%5return "; itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED; itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n"; itype.cs_type = itype.proxy_name; - if (itype.is_singleton) { - itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(" CS_PROPERTY_SINGLETON ")"; - } else { - itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)"; - } + itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)"; itype.cs_out = "%5return (%2)%0(%1);"; @@ -2998,11 +3103,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { List<MethodInfo> virtual_method_list; ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true); - List<MethodInfo> method_list; - ClassDB::get_method_list(type_cname, &method_list, true); - method_list.sort(); + List<Pair<MethodInfo, uint32_t>> method_list_with_hashes; + ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true); + method_list_with_hashes.sort_custom_inplace<SortMethodWithHashes>(); + + List<MethodInterface> compat_methods; + for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) { + const MethodInfo &method_info = E.first; + const uint32_t hash = E.second; - for (const MethodInfo &method_info : method_list) { int argc = method_info.arguments.size(); if (method_info.name.is_empty()) { @@ -3024,6 +3133,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { MethodInterface imethod; imethod.name = method_info.name; imethod.cname = cname; + imethod.hash = hash; if (method_info.flags & METHOD_FLAG_STATIC) { imethod.is_static = true; @@ -3036,7 +3146,17 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = nullptr; + + if (!imethod.is_virtual) { + bool method_exists = false; + m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat); + + if (unlikely(!method_exists)) { + ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false, + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + } + } imethod.is_vararg = m && m->is_vararg(); @@ -3157,6 +3277,14 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); + // Compat methods aren't added to the type yet, they need to be checked for conflicts + // after all the non-compat methods have been added. The compat methods are added in + // reverse so the most recently added ones take precedence over older compat methods. + if (imethod.is_compat) { + compat_methods.push_front(imethod); + continue; + } + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { @@ -3171,6 +3299,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + // Add compat methods that don't conflict with other methods in the type. + for (const MethodInterface &imethod : compat_methods) { + if (_method_has_conflicting_signature(imethod, itype)) { + WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored."); + continue; + } + itype.methods.push_back(imethod); + } + // Populate signals const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; @@ -3263,8 +3400,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { enum_proxy_name += "Enum"; enum_proxy_cname = StringName(enum_proxy_name); } - EnumInterface ienum(enum_proxy_cname); - ienum.is_flags = E.value.is_bitfield; + EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield); const List<StringName> &enum_constants = E.value.constants; for (const StringName &constant_cname : enum_constants) { String constant_name = constant_cname.operator String(); @@ -3331,6 +3467,19 @@ bool BindingsGenerator::_populate_object_type_interfaces() { obj_types.insert(itype.cname, itype); + if (itype.is_singleton) { + // Add singleton instance type. + itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX; + itype.is_singleton = false; + itype.is_singleton_instance = true; + + // Remove constants and enums, those will remain in the static class. + itype.constants.clear(); + itype.enums.clear(); + + obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype); + } + class_list.pop_front(); } @@ -3806,7 +3955,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { builtin_types.insert(itype.cname, itype); // Array_@generic - // Re-use Array's itype + // Reuse Array's itype itype.name = "Array_@generic"; itype.cname = itype.name; itype.cs_out = "%5return new %2(%0(%1));"; @@ -3833,7 +3982,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { builtin_types.insert(itype.cname, itype); // Dictionary_@generic - // Re-use Dictionary's itype + // Reuse Dictionary's itype itype.name = "Dictionary_@generic"; itype.cname = itype.name; itype.cs_out = "%5return new %2(%0(%1));"; @@ -3884,8 +4033,7 @@ void BindingsGenerator::_populate_global_constants() { iconstant.const_doc = const_doc; if (enum_name != StringName()) { - EnumInterface ienum(enum_name); - ienum.is_flags = CoreConstants::is_global_constant_bitfield(i); + EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i)); List<EnumInterface>::Element *enum_match = global_enums.find(ienum); if (enum_match) { enum_match->get().constants.push_back(iconstant); @@ -3903,7 +4051,7 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.is_enum = true; enum_itype.name = ienum.cname.operator String(); enum_itype.cname = ienum.cname; - enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name); + enum_itype.proxy_name = ienum.proxy_name; TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); @@ -3941,12 +4089,60 @@ void BindingsGenerator::_populate_global_constants() { } } +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) { + // Compare p_imethod with all the methods already registered in p_itype. + for (const MethodInterface &method : p_itype.methods) { + if (method.proxy_name == p_imethod.proxy_name) { + if (_method_has_conflicting_signature(p_imethod, method)) { + return true; + } + } + } + + return false; +} + +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) { + // Check if a method already exists in p_itype with a method signature that would conflict with p_imethod. + // The return type is ignored because only changing the return type is not enough to avoid conflicts. + // The const keyword is also ignored since it doesn't generate different C# code. + + if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) { + // Different argument count, so no conflict. + return false; + } + + for (int i = 0; i < p_imethod_left.arguments.size(); i++) { + const ArgumentInterface &iarg_left = p_imethod_left.arguments[i]; + const ArgumentInterface &iarg_right = p_imethod_right.arguments[i]; + + if (iarg_left.type.cname != iarg_right.type.cname) { + // Different types for arguments in the same position, so no conflict. + return false; + } + + if (iarg_left.def_param_mode != iarg_right.def_param_mode) { + // If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T' + // and will not create a conflict. + if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + return false; + } + } + } + + return true; +} + void BindingsGenerator::_initialize_blacklisted_methods() { blacklisted_methods["Object"].push_back("to_string"); // there is already ToString blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it) } +void BindingsGenerator::_initialize_compat_singletons() { + compat_singletons.insert("EditorInterface"); +} + void BindingsGenerator::_log(const char *p_format, ...) { if (log_print_enabled) { va_list list; @@ -3966,6 +4162,8 @@ void BindingsGenerator::_initialize() { _initialize_blacklisted_methods(); + _initialize_compat_singletons(); + bool obj_type_ok = _populate_object_type_interfaces(); ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces"); |