Path: blob/master/modules/mono/editor/bindings_generator.cpp
10278 views
/**************************************************************************/1/* bindings_generator.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "bindings_generator.h"3132#ifdef DEBUG_ENABLED3334#include "../godotsharp_defs.h"35#include "../utils/naming_utils.h"36#include "../utils/path_utils.h"37#include "../utils/string_utils.h"3839#include "core/config/engine.h"40#include "core/core_constants.h"41#include "core/io/compression.h"42#include "core/io/dir_access.h"43#include "core/io/file_access.h"44#include "core/os/os.h"45#include "main/main.h"4647StringBuilder &operator<<(StringBuilder &r_sb, const String &p_string) {48r_sb.append(p_string);49return r_sb;50}5152StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {53r_sb.append(p_cstring);54return r_sb;55}5657#define CS_INDENT " " // 4 whitespaces5859#define INDENT1 CS_INDENT60#define INDENT2 INDENT1 INDENT161#define INDENT3 INDENT2 INDENT162#define INDENT4 INDENT3 INDENT16364#define MEMBER_BEGIN "\n" INDENT16566#define OPEN_BLOCK "{\n"67#define CLOSE_BLOCK "}\n"6869#define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK70#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK71#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK72#define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK73#define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK74#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK7576#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"77#define BINDINGS_NATIVE_NAME_FIELD "NativeName"7879#define BINDINGS_CLASS_CONSTRUCTOR "Constructors"80#define BINDINGS_CLASS_CONSTRUCTOR_EDITOR "EditorConstructors"81#define BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY "BuiltInMethodConstructors"8283#define CS_PARAM_MEMORYOWN "memoryOwn"84#define CS_PARAM_METHODBIND "method"85#define CS_PARAM_INSTANCE "ptr"86#define CS_STATIC_METHOD_GETINSTANCE "GetPtr"87#define CS_METHOD_CALL "Call"88#define CS_PROPERTY_SINGLETON "Singleton"89#define CS_SINGLETON_INSTANCE_SUFFIX "Instance"90#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"91#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"92#define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal"9394#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor"95#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind"96#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_"97#define CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX "SignalProxyName_"9899#define ICALL_PREFIX "godot_icall_"100#define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method"101#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility"102#define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor"103104#define C_LOCAL_RET "ret"105#define C_LOCAL_VARARG_RET "vararg_ret"106#define C_LOCAL_PTRCALL_ARGS "call_args"107108#define C_CLASS_NATIVE_FUNCS "NativeFuncs"109#define C_NS_MONOUTILS "InteropUtils"110#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS ".UnmanagedGetManaged"111#define C_METHOD_ENGINE_GET_SINGLETON C_NS_MONOUTILS ".EngineGetSingleton"112113#define C_NS_MONOMARSHAL "Marshaling"114#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL ".ConvertStringToNative"115#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL ".ConvertStringToManaged"116#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL ".ConvertSystemArrayToNative" #m_type117#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL ".ConvertNative" #m_type "ToSystemArray"118#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToNative"119#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToManaged"120#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToNative"121#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToManaged"122123// Types that will be ignored by the generator and won't be available in C#.124// This must be kept in sync with `ignored_types` in csharp_script.cpp125const Vector<String> ignored_types = {};126127// Special [code] keywords to wrap with <see langword="code"/> instead of <c>code</c>.128// Don't check against all C# reserved words, as many cases are GDScript-specific.129const Vector<String> langword_check = { "true", "false", "null" };130131// The following properties currently need to be defined with `new` to avoid warnings. We treat132// them as a special case instead of silencing the warnings altogether, to be warned if more133// shadowing appears.134const Vector<String> prop_allowed_inherited_member_hiding = {135"ArrayMesh.BlendShapeMode",136"Button.TextDirection",137"Label.TextDirection",138"LineEdit.TextDirection",139"LinkButton.TextDirection",140"MenuBar.TextDirection",141"RichTextLabel.TextDirection",142"TextEdit.TextDirection",143"FoldableContainer.TextDirection",144"VisualShaderNodeReroute.PortType",145// The following instances are uniquely egregious violations, hiding `GetType()` from `object`.146// Included for the sake of CI, with the understanding that they *deserve* warnings.147"GltfAccessor.GetType",148"GltfAccessor.MethodName.GetType",149};150151void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) {152// C interface for enums is the same as that of 'uint32_t'. Remember to apply153// any of the changes done here to the 'uint32_t' type interface as well.154155r_enum_itype.cs_type = r_enum_itype.proxy_name;156r_enum_itype.cs_in_expr = "(int)%0";157r_enum_itype.cs_out = "%5return (%2)%0(%1);";158159{160// The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'.161r_enum_itype.c_in = "%5%0 %1_in = %1;\n";162r_enum_itype.c_out = "%5return (%0)(%1);\n";163r_enum_itype.c_type = "long";164r_enum_itype.c_arg_in = "&%s_in";165}166r_enum_itype.c_type_in = "int";167r_enum_itype.c_type_out = r_enum_itype.c_type_in;168r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name];169}170171static String fix_doc_description(const String &p_bbcode) {172// This seems to be the correct way to do this. It's the same EditorHelp does.173174return p_bbcode.dedent()175.remove_chars("\r")176.strip_edges();177}178179String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInterface *p_itype) {180// Based on the version in EditorHelp.181182if (p_bbcode.is_empty()) {183return String();184}185186DocTools *doc = EditorHelp::get_doc_data();187188String bbcode = p_bbcode;189190StringBuilder output;191192List<String> tag_stack;193bool code_tag = false;194195int pos = 0;196while (pos < bbcode.length()) {197int brk_pos = bbcode.find_char('[', pos);198199if (brk_pos < 0) {200brk_pos = bbcode.length();201}202203if (brk_pos > pos) {204String text = bbcode.substr(pos, brk_pos - pos);205if (code_tag || tag_stack.size() > 0) {206output.append("'" + text + "'");207} else {208output.append(text);209}210}211212if (brk_pos == bbcode.length()) {213// Nothing else to add.214break;215}216217int brk_end = bbcode.find_char(']', brk_pos + 1);218219if (brk_end == -1) {220String text = bbcode.substr(brk_pos);221if (code_tag || tag_stack.size() > 0) {222output.append("'" + text + "'");223}224225break;226}227228String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);229230if (tag.begins_with("/")) {231bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);232233if (!tag_ok) {234output.append("]");235pos = brk_pos + 1;236continue;237}238239tag_stack.pop_front();240pos = brk_end + 1;241code_tag = false;242} else if (code_tag) {243output.append("[");244pos = brk_pos + 1;245} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {246const int tag_end = tag.find_char(' ');247const String link_tag = tag.substr(0, tag_end);248const String link_target = tag.substr(tag_end + 1).lstrip(" ");249250const Vector<String> link_target_parts = link_target.split(".");251252if (link_target_parts.is_empty() || link_target_parts.size() > 2) {253ERR_PRINT("Invalid reference format: '" + tag + "'.");254255output.append(tag);256257pos = brk_end + 1;258continue;259}260261const TypeInterface *target_itype;262StringName target_cname;263264if (link_target_parts.size() == 2) {265target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));266if (!target_itype) {267target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));268}269target_cname = link_target_parts[1];270} else {271target_itype = p_itype;272target_cname = link_target_parts[0];273}274275if (link_tag == "method") {276_append_text_method(output, target_itype, target_cname, link_target, link_target_parts);277} else if (link_tag == "constructor") {278// TODO: Support constructors?279_append_text_undeclared(output, link_target);280} else if (link_tag == "operator") {281// TODO: Support operators?282_append_text_undeclared(output, link_target);283} else if (link_tag == "member") {284_append_text_member(output, target_itype, target_cname, link_target, link_target_parts);285} else if (link_tag == "signal") {286_append_text_signal(output, target_itype, target_cname, link_target, link_target_parts);287} else if (link_tag == "enum") {288_append_text_enum(output, target_itype, target_cname, link_target, link_target_parts);289} else if (link_tag == "constant") {290_append_text_constant(output, target_itype, target_cname, link_target, link_target_parts);291} else if (link_tag == "param") {292_append_text_param(output, link_target);293} else if (link_tag == "theme_item") {294// We do not declare theme_items in any way in C#, so there is nothing to reference.295_append_text_undeclared(output, link_target);296}297298pos = brk_end + 1;299} else if (doc->class_list.has(tag)) {300if (tag == "Array" || tag == "Dictionary") {301output.append("'" BINDINGS_NAMESPACE_COLLECTIONS ".");302output.append(tag);303output.append("'");304} else if (tag == "bool" || tag == "int") {305output.append(tag);306} else if (tag == "float") {307output.append(308#ifdef REAL_T_IS_DOUBLE309"double"310#else311"float"312#endif313);314} else if (tag == "Variant") {315output.append("'Godot.Variant'");316} else if (tag == "String") {317output.append("string");318} else if (tag == "Nil") {319output.append("null");320} else if (tag.begins_with("@")) {321// @GlobalScope, @GDScript, etc.322output.append("'" + tag + "'");323} else if (tag == "PackedByteArray") {324output.append("byte[]");325} else if (tag == "PackedInt32Array") {326output.append("int[]");327} else if (tag == "PackedInt64Array") {328output.append("long[]");329} else if (tag == "PackedFloat32Array") {330output.append("float[]");331} else if (tag == "PackedFloat64Array") {332output.append("double[]");333} else if (tag == "PackedStringArray") {334output.append("string[]");335} else if (tag == "PackedVector2Array") {336output.append("'" BINDINGS_NAMESPACE ".Vector2[]'");337} else if (tag == "PackedVector3Array") {338output.append("'" BINDINGS_NAMESPACE ".Vector3[]'");339} else if (tag == "PackedColorArray") {340output.append("'" BINDINGS_NAMESPACE ".Color[]'");341} else if (tag == "PackedVector4Array") {342output.append("'" BINDINGS_NAMESPACE ".Vector4[]'");343} else {344const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));345346if (!target_itype) {347target_itype = _get_type_or_null(TypeReference("_" + tag));348}349350if (target_itype) {351output.append("'" + target_itype->proxy_name + "'");352} else {353ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");354output.append("'" + tag + "'");355}356}357358pos = brk_end + 1;359} else if (tag == "b") {360// Bold is not supported.361pos = brk_end + 1;362tag_stack.push_front(tag);363} else if (tag == "i") {364// Italic is not supported.365pos = brk_end + 1;366tag_stack.push_front(tag);367} else if (tag == "code" || tag.begins_with("code ")) {368code_tag = true;369pos = brk_end + 1;370tag_stack.push_front("code");371} else if (tag == "kbd") {372// Keyboard combinations are not supported.373pos = brk_end + 1;374tag_stack.push_front(tag);375} else if (tag == "center") {376// Center alignment is not supported.377pos = brk_end + 1;378tag_stack.push_front(tag);379} else if (tag == "br") {380// Break is not supported.381pos = brk_end + 1;382tag_stack.push_front(tag);383} else if (tag == "u") {384// Underline is not supported.385pos = brk_end + 1;386tag_stack.push_front(tag);387} else if (tag == "s") {388// Strikethrough is not supported.389pos = brk_end + 1;390tag_stack.push_front(tag);391} else if (tag == "url") {392int end = bbcode.find_char('[', brk_end);393if (end == -1) {394end = bbcode.length();395}396String url = bbcode.substr(brk_end + 1, end - brk_end - 1);397// Not supported. Just append the url.398output.append(url);399400pos = brk_end + 1;401tag_stack.push_front(tag);402} else if (tag.begins_with("url=")) {403String url = tag.substr(4);404// Not supported. Just append the url.405output.append(url);406407pos = brk_end + 1;408tag_stack.push_front("url");409} else if (tag == "img") {410int end = bbcode.find_char('[', brk_end);411if (end == -1) {412end = bbcode.length();413}414String image = bbcode.substr(brk_end + 1, end - brk_end - 1);415416// Not supported. Just append the bbcode.417output.append("[img]");418output.append(image);419output.append("[/img]");420421pos = end;422tag_stack.push_front(tag);423} else if (tag.begins_with("color=")) {424// Not supported.425pos = brk_end + 1;426tag_stack.push_front("color");427} else if (tag.begins_with("font=")) {428// Not supported.429pos = brk_end + 1;430tag_stack.push_front("font");431} else {432// Ignore unrecognized tag.433output.append("[");434pos = brk_pos + 1;435}436}437438return output.as_string();439}440441String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {442// Based on the version in EditorHelp.443444if (p_bbcode.is_empty()) {445return String();446}447448DocTools *doc = EditorHelp::get_doc_data();449450String bbcode = p_bbcode;451452StringBuilder xml_output;453454xml_output.append("<para>");455456List<String> tag_stack;457bool code_tag = false;458bool line_del = false;459460int pos = 0;461while (pos < bbcode.length()) {462int brk_pos = bbcode.find_char('[', pos);463464if (brk_pos < 0) {465brk_pos = bbcode.length();466}467468if (brk_pos > pos) {469if (!line_del) {470String text = bbcode.substr(pos, brk_pos - pos);471if (code_tag || tag_stack.size() > 0) {472xml_output.append(text.xml_escape());473} else {474Vector<String> lines = text.split("\n");475for (int i = 0; i < lines.size(); i++) {476if (i != 0) {477xml_output.append("<para>");478}479480xml_output.append(lines[i].xml_escape());481482if (i != lines.size() - 1) {483xml_output.append("</para>\n");484}485}486}487}488}489490if (brk_pos == bbcode.length()) {491// Nothing else to add.492break;493}494495int brk_end = bbcode.find_char(']', brk_pos + 1);496497if (brk_end == -1) {498if (!line_del) {499String text = bbcode.substr(brk_pos);500if (code_tag || tag_stack.size() > 0) {501xml_output.append(text.xml_escape());502} else {503Vector<String> lines = text.split("\n");504for (int i = 0; i < lines.size(); i++) {505if (i != 0) {506xml_output.append("<para>");507}508509xml_output.append(lines[i].xml_escape());510511if (i != lines.size() - 1) {512xml_output.append("</para>\n");513}514}515}516}517518break;519}520521String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);522523if (tag.begins_with("/")) {524bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);525526if (!tag_ok) {527if (!line_del) {528xml_output.append("[");529}530pos = brk_pos + 1;531continue;532}533534tag_stack.pop_front();535pos = brk_end + 1;536code_tag = false;537538if (tag == "/url") {539xml_output.append("</a>");540} else if (tag == "/code") {541xml_output.append("</c>");542} else if (tag == "/codeblock") {543xml_output.append("</code>");544} else if (tag == "/b") {545xml_output.append("</b>");546} else if (tag == "/i") {547xml_output.append("</i>");548} else if (tag == "/csharp") {549xml_output.append("</code>");550line_del = true;551} else if (tag == "/codeblocks") {552line_del = false;553}554} else if (code_tag) {555xml_output.append("[");556pos = brk_pos + 1;557} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {558const int tag_end = tag.find_char(' ');559const String link_tag = tag.substr(0, tag_end);560const String link_target = tag.substr(tag_end + 1).lstrip(" ");561562const Vector<String> link_target_parts = link_target.split(".");563564if (link_target_parts.is_empty() || link_target_parts.size() > 2) {565ERR_PRINT("Invalid reference format: '" + tag + "'.");566567xml_output.append("<c>");568xml_output.append(tag);569xml_output.append("</c>");570571pos = brk_end + 1;572continue;573}574575const TypeInterface *target_itype;576StringName target_cname;577578if (link_target_parts.size() == 2) {579target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));580if (!target_itype) {581target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));582}583target_cname = link_target_parts[1];584} else {585target_itype = p_itype;586target_cname = link_target_parts[0];587}588589if (link_tag == "method") {590_append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);591} else if (link_tag == "constructor") {592// TODO: Support constructors?593_append_xml_undeclared(xml_output, link_target);594} else if (link_tag == "operator") {595// TODO: Support operators?596_append_xml_undeclared(xml_output, link_target);597} else if (link_tag == "member") {598_append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);599} else if (link_tag == "signal") {600_append_xml_signal(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);601} else if (link_tag == "enum") {602_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);603} else if (link_tag == "constant") {604_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);605} else if (link_tag == "param") {606_append_xml_param(xml_output, link_target, p_is_signal);607} else if (link_tag == "theme_item") {608// We do not declare theme_items in any way in C#, so there is nothing to reference.609_append_xml_undeclared(xml_output, link_target);610}611612pos = brk_end + 1;613} else if (doc->class_list.has(tag)) {614if (tag == "Array" || tag == "Dictionary") {615xml_output.append("<see cref=\"" BINDINGS_NAMESPACE_COLLECTIONS ".");616xml_output.append(tag);617xml_output.append("\"/>");618} else if (tag == "bool" || tag == "int") {619xml_output.append("<see cref=\"");620xml_output.append(tag);621xml_output.append("\"/>");622} else if (tag == "float") {623xml_output.append("<see cref=\""624#ifdef REAL_T_IS_DOUBLE625"double"626#else627"float"628#endif629"\"/>");630} else if (tag == "Variant") {631xml_output.append("<see cref=\"Godot.Variant\"/>");632} else if (tag == "String") {633xml_output.append("<see cref=\"string\"/>");634} else if (tag == "Nil") {635xml_output.append("<see langword=\"null\"/>");636} else if (tag.begins_with("@")) {637// @GlobalScope, @GDScript, etc.638xml_output.append("<c>");639xml_output.append(tag);640xml_output.append("</c>");641} else if (tag == "PackedByteArray") {642xml_output.append("<see cref=\"byte\"/>[]");643} else if (tag == "PackedInt32Array") {644xml_output.append("<see cref=\"int\"/>[]");645} else if (tag == "PackedInt64Array") {646xml_output.append("<see cref=\"long\"/>[]");647} else if (tag == "PackedFloat32Array") {648xml_output.append("<see cref=\"float\"/>[]");649} else if (tag == "PackedFloat64Array") {650xml_output.append("<see cref=\"double\"/>[]");651} else if (tag == "PackedStringArray") {652xml_output.append("<see cref=\"string\"/>[]");653} else if (tag == "PackedVector2Array") {654xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>[]");655} else if (tag == "PackedVector3Array") {656xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>[]");657} else if (tag == "PackedColorArray") {658xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>[]");659} else if (tag == "PackedVector4Array") {660xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector4\"/>[]");661} else {662const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));663664if (!target_itype) {665target_itype = _get_type_or_null(TypeReference("_" + tag));666}667668if (target_itype) {669if (!_validate_api_type(target_itype, p_itype)) {670_append_xml_undeclared(xml_output, target_itype->proxy_name);671} else {672xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");673xml_output.append(target_itype->proxy_name);674xml_output.append("\"/>");675}676} else {677ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");678679xml_output.append("<c>");680xml_output.append(tag);681xml_output.append("</c>");682}683}684685pos = brk_end + 1;686} else if (tag == "b") {687xml_output.append("<b>");688689pos = brk_end + 1;690tag_stack.push_front(tag);691} else if (tag == "i") {692xml_output.append("<i>");693694pos = brk_end + 1;695tag_stack.push_front(tag);696} else if (tag == "code" || tag.begins_with("code ")) {697int end = bbcode.find_char('[', brk_end);698if (end == -1) {699end = bbcode.length();700}701String code = bbcode.substr(brk_end + 1, end - brk_end - 1);702if (langword_check.has(code)) {703xml_output.append("<see langword=\"");704xml_output.append(code);705xml_output.append("\"/>");706707pos = brk_end + code.length() + 8;708} else {709xml_output.append("<c>");710711code_tag = true;712pos = brk_end + 1;713tag_stack.push_front("code");714}715} else if (tag == "codeblock" || tag.begins_with("codeblock ")) {716xml_output.append("<code>");717718code_tag = true;719pos = brk_end + 1;720tag_stack.push_front("codeblock");721} else if (tag == "codeblocks") {722line_del = true;723pos = brk_end + 1;724tag_stack.push_front(tag);725} else if (tag == "csharp" || tag.begins_with("csharp ")) {726xml_output.append("<code>");727728line_del = false;729code_tag = true;730pos = brk_end + 1;731tag_stack.push_front("csharp");732} else if (tag == "kbd") {733// Keyboard combinations are not supported in xml comments.734pos = brk_end + 1;735tag_stack.push_front(tag);736} else if (tag == "center") {737// Center alignment is not supported in xml comments.738pos = brk_end + 1;739tag_stack.push_front(tag);740} else if (tag == "br") {741xml_output.append("\n"); // FIXME: Should use <para> instead. Luckily this tag isn't used for now.742pos = brk_end + 1;743} else if (tag == "u") {744// Underline is not supported in Rider xml comments.745pos = brk_end + 1;746tag_stack.push_front(tag);747} else if (tag == "s") {748// Strikethrough is not supported in xml comments.749pos = brk_end + 1;750tag_stack.push_front(tag);751} else if (tag == "url") {752int end = bbcode.find_char('[', brk_end);753if (end == -1) {754end = bbcode.length();755}756String url = bbcode.substr(brk_end + 1, end - brk_end - 1);757xml_output.append("<a href=\"");758xml_output.append(url);759xml_output.append("\">");760xml_output.append(url);761762pos = brk_end + 1;763tag_stack.push_front(tag);764} else if (tag.begins_with("url=")) {765String url = tag.substr(4);766xml_output.append("<a href=\"");767xml_output.append(url);768xml_output.append("\">");769770pos = brk_end + 1;771tag_stack.push_front("url");772} else if (tag == "img") {773int end = bbcode.find_char('[', brk_end);774if (end == -1) {775end = bbcode.length();776}777String image = bbcode.substr(brk_end + 1, end - brk_end - 1);778779// Not supported. Just append the bbcode.780xml_output.append("[img]");781xml_output.append(image);782xml_output.append("[/img]");783784pos = end;785tag_stack.push_front(tag);786} else if (tag.begins_with("color=")) {787// Not supported.788pos = brk_end + 1;789tag_stack.push_front("color");790} else if (tag.begins_with("font=")) {791// Not supported.792pos = brk_end + 1;793tag_stack.push_front("font");794} else {795if (!line_del) {796// Ignore unrecognized tag.797xml_output.append("[");798}799pos = brk_pos + 1;800}801}802803xml_output.append("</para>");804805return xml_output.as_string();806}807808void BindingsGenerator::_append_text_method(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {809if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {810if (OS::get_singleton()->is_stdout_verbose()) {811OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());812}813814// TODO Map what we can815_append_text_undeclared(p_output, p_link_target);816} else if (!p_target_itype || !p_target_itype->is_object_type) {817if (OS::get_singleton()->is_stdout_verbose()) {818if (p_target_itype) {819OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());820} else {821OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());822}823}824825// TODO Map what we can826_append_text_undeclared(p_output, p_link_target);827} else {828if (p_target_cname == "_init") {829// The _init method is not declared in C#, reference the constructor instead830p_output.append("'new " BINDINGS_NAMESPACE ".");831p_output.append(p_target_itype->proxy_name);832p_output.append("()'");833} else {834const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);835836if (target_imethod) {837p_output.append("'" BINDINGS_NAMESPACE ".");838p_output.append(p_target_itype->proxy_name);839p_output.append(".");840p_output.append(target_imethod->proxy_name);841p_output.append("(");842bool first_key = true;843for (const ArgumentInterface &iarg : target_imethod->arguments) {844const TypeInterface *arg_type = _get_type_or_null(iarg.type);845846if (first_key) {847first_key = false;848} else {849p_output.append(", ");850}851if (!arg_type) {852ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");853p_output.append(iarg.type.cname);854continue;855}856if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {857p_output.append("Nullable<");858}859String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);860p_output.append(arg_cs_type.replacen("params ", ""));861if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {862p_output.append(">");863}864}865p_output.append(")'");866} else {867if (!p_target_itype->is_intentionally_ignored(p_link_target)) {868ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");869}870871_append_text_undeclared(p_output, p_link_target);872}873}874}875}876877void BindingsGenerator::_append_text_member(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {878if (p_link_target.contains_char('/')) {879// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.880_append_text_undeclared(p_output, p_link_target);881} else if (!p_target_itype || !p_target_itype->is_object_type) {882if (OS::get_singleton()->is_stdout_verbose()) {883if (p_target_itype) {884OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());885} else {886OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());887}888}889890// TODO Map what we can891_append_text_undeclared(p_output, p_link_target);892} else {893const TypeInterface *current_itype = p_target_itype;894const PropertyInterface *target_iprop = nullptr;895896while (target_iprop == nullptr && current_itype != nullptr) {897target_iprop = current_itype->find_property_by_name(p_target_cname);898if (target_iprop == nullptr) {899current_itype = _get_type_or_null(TypeReference(current_itype->base_name));900}901}902903if (target_iprop) {904p_output.append("'" BINDINGS_NAMESPACE ".");905p_output.append(current_itype->proxy_name);906p_output.append(".");907p_output.append(target_iprop->proxy_name);908p_output.append("'");909} else {910if (!p_target_itype->is_intentionally_ignored(p_link_target)) {911ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");912}913914_append_text_undeclared(p_output, p_link_target);915}916}917}918919void BindingsGenerator::_append_text_signal(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {920if (!p_target_itype || !p_target_itype->is_object_type) {921if (OS::get_singleton()->is_stdout_verbose()) {922if (p_target_itype) {923OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());924} else {925OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());926}927}928929// TODO Map what we can930_append_text_undeclared(p_output, p_link_target);931} else {932const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);933934if (target_isignal) {935p_output.append("'" BINDINGS_NAMESPACE ".");936p_output.append(p_target_itype->proxy_name);937p_output.append(".");938p_output.append(target_isignal->proxy_name);939p_output.append("'");940} else {941if (!p_target_itype->is_intentionally_ignored(p_link_target)) {942ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");943}944945_append_text_undeclared(p_output, p_link_target);946}947}948}949950void BindingsGenerator::_append_text_enum(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {951const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);952953HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);954955if (!enum_match && search_cname != p_target_cname) {956enum_match = enum_types.find(p_target_cname);957}958959if (enum_match) {960const TypeInterface &target_enum_itype = enum_match->value;961962p_output.append("'" BINDINGS_NAMESPACE ".");963p_output.append(target_enum_itype.proxy_name); // Includes nesting class if any964p_output.append("'");965} else {966if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_link_target)) {967ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");968}969970_append_text_undeclared(p_output, p_link_target);971}972}973974void BindingsGenerator::_append_text_constant(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {975if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {976_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);977} else if (!p_target_itype || !p_target_itype->is_object_type) {978// Search in @GlobalScope as a last resort if no class was specified979if (p_link_target_parts.size() == 1) {980_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);981return;982}983984if (OS::get_singleton()->is_stdout_verbose()) {985if (p_target_itype) {986OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());987} else {988OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());989}990}991992// TODO Map what we can993_append_text_undeclared(p_output, p_link_target);994} else {995// Try to find the constant in the current class996if (p_target_itype->is_singleton_instance) {997// Constants and enums are declared in the static singleton class.998p_target_itype = &obj_types[p_target_itype->cname];999}10001001const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);10021003if (target_iconst) {1004// Found constant in current class1005p_output.append("'" BINDINGS_NAMESPACE ".");1006p_output.append(p_target_itype->proxy_name);1007p_output.append(".");1008p_output.append(target_iconst->proxy_name);1009p_output.append("'");1010} else {1011// Try to find as enum constant in the current class1012const EnumInterface *target_ienum = nullptr;10131014for (const EnumInterface &ienum : p_target_itype->enums) {1015target_ienum = &ienum;1016target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1017if (target_iconst) {1018break;1019}1020}10211022if (target_iconst) {1023p_output.append("'" BINDINGS_NAMESPACE ".");1024p_output.append(p_target_itype->proxy_name);1025p_output.append(".");1026p_output.append(target_ienum->proxy_name);1027p_output.append(".");1028p_output.append(target_iconst->proxy_name);1029p_output.append("'");1030} else if (p_link_target_parts.size() == 1) {1031// Also search in @GlobalScope as a last resort if no class was specified1032_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);1033} else {1034if (!p_target_itype->is_intentionally_ignored(p_link_target)) {1035ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");1036}10371038_append_xml_undeclared(p_output, p_link_target);1039}1040}1041}1042}10431044void BindingsGenerator::_append_text_constant_in_global_scope(StringBuilder &p_output, const String &p_target_cname, const String &p_link_target) {1045// Try to find as a global constant1046const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);10471048if (target_iconst) {1049// Found global constant1050p_output.append("'" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");1051p_output.append(target_iconst->proxy_name);1052p_output.append("'");1053} else {1054// Try to find as global enum constant1055const EnumInterface *target_ienum = nullptr;10561057for (const EnumInterface &ienum : global_enums) {1058target_ienum = &ienum;1059target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1060if (target_iconst) {1061break;1062}1063}10641065if (target_iconst) {1066p_output.append("'" BINDINGS_NAMESPACE ".");1067p_output.append(target_ienum->proxy_name);1068p_output.append(".");1069p_output.append(target_iconst->proxy_name);1070p_output.append("'");1071} else {1072ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");1073_append_text_undeclared(p_output, p_link_target);1074}1075}1076}10771078void BindingsGenerator::_append_text_param(StringBuilder &p_output, const String &p_link_target) {1079const String link_target = snake_to_camel_case(p_link_target);1080p_output.append("'" + link_target + "'");1081}10821083void BindingsGenerator::_append_text_undeclared(StringBuilder &p_output, const String &p_link_target) {1084p_output.append("'" + p_link_target + "'");1085}10861087void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {1088if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {1089if (OS::get_singleton()->is_stdout_verbose()) {1090OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());1091}10921093// TODO Map what we can1094_append_xml_undeclared(p_xml_output, p_link_target);1095} else if (!p_target_itype || !p_target_itype->is_object_type) {1096if (OS::get_singleton()->is_stdout_verbose()) {1097if (p_target_itype) {1098OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1099} else {1100OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());1101}1102}11031104// TODO Map what we can1105_append_xml_undeclared(p_xml_output, p_link_target);1106} else {1107if (p_target_cname == "_init") {1108// The _init method is not declared in C#, reference the constructor instead1109p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1110p_xml_output.append(p_target_itype->proxy_name);1111p_xml_output.append(".");1112p_xml_output.append(p_target_itype->proxy_name);1113p_xml_output.append("()\"/>");1114} else {1115const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);11161117if (target_imethod) {1118const String method_name = p_target_itype->proxy_name + "." + target_imethod->proxy_name;1119if (!_validate_api_type(p_target_itype, p_source_itype)) {1120_append_xml_undeclared(p_xml_output, method_name);1121} else {1122p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1123p_xml_output.append(method_name);1124p_xml_output.append("(");1125bool first_key = true;1126for (const ArgumentInterface &iarg : target_imethod->arguments) {1127const TypeInterface *arg_type = _get_type_or_null(iarg.type);11281129if (first_key) {1130first_key = false;1131} else {1132p_xml_output.append(", ");1133}1134if (!arg_type) {1135ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");1136p_xml_output.append(iarg.type.cname);1137continue;1138}1139if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {1140p_xml_output.append("Nullable{");1141}1142String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);1143p_xml_output.append(arg_cs_type.replacen("<", "{").replacen(">", "}").replacen("params ", ""));1144if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {1145p_xml_output.append("}");1146}1147}1148p_xml_output.append(")\"/>");1149}1150} else {1151if (!p_target_itype->is_intentionally_ignored(p_link_target)) {1152ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");1153}11541155_append_xml_undeclared(p_xml_output, p_link_target);1156}1157}1158}1159}11601161void BindingsGenerator::_append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {1162if (p_link_target.contains_char('/')) {1163// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.1164_append_xml_undeclared(p_xml_output, p_link_target);1165} else if (!p_target_itype || !p_target_itype->is_object_type) {1166if (OS::get_singleton()->is_stdout_verbose()) {1167if (p_target_itype) {1168OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1169} else {1170OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());1171}1172}11731174// TODO Map what we can1175_append_xml_undeclared(p_xml_output, p_link_target);1176} else {1177const TypeInterface *current_itype = p_target_itype;1178const PropertyInterface *target_iprop = nullptr;11791180while (target_iprop == nullptr && current_itype != nullptr) {1181target_iprop = current_itype->find_property_by_name(p_target_cname);1182if (target_iprop == nullptr) {1183current_itype = _get_type_or_null(TypeReference(current_itype->base_name));1184}1185}11861187if (target_iprop) {1188const String member_name = current_itype->proxy_name + "." + target_iprop->proxy_name;1189if (!_validate_api_type(p_target_itype, p_source_itype)) {1190_append_xml_undeclared(p_xml_output, member_name);1191} else {1192p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1193p_xml_output.append(member_name);1194p_xml_output.append("\"/>");1195}1196} else {1197if (!p_target_itype->is_intentionally_ignored(p_link_target)) {1198ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");1199}12001201_append_xml_undeclared(p_xml_output, p_link_target);1202}1203}1204}12051206void BindingsGenerator::_append_xml_signal(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {1207if (!p_target_itype || !p_target_itype->is_object_type) {1208if (OS::get_singleton()->is_stdout_verbose()) {1209if (p_target_itype) {1210OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1211} else {1212OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());1213}1214}12151216// TODO Map what we can1217_append_xml_undeclared(p_xml_output, p_link_target);1218} else {1219const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);12201221if (target_isignal) {1222const String signal_name = p_target_itype->proxy_name + "." + target_isignal->proxy_name;1223if (!_validate_api_type(p_target_itype, p_source_itype)) {1224_append_xml_undeclared(p_xml_output, signal_name);1225} else {1226p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1227p_xml_output.append(signal_name);1228p_xml_output.append("\"/>");1229}1230} else {1231if (!p_target_itype->is_intentionally_ignored(p_link_target)) {1232ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");1233}12341235_append_xml_undeclared(p_xml_output, p_link_target);1236}1237}1238}12391240void BindingsGenerator::_append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {1241const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);12421243HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);12441245if (!enum_match && search_cname != p_target_cname) {1246enum_match = enum_types.find(p_target_cname);1247}12481249if (enum_match) {1250const TypeInterface &target_enum_itype = enum_match->value;12511252if (!_validate_api_type(p_target_itype, p_source_itype)) {1253_append_xml_undeclared(p_xml_output, target_enum_itype.proxy_name);1254} else {1255p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1256p_xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any1257p_xml_output.append("\"/>");1258}1259} else {1260if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_link_target)) {1261ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");1262}12631264_append_xml_undeclared(p_xml_output, p_link_target);1265}1266}12671268void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {1269if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {1270_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1271} else if (!p_target_itype || !p_target_itype->is_object_type) {1272// Search in @GlobalScope as a last resort if no class was specified1273if (p_link_target_parts.size() == 1) {1274_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1275return;1276}12771278if (OS::get_singleton()->is_stdout_verbose()) {1279if (p_target_itype) {1280OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1281} else {1282OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());1283}1284}12851286// TODO Map what we can1287_append_xml_undeclared(p_xml_output, p_link_target);1288} else {1289// Try to find the constant in the current class1290if (p_target_itype->is_singleton_instance) {1291// Constants and enums are declared in the static singleton class.1292p_target_itype = &obj_types[p_target_itype->cname];1293}12941295const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);12961297if (target_iconst) {1298// Found constant in current class1299p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1300p_xml_output.append(p_target_itype->proxy_name);1301p_xml_output.append(".");1302p_xml_output.append(target_iconst->proxy_name);1303p_xml_output.append("\"/>");1304} else {1305// Try to find as enum constant in the current class1306const EnumInterface *target_ienum = nullptr;13071308for (const EnumInterface &ienum : p_target_itype->enums) {1309target_ienum = &ienum;1310target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1311if (target_iconst) {1312break;1313}1314}13151316if (target_iconst) {1317p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1318p_xml_output.append(p_target_itype->proxy_name);1319p_xml_output.append(".");1320p_xml_output.append(target_ienum->proxy_name);1321p_xml_output.append(".");1322p_xml_output.append(target_iconst->proxy_name);1323p_xml_output.append("\"/>");1324} else if (p_link_target_parts.size() == 1) {1325// Also search in @GlobalScope as a last resort if no class was specified1326_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1327} else {1328if (!p_target_itype->is_intentionally_ignored(p_link_target)) {1329ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");1330}13311332_append_xml_undeclared(p_xml_output, p_link_target);1333}1334}1335}1336}13371338void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target) {1339// Try to find as a global constant1340const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);13411342if (target_iconst) {1343// Found global constant1344p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");1345p_xml_output.append(target_iconst->proxy_name);1346p_xml_output.append("\"/>");1347} else {1348// Try to find as global enum constant1349const EnumInterface *target_ienum = nullptr;13501351for (const EnumInterface &ienum : global_enums) {1352target_ienum = &ienum;1353target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1354if (target_iconst) {1355break;1356}1357}13581359if (target_iconst) {1360p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1361p_xml_output.append(target_ienum->proxy_name);1362p_xml_output.append(".");1363p_xml_output.append(target_iconst->proxy_name);1364p_xml_output.append("\"/>");1365} else {1366ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");1367_append_xml_undeclared(p_xml_output, p_link_target);1368}1369}1370}13711372void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {1373const String link_target = snake_to_camel_case(p_link_target);13741375if (!p_is_signal) {1376p_xml_output.append("<paramref name=\"");1377p_xml_output.append(link_target);1378p_xml_output.append("\"/>");1379} else {1380// Documentation in C# is added to an event, not the delegate itself;1381// as such, we treat these parameters as codeblocks instead.1382// See: https://github.com/godotengine/godot/pull/655291383_append_xml_undeclared(p_xml_output, link_target);1384}1385}13861387void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {1388p_xml_output.append("<c>");1389p_xml_output.append(p_link_target);1390p_xml_output.append("</c>");1391}13921393bool BindingsGenerator::_validate_api_type(const TypeInterface *p_target_itype, const TypeInterface *p_source_itype) {1394static constexpr const char *api_types[5] = {1395"Core",1396"Editor",1397"Extension",1398"Editor Extension",1399"None",1400};14011402const ClassDB::APIType target_api = p_target_itype ? p_target_itype->api_type : ClassDB::API_NONE;1403ERR_FAIL_INDEX_V((int)target_api, 5, false);1404const ClassDB::APIType source_api = p_source_itype ? p_source_itype->api_type : ClassDB::API_NONE;1405ERR_FAIL_INDEX_V((int)source_api, 5, false);1406bool validate = false;14071408switch (target_api) {1409case ClassDB::API_NONE:1410case ClassDB::API_CORE:1411default:1412validate = true;1413break;1414case ClassDB::API_EDITOR:1415validate = source_api == ClassDB::API_EDITOR || source_api == ClassDB::API_EDITOR_EXTENSION;1416break;1417case ClassDB::API_EXTENSION:1418validate = source_api == ClassDB::API_EXTENSION || source_api == ClassDB::API_EDITOR_EXTENSION;1419break;1420case ClassDB::API_EDITOR_EXTENSION:1421validate = source_api == ClassDB::API_EDITOR_EXTENSION;1422break;1423}1424if (!validate) {1425const String target_name = p_target_itype ? p_target_itype->proxy_name : "@GlobalScope";1426const String source_name = p_source_itype ? p_source_itype->proxy_name : "@GlobalScope";1427WARN_PRINT(vformat("Type '%s' has API level '%s'; it cannot be referenced by type '%s' with API level '%s'.",1428target_name, api_types[target_api], source_name, api_types[source_api]));1429}1430return validate;1431}14321433int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {1434CRASH_COND(p_ienum.constants.is_empty());14351436const ConstantInterface &front_iconstant = p_ienum.constants.front()->get();1437Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true);1438int candidate_len = front_parts.size() - 1;14391440if (candidate_len == 0) {1441return 0;1442}14431444for (const ConstantInterface &iconstant : p_ienum.constants) {1445Vector<String> parts = iconstant.name.split("_", /* p_allow_empty: */ true);14461447int i;1448for (i = 0; i < candidate_len && i < parts.size(); i++) {1449if (front_parts[i] != parts[i]) {1450// HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT').1451bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS")));1452if (!hardcoded_exc) {1453break;1454}1455}1456}1457candidate_len = i;14581459if (candidate_len == 0) {1460return 0;1461}1462}14631464return candidate_len;1465}14661467void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) {1468if (p_prefix_length > 0) {1469for (ConstantInterface &iconstant : p_ienum.constants) {1470int curr_prefix_length = p_prefix_length;14711472String constant_name = iconstant.name;14731474Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true);14751476if (parts.size() <= curr_prefix_length) {1477continue;1478}14791480if (is_digit(parts[curr_prefix_length][0])) {1481// The name of enum constants may begin with a numeric digit when strip from the enum prefix,1482// so we make the prefix for this constant one word shorter in those cases.1483for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {1484if (!is_digit(parts[curr_prefix_length][0])) {1485break;1486}1487}1488}14891490constant_name = "";1491for (int i = curr_prefix_length; i < parts.size(); i++) {1492if (i > curr_prefix_length) {1493constant_name += "_";1494}1495constant_name += parts[i];1496}14971498iconstant.proxy_name = snake_to_pascal_case(constant_name, true);1499}1500}1501}15021503Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_itype) {1504for (const MethodInterface &imethod : p_itype.methods) {1505if (imethod.is_virtual) {1506continue;1507}15081509const TypeInterface *return_type = _get_type_or_null(imethod.return_type);1510ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");15111512String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind";15131514if (!imethod.is_static) {1515im_unique_sig += ",CallInstance";1516}15171518// Get arguments information1519for (const ArgumentInterface &iarg : imethod.arguments) {1520const TypeInterface *arg_type = _get_type_or_null(iarg.type);1521ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");15221523im_unique_sig += ",";1524im_unique_sig += get_arg_unique_sig(*arg_type);1525}15261527// godot_icall_{argc}_{icallcount}1528String icall_method = ICALL_PREFIX;1529icall_method += itos(imethod.arguments.size());1530icall_method += "_";1531icall_method += itos(method_icalls.size());15321533InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_unique_sig);15341535im_icall.is_vararg = imethod.is_vararg;1536im_icall.is_static = imethod.is_static;1537im_icall.return_type = imethod.return_type;15381539for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) {1540im_icall.argument_types.push_back(F->get().type);1541}15421543List<InternalCall>::Element *match = method_icalls.find(im_icall);15441545if (match) {1546if (p_itype.api_type != ClassDB::API_EDITOR) {1547match->get().editor_only = false;1548}1549method_icalls_map.insert(&imethod, &match->get());1550} else {1551List<InternalCall>::Element *added = method_icalls.push_back(im_icall);1552method_icalls_map.insert(&imethod, &added->get());1553}1554}15551556return OK;1557}15581559void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) {1560p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");1561p_output.append("using System;\n\n");1562// The class where we put the extensions doesn't matter, so just use "GD".1563p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{");15641565#define ARRAY_IS_EMPTY(m_type) \1566p_output.append("\n" INDENT1 "/// <summary>\n"); \1567p_output.append(INDENT1 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \1568p_output.append(INDENT1 "/// </summary>\n"); \1569p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \1570p_output.append(INDENT1 "/// <returns>Whether or not the array is empty.</returns>\n"); \1571p_output.append(INDENT1 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \1572p_output.append(OPEN_BLOCK_L1); \1573p_output.append(INDENT2 "return instance == null || instance.Length == 0;\n"); \1574p_output.append(INDENT1 CLOSE_BLOCK);15751576#define ARRAY_JOIN(m_type) \1577p_output.append("\n" INDENT1 "/// <summary>\n"); \1578p_output.append(INDENT1 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \1579p_output.append(INDENT1 "/// </summary>\n"); \1580p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \1581p_output.append(INDENT1 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \1582p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \1583p_output.append(INDENT1 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \1584p_output.append(OPEN_BLOCK_L1); \1585p_output.append(INDENT2 "return String.Join(delimiter, instance);\n"); \1586p_output.append(INDENT1 CLOSE_BLOCK);15871588#define ARRAY_STRINGIFY(m_type) \1589p_output.append("\n" INDENT1 "/// <summary>\n"); \1590p_output.append(INDENT1 "/// Converts this " #m_type " array to a string with brackets.\n"); \1591p_output.append(INDENT1 "/// </summary>\n"); \1592p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \1593p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \1594p_output.append(INDENT1 "public static string Stringify(this " #m_type "[] instance)\n"); \1595p_output.append(OPEN_BLOCK_L1); \1596p_output.append(INDENT2 "return \"[\" + instance.Join() + \"]\";\n"); \1597p_output.append(INDENT1 CLOSE_BLOCK);15981599#define ARRAY_ALL(m_type) \1600ARRAY_IS_EMPTY(m_type) \1601ARRAY_JOIN(m_type) \1602ARRAY_STRINGIFY(m_type)16031604ARRAY_ALL(byte);1605ARRAY_ALL(int);1606ARRAY_ALL(long);1607ARRAY_ALL(float);1608ARRAY_ALL(double);1609ARRAY_ALL(string);1610ARRAY_ALL(Color);1611ARRAY_ALL(Vector2);1612ARRAY_ALL(Vector2I);1613ARRAY_ALL(Vector3);1614ARRAY_ALL(Vector3I);1615ARRAY_ALL(Vector4);1616ARRAY_ALL(Vector4I);16171618#undef ARRAY_ALL1619#undef ARRAY_IS_EMPTY1620#undef ARRAY_JOIN1621#undef ARRAY_STRINGIFY16221623p_output.append(CLOSE_BLOCK); // End of GD class.1624}16251626void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {1627// Constants (in partial GD class)16281629p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");16301631p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" OPEN_BLOCK);16321633for (const ConstantInterface &iconstant : global_constants) {1634if (iconstant.const_doc && iconstant.const_doc->description.size()) {1635String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);1636Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();16371638if (summary_lines.size()) {1639p_output.append(MEMBER_BEGIN "/// <summary>\n");16401641for (int i = 0; i < summary_lines.size(); i++) {1642p_output.append(INDENT1 "/// ");1643p_output.append(summary_lines[i]);1644p_output.append("\n");1645}16461647p_output.append(INDENT1 "/// </summary>");1648}1649}16501651p_output.append(MEMBER_BEGIN "public const long ");1652p_output.append(iconstant.proxy_name);1653p_output.append(" = ");1654p_output.append(itos(iconstant.value));1655p_output.append(";");1656}16571658if (!global_constants.is_empty()) {1659p_output.append("\n");1660}16611662p_output.append(CLOSE_BLOCK); // end of GD class16631664// Enums16651666for (const EnumInterface &ienum : global_enums) {1667CRASH_COND(ienum.constants.is_empty());16681669String enum_proxy_name = ienum.proxy_name;16701671bool enum_in_static_class = false;16721673if (enum_proxy_name.find_char('.') > 0) {1674enum_in_static_class = true;1675String enum_class_name = enum_proxy_name.get_slicec('.', 0);1676enum_proxy_name = enum_proxy_name.get_slicec('.', 1);16771678CRASH_COND(enum_class_name != "Variant"); // Hard-coded...16791680_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());16811682p_output << "\npublic partial struct " << enum_class_name << "\n" OPEN_BLOCK;1683}16841685const String maybe_indent = !enum_in_static_class ? "" : INDENT1;16861687if (ienum.is_flags) {1688p_output << "\n"1689<< maybe_indent << "[System.Flags]";1690}16911692p_output << "\n"1693<< maybe_indent << "public enum " << enum_proxy_name << " : long"1694<< "\n"1695<< maybe_indent << OPEN_BLOCK;16961697for (const ConstantInterface &iconstant : ienum.constants) {1698if (iconstant.const_doc && iconstant.const_doc->description.size()) {1699String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);1700Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();17011702if (summary_lines.size()) {1703p_output << maybe_indent << INDENT1 "/// <summary>\n";17041705for (int i = 0; i < summary_lines.size(); i++) {1706p_output << maybe_indent << INDENT1 "/// " << summary_lines[i] << "\n";1707}17081709p_output << maybe_indent << INDENT1 "/// </summary>\n";1710}1711}17121713p_output << maybe_indent << INDENT11714<< iconstant.proxy_name1715<< " = "1716<< itos(iconstant.value)1717<< ",\n";1718}17191720p_output << maybe_indent << CLOSE_BLOCK;17211722if (enum_in_static_class) {1723p_output << CLOSE_BLOCK;1724}1725}1726}17271728Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {1729ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);17301731Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1732ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);17331734if (!DirAccess::exists(p_proj_dir)) {1735Error err = da->make_dir_recursive(p_proj_dir);1736ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");1737}17381739da->change_dir(p_proj_dir);1740da->make_dir("Generated");1741da->make_dir("Generated/GodotObjects");17421743String base_gen_dir = Path::join(p_proj_dir, "Generated");1744String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");17451746Vector<String> compile_items;17471748// Generate source file for global scope constants and enums1749{1750StringBuilder constants_source;1751_generate_global_constants(constants_source);1752String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");1753Error save_err = _save_file(output_file, constants_source);1754if (save_err != OK) {1755return save_err;1756}17571758compile_items.push_back(output_file);1759}17601761// Generate source file for array extensions1762{1763StringBuilder extensions_source;1764_generate_array_extensions(extensions_source);1765String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_extensions.cs");1766Error save_err = _save_file(output_file, extensions_source);1767if (save_err != OK) {1768return save_err;1769}17701771compile_items.push_back(output_file);1772}17731774for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1775const TypeInterface &itype = E.value;17761777if (itype.api_type == ClassDB::API_EDITOR) {1778continue;1779}17801781String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");1782Error err = _generate_cs_type(itype, output_file);17831784if (err == ERR_SKIP) {1785continue;1786}17871788if (err != OK) {1789return err;1790}17911792compile_items.push_back(output_file);1793}17941795// Generate source file for built-in type constructor dictionary.17961797{1798StringBuilder cs_built_in_ctors_content;17991800cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1801cs_built_in_ctors_content.append("using System;\n"1802"using System.Collections.Generic;\n"1803"\n");1804cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{");18051806cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");18071808cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n");1809cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1810cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n");1811cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n");1812cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n");1813cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);18141815cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n");1816cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1817cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n");18181819for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1820const TypeInterface &itype = E.value;18211822if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) {1823continue;1824}18251826if (itype.is_deprecated) {1827cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");1828}18291830cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\"");1831cs_built_in_ctors_content.append(itype.name);1832cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");1833cs_built_in_ctors_content.append(itype.proxy_name);1834if (itype.is_singleton && !itype.is_compat_singleton) {1835cs_built_in_ctors_content.append("Instance");1836}1837cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");18381839if (itype.is_deprecated) {1840cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");1841}1842}18431844cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);18451846cs_built_in_ctors_content.append(CLOSE_BLOCK);18471848String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs");1849Error err = _save_file(constructors_file, cs_built_in_ctors_content);18501851if (err != OK) {1852return err;1853}18541855compile_items.push_back(constructors_file);1856}18571858// Generate native calls18591860StringBuilder cs_icalls_content;18611862cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1863cs_icalls_content.append("using System;\n"1864"using System.Diagnostics.CodeAnalysis;\n"1865"using System.Runtime.InteropServices;\n"1866"using Godot.NativeInterop;\n"1867"\n");1868cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");1869cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");1870cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");1871cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");1872cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS "\n{");18731874cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = ");1875cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_CORE)) + ";\n");18761877cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");18781879for (const InternalCall &icall : method_icalls) {1880if (icall.editor_only) {1881continue;1882}1883Error err = _generate_cs_native_calls(icall, cs_icalls_content);1884if (err != OK) {1885return err;1886}1887}18881889cs_icalls_content.append(CLOSE_BLOCK);18901891String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs");18921893Error err = _save_file(internal_methods_file, cs_icalls_content);1894if (err != OK) {1895return err;1896}18971898compile_items.push_back(internal_methods_file);18991900// Generate GeneratedIncludes.props19011902StringBuilder includes_props_content;1903includes_props_content.append("<Project>\n"1904" <ItemGroup>\n");19051906for (int i = 0; i < compile_items.size(); i++) {1907String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');1908includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");1909}19101911includes_props_content.append(" </ItemGroup>\n"1912"</Project>\n");19131914String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");19151916err = _save_file(includes_props_file, includes_props_content);1917if (err != OK) {1918return err;1919}19201921return OK;1922}19231924Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {1925ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);19261927Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1928ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);19291930if (!DirAccess::exists(p_proj_dir)) {1931Error err = da->make_dir_recursive(p_proj_dir);1932ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);1933}19341935da->change_dir(p_proj_dir);1936da->make_dir("Generated");1937da->make_dir("Generated/GodotObjects");19381939String base_gen_dir = Path::join(p_proj_dir, "Generated");1940String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");19411942Vector<String> compile_items;19431944for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1945const TypeInterface &itype = E.value;19461947if (itype.api_type != ClassDB::API_EDITOR) {1948continue;1949}19501951String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");1952Error err = _generate_cs_type(itype, output_file);19531954if (err == ERR_SKIP) {1955continue;1956}19571958if (err != OK) {1959return err;1960}19611962compile_items.push_back(output_file);1963}19641965// Generate source file for editor type constructor dictionary.19661967{1968StringBuilder cs_built_in_ctors_content;19691970cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1971cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{");19721973cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n");1974cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1975cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");19761977for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1978const TypeInterface &itype = E.value;19791980if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) {1981continue;1982}19831984if (itype.is_deprecated) {1985cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");1986}19871988cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\"");1989cs_built_in_ctors_content.append(itype.name);1990cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");1991cs_built_in_ctors_content.append(itype.proxy_name);1992if (itype.is_singleton && !itype.is_compat_singleton) {1993cs_built_in_ctors_content.append("Instance");1994}1995cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");19961997if (itype.is_deprecated) {1998cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");1999}2000}20012002cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);20032004cs_built_in_ctors_content.append(CLOSE_BLOCK);20052006String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs");2007Error err = _save_file(constructors_file, cs_built_in_ctors_content);20082009if (err != OK) {2010return err;2011}20122013compile_items.push_back(constructors_file);2014}20152016// Generate native calls20172018StringBuilder cs_icalls_content;20192020cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");2021cs_icalls_content.append("using System;\n"2022"using System.Diagnostics.CodeAnalysis;\n"2023"using System.Runtime.InteropServices;\n"2024"using Godot.NativeInterop;\n"2025"\n");2026cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");2027cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");2028cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");2029cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");2030cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" OPEN_BLOCK);20312032cs_icalls_content.append(INDENT1 "internal static ulong godot_api_hash = ");2033cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_EDITOR)) + ";\n");20342035cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");20362037cs_icalls_content.append("\n");20382039for (const InternalCall &icall : method_icalls) {2040if (!icall.editor_only) {2041continue;2042}2043Error err = _generate_cs_native_calls(icall, cs_icalls_content);2044if (err != OK) {2045return err;2046}2047}20482049cs_icalls_content.append(CLOSE_BLOCK);20502051String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");20522053Error err = _save_file(internal_methods_file, cs_icalls_content);2054if (err != OK) {2055return err;2056}20572058compile_items.push_back(internal_methods_file);20592060// Generate GeneratedIncludes.props20612062StringBuilder includes_props_content;2063includes_props_content.append("<Project>\n"2064" <ItemGroup>\n");20652066for (int i = 0; i < compile_items.size(); i++) {2067String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');2068includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");2069}20702071includes_props_content.append(" </ItemGroup>\n"2072"</Project>\n");20732074String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");20752076err = _save_file(includes_props_file, includes_props_content);2077if (err != OK) {2078return err;2079}20802081return OK;2082}20832084Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {2085ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);20862087String output_dir = Path::abspath(Path::realpath(p_output_dir));20882089Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);2090ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);20912092if (!DirAccess::exists(output_dir)) {2093Error err = da->make_dir_recursive(output_dir);2094ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);2095}20962097Error proj_err;20982099// Generate GodotSharp source files21002101String core_proj_dir = output_dir.path_join(CORE_API_ASSEMBLY_NAME);21022103proj_err = generate_cs_core_project(core_proj_dir);2104if (proj_err != OK) {2105ERR_PRINT("Generation of the Core API C# project failed.");2106return proj_err;2107}21082109// Generate GodotSharpEditor source files21102111String editor_proj_dir = output_dir.path_join(EDITOR_API_ASSEMBLY_NAME);21122113proj_err = generate_cs_editor_project(editor_proj_dir);2114if (proj_err != OK) {2115ERR_PRINT("Generation of the Editor API C# project failed.");2116return proj_err;2117}21182119_log("The Godot API sources were successfully generated\n");21202121return OK;2122}21232124// FIXME: There are some members that hide other inherited members.2125// - In the case of both members being the same kind, the new one must be declared2126// explicitly as 'new' to avoid the warning (and we must print a message about it).2127// - In the case of both members being of a different kind, then the new one must2128// be renamed to avoid the name collision (and we must print a warning about it).2129// - Csc warning e.g.:2130// ObjectType/LineEdit.cs(140,38): warning CS0108: 'LineEdit.FocusMode' hides inherited member 'Control.FocusMode'. Use the new keyword if hiding was intended.2131Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) {2132CRASH_COND(!itype.is_object_type);21332134bool is_derived_type = itype.base_name != StringName();21352136if (!is_derived_type) {2137// Some GodotObject assertions2138CRASH_COND(itype.cname != name_cache.type_Object);2139CRASH_COND(!itype.is_instantiable);2140CRASH_COND(itype.api_type != ClassDB::API_CORE);2141CRASH_COND(itype.is_ref_counted);2142CRASH_COND(itype.is_singleton);2143}21442145_log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data());21462147StringBuilder output;21482149output.append("namespace " BINDINGS_NAMESPACE ";\n\n");21502151output.append("using System;\n"); // IntPtr2152output.append("using System.ComponentModel;\n"); // EditorBrowsable2153output.append("using System.Diagnostics;\n"); // DebuggerBrowsable2154output.append("using Godot.NativeInterop;\n");21552156output.append("\n#nullable disable\n");21572158const DocData::ClassDoc *class_doc = itype.class_doc;21592160if (class_doc && class_doc->description.size()) {2161String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype);2162Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();21632164if (summary_lines.size()) {2165output.append("/// <summary>\n");21662167for (int i = 0; i < summary_lines.size(); i++) {2168output.append("/// ");2169output.append(summary_lines[i]);2170output.append("\n");2171}21722173output.append("/// </summary>\n");2174}2175}21762177if (itype.is_deprecated) {2178output.append("[Obsolete(\"");2179output.append(bbcode_to_text(itype.deprecation_message, &itype));2180output.append("\")]\n");2181}21822183// We generate a `GodotClassName` attribute if the engine class name is not the same as the2184// generated C# class name. This allows introspection code to find the name associated with2185// the class. If the attribute is not present, the C# class name can be used instead.2186if (itype.name != itype.proxy_name) {2187output << "[GodotClassName(\"" << itype.name << "\")]\n";2188}21892190output.append("public ");2191if (itype.is_singleton) {2192output.append("static partial class ");2193} else {2194// Even if the class is not instantiable, we can't declare it abstract because2195// the engine can still instantiate them and return them via the scripting API.2196// Example: `SceneTreeTimer` returned from `SceneTree.create_timer`.2197// See the reverted commit: ef5672d3f94a7321ed779c922088bb72adbb15212198output.append("partial class ");2199}2200output.append(itype.proxy_name);22012202if (is_derived_type && !itype.is_singleton) {2203if (obj_types.has(itype.base_name)) {2204TypeInterface base_type = obj_types[itype.base_name];2205output.append(" : ");2206output.append(base_type.proxy_name);2207if (base_type.is_singleton) {2208// If the type is a singleton, use the instance type.2209output.append(CS_SINGLETON_INSTANCE_SUFFIX);2210}2211} else {2212ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");2213return ERR_INVALID_DATA;2214}2215}22162217output.append("\n{");22182219// Add constants22202221for (const ConstantInterface &iconstant : itype.constants) {2222if (iconstant.const_doc && iconstant.const_doc->description.size()) {2223String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);2224Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();22252226if (summary_lines.size()) {2227output.append(MEMBER_BEGIN "/// <summary>\n");22282229for (int i = 0; i < summary_lines.size(); i++) {2230output.append(INDENT1 "/// ");2231output.append(summary_lines[i]);2232output.append("\n");2233}22342235output.append(INDENT1 "/// </summary>");2236}2237}22382239if (iconstant.is_deprecated) {2240output.append(MEMBER_BEGIN "[Obsolete(\"");2241output.append(bbcode_to_text(iconstant.deprecation_message, &itype));2242output.append("\")]");2243}22442245output.append(MEMBER_BEGIN "public const long ");2246output.append(iconstant.proxy_name);2247output.append(" = ");2248output.append(itos(iconstant.value));2249output.append(";");2250}22512252if (itype.constants.size()) {2253output.append("\n");2254}22552256// Add enums22572258for (const EnumInterface &ienum : itype.enums) {2259ERR_FAIL_COND_V(ienum.constants.is_empty(), ERR_BUG);22602261if (ienum.is_flags) {2262output.append(MEMBER_BEGIN "[System.Flags]");2263}22642265output.append(MEMBER_BEGIN "public enum ");2266output.append(ienum.proxy_name);2267output.append(" : long");2268output.append(MEMBER_BEGIN OPEN_BLOCK);22692270const ConstantInterface &last = ienum.constants.back()->get();2271for (const ConstantInterface &iconstant : ienum.constants) {2272if (iconstant.const_doc && iconstant.const_doc->description.size()) {2273String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);2274Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();22752276if (summary_lines.size()) {2277output.append(INDENT2 "/// <summary>\n");22782279for (int i = 0; i < summary_lines.size(); i++) {2280output.append(INDENT2 "/// ");2281output.append(summary_lines[i]);2282output.append("\n");2283}22842285output.append(INDENT2 "/// </summary>\n");2286}2287}22882289if (iconstant.is_deprecated) {2290output.append(INDENT2 "[Obsolete(\"");2291output.append(bbcode_to_text(iconstant.deprecation_message, &itype));2292output.append("\")]\n");2293}22942295output.append(INDENT2);2296output.append(iconstant.proxy_name);2297output.append(" = ");2298output.append(itos(iconstant.value));2299output.append(&iconstant != &last ? ",\n" : "\n");2300}23012302output.append(INDENT1 CLOSE_BLOCK);2303}23042305// Add properties23062307for (const PropertyInterface &iprop : itype.properties) {2308Error prop_err = _generate_cs_property(itype, iprop, output);2309ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,2310"Failed to generate property '" + iprop.cname.operator String() +2311"' for class '" + itype.name + "'.");2312}23132314// Add native name static field and cached type.23152316if (is_derived_type && !itype.is_singleton) {2317output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";2318}23192320output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");2321output.append(itype.name);2322output.append("\";\n");23232324if (itype.is_singleton || itype.is_compat_singleton) {2325// Add the Singleton static property.23262327String instance_type_name;23282329if (itype.is_singleton) {2330StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX;2331instance_type_name = obj_types.has(instance_name)2332? obj_types[instance_name].proxy_name2333: "GodotObject";2334} else {2335instance_type_name = itype.proxy_name;2336}23372338output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n");23392340output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n"2341<< INDENT2 "singleton \?\?= (" + instance_type_name + ")"2342<< C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n";2343}23442345if (!itype.is_singleton) {2346// IMPORTANT: We also generate the static fields for GodotObject instead of declaring2347// them manually in the `GodotObject.base.cs` partial class declaration, because they're2348// required by other static fields in this generated partial class declaration.2349// Static fields are initialized in order of declaration, but when they're in different2350// partial class declarations then it becomes harder to tell (Rider warns about this).23512352if (itype.is_instantiable) {2353// Add native constructor static field23542355output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2356<< INDENT1 "private static readonly unsafe delegate* unmanaged<godot_bool, IntPtr> "2357<< CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR2358<< "(" BINDINGS_NATIVE_NAME_FIELD ");\n";2359}23602361if (is_derived_type) {2362// Add default constructor2363if (itype.is_instantiable) {2364output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this("2365<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12366<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2367<< INDENT3 "ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", "2368<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2369<< (itype.is_ref_counted ? "true" : "false") << ");\n"2370<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;2371} else {2372// Hide the constructor2373output << MEMBER_BEGIN "internal " << itype.proxy_name << "() : this("2374<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12375<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2376<< INDENT3 "ConstructAndInitialize(null, "2377<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2378<< (itype.is_ref_counted ? "true" : "false") << ");\n"2379<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;2380}23812382output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this("2383<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12384<< INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n"2385<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2386<< INDENT3 "ConstructAndInitialize(null, "2387<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2388<< (itype.is_ref_counted ? "true" : "false") << ");\n"2389<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;23902391// Add.. em.. trick constructor. Sort of.2392output.append(MEMBER_BEGIN "internal ");2393output.append(itype.proxy_name);2394output.append("(bool " CS_PARAM_MEMORYOWN ") : base(" CS_PARAM_MEMORYOWN ") { }\n");2395}2396}23972398// Methods23992400int method_bind_count = 0;2401for (const MethodInterface &imethod : itype.methods) {2402Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);2403ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2404"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");2405if (imethod.is_internal) {2406// No need to generate span overloads for internal methods.2407continue;2408}24092410method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);2411ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2412"Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");2413}24142415// Signals24162417for (const SignalInterface &isignal : itype.signals_) {2418Error method_err = _generate_cs_signal(itype, isignal, output);2419ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2420"Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'.");2421}24222423// Script members look-up24242425if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) {2426// Generate method names cache fields24272428for (const MethodInterface &imethod : itype.methods) {2429if (!imethod.is_virtual) {2430continue;2431}24322433output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"2434<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2435<< INDENT1 "private static readonly StringName "2436<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2437<< " = \"" << imethod.proxy_name << "\";\n";2438}24392440// Generate signal names cache fields24412442for (const SignalInterface &isignal : itype.signals_) {2443output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"2444<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2445<< INDENT1 "private static readonly StringName "2446<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name2447<< " = \"" << isignal.proxy_name << "\";\n";2448}24492450// TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method24512452// Generate InvokeGodotClassMethod24532454output << MEMBER_BEGIN "/// <summary>\n"2455<< INDENT1 "/// Invokes the method with the given name, using the given arguments.\n"2456<< INDENT1 "/// This method is used by Godot to invoke methods from the engine side.\n"2457<< INDENT1 "/// Do not call or override this method.\n"2458<< INDENT1 "/// </summary>\n"2459<< INDENT1 "/// <param name=\"method\">Name of the method to invoke.</param>\n"2460<< INDENT1 "/// <param name=\"args\">Arguments to use with the invoked method.</param>\n"2461<< INDENT1 "/// <param name=\"ret\">Value returned by the invoked method.</param>\n";24622463// Avoid raising diagnostics because of calls to obsolete methods.2464output << "#pragma warning disable CS0618 // Member is obsolete\n";24652466output << INDENT1 "protected internal " << (is_derived_type ? "override" : "virtual")2467<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "2468<< "NativeVariantPtrArgs args, out godot_variant ret)\n"2469<< INDENT1 "{\n";24702471for (const MethodInterface &imethod : itype.methods) {2472if (!imethod.is_virtual) {2473continue;2474}24752476// We also call HasGodotClassMethod to ensure the method is overridden and avoid calling2477// the stub implementation. This solution adds some extra overhead to calls, but it's2478// much simpler than other solutions. This won't be a problem once we move to function2479// pointers of generated wrappers for each method, as lookup will only happen once.24802481// We check both native names (snake_case) and proxy names (PascalCase)2482output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2483<< " || method == MethodName." << imethod.proxy_name2484<< ") && args.Count == " << itos(imethod.arguments.size())2485<< " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"2486<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"2487<< INDENT2 "{\n";24882489if (imethod.return_type.cname != name_cache.type_void) {2490output << INDENT3 "var callRet = ";2491} else {2492output << INDENT3;2493}24942495output << imethod.proxy_name << "(";24962497int i = 0;2498for (List<BindingsGenerator::ArgumentInterface>::ConstIterator itr = imethod.arguments.begin(); itr != imethod.arguments.end(); ++itr, ++i) {2499const ArgumentInterface &iarg = *itr;25002501const TypeInterface *arg_type = _get_type_or_null(iarg.type);2502ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");25032504if (i != 0) {2505output << ", ";2506}25072508if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {2509String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);25102511output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name) << ")";2512} else {2513output << sformat(arg_type->cs_variant_to_managed,2514"args[" + itos(i) + "]", arg_type->cs_type, arg_type->name);2515}2516}25172518output << ");\n";25192520if (imethod.return_type.cname != name_cache.type_void) {2521const TypeInterface *return_type = _get_type_or_null(imethod.return_type);2522ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");25232524output << INDENT3 "ret = "2525<< sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name)2526<< ";\n"2527<< INDENT3 "return true;\n";2528} else {2529output << INDENT3 "ret = default;\n"2530<< INDENT3 "return true;\n";2531}25322533output << INDENT2 "}\n";2534}25352536if (is_derived_type) {2537output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n";2538} else {2539output << INDENT2 "ret = default;\n"2540<< INDENT2 "return false;\n";2541}25422543output << INDENT1 "}\n";25442545output << "#pragma warning restore CS0618\n";25462547// Generate HasGodotClassMethod25482549output << MEMBER_BEGIN "/// <summary>\n"2550<< INDENT1 "/// Check if the type contains a method with the given name.\n"2551<< INDENT1 "/// This method is used by Godot to check if a method exists before invoking it.\n"2552<< INDENT1 "/// Do not call or override this method.\n"2553<< INDENT1 "/// </summary>\n"2554<< INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n";25552556output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")2557<< " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n"2558<< INDENT1 "{\n";25592560for (const MethodInterface &imethod : itype.methods) {2561if (!imethod.is_virtual) {2562continue;2563}25642565// We check for native names (snake_case). If we detect one, we call HasGodotClassMethod2566// again, but this time with the respective proxy name (PascalCase). It's the job of2567// user derived classes to override the method and check for those. Our C# source2568// generators take care of generating those override methods.2569output << INDENT2 "if (method == MethodName." << imethod.proxy_name2570<< ")\n" INDENT2 "{\n"2571<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "("2572<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2573<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"2574<< INDENT4 "return true;\n"2575<< INDENT3 "}\n" INDENT2 "}\n";2576}25772578if (is_derived_type) {2579output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n";2580} else {2581output << INDENT2 "return false;\n";2582}25832584output << INDENT1 "}\n";25852586// Generate HasGodotClassSignal25872588output << MEMBER_BEGIN "/// <summary>\n"2589<< INDENT1 "/// Check if the type contains a signal with the given name.\n"2590<< INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n"2591<< INDENT1 "/// Do not call or override this method.\n"2592<< INDENT1 "/// </summary>\n"2593<< INDENT1 "/// <param name=\"signal\">Name of the signal to check for.</param>\n";25942595output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")2596<< " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n"2597<< INDENT1 "{\n";25982599for (const SignalInterface &isignal : itype.signals_) {2600// We check for native names (snake_case). If we detect one, we call HasGodotClassSignal2601// again, but this time with the respective proxy name (PascalCase). It's the job of2602// user derived classes to override the method and check for those. Our C# source2603// generators take care of generating those override methods.2604output << INDENT2 "if (signal == SignalName." << isignal.proxy_name2605<< ")\n" INDENT2 "{\n"2606<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_SIGNAL "("2607<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name2608<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"2609<< INDENT4 "return true;\n"2610<< INDENT3 "}\n" INDENT2 "}\n";2611}26122613if (is_derived_type) {2614output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(signal);\n";2615} else {2616output << INDENT2 "return false;\n";2617}26182619output << INDENT1 "}\n";2620}26212622//Generate StringName for all class members2623bool is_inherit = !itype.is_singleton && obj_types.has(itype.base_name);2624//PropertyName2625output << MEMBER_BEGIN "/// <summary>\n"2626<< INDENT1 "/// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n"2627<< INDENT1 "/// </summary>\n";2628if (is_inherit) {2629output << INDENT1 "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName";2630} else {2631output << INDENT1 "public class PropertyName";2632}2633output << "\n"2634<< INDENT1 "{\n";2635for (const PropertyInterface &iprop : itype.properties) {2636output << INDENT2 "/// <summary>\n"2637<< INDENT2 "/// Cached name for the '" << iprop.cname << "' property.\n"2638<< INDENT2 "/// </summary>\n"2639<< INDENT2 "public static "2640<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".PropertyName." + iprop.proxy_name) ? "new " : "")2641<< "readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n";2642}2643output << INDENT1 "}\n";2644//MethodName2645output << MEMBER_BEGIN "/// <summary>\n"2646<< INDENT1 "/// Cached StringNames for the methods contained in this class, for fast lookup.\n"2647<< INDENT1 "/// </summary>\n";2648if (is_inherit) {2649output << INDENT1 "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName";2650} else {2651output << INDENT1 "public class MethodName";2652}2653output << "\n"2654<< INDENT1 "{\n";2655HashMap<String, StringName> method_names;2656for (const MethodInterface &imethod : itype.methods) {2657if (method_names.has(imethod.proxy_name)) {2658ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value.");2659continue;2660}2661method_names[imethod.proxy_name] = imethod.cname;2662output << INDENT2 "/// <summary>\n"2663<< INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n"2664<< INDENT2 "/// </summary>\n"2665<< INDENT2 "public static "2666<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".MethodName." + imethod.proxy_name) ? "new " : "")2667<< "readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n";2668}2669output << INDENT1 "}\n";2670//SignalName2671output << MEMBER_BEGIN "/// <summary>\n"2672<< INDENT1 "/// Cached StringNames for the signals contained in this class, for fast lookup.\n"2673<< INDENT1 "/// </summary>\n";2674if (is_inherit) {2675output << INDENT1 "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName";2676} else {2677output << INDENT1 "public class SignalName";2678}2679output << "\n"2680<< INDENT1 "{\n";2681for (const SignalInterface &isignal : itype.signals_) {2682output << INDENT2 "/// <summary>\n"2683<< INDENT2 "/// Cached name for the '" << isignal.cname << "' signal.\n"2684<< INDENT2 "/// </summary>\n"2685<< INDENT2 "public static "2686<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".SignalName." + isignal.proxy_name) ? "new " : "")2687<< "readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n";2688}2689output << INDENT1 "}\n";26902691output.append(CLOSE_BLOCK /* class */);26922693return _save_file(p_output_file, output);2694}26952696Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) {2697const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter);26982699// Search it in base types too2700const TypeInterface *current_type = &p_itype;2701while (!setter && current_type->base_name != StringName()) {2702HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);2703ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");2704current_type = &base_match->value;2705setter = current_type->find_method_by_name(p_iprop.setter);2706}27072708const MethodInterface *getter = p_itype.find_method_by_name(p_iprop.getter);27092710// Search it in base types too2711current_type = &p_itype;2712while (!getter && current_type->base_name != StringName()) {2713HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);2714ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");2715current_type = &base_match->value;2716getter = current_type->find_method_by_name(p_iprop.getter);2717}27182719ERR_FAIL_COND_V(!setter && !getter, ERR_BUG);27202721if (setter) {2722int setter_argc = p_iprop.index != -1 ? 2 : 1;2723ERR_FAIL_COND_V(setter->arguments.size() != setter_argc, ERR_BUG);2724}27252726if (getter) {2727int getter_argc = p_iprop.index != -1 ? 1 : 0;2728ERR_FAIL_COND_V(getter->arguments.size() != getter_argc, ERR_BUG);2729}27302731if (getter && setter) {2732const ArgumentInterface &setter_first_arg = setter->arguments.back()->get();2733if (getter->return_type.cname != setter_first_arg.type.cname) {2734ERR_FAIL_V_MSG(ERR_BUG,2735"Return type from getter doesn't match first argument of setter for property: '" +2736p_itype.name + "." + String(p_iprop.cname) + "'.");2737}2738}27392740const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;27412742const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name);2743ERR_FAIL_NULL_V_MSG(prop_itype, ERR_BUG, "Property type '" + proptype_name.cname + "' was not found.");27442745ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,2746"Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'.");27472748if (p_itype.api_type == ClassDB::API_CORE) {2749ERR_FAIL_COND_V_MSG(prop_itype->api_type == ClassDB::API_EDITOR, ERR_BUG,2750"Property '" + p_itype.name + "." + String(p_iprop.cname) + "' has type '" + prop_itype->name +2751"' from the editor API. Core API cannot have dependencies on the editor API.");2752}27532754if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) {2755String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype);2756Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();27572758if (summary_lines.size()) {2759p_output.append(MEMBER_BEGIN "/// <summary>\n");27602761for (int i = 0; i < summary_lines.size(); i++) {2762p_output.append(INDENT1 "/// ");2763p_output.append(summary_lines[i]);2764p_output.append("\n");2765}27662767p_output.append(INDENT1 "/// </summary>");2768}2769}27702771if (p_iprop.is_deprecated) {2772p_output.append(MEMBER_BEGIN "[Obsolete(\"");2773p_output.append(bbcode_to_text(p_iprop.deprecation_message, &p_itype));2774p_output.append("\")]");2775}27762777if (p_iprop.is_hidden) {2778p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");2779}27802781p_output.append(MEMBER_BEGIN "public ");27822783if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) {2784p_output.append("new ");2785}27862787if (p_itype.is_singleton) {2788p_output.append("static ");2789}27902791String prop_cs_type = prop_itype->cs_type + _get_generic_type_parameters(*prop_itype, proptype_name.generic_type_parameters);27922793p_output.append(prop_cs_type);2794p_output.append(" ");2795p_output.append(p_iprop.proxy_name);2796p_output.append("\n" OPEN_BLOCK_L1);27972798if (getter) {2799p_output.append(INDENT2 "get\n" OPEN_BLOCK_L2 INDENT3);28002801p_output.append("return ");2802p_output.append(getter->proxy_name + "(");2803if (p_iprop.index != -1) {2804const ArgumentInterface &idx_arg = getter->arguments.front()->get();2805if (idx_arg.type.cname != name_cache.type_int) {2806// Assume the index parameter is an enum2807const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);2808CRASH_COND(idx_arg_type == nullptr);2809p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + ")");2810} else {2811p_output.append(itos(p_iprop.index));2812}2813}2814p_output.append(");\n" CLOSE_BLOCK_L2);2815}28162817if (setter) {2818p_output.append(INDENT2 "set\n" OPEN_BLOCK_L2 INDENT3);28192820p_output.append(setter->proxy_name + "(");2821if (p_iprop.index != -1) {2822const ArgumentInterface &idx_arg = setter->arguments.front()->get();2823if (idx_arg.type.cname != name_cache.type_int) {2824// Assume the index parameter is an enum2825const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);2826CRASH_COND(idx_arg_type == nullptr);2827p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + "), ");2828} else {2829p_output.append(itos(p_iprop.index) + ", ");2830}2831}2832p_output.append("value);\n" CLOSE_BLOCK_L2);2833}28342835p_output.append(CLOSE_BLOCK_L1);28362837return OK;2838}28392840Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span) {2841const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);2842ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");28432844ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,2845"Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'.");28462847if (p_itype.api_type == ClassDB::API_CORE) {2848ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG,2849"Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name +2850"' from the editor API. Core API cannot have dependencies on the editor API.");2851}28522853if (p_imethod.is_virtual && p_use_span) {2854return OK;2855}28562857bool has_span_argument = false;28582859if (p_use_span) {2860if (p_imethod.is_vararg) {2861has_span_argument = true;2862} else {2863for (const ArgumentInterface &iarg : p_imethod.arguments) {2864const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);2865ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");28662867if (arg_type->is_span_compatible) {2868has_span_argument = true;2869break;2870}2871}2872}28732874if (has_span_argument) {2875// Span overloads use the same method bind as the array overloads.2876// Since both overloads are generated one after the other, we can decrease the count here2877// to ensure the span overload uses the same method bind.2878p_method_bind_count--;2879}2880}28812882String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);28832884String arguments_sig;2885StringBuilder cs_in_statements;2886bool cs_in_expr_is_unsafe = false;28872888String icall_params = method_bind_field;28892890if (!p_imethod.is_static) {2891String self_reference = "this";2892if (p_itype.is_singleton) {2893self_reference = CS_PROPERTY_SINGLETON;2894}28952896if (p_itype.cs_in.size()) {2897cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference,2898String(), String(), String(), INDENT2);2899}29002901icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference);2902}29032904StringBuilder default_args_doc;29052906// Retrieve information from the arguments2907const ArgumentInterface &first = p_imethod.arguments.front()->get();2908for (const ArgumentInterface &iarg : p_imethod.arguments) {2909const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);2910ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");29112912ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,2913"Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");29142915if (p_itype.api_type == ClassDB::API_CORE) {2916ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,2917"Argument '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "' has type '" +2918arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");2919}29202921if (iarg.default_argument.size()) {2922CRASH_COND_MSG(!_arg_default_value_is_assignable_to_type(iarg.def_param_value, *arg_type),2923"Invalid default value for parameter '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");2924}29252926String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);29272928bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;29292930// Add the current arguments to the signature2931// If the argument has a default value which is not a constant, we will make it Nullable2932{2933if (&iarg != &first) {2934arguments_sig += ", ";2935}29362937if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2938arguments_sig += "Nullable<";2939}29402941if (use_span_for_arg) {2942arguments_sig += arg_type->c_type_in;2943} else {2944arguments_sig += arg_cs_type;2945}29462947if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2948arguments_sig += "> ";2949} else {2950arguments_sig += " ";2951}29522953arguments_sig += iarg.name;29542955if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {2956if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {2957arguments_sig += " = null";2958} else {2959arguments_sig += " = " + sformat(iarg.default_argument, arg_type->cs_type);2960}2961}2962}29632964icall_params += ", ";29652966if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {2967// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:2968// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;2969String arg_or_defval_local = iarg.name;2970arg_or_defval_local += "OrDefVal";29712972cs_in_statements << INDENT2 << arg_cs_type << " " << arg_or_defval_local << " = " << iarg.name;29732974if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2975cs_in_statements << ".HasValue ? ";2976} else {2977cs_in_statements << " != null ? ";2978}29792980cs_in_statements << iarg.name;29812982if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2983cs_in_statements << ".Value : ";2984} else {2985cs_in_statements << " : ";2986}29872988String cs_type = arg_cs_type;2989if (cs_type.ends_with("[]")) {2990cs_type = cs_type.substr(0, cs_type.length() - 2);2991}29922993String def_arg = sformat(iarg.default_argument, cs_type);29942995cs_in_statements << def_arg << ";\n";29962997if (arg_type->cs_in.size()) {2998cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, arg_or_defval_local,2999String(), String(), String(), INDENT2);3000}30013002if (arg_type->cs_in_expr.is_empty()) {3003icall_params += arg_or_defval_local;3004} else {3005icall_params += sformat(arg_type->cs_in_expr, arg_or_defval_local, arg_type->c_type);3006}30073008// Apparently the name attribute must not include the @3009String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1) : iarg.name;3010// Escape < and > in the attribute default value3011String param_def_arg = def_arg.replacen("<", "<").replacen(">", ">");30123013default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is <c>" + param_def_arg + "</c>.</param>");3014} else {3015if (arg_type->cs_in.size()) {3016cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, iarg.name,3017String(), String(), String(), INDENT2);3018}30193020icall_params += arg_type->cs_in_expr.is_empty() ? iarg.name : sformat(arg_type->cs_in_expr, iarg.name, arg_type->c_type);3021}30223023cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;3024}30253026if (p_use_span && !has_span_argument) {3027return OK;3028}30293030// Collect caller name for MethodBind3031if (p_imethod.is_vararg) {3032icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";3033}30343035// Generate method3036{3037if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {3038p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"3039<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";30403041if (p_itype.is_singleton) {3042// Singletons are static classes. They don't derive GodotObject,3043// so we need to specify the type to call the static method.3044p_output << "GodotObject.";3045}30463047p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName."3048<< p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul"3049<< ");\n";3050}30513052if (p_imethod.method_doc && p_imethod.method_doc->description.size()) {3053String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype);3054Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();30553056if (summary_lines.size()) {3057p_output.append(MEMBER_BEGIN "/// <summary>\n");30583059for (int i = 0; i < summary_lines.size(); i++) {3060p_output.append(INDENT1 "/// ");3061p_output.append(summary_lines[i]);3062p_output.append("\n");3063}30643065p_output.append(INDENT1 "/// </summary>");3066}3067}30683069if (default_args_doc.get_string_length()) {3070p_output.append(default_args_doc.as_string());3071}30723073if (p_imethod.is_deprecated) {3074p_output.append(MEMBER_BEGIN "[Obsolete(\"");3075p_output.append(bbcode_to_text(p_imethod.deprecation_message, &p_itype));3076p_output.append("\")]");3077}30783079if (p_imethod.is_hidden) {3080p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");3081}30823083p_output.append(MEMBER_BEGIN);3084p_output.append(p_imethod.is_internal ? "internal " : "public ");30853086if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_imethod.proxy_name)) {3087p_output.append("new ");3088}30893090if (p_itype.is_singleton || p_imethod.is_static) {3091p_output.append("static ");3092} else if (p_imethod.is_virtual) {3093p_output.append("virtual ");3094}30953096if (cs_in_expr_is_unsafe) {3097p_output.append("unsafe ");3098}30993100String return_cs_type = return_type->cs_type + _get_generic_type_parameters(*return_type, p_imethod.return_type.generic_type_parameters);31013102p_output.append(return_cs_type + " ");3103p_output.append(p_imethod.proxy_name + "(");3104p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L1);31053106if (p_imethod.is_virtual) {3107// Godot virtual method must be overridden, therefore we return a default value by default.31083109if (return_type->cname == name_cache.type_void) {3110p_output.append(CLOSE_BLOCK_L1);3111} else {3112p_output.append(INDENT2 "return default;\n" CLOSE_BLOCK_L1);3113}31143115return OK; // Won't increment method bind count3116}31173118if (p_imethod.requires_object_call) {3119// Fallback to Godot's object.Call(string, params)31203121p_output.append(INDENT2 CS_METHOD_CALL "(");3122p_output.append("MethodName." + p_imethod.proxy_name);31233124for (const ArgumentInterface &iarg : p_imethod.arguments) {3125p_output.append(", ");3126p_output.append(iarg.name);3127}31283129p_output.append(");\n" CLOSE_BLOCK_L1);31303131return OK; // Won't increment method bind count3132}31333134HashMap<const MethodInterface *, const InternalCall *>::ConstIterator match = method_icalls_map.find(&p_imethod);3135ERR_FAIL_NULL_V(match, ERR_BUG);31363137const InternalCall *im_icall = match->value;31383139String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS;3140im_call += ".";3141im_call += im_icall->name;31423143if (p_imethod.arguments.size() && cs_in_statements.get_string_length() > 0) {3144p_output.append(cs_in_statements.as_string());3145}31463147if (return_type->cname == name_cache.type_void) {3148p_output << INDENT2 << im_call << "(" << icall_params << ");\n";3149} else if (return_type->cs_out.is_empty()) {3150p_output << INDENT2 "return " << im_call << "(" << icall_params << ");\n";3151} else {3152p_output.append(sformat(return_type->cs_out, im_call, icall_params,3153return_cs_type, return_type->c_type_out, String(), INDENT2));3154p_output.append("\n");3155}31563157p_output.append(CLOSE_BLOCK_L1);3158}31593160p_method_bind_count++;31613162return OK;3163}31643165Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {3166String arguments_sig;31673168// Retrieve information from the arguments3169const ArgumentInterface &first = p_isignal.arguments.front()->get();3170for (const ArgumentInterface &iarg : p_isignal.arguments) {3171const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);3172ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");31733174ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,3175"Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'.");31763177if (p_itype.api_type == ClassDB::API_CORE) {3178ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,3179"Argument '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "' has type '" +3180arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");3181}31823183// Add the current arguments to the signature31843185if (&iarg != &first) {3186arguments_sig += ", ";3187}31883189String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);31903191arguments_sig += arg_cs_type;3192arguments_sig += " ";3193arguments_sig += iarg.name;3194}31953196// Generate signal3197{3198bool is_parameterless = p_isignal.arguments.is_empty();31993200// Delegate name is [SignalName]EventHandler3201String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";32023203if (!is_parameterless) {3204p_output.append(MEMBER_BEGIN "/// <summary>\n");3205p_output.append(INDENT1 "/// ");3206p_output.append("Represents the method that handles the ");3207p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");3208p_output.append(" event of a ");3209p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");3210p_output.append(" class.\n");3211p_output.append(INDENT1 "/// </summary>");32123213// Generate delegate3214if (p_isignal.is_deprecated) {3215p_output.append(MEMBER_BEGIN "[Obsolete(\"");3216p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3217p_output.append("\")]");3218}3219p_output.append(MEMBER_BEGIN "public delegate void ");3220p_output.append(delegate_name);3221p_output.append("(");3222p_output.append(arguments_sig);3223p_output.append(");\n");32243225// Generate Callable trampoline for the delegate3226if (p_isignal.is_deprecated) {3227p_output.append(MEMBER_BEGIN "[Obsolete(\"");3228p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3229p_output.append("\")]");3230}3231p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline"3232<< "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"3233<< INDENT1 "{\n"3234<< INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"3235<< INDENT2 "((" << delegate_name << ")delegateObj)(";32363237int idx = 0;3238for (const ArgumentInterface &iarg : p_isignal.arguments) {3239const TypeInterface *arg_type = _get_type_or_null(iarg.type);3240ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");32413242if (idx != 0) {3243p_output << ", ";3244}32453246if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {3247String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);32483249p_output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name) << ")";3250} else {3251p_output << sformat(arg_type->cs_variant_to_managed,3252"args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name);3253}32543255idx++;3256}32573258p_output << ");\n"3259<< INDENT2 "ret = default;\n"3260<< INDENT1 "}\n";3261}32623263if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {3264String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);3265Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();32663267if (summary_lines.size()) {3268p_output.append(MEMBER_BEGIN "/// <summary>\n");32693270for (int i = 0; i < summary_lines.size(); i++) {3271p_output.append(INDENT1 "/// ");3272p_output.append(summary_lines[i]);3273p_output.append("\n");3274}32753276p_output.append(INDENT1 "/// </summary>");3277}3278}32793280// TODO:3281// Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded?3282// If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here.32833284// Generate event3285if (p_isignal.is_deprecated) {3286p_output.append(MEMBER_BEGIN "[Obsolete(\"");3287p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3288p_output.append("\")]");3289}3290p_output.append(MEMBER_BEGIN "public ");32913292if (p_itype.is_singleton) {3293p_output.append("static ");3294}32953296if (!is_parameterless) {3297// `unsafe` is needed for taking the trampoline's function pointer3298p_output << "unsafe ";3299}33003301p_output.append("event ");3302p_output.append(delegate_name);3303p_output.append(" ");3304p_output.append(p_isignal.proxy_name);3305p_output.append("\n" OPEN_BLOCK_L1 INDENT2);33063307if (p_itype.is_singleton) {3308p_output.append("add => " CS_PROPERTY_SINGLETON ".Connect(SignalName.");3309} else {3310p_output.append("add => Connect(SignalName.");3311}33123313if (is_parameterless) {3314// Delegate type is Action. No need for custom trampoline.3315p_output << p_isignal.proxy_name << ", Callable.From(value));\n";3316} else {3317p_output << p_isignal.proxy_name3318<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";3319}33203321if (p_itype.is_singleton) {3322p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName.");3323} else {3324p_output.append(INDENT2 "remove => Disconnect(SignalName.");3325}33263327if (is_parameterless) {3328// Delegate type is Action. No need for custom trampoline.3329p_output << p_isignal.proxy_name << ", Callable.From(value));\n";3330} else {3331p_output << p_isignal.proxy_name3332<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";3333}33343335p_output.append(CLOSE_BLOCK_L1);33363337// Generate EmitSignal{EventName} method to raise the event.3338if (!p_itype.is_singleton) {3339if (p_isignal.is_deprecated) {3340p_output.append(MEMBER_BEGIN "[Obsolete(\"");3341p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3342p_output.append("\")]");3343}3344p_output.append(MEMBER_BEGIN "protected void ");3345p_output << "EmitSignal" << p_isignal.proxy_name;3346if (is_parameterless) {3347p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);3348p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";3349p_output.append(CLOSE_BLOCK_L1);3350} else {3351p_output.append("(");33523353StringBuilder cs_emitsignal_params;33543355int idx = 0;3356for (const ArgumentInterface &iarg : p_isignal.arguments) {3357const TypeInterface *arg_type = _get_type_or_null(iarg.type);3358ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");33593360if (idx != 0) {3361p_output << ", ";3362cs_emitsignal_params << ", ";3363}33643365String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);33663367p_output << arg_cs_type << " " << iarg.name;33683369if (arg_type->is_enum) {3370cs_emitsignal_params << "(long)";3371}33723373cs_emitsignal_params << iarg.name;33743375idx++;3376}33773378p_output.append(")\n" OPEN_BLOCK_L1 INDENT2);3379p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ", " << cs_emitsignal_params << ");\n";3380p_output.append(CLOSE_BLOCK_L1);3381}3382}3383}33843385return OK;3386}33873388Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output) {3389bool ret_void = p_icall.return_type.cname == name_cache.type_void;33903391const TypeInterface *return_type = _get_type_or_null(p_icall.return_type);3392ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_icall.return_type.cname + "' was not found.");33933394StringBuilder c_func_sig;3395StringBuilder c_in_statements;3396StringBuilder c_args_var_content;33973398c_func_sig << "IntPtr " CS_PARAM_METHODBIND;33993400if (!p_icall.is_static) {3401c_func_sig += ", IntPtr " CS_PARAM_INSTANCE;3402}34033404// Get arguments information3405int i = 0;3406for (const TypeReference &arg_type_ref : p_icall.argument_types) {3407const TypeInterface *arg_type = _get_type_or_null(arg_type_ref);3408ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + arg_type_ref.cname + "' was not found.");34093410String c_param_name = "arg" + itos(i + 1);34113412if (p_icall.is_vararg) {3413if (i < p_icall.get_arguments_count() - 1) {3414String c_in_vararg = arg_type->c_in_vararg;34153416if (arg_type->is_object_type) {3417c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObjectPtr(%1);\n";3418}34193420ERR_FAIL_COND_V_MSG(c_in_vararg.is_empty(), ERR_BUG,3421"VarArg support not implemented for parameter type: " + arg_type->name);34223423c_in_statements3424<< sformat(c_in_vararg, return_type->c_type, c_param_name,3425String(), String(), String(), INDENT3)3426<< INDENT3 C_LOCAL_PTRCALL_ARGS "[" << itos(i)3427<< "] = new IntPtr(&" << c_param_name << "_in);\n";3428}3429} else {3430if (i > 0) {3431c_args_var_content << ", ";3432}3433if (arg_type->c_in.size()) {3434c_in_statements << sformat(arg_type->c_in, arg_type->c_type, c_param_name,3435String(), String(), String(), INDENT2);3436}3437c_args_var_content << sformat(arg_type->c_arg_in, c_param_name);3438}34393440c_func_sig << ", " << arg_type->c_type_in << " " << c_param_name;34413442i++;3443}34443445// Collect caller name for MethodBind3446if (p_icall.is_vararg) {3447c_func_sig << ", godot_string_name caller";3448}34493450String icall_method = p_icall.name;34513452// Generate icall function34533454r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " "3455<< icall_method << "(" << c_func_sig.as_string() << ")\n" OPEN_BLOCK_L1;34563457if (!p_icall.is_static) {3458r_output << INDENT2 "ExceptionUtils.ThrowIfNullPtr(" CS_PARAM_INSTANCE ");\n";3459}34603461if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) {3462String ptrcall_return_type;3463String initialization;34643465if (return_type->is_object_type) {3466ptrcall_return_type = return_type->is_ref_counted ? "godot_ref" : return_type->c_type;3467initialization = " = default";3468} else {3469ptrcall_return_type = return_type->c_type;3470}34713472r_output << INDENT2;34733474if (return_type->is_ref_counted || return_type->c_type_is_disposable_struct) {3475r_output << "using ";34763477if (initialization.is_empty()) {3478initialization = " = default";3479}3480} else if (return_type->c_ret_needs_default_initialization) {3481initialization = " = default";3482}34833484r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n";3485}34863487String argc_str = itos(p_icall.get_arguments_count());34883489auto generate_call_and_return_stmts = [&](const char *base_indent) {3490if (p_icall.is_vararg) {3491// MethodBind Call3492r_output << base_indent;34933494// VarArg methods always return Variant, but there are some cases in which MethodInfo provides3495// a specific return type. We trust this information is valid. We need a temporary local to keep3496// the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr,3497// it could be deleted too early. This is the case with GDScript.new() which returns OBJECT.3498// Alternatively, we could just return Variant, but that would result in a worse API.34993500if (!ret_void) {3501if (return_type->cname != name_cache.type_Variant) {3502// Usually the return value takes ownership, but in this case the variant is only used3503// for conversion to another return type. As such, the local variable takes ownership.3504r_output << "using godot_variant " << C_LOCAL_VARARG_RET " = ";3505} else {3506// Variant's [c_out] takes ownership of the variant value3507r_output << "godot_variant " << C_LOCAL_RET " = ";3508}3509}35103511r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("3512<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3513<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")3514<< ", total_length, out godot_variant_call_error vcall_error);\n";35153516r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"3517<< ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3518<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")3519<< ", total_length, vcall_error);\n";35203521if (!ret_void) {3522if (return_type->cname != name_cache.type_Variant) {3523if (return_type->cname == name_cache.enum_Error) {3524r_output << base_indent << C_LOCAL_RET " = VariantUtils.ConvertToInt64(" C_LOCAL_VARARG_RET ");\n";3525} else {3526// TODO: Use something similar to c_in_vararg (see usage above, with error if not implemented)3527CRASH_NOW_MSG("Custom VarArg return type not implemented: " + return_type->name);3528r_output << base_indent << C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n";3529}3530}3531}3532} else {3533// MethodBind PtrCall3534r_output << base_indent << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_ptrcall("3535<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3536<< ", " << (p_icall.get_arguments_count() ? C_LOCAL_PTRCALL_ARGS : "null")3537<< ", " << (!ret_void ? "&" C_LOCAL_RET ");\n" : "null);\n");3538}35393540// Return statement35413542if (!ret_void) {3543if (return_type->c_out.is_empty()) {3544r_output << base_indent << "return " C_LOCAL_RET ";\n";3545} else {3546r_output << sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET,3547return_type->name, String(), String(), base_indent);3548}3549}3550};35513552if (p_icall.get_arguments_count()) {3553if (p_icall.is_vararg) {3554String vararg_arg = "arg" + argc_str;3555String real_argc_str = itos(p_icall.get_arguments_count() - 1); // Arguments count without vararg35563557p_icall.get_arguments_count();35583559r_output << INDENT2 "int vararg_length = " << vararg_arg << ".Length;\n"3560<< INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n";35613562r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n"3563<< INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold] :\n"3564<< INDENT3 "new godot_variant.movable[vararg_length];\n";35653566r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n"3567<< INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n"3568<< INDENT3 "new IntPtr[total_length];\n";35693570r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n"3571<< INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = "3572"&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n"3573<< OPEN_BLOCK_L2;35743575r_output << c_in_statements.as_string();35763577r_output << INDENT3 "for (int i = 0; i < vararg_length; i++)\n" OPEN_BLOCK_L33578<< INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n"3579<< INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n"3580<< CLOSE_BLOCK_L3;35813582generate_call_and_return_stmts(INDENT3);35833584r_output << CLOSE_BLOCK_L2;3585} else {3586r_output << c_in_statements.as_string();35873588r_output << INDENT2 "void** " C_LOCAL_PTRCALL_ARGS " = stackalloc void*["3589<< argc_str << "] { " << c_args_var_content.as_string() << " };\n";35903591generate_call_and_return_stmts(INDENT2);3592}3593} else {3594generate_call_and_return_stmts(INDENT2);3595}35963597r_output << CLOSE_BLOCK_L1;35983599return OK;3600}36013602Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) {3603Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);3604ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'.");36053606file->store_string(p_content.as_string());36073608return OK;3609}36103611const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(const TypeReference &p_typeref) {3612HashMap<StringName, TypeInterface>::ConstIterator builtin_type_match = builtin_types.find(p_typeref.cname);36133614if (builtin_type_match) {3615return &builtin_type_match->value;3616}36173618HashMap<StringName, TypeInterface>::ConstIterator obj_type_match = obj_types.find(p_typeref.cname);36193620if (obj_type_match) {3621return &obj_type_match->value;3622}36233624if (p_typeref.is_enum) {3625HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(p_typeref.cname);36263627if (enum_match) {3628return &enum_match->value;3629}36303631// Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.3632HashMap<StringName, TypeInterface>::ConstIterator int_match = builtin_types.find(name_cache.type_int);3633ERR_FAIL_NULL_V(int_match, nullptr);3634return &int_match->value;3635}36363637return nullptr;3638}36393640const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) {3641const TypeInterface *itype = _get_type_or_null(p_typeref);3642if (itype == nullptr) {3643return nullptr;3644}36453646if (itype->is_singleton) {3647StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX;3648itype = &obj_types.find(instance_type_name)->value;3649}36503651return itype;3652}36533654const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) {3655if (p_generic_type_parameters.is_empty()) {3656return "";3657}36583659ERR_FAIL_COND_V_MSG(p_itype.type_parameter_count != p_generic_type_parameters.size(), "",3660"Generic type parameter count mismatch for type '" + p_itype.name + "'." +3661" Found " + itos(p_generic_type_parameters.size()) + ", but requires " +3662itos(p_itype.type_parameter_count) + ".");36633664int i = 0;3665String params = "<";3666for (const TypeReference ¶m_type : p_generic_type_parameters) {3667const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type);3668ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found.");36693670ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "",3671"Generic type parameter is a singleton: '" + param_itype->name + "'.");36723673if (p_itype.api_type == ClassDB::API_CORE) {3674ERR_FAIL_COND_V_MSG(param_itype->api_type == ClassDB::API_EDITOR, "",3675"Generic type parameter '" + param_itype->name + "' has type from the editor API." +3676" Core API cannot have dependencies on the editor API.");3677}36783679params += param_itype->cs_type;3680if (i < p_generic_type_parameters.size() - 1) {3681params += ", ";3682}36833684i++;3685}3686params += ">";36873688return params;3689}36903691StringName BindingsGenerator::_get_type_name_from_meta(Variant::Type p_type, GodotTypeInfo::Metadata p_meta) {3692if (p_type == Variant::INT) {3693return _get_int_type_name_from_meta(p_meta);3694} else if (p_type == Variant::FLOAT) {3695return _get_float_type_name_from_meta(p_meta);3696} else {3697return Variant::get_type_name(p_type);3698}3699}37003701StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {3702switch (p_meta) {3703case GodotTypeInfo::METADATA_INT_IS_INT8:3704return "sbyte";3705break;3706case GodotTypeInfo::METADATA_INT_IS_INT16:3707return "short";3708break;3709case GodotTypeInfo::METADATA_INT_IS_INT32:3710return "int";3711break;3712case GodotTypeInfo::METADATA_INT_IS_INT64:3713return "long";3714break;3715case GodotTypeInfo::METADATA_INT_IS_UINT8:3716return "byte";3717break;3718case GodotTypeInfo::METADATA_INT_IS_UINT16:3719return "ushort";3720break;3721case GodotTypeInfo::METADATA_INT_IS_UINT32:3722return "uint";3723break;3724case GodotTypeInfo::METADATA_INT_IS_UINT64:3725return "ulong";3726break;3727case GodotTypeInfo::METADATA_INT_IS_CHAR16:3728return "char";3729break;3730case GodotTypeInfo::METADATA_INT_IS_CHAR32:3731// To prevent breaking compatibility, C# bindings need to keep using `long`.3732return "long";3733default:3734// Assume INT643735return "long";3736}3737}37383739StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {3740switch (p_meta) {3741case GodotTypeInfo::METADATA_REAL_IS_FLOAT:3742return "float";3743break;3744case GodotTypeInfo::METADATA_REAL_IS_DOUBLE:3745return "double";3746break;3747default:3748// Assume FLOAT643749return "double";3750}3751}37523753bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant &p_val, const TypeInterface &p_arg_type) {3754if (p_arg_type.name == name_cache.type_Variant) {3755// Variant can take anything3756return true;3757}37583759switch (p_val.get_type()) {3760case Variant::NIL:3761return p_arg_type.is_object_type ||3762name_cache.is_nullable_type(p_arg_type.name);3763case Variant::BOOL:3764return p_arg_type.name == name_cache.type_bool;3765case Variant::INT:3766return p_arg_type.name == name_cache.type_sbyte ||3767p_arg_type.name == name_cache.type_short ||3768p_arg_type.name == name_cache.type_int ||3769p_arg_type.name == name_cache.type_byte ||3770p_arg_type.name == name_cache.type_ushort ||3771p_arg_type.name == name_cache.type_uint ||3772p_arg_type.name == name_cache.type_long ||3773p_arg_type.name == name_cache.type_ulong ||3774p_arg_type.name == name_cache.type_float ||3775p_arg_type.name == name_cache.type_double ||3776p_arg_type.is_enum;3777case Variant::FLOAT:3778return p_arg_type.name == name_cache.type_float ||3779p_arg_type.name == name_cache.type_double;3780case Variant::STRING:3781case Variant::STRING_NAME:3782return p_arg_type.name == name_cache.type_String ||3783p_arg_type.name == name_cache.type_StringName ||3784p_arg_type.name == name_cache.type_NodePath;3785case Variant::NODE_PATH:3786return p_arg_type.name == name_cache.type_NodePath;3787case Variant::TRANSFORM2D:3788case Variant::TRANSFORM3D:3789case Variant::BASIS:3790case Variant::QUATERNION:3791case Variant::PLANE:3792case Variant::AABB:3793case Variant::COLOR:3794case Variant::VECTOR2:3795case Variant::RECT2:3796case Variant::VECTOR3:3797case Variant::VECTOR4:3798case Variant::PROJECTION:3799case Variant::RID:3800case Variant::PACKED_BYTE_ARRAY:3801case Variant::PACKED_INT32_ARRAY:3802case Variant::PACKED_INT64_ARRAY:3803case Variant::PACKED_FLOAT32_ARRAY:3804case Variant::PACKED_FLOAT64_ARRAY:3805case Variant::PACKED_STRING_ARRAY:3806case Variant::PACKED_VECTOR2_ARRAY:3807case Variant::PACKED_VECTOR3_ARRAY:3808case Variant::PACKED_VECTOR4_ARRAY:3809case Variant::PACKED_COLOR_ARRAY:3810case Variant::CALLABLE:3811case Variant::SIGNAL:3812return p_arg_type.name == Variant::get_type_name(p_val.get_type());3813case Variant::ARRAY:3814return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Array_generic;3815case Variant::DICTIONARY:3816return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Dictionary_generic;3817case Variant::OBJECT:3818return p_arg_type.is_object_type;3819case Variant::VECTOR2I:3820return p_arg_type.name == name_cache.type_Vector2 ||3821p_arg_type.name == Variant::get_type_name(p_val.get_type());3822case Variant::RECT2I:3823return p_arg_type.name == name_cache.type_Rect2 ||3824p_arg_type.name == Variant::get_type_name(p_val.get_type());3825case Variant::VECTOR3I:3826return p_arg_type.name == name_cache.type_Vector3 ||3827p_arg_type.name == Variant::get_type_name(p_val.get_type());3828case Variant::VECTOR4I:3829return p_arg_type.name == name_cache.type_Vector4 ||3830p_arg_type.name == Variant::get_type_name(p_val.get_type());3831case Variant::VARIANT_MAX:3832CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type()));3833break;3834}38353836return false;3837}38383839bool method_has_ptr_parameter(MethodInfo p_method_info) {3840if (p_method_info.return_val.type == Variant::INT && p_method_info.return_val.hint == PROPERTY_HINT_INT_IS_POINTER) {3841return true;3842}3843for (PropertyInfo arg : p_method_info.arguments) {3844if (arg.type == Variant::INT && arg.hint == PROPERTY_HINT_INT_IS_POINTER) {3845return true;3846}3847}3848return false;3849}38503851struct SortMethodWithHashes {3852_FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const {3853return p_a.first < p_b.first;3854}3855};38563857bool BindingsGenerator::_populate_object_type_interfaces() {3858obj_types.clear();38593860List<StringName> class_list;3861ClassDB::get_class_list(&class_list);3862class_list.sort_custom<StringName::AlphCompare>();38633864while (class_list.size()) {3865StringName type_cname = class_list.front()->get();38663867ClassDB::APIType api_type = ClassDB::get_api_type(type_cname);38683869if (api_type == ClassDB::API_NONE) {3870class_list.pop_front();3871continue;3872}38733874if (ignored_types.has(type_cname)) {3875_log("Ignoring type '%s' because it's in the list of ignored types\n", String(type_cname).utf8().get_data());3876class_list.pop_front();3877continue;3878}38793880if (!ClassDB::is_class_exposed(type_cname)) {3881_log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data());3882class_list.pop_front();3883continue;3884}38853886if (!ClassDB::is_class_enabled(type_cname)) {3887_log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data());3888class_list.pop_front();3889continue;3890}38913892ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname);38933894TypeInterface itype = TypeInterface::create_object_type(type_cname, pascal_to_pascal_case(type_cname), api_type);38953896itype.base_name = ClassDB::get_parent_class(type_cname);3897itype.is_singleton = Engine::get_singleton()->has_singleton(type_cname);3898itype.is_instantiable = class_info->creation_func && !itype.is_singleton;3899itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);3900itype.memory_own = itype.is_ref_counted;39013902if (itype.class_doc) {3903itype.is_deprecated = itype.class_doc->is_deprecated;3904itype.deprecation_message = itype.class_doc->deprecated_message;39053906if (itype.is_deprecated && itype.deprecation_message.is_empty()) {3907WARN_PRINT("An empty deprecation message is discouraged. Type: '" + itype.proxy_name + "'.");3908itype.deprecation_message = "This class is deprecated.";3909}3910}39113912if (itype.is_singleton && compat_singletons.has(itype.cname)) {3913itype.is_singleton = false;3914itype.is_compat_singleton = true;3915}39163917itype.c_out = "%5return ";3918itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;3919itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";39203921itype.cs_type = itype.proxy_name;39223923itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";39243925itype.cs_out = "%5return (%2)%0(%1);";39263927itype.c_arg_in = "&%s";3928itype.c_type = "IntPtr";3929itype.c_type_in = itype.c_type;3930itype.c_type_out = "GodotObject";39313932// Populate properties39333934List<PropertyInfo> property_list;3935ClassDB::get_property_list(type_cname, &property_list, true);39363937HashMap<StringName, StringName> accessor_methods;39383939for (const PropertyInfo &property : property_list) {3940if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {3941continue;3942}39433944if (property.name.contains_char('/')) {3945// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.3946continue;3947}39483949PropertyInterface iprop;3950iprop.cname = property.name;3951iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);3952iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);39533954// If the property is internal hide it; otherwise, hide the getter and setter.3955if (property.usage & PROPERTY_USAGE_INTERNAL) {3956iprop.is_hidden = true;3957} else {3958if (iprop.setter != StringName()) {3959accessor_methods[iprop.setter] = iprop.cname;3960}3961if (iprop.getter != StringName()) {3962accessor_methods[iprop.getter] = iprop.cname;3963}3964}39653966bool valid = false;3967iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);3968ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");39693970iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));39713972// Prevent the property and its enclosing type from sharing the same name3973if (iprop.proxy_name == itype.proxy_name) {3974_log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n",3975iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data());39763977iprop.proxy_name += "_";3978}39793980iprop.prop_doc = nullptr;39813982for (int i = 0; i < itype.class_doc->properties.size(); i++) {3983const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i];39843985if (prop_doc.name == iprop.cname) {3986iprop.prop_doc = &prop_doc;3987break;3988}3989}39903991if (iprop.prop_doc) {3992iprop.is_deprecated = iprop.prop_doc->is_deprecated;3993iprop.deprecation_message = iprop.prop_doc->deprecated_message;39943995if (iprop.is_deprecated && iprop.deprecation_message.is_empty()) {3996WARN_PRINT("An empty deprecation message is discouraged. Property: '" + itype.proxy_name + "." + iprop.proxy_name + "'.");3997iprop.deprecation_message = "This property is deprecated.";3998}3999}40004001itype.properties.push_back(iprop);4002}40034004// Populate methods40054006List<MethodInfo> virtual_method_list;4007ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true);40084009List<Pair<MethodInfo, uint32_t>> method_list_with_hashes;4010ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true);4011method_list_with_hashes.sort_custom<SortMethodWithHashes>();40124013List<MethodInterface> compat_methods;4014for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) {4015const MethodInfo &method_info = E.first;4016const uint32_t hash = E.second;40174018if (method_info.name.is_empty()) {4019continue;4020}40214022String cname = method_info.name;40234024if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) {4025continue;4026}40274028if (method_has_ptr_parameter(method_info)) {4029// Pointers are not supported.4030itype.ignored_members.insert(method_info.name);4031continue;4032}40334034MethodInterface imethod;4035imethod.name = method_info.name;4036imethod.cname = cname;4037imethod.hash = hash;40384039if (method_info.flags & METHOD_FLAG_STATIC) {4040imethod.is_static = true;4041}40424043if (method_info.flags & METHOD_FLAG_VIRTUAL) {4044imethod.is_virtual = true;4045itype.has_virtual_methods = true;4046}40474048PropertyInfo return_info = method_info.return_val;40494050MethodBind *m = nullptr;40514052if (!imethod.is_virtual) {4053bool method_exists = false;4054m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat);40554056if (unlikely(!method_exists)) {4057ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4058"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");4059}4060}40614062imethod.is_vararg = m && m->is_vararg();40634064if (!m && !imethod.is_virtual) {4065ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4066"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");40674068// A virtual method without the virtual flag. This is a special case.40694070// There is no method bind, so let's fallback to Godot's object.Call(string, params)4071imethod.requires_object_call = true;40724073// The method Object.free is registered as a virtual method, but without the virtual flag.4074// This is because this method is not supposed to be overridden, but called.4075// We assume the return type is void.4076imethod.return_type.cname = name_cache.type_void;40774078// Actually, more methods like this may be added in the future, which could return4079// something different. Let's put this check to notify us if that ever happens.4080if (itype.cname != name_cache.type_Object || imethod.name != "free") {4081WARN_PRINT("Notification: New unexpected virtual non-overridable method found."4082" We only expected Object.free, but found '" +4083itype.name + "." + imethod.name + "'.");4084}4085} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4086imethod.return_type.cname = return_info.class_name;4087imethod.return_type.is_enum = true;4088} else if (return_info.class_name != StringName()) {4089imethod.return_type.cname = return_info.class_name;40904091bool bad_reference_hint = !imethod.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&4092ClassDB::is_parent_class(return_info.class_name, name_cache.type_RefCounted);4093ERR_FAIL_COND_V_MSG(bad_reference_hint, false,4094String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +4095" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");4096} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {4097imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4098imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));4099} else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4100imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4101Vector<String> split = return_info.hint_string.split(";");4102imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));4103imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));4104} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {4105imethod.return_type.cname = return_info.hint_string;4106} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {4107imethod.return_type.cname = name_cache.type_Variant;4108} else if (return_info.type == Variant::NIL) {4109imethod.return_type.cname = name_cache.type_void;4110} else {4111imethod.return_type.cname = _get_type_name_from_meta(return_info.type, m ? m->get_argument_meta(-1) : (GodotTypeInfo::Metadata)method_info.return_val_metadata);4112}41134114for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4115const PropertyInfo &arginfo = method_info.arguments[idx];41164117String orig_arg_name = arginfo.name;41184119ArgumentInterface iarg;4120iarg.name = orig_arg_name;41214122if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4123iarg.type.cname = arginfo.class_name;4124iarg.type.is_enum = true;4125} else if (arginfo.class_name != StringName()) {4126iarg.type.cname = arginfo.class_name;4127} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4128iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4129iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4130} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4131iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4132Vector<String> split = arginfo.hint_string.split(";");4133iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4134iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4135} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4136iarg.type.cname = arginfo.hint_string;4137} else if (arginfo.type == Variant::NIL) {4138iarg.type.cname = name_cache.type_Variant;4139} else {4140iarg.type.cname = _get_type_name_from_meta(arginfo.type, m ? m->get_argument_meta(idx) : (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4141}41424143iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));41444145if (m && m->has_default_argument(idx)) {4146bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(idx), iarg);4147ERR_FAIL_COND_V_MSG(!defval_ok, false,4148"Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");4149}41504151imethod.add_argument(iarg);4152}41534154if (imethod.is_vararg) {4155ArgumentInterface ivararg;4156ivararg.type.cname = name_cache.type_VarArg;4157ivararg.name = "@args";4158imethod.add_argument(ivararg);4159}41604161imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name));41624163// Prevent the method and its enclosing type from sharing the same name4164if (imethod.proxy_name == itype.proxy_name) {4165_log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n",4166imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data());41674168imethod.proxy_name += "_";4169}41704171HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname);4172if (accessor) {4173// We only hide an accessor method if it's in the same class as the property.4174// It's easier this way, but also we don't know if an accessor method in a different class4175// could have other purposes, so better leave those untouched.4176imethod.is_hidden = true;4177}41784179if (itype.class_doc) {4180for (int i = 0; i < itype.class_doc->methods.size(); i++) {4181if (itype.class_doc->methods[i].name == imethod.name) {4182imethod.method_doc = &itype.class_doc->methods[i];4183break;4184}4185}4186}41874188if (imethod.method_doc) {4189imethod.is_deprecated = imethod.method_doc->is_deprecated;4190imethod.deprecation_message = imethod.method_doc->deprecated_message;41914192if (imethod.is_deprecated && imethod.deprecation_message.is_empty()) {4193WARN_PRINT("An empty deprecation message is discouraged. Method: '" + itype.proxy_name + "." + imethod.proxy_name + "'.");4194imethod.deprecation_message = "This method is deprecated.";4195}4196}41974198ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,4199"Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");42004201// Compat methods aren't added to the type yet, they need to be checked for conflicts4202// after all the non-compat methods have been added. The compat methods are added in4203// reverse so the most recently added ones take precedence over older compat methods.4204if (imethod.is_compat) {4205// If the method references deprecated types, mark the method as deprecated as well.4206for (const ArgumentInterface &iarg : imethod.arguments) {4207String arg_type_name = iarg.type.cname;4208String doc_name = arg_type_name.begins_with("_") ? arg_type_name.substr(1) : arg_type_name;4209const DocData::ClassDoc &class_doc = EditorHelp::get_doc_data()->class_list[doc_name];4210if (class_doc.is_deprecated) {4211imethod.is_deprecated = true;4212imethod.deprecation_message = "This method overload is deprecated.";4213break;4214}4215}42164217imethod.is_hidden = true;4218compat_methods.push_front(imethod);4219continue;4220}42214222// Methods starting with an underscore are ignored unless they're used as a property setter or getter4223if (!imethod.is_virtual && imethod.name[0] == '_') {4224for (const PropertyInterface &iprop : itype.properties) {4225if (iprop.setter == imethod.name || iprop.getter == imethod.name) {4226imethod.is_internal = true;4227itype.methods.push_back(imethod);4228break;4229}4230}4231} else {4232itype.methods.push_back(imethod);4233}4234}42354236// Add compat methods that don't conflict with other methods in the type.4237for (const MethodInterface &imethod : compat_methods) {4238if (_method_has_conflicting_signature(imethod, itype)) {4239WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored.");4240continue;4241}4242itype.methods.push_back(imethod);4243}42444245// Populate signals42464247const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;42484249for (const KeyValue<StringName, MethodInfo> &E : signal_map) {4250SignalInterface isignal;42514252const MethodInfo &method_info = E.value;42534254isignal.name = method_info.name;4255isignal.cname = method_info.name;42564257for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4258const PropertyInfo &arginfo = method_info.arguments[idx];42594260String orig_arg_name = arginfo.name;42614262ArgumentInterface iarg;4263iarg.name = orig_arg_name;42644265if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4266iarg.type.cname = arginfo.class_name;4267iarg.type.is_enum = true;4268} else if (arginfo.class_name != StringName()) {4269iarg.type.cname = arginfo.class_name;4270} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4271iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4272iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4273} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4274iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4275Vector<String> split = arginfo.hint_string.split(";");4276iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4277iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4278} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4279iarg.type.cname = arginfo.hint_string;4280} else if (arginfo.type == Variant::NIL) {4281iarg.type.cname = name_cache.type_Variant;4282} else {4283iarg.type.cname = _get_type_name_from_meta(arginfo.type, (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4284}42854286iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));42874288isignal.add_argument(iarg);4289}42904291isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name));42924293// Prevent the signal and its enclosing type from sharing the same name4294if (isignal.proxy_name == itype.proxy_name) {4295_log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n",4296isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data());42974298isignal.proxy_name += "_";4299}43004301if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) {4302// ClassDB allows signal names that conflict with method or property names.4303// While registering a signal with a conflicting name is considered wrong,4304// it may still happen and it may take some time until someone fixes the name.4305// We can't allow the bindings to be in a broken state while we wait for a fix;4306// that's why we must handle this possibility by renaming the signal.4307isignal.proxy_name += "Signal";4308}43094310if (itype.class_doc) {4311for (int i = 0; i < itype.class_doc->signals.size(); i++) {4312const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i];4313if (signal_doc.name == isignal.name) {4314isignal.method_doc = &signal_doc;4315break;4316}4317}4318}43194320if (isignal.method_doc) {4321isignal.is_deprecated = isignal.method_doc->is_deprecated;4322isignal.deprecation_message = isignal.method_doc->deprecated_message;43234324if (isignal.is_deprecated && isignal.deprecation_message.is_empty()) {4325WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + itype.proxy_name + "." + isignal.proxy_name + "'.");4326isignal.deprecation_message = "This signal is deprecated.";4327}4328}43294330itype.signals_.push_back(isignal);4331}43324333// Populate enums and constants43344335List<String> constants;4336ClassDB::get_integer_constant_list(type_cname, &constants, true);43374338const HashMap<StringName, ClassDB::ClassInfo::EnumInfo> &enum_map = class_info->enum_map;43394340for (const KeyValue<StringName, ClassDB::ClassInfo::EnumInfo> &E : enum_map) {4341StringName enum_proxy_cname = E.key;4342String enum_proxy_name = pascal_to_pascal_case(enum_proxy_cname.operator String());4343if (itype.find_property_by_proxy_name(enum_proxy_name) || itype.find_method_by_proxy_name(enum_proxy_name) || itype.find_signal_by_proxy_name(enum_proxy_name)) {4344// In case the enum name conflicts with other PascalCase members,4345// we append 'Enum' to the enum name in those cases.4346// We have several conflicts between enums and PascalCase properties.4347enum_proxy_name += "Enum";4348enum_proxy_cname = StringName(enum_proxy_name);4349}4350EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);4351const List<StringName> &enum_constants = E.value.constants;4352for (const StringName &constant_cname : enum_constants) {4353String constant_name = constant_cname.operator String();4354int64_t *value = class_info->constant_map.getptr(constant_cname);4355ERR_FAIL_NULL_V(value, false);4356constants.erase(constant_name);43574358ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);43594360iconstant.const_doc = nullptr;4361for (int i = 0; i < itype.class_doc->constants.size(); i++) {4362const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];43634364if (const_doc.name == iconstant.name) {4365iconstant.const_doc = &const_doc;4366break;4367}4368}43694370if (iconstant.const_doc) {4371iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4372iconstant.deprecation_message = iconstant.const_doc->deprecated_message;43734374if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4375WARN_PRINT("An empty deprecation message is discouraged. Enum member: '" + itype.proxy_name + "." + ienum.proxy_name + "." + iconstant.proxy_name + "'.");4376iconstant.deprecation_message = "This enum member is deprecated.";4377}4378}43794380ienum.constants.push_back(iconstant);4381}43824383int prefix_length = _determine_enum_prefix(ienum);43844385_apply_prefix_to_enum_constants(ienum, prefix_length);43864387itype.enums.push_back(ienum);43884389TypeInterface enum_itype;4390enum_itype.is_enum = true;4391enum_itype.name = itype.name + "." + String(E.key);4392enum_itype.cname = StringName(enum_itype.name);4393enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name;4394TypeInterface::postsetup_enum_type(enum_itype);4395enum_types.insert(enum_itype.cname, enum_itype);4396}43974398for (const String &constant_name : constants) {4399int64_t *value = class_info->constant_map.getptr(StringName(constant_name));4400ERR_FAIL_NULL_V(value, false);44014402String constant_proxy_name = snake_to_pascal_case(constant_name, true);44034404if (itype.find_property_by_proxy_name(constant_proxy_name) || itype.find_method_by_proxy_name(constant_proxy_name) || itype.find_signal_by_proxy_name(constant_proxy_name)) {4405// In case the constant name conflicts with other PascalCase members,4406// we append 'Constant' to the constant name in those cases.4407constant_proxy_name += "Constant";4408}44094410ConstantInterface iconstant(constant_name, constant_proxy_name, *value);44114412iconstant.const_doc = nullptr;4413for (int i = 0; i < itype.class_doc->constants.size(); i++) {4414const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];44154416if (const_doc.name == iconstant.name) {4417iconstant.const_doc = &const_doc;4418break;4419}4420}44214422if (iconstant.const_doc) {4423iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4424iconstant.deprecation_message = iconstant.const_doc->deprecated_message;44254426if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4427WARN_PRINT("An empty deprecation message is discouraged. Constant: '" + itype.proxy_name + "." + iconstant.proxy_name + "'.");4428iconstant.deprecation_message = "This constant is deprecated.";4429}4430}44314432itype.constants.push_back(iconstant);4433}44344435obj_types.insert(itype.cname, itype);44364437if (itype.is_singleton) {4438// Add singleton instance type.4439itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;4440itype.is_singleton = false;4441itype.is_singleton_instance = true;44424443// Remove constants and enums, those will remain in the static class.4444itype.constants.clear();4445itype.enums.clear();44464447obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);4448}44494450class_list.pop_front();4451}44524453return true;4454}44554456static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {4457return String::num_real(p_vec2.x, true) + "f, " +4458String::num_real(p_vec2.y, true) + "f";4459}44604461static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {4462return String::num_real(p_vec3.x, true) + "f, " +4463String::num_real(p_vec3.y, true) + "f, " +4464String::num_real(p_vec3.z, true) + "f";4465}44664467static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {4468return String::num_real(p_vec4.x, true) + "f, " +4469String::num_real(p_vec4.y, true) + "f, " +4470String::num_real(p_vec4.z, true) + "f, " +4471String::num_real(p_vec4.w, true) + "f";4472}44734474static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {4475return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);4476}44774478static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {4479return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);4480}44814482static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {4483return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);4484}44854486static String _get_color_cs_ctor_args(const Color &p_color) {4487return String::num(p_color.r, 4) + "f, " +4488String::num(p_color.g, 4) + "f, " +4489String::num(p_color.b, 4) + "f, " +4490String::num(p_color.a, 4) + "f";4491}44924493bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {4494r_iarg.def_param_value = p_val;44954496switch (p_val.get_type()) {4497case Variant::NIL:4498// Either Object type or Variant4499r_iarg.default_argument = "default";4500break;4501// Atomic types4502case Variant::BOOL:4503r_iarg.default_argument = bool(p_val) ? "true" : "false";4504break;4505case Variant::INT:4506if (r_iarg.type.cname != name_cache.type_int) {4507r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";4508} else {4509r_iarg.default_argument = p_val.operator String();4510}4511break;4512case Variant::FLOAT:4513r_iarg.default_argument = p_val.operator String();45144515if (r_iarg.type.cname == name_cache.type_float) {4516r_iarg.default_argument += "f";4517}4518break;4519case Variant::STRING:4520case Variant::STRING_NAME:4521case Variant::NODE_PATH:4522if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {4523if (r_iarg.default_argument.length() > 0) {4524r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";4525r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4526} else {4527// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.4528r_iarg.default_argument = "null";4529}4530} else {4531CRASH_COND(r_iarg.type.cname != name_cache.type_String);4532r_iarg.default_argument = "\"" + p_val.operator String() + "\"";4533}4534break;4535case Variant::PLANE: {4536Plane plane = p_val.operator Plane();4537r_iarg.default_argument = "new Plane(new Vector3(" +4538_get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";4539r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4540} break;4541case Variant::AABB: {4542AABB aabb = p_val.operator ::AABB();4543r_iarg.default_argument = "new Aabb(new Vector3(" +4544_get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +4545_get_vector3_cs_ctor_args(aabb.size) + "))";4546r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4547} break;4548case Variant::RECT2: {4549Rect2 rect = p_val.operator Rect2();4550r_iarg.default_argument = "new Rect2(new Vector2(" +4551_get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +4552_get_vector2_cs_ctor_args(rect.size) + "))";4553r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4554} break;4555case Variant::RECT2I: {4556Rect2i rect = p_val.operator Rect2i();4557r_iarg.default_argument = "new Rect2I(new Vector2I(" +4558_get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +4559_get_vector2i_cs_ctor_args(rect.size) + "))";4560r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4561} break;4562case Variant::COLOR:4563r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";4564r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4565break;4566case Variant::VECTOR2:4567r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";4568r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4569break;4570case Variant::VECTOR2I:4571r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";4572r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4573break;4574case Variant::VECTOR3:4575r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";4576r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4577break;4578case Variant::VECTOR3I:4579r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";4580r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4581break;4582case Variant::VECTOR4:4583r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";4584r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4585break;4586case Variant::VECTOR4I:4587r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";4588r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4589break;4590case Variant::OBJECT:4591ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4592"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");45934594r_iarg.default_argument = "null";4595break;4596case Variant::DICTIONARY:4597ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false,4598"Default value of type 'Dictionary' must be an empty dictionary.");4599// The [cs_in] expression already interprets null values as empty dictionaries.4600r_iarg.default_argument = "null";4601r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4602break;4603case Variant::RID:4604ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,4605"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");46064607ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4608"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");46094610r_iarg.default_argument = "default";4611break;4612case Variant::ARRAY:4613ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false,4614"Default value of type 'Array' must be an empty array.");4615// The [cs_in] expression already interprets null values as empty arrays.4616r_iarg.default_argument = "null";4617r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4618break;4619case Variant::PACKED_BYTE_ARRAY:4620case Variant::PACKED_INT32_ARRAY:4621case Variant::PACKED_INT64_ARRAY:4622case Variant::PACKED_FLOAT32_ARRAY:4623case Variant::PACKED_FLOAT64_ARRAY:4624case Variant::PACKED_STRING_ARRAY:4625case Variant::PACKED_VECTOR2_ARRAY:4626case Variant::PACKED_VECTOR3_ARRAY:4627case Variant::PACKED_VECTOR4_ARRAY:4628case Variant::PACKED_COLOR_ARRAY:4629r_iarg.default_argument = "Array.Empty<%s>()";4630r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4631break;4632case Variant::TRANSFORM2D: {4633Transform2D transform = p_val.operator Transform2D();4634if (transform == Transform2D()) {4635r_iarg.default_argument = "Transform2D.Identity";4636} else {4637r_iarg.default_argument = "new Transform2D(new Vector2(" +4638_get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +4639_get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +4640_get_vector2_cs_ctor_args(transform.columns[2]) + "))";4641}4642r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4643} break;4644case Variant::TRANSFORM3D: {4645Transform3D transform = p_val.operator Transform3D();4646if (transform == Transform3D()) {4647r_iarg.default_argument = "Transform3D.Identity";4648} else {4649Basis basis = transform.basis;4650r_iarg.default_argument = "new Transform3D(new Vector3(" +4651_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4652_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4653_get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +4654_get_vector3_cs_ctor_args(transform.origin) + "))";4655}4656r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4657} break;4658case Variant::PROJECTION: {4659Projection projection = p_val.operator Projection();4660if (projection == Projection()) {4661r_iarg.default_argument = "Projection.Identity";4662} else {4663r_iarg.default_argument = "new Projection(new Vector4(" +4664_get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +4665_get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +4666_get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +4667_get_vector4_cs_ctor_args(projection.columns[3]) + "))";4668}4669r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4670} break;4671case Variant::BASIS: {4672Basis basis = p_val.operator Basis();4673if (basis == Basis()) {4674r_iarg.default_argument = "Basis.Identity";4675} else {4676r_iarg.default_argument = "new Basis(new Vector3(" +4677_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4678_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4679_get_vector3_cs_ctor_args(basis.get_column(2)) + "))";4680}4681r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4682} break;4683case Variant::QUATERNION: {4684Quaternion quaternion = p_val.operator Quaternion();4685if (quaternion == Quaternion()) {4686r_iarg.default_argument = "Quaternion.Identity";4687} else {4688r_iarg.default_argument = "new Quaternion(" +4689String::num_real(quaternion.x, false) + "f, " +4690String::num_real(quaternion.y, false) + "f, " +4691String::num_real(quaternion.z, false) + "f, " +4692String::num_real(quaternion.w, false) + "f)";4693}4694r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4695} break;4696case Variant::CALLABLE:4697ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Callable, false,4698"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Callable) + "'.");4699ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4700"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4701r_iarg.default_argument = "default";4702break;4703case Variant::SIGNAL:4704ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Signal, false,4705"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Signal) + "'.");4706ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4707"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4708r_iarg.default_argument = "default";4709break;4710case Variant::VARIANT_MAX:4711ERR_FAIL_V_MSG(false, "Unexpected Variant type: " + itos(p_val.get_type()));4712break;4713}47144715if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") {4716r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4717}47184719return true;4720}47214722void BindingsGenerator::_populate_builtin_type_interfaces() {4723builtin_types.clear();47244725TypeInterface itype;47264727#define INSERT_STRUCT_TYPE(m_type, m_proxy_name) \4728{ \4729itype = TypeInterface::create_value_type(String(#m_type), String(#m_proxy_name)); \4730itype.cs_in_expr = "&%0"; \4731itype.cs_in_expr_is_unsafe = true; \4732builtin_types.insert(itype.cname, itype); \4733}47344735INSERT_STRUCT_TYPE(Vector2, Vector2)4736INSERT_STRUCT_TYPE(Vector2i, Vector2I)4737INSERT_STRUCT_TYPE(Rect2, Rect2)4738INSERT_STRUCT_TYPE(Rect2i, Rect2I)4739INSERT_STRUCT_TYPE(Transform2D, Transform2D)4740INSERT_STRUCT_TYPE(Vector3, Vector3)4741INSERT_STRUCT_TYPE(Vector3i, Vector3I)4742INSERT_STRUCT_TYPE(Basis, Basis)4743INSERT_STRUCT_TYPE(Quaternion, Quaternion)4744INSERT_STRUCT_TYPE(Transform3D, Transform3D)4745INSERT_STRUCT_TYPE(AABB, Aabb)4746INSERT_STRUCT_TYPE(Color, Color)4747INSERT_STRUCT_TYPE(Plane, Plane)4748INSERT_STRUCT_TYPE(Vector4, Vector4)4749INSERT_STRUCT_TYPE(Vector4i, Vector4I)4750INSERT_STRUCT_TYPE(Projection, Projection)47514752#undef INSERT_STRUCT_TYPE47534754// bool4755itype = TypeInterface::create_value_type(String("bool"));4756itype.cs_in_expr = "%0.ToGodotBool()";4757itype.cs_out = "%5return %0(%1).ToBool();";4758itype.c_type = "godot_bool";4759itype.c_type_in = itype.c_type;4760itype.c_type_out = itype.c_type;4761itype.c_arg_in = "&%s";4762itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n";4763builtin_types.insert(itype.cname, itype);47644765// Integer types4766{4767// C interface for 'uint32_t' is the same as that of enums. Remember to apply4768// any of the changes done here to 'TypeInterface::postsetup_enum_type' as well.4769#define INSERT_INT_TYPE(m_name, m_int_struct_name) \4770{ \4771itype = TypeInterface::create_value_type(String(m_name)); \4772if (itype.name != "long" && itype.name != "ulong") { \4773itype.c_in = "%5%0 %1_in = %1;\n"; \4774itype.c_out = "%5return (%0)(%1);\n"; \4775itype.c_type = "long"; \4776itype.c_arg_in = "&%s_in"; \4777} else { \4778itype.c_arg_in = "&%s"; \4779} \4780itype.c_type_in = itype.name; \4781itype.c_type_out = itype.name; \4782itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \4783builtin_types.insert(itype.cname, itype); \4784}47854786// The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type'47874788INSERT_INT_TYPE("sbyte", "Int8");4789INSERT_INT_TYPE("short", "Int16");4790INSERT_INT_TYPE("int", "Int32");4791INSERT_INT_TYPE("long", "Int64");4792INSERT_INT_TYPE("byte", "UInt8");4793INSERT_INT_TYPE("ushort", "UInt16");4794INSERT_INT_TYPE("uint", "UInt32");4795INSERT_INT_TYPE("ulong", "UInt64");47964797#undef INSERT_INT_TYPE4798}47994800// Floating point types4801{4802// float4803itype = TypeInterface();4804itype.name = "float";4805itype.cname = itype.name;4806itype.proxy_name = "float";4807itype.cs_type = itype.proxy_name;4808{4809// The expected type for 'float' in ptrcall is 'double'4810itype.c_in = "%5%0 %1_in = %1;\n";4811itype.c_out = "%5return (%0)%1;\n";4812itype.c_type = "double";4813itype.c_arg_in = "&%s_in";4814}4815itype.c_type_in = itype.proxy_name;4816itype.c_type_out = itype.proxy_name;4817itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4818builtin_types.insert(itype.cname, itype);48194820// double4821itype = TypeInterface();4822itype.name = "double";4823itype.cname = itype.name;4824itype.proxy_name = "double";4825itype.cs_type = itype.proxy_name;4826itype.c_type = "double";4827itype.c_arg_in = "&%s";4828itype.c_type_in = itype.proxy_name;4829itype.c_type_out = itype.proxy_name;4830itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4831builtin_types.insert(itype.cname, itype);4832}48334834// String4835itype = TypeInterface();4836itype.name = "String";4837itype.cname = itype.name;4838itype.proxy_name = "string";4839itype.cs_type = itype.proxy_name;4840itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n";4841itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n";4842itype.c_arg_in = "&%s_in";4843itype.c_type = "godot_string";4844itype.c_type_in = itype.cs_type;4845itype.c_type_out = itype.cs_type;4846itype.c_type_is_disposable_struct = true;4847itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n";4848builtin_types.insert(itype.cname, itype);48494850// StringName4851itype = TypeInterface();4852itype.name = "StringName";4853itype.cname = itype.name;4854itype.proxy_name = "StringName";4855itype.cs_type = itype.proxy_name;4856itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4857// Cannot pass null StringName to ptrcall4858itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4859itype.c_arg_in = "&%s";4860itype.c_type = "godot_string_name";4861itype.c_type_in = itype.c_type;4862itype.c_type_out = itype.cs_type;4863itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n";4864itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4865itype.c_ret_needs_default_initialization = true;4866builtin_types.insert(itype.cname, itype);48674868// NodePath4869itype = TypeInterface();4870itype.name = "NodePath";4871itype.cname = itype.name;4872itype.proxy_name = "NodePath";4873itype.cs_type = itype.proxy_name;4874itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4875// Cannot pass null NodePath to ptrcall4876itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4877itype.c_arg_in = "&%s";4878itype.c_type = "godot_node_path";4879itype.c_type_in = itype.c_type;4880itype.c_type_out = itype.cs_type;4881itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4882itype.c_ret_needs_default_initialization = true;4883builtin_types.insert(itype.cname, itype);48844885// RID4886itype = TypeInterface();4887itype.name = "RID";4888itype.cname = itype.name;4889itype.proxy_name = "Rid";4890itype.cs_type = itype.proxy_name;4891itype.c_arg_in = "&%s";4892itype.c_type = itype.cs_type;4893itype.c_type_in = itype.c_type;4894itype.c_type_out = itype.c_type;4895builtin_types.insert(itype.cname, itype);48964897// Variant4898itype = TypeInterface();4899itype.name = "Variant";4900itype.cname = itype.name;4901itype.proxy_name = "Variant";4902itype.cs_type = itype.proxy_name;4903itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n";4904itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n";4905itype.c_arg_in = "&%s_in";4906itype.c_type = "godot_variant";4907itype.c_type_in = itype.cs_type;4908itype.c_type_out = itype.cs_type;4909itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4910itype.c_ret_needs_default_initialization = true;4911builtin_types.insert(itype.cname, itype);49124913// Callable4914itype = TypeInterface::create_value_type(String("Callable"));4915itype.cs_in_expr = "%0";4916itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n";4917itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %1);\n";4918itype.c_arg_in = "&%s_in";4919itype.c_type = "godot_callable";4920itype.c_type_in = "in " + itype.cs_type;4921itype.c_type_out = itype.cs_type;4922itype.c_type_is_disposable_struct = true;4923builtin_types.insert(itype.cname, itype);49244925// Signal4926itype = TypeInterface();4927itype.name = "Signal";4928itype.cname = itype.name;4929itype.proxy_name = "Signal";4930itype.cs_type = itype.proxy_name;4931itype.cs_in_expr = "%0";4932itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n";4933itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(in %1);\n";4934itype.c_arg_in = "&%s_in";4935itype.c_type = "godot_signal";4936itype.c_type_in = "in " + itype.cs_type;4937itype.c_type_out = itype.cs_type;4938itype.c_type_is_disposable_struct = true;4939builtin_types.insert(itype.cname, itype);49404941// VarArg (fictitious type to represent variable arguments)4942itype = TypeInterface();4943itype.name = "VarArg";4944itype.cname = itype.name;4945itype.proxy_name = "ReadOnlySpan<Variant>";4946itype.cs_type = "params Variant[]";4947itype.cs_in_expr = "%0";4948// c_type, c_in and c_arg_in are hard-coded in the generator.4949// c_out and c_type_out are not applicable to VarArg.4950itype.c_arg_in = "&%s_in";4951itype.c_type_in = "ReadOnlySpan<Variant>";4952itype.is_span_compatible = true;4953builtin_types.insert(itype.cname, itype);49544955#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \4956{ \4957itype = TypeInterface(); \4958itype.name = #m_name; \4959itype.cname = itype.name; \4960itype.proxy_name = #m_proxy_t "[]"; \4961itype.cs_type = itype.proxy_name; \4962itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \4963itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \4964itype.c_arg_in = "&%s_in"; \4965itype.c_type = #m_managed_type; \4966itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \4967itype.c_type_out = itype.proxy_name; \4968itype.c_type_is_disposable_struct = true; \4969itype.is_span_compatible = true; \4970builtin_types.insert(itype.name, itype); \4971}49724973#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t)49744975INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int);4976INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long);4977INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte);49784979INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float);4980INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double);49814982INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string);49834984INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color);4985INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2);4986INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3);4987INSERT_ARRAY(PackedVector4Array, godot_packed_vector4_array, Vector4);49884989#undef INSERT_ARRAY49904991// Array4992itype = TypeInterface();4993itype.name = "Array";4994itype.cname = itype.name;4995itype.proxy_name = itype.name;4996itype.type_parameter_count = 1;4997itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;4998itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";4999itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";5000itype.c_arg_in = "&%s";5001itype.c_type = "godot_array";5002itype.c_type_in = itype.c_type;5003itype.c_type_out = itype.cs_type;5004itype.c_type_is_disposable_struct = false; // [c_out] takes ownership5005itype.c_ret_needs_default_initialization = true;5006builtin_types.insert(itype.cname, itype);50075008// Array_@generic5009// Reuse Array's itype5010itype.name = "Array_@generic";5011itype.cname = itype.name;5012itype.cs_out = "%5return new %2(%0(%1));";5013// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5014itype.cs_variant_to_managed = "VariantUtils.ConvertToArray(%0)";5015itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)";5016builtin_types.insert(itype.cname, itype);50175018// Dictionary5019itype = TypeInterface();5020itype.name = "Dictionary";5021itype.cname = itype.name;5022itype.proxy_name = itype.name;5023itype.type_parameter_count = 2;5024itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;5025itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";5026itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";5027itype.c_arg_in = "&%s";5028itype.c_type = "godot_dictionary";5029itype.c_type_in = itype.c_type;5030itype.c_type_out = itype.cs_type;5031itype.c_type_is_disposable_struct = false; // [c_out] takes ownership5032itype.c_ret_needs_default_initialization = true;5033builtin_types.insert(itype.cname, itype);50345035// Dictionary_@generic5036// Reuse Dictionary's itype5037itype.name = "Dictionary_@generic";5038itype.cname = itype.name;5039itype.cs_out = "%5return new %2(%0(%1));";5040// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5041itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionary(%0)";5042itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)";5043builtin_types.insert(itype.cname, itype);50445045// void (fictitious type to represent the return type of methods that do not return anything)5046itype = TypeInterface();5047itype.name = "void";5048itype.cname = itype.name;5049itype.proxy_name = itype.name;5050itype.cs_type = itype.proxy_name;5051itype.c_type = itype.proxy_name;5052itype.c_type_in = itype.c_type;5053itype.c_type_out = itype.c_type;5054builtin_types.insert(itype.cname, itype);5055}50565057void BindingsGenerator::_populate_global_constants() {5058int global_constants_count = CoreConstants::get_global_constant_count();50595060if (global_constants_count > 0) {5061HashMap<String, DocData::ClassDoc>::Iterator match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");50625063CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData.");50645065const DocData::ClassDoc &global_scope_doc = match->value;50665067for (int i = 0; i < global_constants_count; i++) {5068String constant_name = CoreConstants::get_global_constant_name(i);50695070const DocData::ConstantDoc *const_doc = nullptr;5071for (int j = 0; j < global_scope_doc.constants.size(); j++) {5072const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];50735074if (curr_const_doc.name == constant_name) {5075const_doc = &curr_const_doc;5076break;5077}5078}50795080int64_t constant_value = CoreConstants::get_global_constant_value(i);5081StringName enum_name = CoreConstants::get_global_constant_enum(i);50825083ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);5084iconstant.const_doc = const_doc;50855086if (enum_name != StringName()) {5087EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));5088List<EnumInterface>::Element *enum_match = global_enums.find(ienum);5089if (enum_match) {5090enum_match->get().constants.push_back(iconstant);5091} else {5092ienum.constants.push_back(iconstant);5093global_enums.push_back(ienum);5094}5095} else {5096global_constants.push_back(iconstant);5097}5098}50995100for (EnumInterface &ienum : global_enums) {5101TypeInterface enum_itype;5102enum_itype.is_enum = true;5103enum_itype.name = ienum.cname.operator String();5104enum_itype.cname = ienum.cname;5105enum_itype.proxy_name = ienum.proxy_name;5106TypeInterface::postsetup_enum_type(enum_itype);5107enum_types.insert(enum_itype.cname, enum_itype);51085109int prefix_length = _determine_enum_prefix(ienum);51105111// HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.5112if (ienum.cname == name_cache.enum_Error) {5113if (prefix_length > 0) { // Just in case it ever changes5114ERR_PRINT("Prefix for enum '" _STR(Error) "' is not empty.");5115}51165117prefix_length = 1; // 'ERR_'5118}51195120_apply_prefix_to_enum_constants(ienum, prefix_length);5121}5122}51235124for (int i = 0; i < Variant::VARIANT_MAX; i++) {5125if (i == Variant::OBJECT) {5126continue;5127}51285129const Variant::Type type = Variant::Type(i);51305131List<StringName> enum_names;5132Variant::get_enums_for_type(type, &enum_names);51335134for (const StringName &enum_name : enum_names) {5135TypeInterface enum_itype;5136enum_itype.is_enum = true;5137enum_itype.name = Variant::get_type_name(type) + "." + enum_name;5138enum_itype.cname = enum_itype.name;5139enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);5140TypeInterface::postsetup_enum_type(enum_itype);5141enum_types.insert(enum_itype.cname, enum_itype);5142}5143}5144}51455146bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) {5147// Compare p_imethod with all the methods already registered in p_itype.5148for (const MethodInterface &method : p_itype.methods) {5149if (method.proxy_name == p_imethod.proxy_name) {5150if (_method_has_conflicting_signature(p_imethod, method)) {5151return true;5152}5153}5154}51555156return false;5157}51585159bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) {5160// Check if a method already exists in p_itype with a method signature that would conflict with p_imethod.5161// The return type is ignored because only changing the return type is not enough to avoid conflicts.5162// The const keyword is also ignored since it doesn't generate different C# code.51635164if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) {5165// Different argument count, so no conflict.5166return false;5167}51685169List<BindingsGenerator::ArgumentInterface>::ConstIterator left_itr = p_imethod_left.arguments.begin();5170List<BindingsGenerator::ArgumentInterface>::ConstIterator right_itr = p_imethod_right.arguments.begin();5171for (; left_itr != p_imethod_left.arguments.end(); ++left_itr, ++right_itr) {5172const ArgumentInterface &iarg_left = *left_itr;5173const ArgumentInterface &iarg_right = *right_itr;51745175if (iarg_left.type.cname != iarg_right.type.cname) {5176// Different types for arguments in the same position, so no conflict.5177return false;5178}51795180if (iarg_left.def_param_mode != iarg_right.def_param_mode) {5181// If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T'5182// and will not create a conflict.5183if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) {5184return false;5185}5186}5187}51885189return true;5190}51915192void BindingsGenerator::_initialize_blacklisted_methods() {5193blacklisted_methods["Object"].push_back("to_string"); // there is already ToString5194blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead5195blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)5196}51975198void BindingsGenerator::_initialize_compat_singletons() {5199compat_singletons.insert("EditorInterface");5200}52015202void BindingsGenerator::_log(const char *p_format, ...) {5203if (log_print_enabled) {5204va_list list;52055206va_start(list, p_format);5207OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data());5208va_end(list);5209}5210}52115212void BindingsGenerator::_initialize() {5213initialized = false;52145215EditorHelp::generate_doc(false);52165217enum_types.clear();52185219_initialize_blacklisted_methods();52205221_initialize_compat_singletons();52225223bool obj_type_ok = _populate_object_type_interfaces();5224ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");52255226_populate_builtin_type_interfaces();52275228_populate_global_constants();52295230// Generate internal calls (after populating type interfaces and global constants)52315232for (const KeyValue<StringName, TypeInterface> &E : obj_types) {5233const TypeInterface &itype = E.value;5234Error err = _populate_method_icalls_table(itype);5235ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name);5236}52375238initialized = true;5239}52405241static String generate_all_glue_option = "--generate-mono-glue";52425243static void handle_cmdline_options(String glue_dir_path) {5244BindingsGenerator bindings_generator;5245bindings_generator.set_log_print_enabled(true);52465247if (!bindings_generator.is_initialized()) {5248ERR_PRINT("Failed to initialize the bindings generator");5249return;5250}52515252CRASH_COND(glue_dir_path.is_empty());52535254if (bindings_generator.generate_cs_api(glue_dir_path.path_join(API_SOLUTION_NAME)) != OK) {5255ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API.");5256}5257}52585259static void cleanup_and_exit_godot() {5260// Exit once done.5261Main::cleanup(true);5262::exit(0);5263}52645265void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {5266String glue_dir_path;52675268const List<String>::Element *elem = p_cmdline_args.front();52695270while (elem) {5271if (elem->get() == generate_all_glue_option) {5272const List<String>::Element *path_elem = elem->next();52735274if (path_elem) {5275glue_dir_path = path_elem->get();5276elem = elem->next();5277} else {5278ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");5279// Exit once done with invalid command line arguments.5280cleanup_and_exit_godot();5281}52825283break;5284}52855286elem = elem->next();5287}52885289if (glue_dir_path.length()) {5290if (Engine::get_singleton()->is_editor_hint() ||5291Engine::get_singleton()->is_project_manager_hint()) {5292handle_cmdline_options(glue_dir_path);5293} else {5294// Running from a project folder, which doesn't make sense and crashes.5295ERR_PRINT(generate_all_glue_option + ": Cannot generate Mono glue while running a game project. Change current directory or enable --editor.");5296}5297// Exit once done.5298cleanup_and_exit_godot();5299}5300}53015302#endif // DEBUG_ENABLED530353045305