Path: blob/master/modules/mono/editor/bindings_generator.cpp
11353 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();38593860LocalVector<StringName> class_list;3861ClassDB::get_class_list(class_list);38623863for (const StringName &type_cname : class_list) {3864ClassDB::APIType api_type = ClassDB::get_api_type(type_cname);38653866if (api_type == ClassDB::API_NONE) {3867continue;3868}38693870if (ignored_types.has(type_cname)) {3871_log("Ignoring type '%s' because it's in the list of ignored types\n", String(type_cname).utf8().get_data());3872continue;3873}38743875if (!ClassDB::is_class_exposed(type_cname)) {3876_log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data());3877continue;3878}38793880if (!ClassDB::is_class_enabled(type_cname)) {3881_log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data());3882continue;3883}38843885ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname);38863887TypeInterface itype = TypeInterface::create_object_type(type_cname, pascal_to_pascal_case(type_cname), api_type);38883889itype.base_name = ClassDB::get_parent_class(type_cname);3890itype.is_singleton = Engine::get_singleton()->has_singleton(type_cname);3891itype.is_instantiable = class_info->creation_func && !itype.is_singleton;3892itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);3893itype.memory_own = itype.is_ref_counted;38943895if (itype.class_doc) {3896itype.is_deprecated = itype.class_doc->is_deprecated;3897itype.deprecation_message = itype.class_doc->deprecated_message;38983899if (itype.is_deprecated && itype.deprecation_message.is_empty()) {3900WARN_PRINT("An empty deprecation message is discouraged. Type: '" + itype.proxy_name + "'.");3901itype.deprecation_message = "This class is deprecated.";3902}3903}39043905if (itype.is_singleton && compat_singletons.has(itype.cname)) {3906itype.is_singleton = false;3907itype.is_compat_singleton = true;3908}39093910itype.c_out = "%5return ";3911itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;3912itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";39133914itype.cs_type = itype.proxy_name;39153916itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";39173918itype.cs_out = "%5return (%2)%0(%1);";39193920itype.c_arg_in = "&%s";3921itype.c_type = "IntPtr";3922itype.c_type_in = itype.c_type;3923itype.c_type_out = "GodotObject";39243925// Populate properties39263927List<PropertyInfo> property_list;3928ClassDB::get_property_list(type_cname, &property_list, true);39293930HashMap<StringName, StringName> accessor_methods;39313932for (const PropertyInfo &property : property_list) {3933if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {3934continue;3935}39363937if (property.name.contains_char('/')) {3938// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.3939continue;3940}39413942PropertyInterface iprop;3943iprop.cname = property.name;3944iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);3945iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);39463947// If the property is internal hide it; otherwise, hide the getter and setter.3948if (property.usage & PROPERTY_USAGE_INTERNAL) {3949iprop.is_hidden = true;3950} else {3951if (iprop.setter != StringName()) {3952accessor_methods[iprop.setter] = iprop.cname;3953}3954if (iprop.getter != StringName()) {3955accessor_methods[iprop.getter] = iprop.cname;3956}3957}39583959bool valid = false;3960iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);3961ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");39623963iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));39643965// Prevent the property and its enclosing type from sharing the same name3966if (iprop.proxy_name == itype.proxy_name) {3967_log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n",3968iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data());39693970iprop.proxy_name += "_";3971}39723973iprop.prop_doc = nullptr;39743975for (int i = 0; i < itype.class_doc->properties.size(); i++) {3976const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i];39773978if (prop_doc.name == iprop.cname) {3979iprop.prop_doc = &prop_doc;3980break;3981}3982}39833984if (iprop.prop_doc) {3985iprop.is_deprecated = iprop.prop_doc->is_deprecated;3986iprop.deprecation_message = iprop.prop_doc->deprecated_message;39873988if (iprop.is_deprecated && iprop.deprecation_message.is_empty()) {3989WARN_PRINT("An empty deprecation message is discouraged. Property: '" + itype.proxy_name + "." + iprop.proxy_name + "'.");3990iprop.deprecation_message = "This property is deprecated.";3991}3992}39933994itype.properties.push_back(iprop);3995}39963997// Populate methods39983999List<MethodInfo> virtual_method_list;4000ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true);40014002List<Pair<MethodInfo, uint32_t>> method_list_with_hashes;4003ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true);4004method_list_with_hashes.sort_custom<SortMethodWithHashes>();40054006List<MethodInterface> compat_methods;4007for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) {4008const MethodInfo &method_info = E.first;4009const uint32_t hash = E.second;40104011if (method_info.name.is_empty()) {4012continue;4013}40144015String cname = method_info.name;40164017if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) {4018continue;4019}40204021if (method_has_ptr_parameter(method_info)) {4022// Pointers are not supported.4023itype.ignored_members.insert(method_info.name);4024continue;4025}40264027MethodInterface imethod;4028imethod.name = method_info.name;4029imethod.cname = cname;4030imethod.hash = hash;40314032if (method_info.flags & METHOD_FLAG_STATIC) {4033imethod.is_static = true;4034}40354036if (method_info.flags & METHOD_FLAG_VIRTUAL) {4037imethod.is_virtual = true;4038itype.has_virtual_methods = true;4039}40404041PropertyInfo return_info = method_info.return_val;40424043MethodBind *m = nullptr;40444045if (!imethod.is_virtual) {4046bool method_exists = false;4047m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat);40484049if (unlikely(!method_exists)) {4050ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4051"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");4052}4053}40544055imethod.is_vararg = m && m->is_vararg();40564057if (!m && !imethod.is_virtual) {4058ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4059"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");40604061// A virtual method without the virtual flag. This is a special case.40624063// There is no method bind, so let's fallback to Godot's object.Call(string, params)4064imethod.requires_object_call = true;40654066// The method Object.free is registered as a virtual method, but without the virtual flag.4067// This is because this method is not supposed to be overridden, but called.4068// We assume the return type is void.4069imethod.return_type.cname = name_cache.type_void;40704071// Actually, more methods like this may be added in the future, which could return4072// something different. Let's put this check to notify us if that ever happens.4073if (itype.cname != name_cache.type_Object || imethod.name != "free") {4074WARN_PRINT("Notification: New unexpected virtual non-overridable method found."4075" We only expected Object.free, but found '" +4076itype.name + "." + imethod.name + "'.");4077}4078} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4079imethod.return_type.cname = return_info.class_name;4080imethod.return_type.is_enum = true;4081} else if (return_info.class_name != StringName()) {4082imethod.return_type.cname = return_info.class_name;40834084bool bad_reference_hint = !imethod.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&4085ClassDB::is_parent_class(return_info.class_name, name_cache.type_RefCounted);4086ERR_FAIL_COND_V_MSG(bad_reference_hint, false,4087String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +4088" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");4089} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {4090imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4091imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));4092} else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4093imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4094Vector<String> split = return_info.hint_string.split(";");4095imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));4096imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));4097} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {4098imethod.return_type.cname = return_info.hint_string;4099} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {4100imethod.return_type.cname = name_cache.type_Variant;4101} else if (return_info.type == Variant::NIL) {4102imethod.return_type.cname = name_cache.type_void;4103} else {4104imethod.return_type.cname = _get_type_name_from_meta(return_info.type, m ? m->get_argument_meta(-1) : (GodotTypeInfo::Metadata)method_info.return_val_metadata);4105}41064107for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4108const PropertyInfo &arginfo = method_info.arguments[idx];41094110String orig_arg_name = arginfo.name;41114112ArgumentInterface iarg;4113iarg.name = orig_arg_name;41144115if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4116iarg.type.cname = arginfo.class_name;4117iarg.type.is_enum = true;4118} else if (arginfo.class_name != StringName()) {4119iarg.type.cname = arginfo.class_name;4120} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4121iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4122iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4123} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4124iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4125Vector<String> split = arginfo.hint_string.split(";");4126iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4127iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4128} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4129iarg.type.cname = arginfo.hint_string;4130} else if (arginfo.type == Variant::NIL) {4131iarg.type.cname = name_cache.type_Variant;4132} else {4133iarg.type.cname = _get_type_name_from_meta(arginfo.type, m ? m->get_argument_meta(idx) : (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4134}41354136iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));41374138if (m && m->has_default_argument(idx)) {4139bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(idx), iarg);4140ERR_FAIL_COND_V_MSG(!defval_ok, false,4141"Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");4142}41434144imethod.add_argument(iarg);4145}41464147if (imethod.is_vararg) {4148ArgumentInterface ivararg;4149ivararg.type.cname = name_cache.type_VarArg;4150ivararg.name = "@args";4151imethod.add_argument(ivararg);4152}41534154imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name));41554156// Prevent the method and its enclosing type from sharing the same name4157if (imethod.proxy_name == itype.proxy_name) {4158_log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n",4159imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data());41604161imethod.proxy_name += "_";4162}41634164HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname);4165if (accessor) {4166// We only hide an accessor method if it's in the same class as the property.4167// It's easier this way, but also we don't know if an accessor method in a different class4168// could have other purposes, so better leave those untouched.4169imethod.is_hidden = true;4170}41714172if (itype.class_doc) {4173for (int i = 0; i < itype.class_doc->methods.size(); i++) {4174if (itype.class_doc->methods[i].name == imethod.name) {4175imethod.method_doc = &itype.class_doc->methods[i];4176break;4177}4178}4179}41804181if (imethod.method_doc) {4182imethod.is_deprecated = imethod.method_doc->is_deprecated;4183imethod.deprecation_message = imethod.method_doc->deprecated_message;41844185if (imethod.is_deprecated && imethod.deprecation_message.is_empty()) {4186WARN_PRINT("An empty deprecation message is discouraged. Method: '" + itype.proxy_name + "." + imethod.proxy_name + "'.");4187imethod.deprecation_message = "This method is deprecated.";4188}4189}41904191ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,4192"Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");41934194// Compat methods aren't added to the type yet, they need to be checked for conflicts4195// after all the non-compat methods have been added. The compat methods are added in4196// reverse so the most recently added ones take precedence over older compat methods.4197if (imethod.is_compat) {4198// If the method references deprecated types, mark the method as deprecated as well.4199for (const ArgumentInterface &iarg : imethod.arguments) {4200String arg_type_name = iarg.type.cname;4201String doc_name = arg_type_name.begins_with("_") ? arg_type_name.substr(1) : arg_type_name;4202const DocData::ClassDoc &class_doc = EditorHelp::get_doc_data()->class_list[doc_name];4203if (class_doc.is_deprecated) {4204imethod.is_deprecated = true;4205imethod.deprecation_message = "This method overload is deprecated.";4206break;4207}4208}42094210imethod.is_hidden = true;4211compat_methods.push_front(imethod);4212continue;4213}42144215// Methods starting with an underscore are ignored unless they're used as a property setter or getter4216if (!imethod.is_virtual && imethod.name[0] == '_') {4217for (const PropertyInterface &iprop : itype.properties) {4218if (iprop.setter == imethod.name || iprop.getter == imethod.name) {4219imethod.is_internal = true;4220itype.methods.push_back(imethod);4221break;4222}4223}4224} else {4225itype.methods.push_back(imethod);4226}4227}42284229// Add compat methods that don't conflict with other methods in the type.4230for (const MethodInterface &imethod : compat_methods) {4231if (_method_has_conflicting_signature(imethod, itype)) {4232WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored.");4233continue;4234}4235itype.methods.push_back(imethod);4236}42374238// Populate signals42394240const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;42414242for (const KeyValue<StringName, MethodInfo> &E : signal_map) {4243SignalInterface isignal;42444245const MethodInfo &method_info = E.value;42464247isignal.name = method_info.name;4248isignal.cname = method_info.name;42494250for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4251const PropertyInfo &arginfo = method_info.arguments[idx];42524253String orig_arg_name = arginfo.name;42544255ArgumentInterface iarg;4256iarg.name = orig_arg_name;42574258if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4259iarg.type.cname = arginfo.class_name;4260iarg.type.is_enum = true;4261} else if (arginfo.class_name != StringName()) {4262iarg.type.cname = arginfo.class_name;4263} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4264iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4265iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4266} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4267iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4268Vector<String> split = arginfo.hint_string.split(";");4269iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4270iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4271} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4272iarg.type.cname = arginfo.hint_string;4273} else if (arginfo.type == Variant::NIL) {4274iarg.type.cname = name_cache.type_Variant;4275} else {4276iarg.type.cname = _get_type_name_from_meta(arginfo.type, (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4277}42784279iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));42804281isignal.add_argument(iarg);4282}42834284isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name));42854286// Prevent the signal and its enclosing type from sharing the same name4287if (isignal.proxy_name == itype.proxy_name) {4288_log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n",4289isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data());42904291isignal.proxy_name += "_";4292}42934294if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) {4295// ClassDB allows signal names that conflict with method or property names.4296// While registering a signal with a conflicting name is considered wrong,4297// it may still happen and it may take some time until someone fixes the name.4298// We can't allow the bindings to be in a broken state while we wait for a fix;4299// that's why we must handle this possibility by renaming the signal.4300isignal.proxy_name += "Signal";4301}43024303if (itype.class_doc) {4304for (int i = 0; i < itype.class_doc->signals.size(); i++) {4305const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i];4306if (signal_doc.name == isignal.name) {4307isignal.method_doc = &signal_doc;4308break;4309}4310}4311}43124313if (isignal.method_doc) {4314isignal.is_deprecated = isignal.method_doc->is_deprecated;4315isignal.deprecation_message = isignal.method_doc->deprecated_message;43164317if (isignal.is_deprecated && isignal.deprecation_message.is_empty()) {4318WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + itype.proxy_name + "." + isignal.proxy_name + "'.");4319isignal.deprecation_message = "This signal is deprecated.";4320}4321}43224323itype.signals_.push_back(isignal);4324}43254326// Populate enums and constants43274328List<String> constants;4329ClassDB::get_integer_constant_list(type_cname, &constants, true);43304331const HashMap<StringName, ClassDB::ClassInfo::EnumInfo> &enum_map = class_info->enum_map;43324333for (const KeyValue<StringName, ClassDB::ClassInfo::EnumInfo> &E : enum_map) {4334StringName enum_proxy_cname = E.key;4335String enum_proxy_name = pascal_to_pascal_case(enum_proxy_cname.operator String());4336if (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)) {4337// In case the enum name conflicts with other PascalCase members,4338// we append 'Enum' to the enum name in those cases.4339// We have several conflicts between enums and PascalCase properties.4340enum_proxy_name += "Enum";4341enum_proxy_cname = StringName(enum_proxy_name);4342}4343EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);4344const List<StringName> &enum_constants = E.value.constants;4345for (const StringName &constant_cname : enum_constants) {4346String constant_name = constant_cname.operator String();4347int64_t *value = class_info->constant_map.getptr(constant_cname);4348ERR_FAIL_NULL_V(value, false);4349constants.erase(constant_name);43504351ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);43524353iconstant.const_doc = nullptr;4354for (int i = 0; i < itype.class_doc->constants.size(); i++) {4355const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];43564357if (const_doc.name == iconstant.name) {4358iconstant.const_doc = &const_doc;4359break;4360}4361}43624363if (iconstant.const_doc) {4364iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4365iconstant.deprecation_message = iconstant.const_doc->deprecated_message;43664367if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4368WARN_PRINT("An empty deprecation message is discouraged. Enum member: '" + itype.proxy_name + "." + ienum.proxy_name + "." + iconstant.proxy_name + "'.");4369iconstant.deprecation_message = "This enum member is deprecated.";4370}4371}43724373ienum.constants.push_back(iconstant);4374}43754376int prefix_length = _determine_enum_prefix(ienum);43774378_apply_prefix_to_enum_constants(ienum, prefix_length);43794380itype.enums.push_back(ienum);43814382TypeInterface enum_itype;4383enum_itype.is_enum = true;4384enum_itype.name = itype.name + "." + String(E.key);4385enum_itype.cname = StringName(enum_itype.name);4386enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name;4387TypeInterface::postsetup_enum_type(enum_itype);4388enum_types.insert(enum_itype.cname, enum_itype);4389}43904391for (const String &constant_name : constants) {4392int64_t *value = class_info->constant_map.getptr(StringName(constant_name));4393ERR_FAIL_NULL_V(value, false);43944395String constant_proxy_name = snake_to_pascal_case(constant_name, true);43964397if (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)) {4398// In case the constant name conflicts with other PascalCase members,4399// we append 'Constant' to the constant name in those cases.4400constant_proxy_name += "Constant";4401}44024403ConstantInterface iconstant(constant_name, constant_proxy_name, *value);44044405iconstant.const_doc = nullptr;4406for (int i = 0; i < itype.class_doc->constants.size(); i++) {4407const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];44084409if (const_doc.name == iconstant.name) {4410iconstant.const_doc = &const_doc;4411break;4412}4413}44144415if (iconstant.const_doc) {4416iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4417iconstant.deprecation_message = iconstant.const_doc->deprecated_message;44184419if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4420WARN_PRINT("An empty deprecation message is discouraged. Constant: '" + itype.proxy_name + "." + iconstant.proxy_name + "'.");4421iconstant.deprecation_message = "This constant is deprecated.";4422}4423}44244425itype.constants.push_back(iconstant);4426}44274428obj_types.insert(itype.cname, itype);44294430if (itype.is_singleton) {4431// Add singleton instance type.4432itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;4433itype.is_singleton = false;4434itype.is_singleton_instance = true;44354436// Remove constants and enums, those will remain in the static class.4437itype.constants.clear();4438itype.enums.clear();44394440obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);4441}4442}44434444return true;4445}44464447static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {4448return String::num_real(p_vec2.x, true) + "f, " +4449String::num_real(p_vec2.y, true) + "f";4450}44514452static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {4453return String::num_real(p_vec3.x, true) + "f, " +4454String::num_real(p_vec3.y, true) + "f, " +4455String::num_real(p_vec3.z, true) + "f";4456}44574458static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {4459return String::num_real(p_vec4.x, true) + "f, " +4460String::num_real(p_vec4.y, true) + "f, " +4461String::num_real(p_vec4.z, true) + "f, " +4462String::num_real(p_vec4.w, true) + "f";4463}44644465static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {4466return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);4467}44684469static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {4470return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);4471}44724473static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {4474return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);4475}44764477static String _get_color_cs_ctor_args(const Color &p_color) {4478return String::num(p_color.r, 4) + "f, " +4479String::num(p_color.g, 4) + "f, " +4480String::num(p_color.b, 4) + "f, " +4481String::num(p_color.a, 4) + "f";4482}44834484bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {4485r_iarg.def_param_value = p_val;44864487switch (p_val.get_type()) {4488case Variant::NIL:4489// Either Object type or Variant4490r_iarg.default_argument = "default";4491break;4492// Atomic types4493case Variant::BOOL:4494r_iarg.default_argument = bool(p_val) ? "true" : "false";4495break;4496case Variant::INT:4497if (r_iarg.type.cname != name_cache.type_int) {4498r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";4499} else {4500r_iarg.default_argument = p_val.operator String();4501}4502break;4503case Variant::FLOAT:4504r_iarg.default_argument = p_val.operator String();45054506if (r_iarg.type.cname == name_cache.type_float) {4507r_iarg.default_argument += "f";4508}4509break;4510case Variant::STRING:4511case Variant::STRING_NAME:4512case Variant::NODE_PATH:4513if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {4514if (r_iarg.default_argument.length() > 0) {4515r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";4516r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4517} else {4518// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.4519r_iarg.default_argument = "null";4520}4521} else {4522CRASH_COND(r_iarg.type.cname != name_cache.type_String);4523r_iarg.default_argument = "\"" + p_val.operator String() + "\"";4524}4525break;4526case Variant::PLANE: {4527Plane plane = p_val.operator Plane();4528r_iarg.default_argument = "new Plane(new Vector3(" +4529_get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";4530r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4531} break;4532case Variant::AABB: {4533AABB aabb = p_val.operator ::AABB();4534r_iarg.default_argument = "new Aabb(new Vector3(" +4535_get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +4536_get_vector3_cs_ctor_args(aabb.size) + "))";4537r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4538} break;4539case Variant::RECT2: {4540Rect2 rect = p_val.operator Rect2();4541r_iarg.default_argument = "new Rect2(new Vector2(" +4542_get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +4543_get_vector2_cs_ctor_args(rect.size) + "))";4544r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4545} break;4546case Variant::RECT2I: {4547Rect2i rect = p_val.operator Rect2i();4548r_iarg.default_argument = "new Rect2I(new Vector2I(" +4549_get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +4550_get_vector2i_cs_ctor_args(rect.size) + "))";4551r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4552} break;4553case Variant::COLOR:4554r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";4555r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4556break;4557case Variant::VECTOR2:4558r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";4559r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4560break;4561case Variant::VECTOR2I:4562r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";4563r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4564break;4565case Variant::VECTOR3:4566r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";4567r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4568break;4569case Variant::VECTOR3I:4570r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";4571r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4572break;4573case Variant::VECTOR4:4574r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";4575r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4576break;4577case Variant::VECTOR4I:4578r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";4579r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4580break;4581case Variant::OBJECT:4582ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4583"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");45844585r_iarg.default_argument = "null";4586break;4587case Variant::DICTIONARY:4588ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false,4589"Default value of type 'Dictionary' must be an empty dictionary.");4590// The [cs_in] expression already interprets null values as empty dictionaries.4591r_iarg.default_argument = "null";4592r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4593break;4594case Variant::RID:4595ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,4596"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");45974598ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4599"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");46004601r_iarg.default_argument = "default";4602break;4603case Variant::ARRAY:4604ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false,4605"Default value of type 'Array' must be an empty array.");4606// The [cs_in] expression already interprets null values as empty arrays.4607r_iarg.default_argument = "null";4608r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4609break;4610case Variant::PACKED_BYTE_ARRAY:4611case Variant::PACKED_INT32_ARRAY:4612case Variant::PACKED_INT64_ARRAY:4613case Variant::PACKED_FLOAT32_ARRAY:4614case Variant::PACKED_FLOAT64_ARRAY:4615case Variant::PACKED_STRING_ARRAY:4616case Variant::PACKED_VECTOR2_ARRAY:4617case Variant::PACKED_VECTOR3_ARRAY:4618case Variant::PACKED_VECTOR4_ARRAY:4619case Variant::PACKED_COLOR_ARRAY:4620r_iarg.default_argument = "Array.Empty<%s>()";4621r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4622break;4623case Variant::TRANSFORM2D: {4624Transform2D transform = p_val.operator Transform2D();4625if (transform == Transform2D()) {4626r_iarg.default_argument = "Transform2D.Identity";4627} else {4628r_iarg.default_argument = "new Transform2D(new Vector2(" +4629_get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +4630_get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +4631_get_vector2_cs_ctor_args(transform.columns[2]) + "))";4632}4633r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4634} break;4635case Variant::TRANSFORM3D: {4636Transform3D transform = p_val.operator Transform3D();4637if (transform == Transform3D()) {4638r_iarg.default_argument = "Transform3D.Identity";4639} else {4640Basis basis = transform.basis;4641r_iarg.default_argument = "new Transform3D(new Vector3(" +4642_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4643_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4644_get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +4645_get_vector3_cs_ctor_args(transform.origin) + "))";4646}4647r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4648} break;4649case Variant::PROJECTION: {4650Projection projection = p_val.operator Projection();4651if (projection == Projection()) {4652r_iarg.default_argument = "Projection.Identity";4653} else {4654r_iarg.default_argument = "new Projection(new Vector4(" +4655_get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +4656_get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +4657_get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +4658_get_vector4_cs_ctor_args(projection.columns[3]) + "))";4659}4660r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4661} break;4662case Variant::BASIS: {4663Basis basis = p_val.operator Basis();4664if (basis == Basis()) {4665r_iarg.default_argument = "Basis.Identity";4666} else {4667r_iarg.default_argument = "new Basis(new Vector3(" +4668_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4669_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4670_get_vector3_cs_ctor_args(basis.get_column(2)) + "))";4671}4672r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4673} break;4674case Variant::QUATERNION: {4675Quaternion quaternion = p_val.operator Quaternion();4676if (quaternion == Quaternion()) {4677r_iarg.default_argument = "Quaternion.Identity";4678} else {4679r_iarg.default_argument = "new Quaternion(" +4680String::num_real(quaternion.x, false) + "f, " +4681String::num_real(quaternion.y, false) + "f, " +4682String::num_real(quaternion.z, false) + "f, " +4683String::num_real(quaternion.w, false) + "f)";4684}4685r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4686} break;4687case Variant::CALLABLE:4688ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Callable, false,4689"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Callable) + "'.");4690ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4691"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4692r_iarg.default_argument = "default";4693break;4694case Variant::SIGNAL:4695ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Signal, false,4696"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Signal) + "'.");4697ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4698"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4699r_iarg.default_argument = "default";4700break;4701case Variant::VARIANT_MAX:4702ERR_FAIL_V_MSG(false, "Unexpected Variant type: " + itos(p_val.get_type()));4703break;4704}47054706if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") {4707r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4708}47094710return true;4711}47124713void BindingsGenerator::_populate_builtin_type_interfaces() {4714builtin_types.clear();47154716TypeInterface itype;47174718#define INSERT_STRUCT_TYPE(m_type, m_proxy_name) \4719{ \4720itype = TypeInterface::create_value_type(String(#m_type), String(#m_proxy_name)); \4721itype.cs_in_expr = "&%0"; \4722itype.cs_in_expr_is_unsafe = true; \4723builtin_types.insert(itype.cname, itype); \4724}47254726INSERT_STRUCT_TYPE(Vector2, Vector2)4727INSERT_STRUCT_TYPE(Vector2i, Vector2I)4728INSERT_STRUCT_TYPE(Rect2, Rect2)4729INSERT_STRUCT_TYPE(Rect2i, Rect2I)4730INSERT_STRUCT_TYPE(Transform2D, Transform2D)4731INSERT_STRUCT_TYPE(Vector3, Vector3)4732INSERT_STRUCT_TYPE(Vector3i, Vector3I)4733INSERT_STRUCT_TYPE(Basis, Basis)4734INSERT_STRUCT_TYPE(Quaternion, Quaternion)4735INSERT_STRUCT_TYPE(Transform3D, Transform3D)4736INSERT_STRUCT_TYPE(AABB, Aabb)4737INSERT_STRUCT_TYPE(Color, Color)4738INSERT_STRUCT_TYPE(Plane, Plane)4739INSERT_STRUCT_TYPE(Vector4, Vector4)4740INSERT_STRUCT_TYPE(Vector4i, Vector4I)4741INSERT_STRUCT_TYPE(Projection, Projection)47424743#undef INSERT_STRUCT_TYPE47444745// bool4746itype = TypeInterface::create_value_type(String("bool"));4747itype.cs_in_expr = "%0.ToGodotBool()";4748itype.cs_out = "%5return %0(%1).ToBool();";4749itype.c_type = "godot_bool";4750itype.c_type_in = itype.c_type;4751itype.c_type_out = itype.c_type;4752itype.c_arg_in = "&%s";4753itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n";4754builtin_types.insert(itype.cname, itype);47554756// Integer types4757{4758// C interface for 'uint32_t' is the same as that of enums. Remember to apply4759// any of the changes done here to 'TypeInterface::postsetup_enum_type' as well.4760#define INSERT_INT_TYPE(m_name, m_int_struct_name) \4761{ \4762itype = TypeInterface::create_value_type(String(m_name)); \4763if (itype.name != "long" && itype.name != "ulong") { \4764itype.c_in = "%5%0 %1_in = %1;\n"; \4765itype.c_out = "%5return (%0)(%1);\n"; \4766itype.c_type = "long"; \4767itype.c_arg_in = "&%s_in"; \4768} else { \4769itype.c_arg_in = "&%s"; \4770} \4771itype.c_type_in = itype.name; \4772itype.c_type_out = itype.name; \4773itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \4774builtin_types.insert(itype.cname, itype); \4775}47764777// The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type'47784779INSERT_INT_TYPE("sbyte", "Int8");4780INSERT_INT_TYPE("short", "Int16");4781INSERT_INT_TYPE("int", "Int32");4782INSERT_INT_TYPE("long", "Int64");4783INSERT_INT_TYPE("byte", "UInt8");4784INSERT_INT_TYPE("ushort", "UInt16");4785INSERT_INT_TYPE("uint", "UInt32");4786INSERT_INT_TYPE("ulong", "UInt64");47874788#undef INSERT_INT_TYPE4789}47904791// Floating point types4792{4793// float4794itype = TypeInterface();4795itype.name = "float";4796itype.cname = itype.name;4797itype.proxy_name = "float";4798itype.cs_type = itype.proxy_name;4799{4800// The expected type for 'float' in ptrcall is 'double'4801itype.c_in = "%5%0 %1_in = %1;\n";4802itype.c_out = "%5return (%0)%1;\n";4803itype.c_type = "double";4804itype.c_arg_in = "&%s_in";4805}4806itype.c_type_in = itype.proxy_name;4807itype.c_type_out = itype.proxy_name;4808itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4809builtin_types.insert(itype.cname, itype);48104811// double4812itype = TypeInterface();4813itype.name = "double";4814itype.cname = itype.name;4815itype.proxy_name = "double";4816itype.cs_type = itype.proxy_name;4817itype.c_type = "double";4818itype.c_arg_in = "&%s";4819itype.c_type_in = itype.proxy_name;4820itype.c_type_out = itype.proxy_name;4821itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4822builtin_types.insert(itype.cname, itype);4823}48244825// String4826itype = TypeInterface();4827itype.name = "String";4828itype.cname = itype.name;4829itype.proxy_name = "string";4830itype.cs_type = itype.proxy_name;4831itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n";4832itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n";4833itype.c_arg_in = "&%s_in";4834itype.c_type = "godot_string";4835itype.c_type_in = itype.cs_type;4836itype.c_type_out = itype.cs_type;4837itype.c_type_is_disposable_struct = true;4838itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n";4839builtin_types.insert(itype.cname, itype);48404841// StringName4842itype = TypeInterface();4843itype.name = "StringName";4844itype.cname = itype.name;4845itype.proxy_name = "StringName";4846itype.cs_type = itype.proxy_name;4847itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4848// Cannot pass null StringName to ptrcall4849itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4850itype.c_arg_in = "&%s";4851itype.c_type = "godot_string_name";4852itype.c_type_in = itype.c_type;4853itype.c_type_out = itype.cs_type;4854itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n";4855itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4856itype.c_ret_needs_default_initialization = true;4857builtin_types.insert(itype.cname, itype);48584859// NodePath4860itype = TypeInterface();4861itype.name = "NodePath";4862itype.cname = itype.name;4863itype.proxy_name = "NodePath";4864itype.cs_type = itype.proxy_name;4865itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4866// Cannot pass null NodePath to ptrcall4867itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4868itype.c_arg_in = "&%s";4869itype.c_type = "godot_node_path";4870itype.c_type_in = itype.c_type;4871itype.c_type_out = itype.cs_type;4872itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4873itype.c_ret_needs_default_initialization = true;4874builtin_types.insert(itype.cname, itype);48754876// RID4877itype = TypeInterface();4878itype.name = "RID";4879itype.cname = itype.name;4880itype.proxy_name = "Rid";4881itype.cs_type = itype.proxy_name;4882itype.c_arg_in = "&%s";4883itype.c_type = itype.cs_type;4884itype.c_type_in = itype.c_type;4885itype.c_type_out = itype.c_type;4886builtin_types.insert(itype.cname, itype);48874888// Variant4889itype = TypeInterface();4890itype.name = "Variant";4891itype.cname = itype.name;4892itype.proxy_name = "Variant";4893itype.cs_type = itype.proxy_name;4894itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n";4895itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n";4896itype.c_arg_in = "&%s_in";4897itype.c_type = "godot_variant";4898itype.c_type_in = itype.cs_type;4899itype.c_type_out = itype.cs_type;4900itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4901itype.c_ret_needs_default_initialization = true;4902builtin_types.insert(itype.cname, itype);49034904// Callable4905itype = TypeInterface::create_value_type(String("Callable"));4906itype.cs_in_expr = "%0";4907itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n";4908itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %1);\n";4909itype.c_arg_in = "&%s_in";4910itype.c_type = "godot_callable";4911itype.c_type_in = "in " + itype.cs_type;4912itype.c_type_out = itype.cs_type;4913itype.c_type_is_disposable_struct = true;4914builtin_types.insert(itype.cname, itype);49154916// Signal4917itype = TypeInterface();4918itype.name = "Signal";4919itype.cname = itype.name;4920itype.proxy_name = "Signal";4921itype.cs_type = itype.proxy_name;4922itype.cs_in_expr = "%0";4923itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n";4924itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(in %1);\n";4925itype.c_arg_in = "&%s_in";4926itype.c_type = "godot_signal";4927itype.c_type_in = "in " + itype.cs_type;4928itype.c_type_out = itype.cs_type;4929itype.c_type_is_disposable_struct = true;4930builtin_types.insert(itype.cname, itype);49314932// VarArg (fictitious type to represent variable arguments)4933itype = TypeInterface();4934itype.name = "VarArg";4935itype.cname = itype.name;4936itype.proxy_name = "ReadOnlySpan<Variant>";4937itype.cs_type = "params Variant[]";4938itype.cs_in_expr = "%0";4939// c_type, c_in and c_arg_in are hard-coded in the generator.4940// c_out and c_type_out are not applicable to VarArg.4941itype.c_arg_in = "&%s_in";4942itype.c_type_in = "ReadOnlySpan<Variant>";4943itype.is_span_compatible = true;4944builtin_types.insert(itype.cname, itype);49454946#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \4947{ \4948itype = TypeInterface(); \4949itype.name = #m_name; \4950itype.cname = itype.name; \4951itype.proxy_name = #m_proxy_t "[]"; \4952itype.cs_type = itype.proxy_name; \4953itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \4954itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \4955itype.c_arg_in = "&%s_in"; \4956itype.c_type = #m_managed_type; \4957itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \4958itype.c_type_out = itype.proxy_name; \4959itype.c_type_is_disposable_struct = true; \4960itype.is_span_compatible = true; \4961builtin_types.insert(itype.name, itype); \4962}49634964#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t)49654966INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int);4967INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long);4968INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte);49694970INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float);4971INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double);49724973INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string);49744975INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color);4976INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2);4977INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3);4978INSERT_ARRAY(PackedVector4Array, godot_packed_vector4_array, Vector4);49794980#undef INSERT_ARRAY49814982// Array4983itype = TypeInterface();4984itype.name = "Array";4985itype.cname = itype.name;4986itype.proxy_name = itype.name;4987itype.type_parameter_count = 1;4988itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;4989itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";4990itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4991itype.c_arg_in = "&%s";4992itype.c_type = "godot_array";4993itype.c_type_in = itype.c_type;4994itype.c_type_out = itype.cs_type;4995itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4996itype.c_ret_needs_default_initialization = true;4997builtin_types.insert(itype.cname, itype);49984999// Array_@generic5000// Reuse Array's itype5001itype.name = "Array_@generic";5002itype.cname = itype.name;5003itype.cs_out = "%5return new %2(%0(%1));";5004// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5005itype.cs_variant_to_managed = "VariantUtils.ConvertToArray(%0)";5006itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)";5007builtin_types.insert(itype.cname, itype);50085009// Dictionary5010itype = TypeInterface();5011itype.name = "Dictionary";5012itype.cname = itype.name;5013itype.proxy_name = itype.name;5014itype.type_parameter_count = 2;5015itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;5016itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";5017itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";5018itype.c_arg_in = "&%s";5019itype.c_type = "godot_dictionary";5020itype.c_type_in = itype.c_type;5021itype.c_type_out = itype.cs_type;5022itype.c_type_is_disposable_struct = false; // [c_out] takes ownership5023itype.c_ret_needs_default_initialization = true;5024builtin_types.insert(itype.cname, itype);50255026// Dictionary_@generic5027// Reuse Dictionary's itype5028itype.name = "Dictionary_@generic";5029itype.cname = itype.name;5030itype.cs_out = "%5return new %2(%0(%1));";5031// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5032itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionary(%0)";5033itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)";5034builtin_types.insert(itype.cname, itype);50355036// void (fictitious type to represent the return type of methods that do not return anything)5037itype = TypeInterface();5038itype.name = "void";5039itype.cname = itype.name;5040itype.proxy_name = itype.name;5041itype.cs_type = itype.proxy_name;5042itype.c_type = itype.proxy_name;5043itype.c_type_in = itype.c_type;5044itype.c_type_out = itype.c_type;5045builtin_types.insert(itype.cname, itype);5046}50475048void BindingsGenerator::_populate_global_constants() {5049int global_constants_count = CoreConstants::get_global_constant_count();50505051if (global_constants_count > 0) {5052HashMap<String, DocData::ClassDoc>::Iterator match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");50535054CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData.");50555056const DocData::ClassDoc &global_scope_doc = match->value;50575058for (int i = 0; i < global_constants_count; i++) {5059String constant_name = CoreConstants::get_global_constant_name(i);50605061const DocData::ConstantDoc *const_doc = nullptr;5062for (int j = 0; j < global_scope_doc.constants.size(); j++) {5063const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];50645065if (curr_const_doc.name == constant_name) {5066const_doc = &curr_const_doc;5067break;5068}5069}50705071int64_t constant_value = CoreConstants::get_global_constant_value(i);5072StringName enum_name = CoreConstants::get_global_constant_enum(i);50735074ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);5075iconstant.const_doc = const_doc;50765077if (enum_name != StringName()) {5078EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));5079List<EnumInterface>::Element *enum_match = global_enums.find(ienum);5080if (enum_match) {5081enum_match->get().constants.push_back(iconstant);5082} else {5083ienum.constants.push_back(iconstant);5084global_enums.push_back(ienum);5085}5086} else {5087global_constants.push_back(iconstant);5088}5089}50905091for (EnumInterface &ienum : global_enums) {5092TypeInterface enum_itype;5093enum_itype.is_enum = true;5094enum_itype.name = ienum.cname.operator String();5095enum_itype.cname = ienum.cname;5096enum_itype.proxy_name = ienum.proxy_name;5097TypeInterface::postsetup_enum_type(enum_itype);5098enum_types.insert(enum_itype.cname, enum_itype);50995100int prefix_length = _determine_enum_prefix(ienum);51015102// HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.5103if (ienum.cname == name_cache.enum_Error) {5104if (prefix_length > 0) { // Just in case it ever changes5105ERR_PRINT("Prefix for enum '" _STR(Error) "' is not empty.");5106}51075108prefix_length = 1; // 'ERR_'5109}51105111_apply_prefix_to_enum_constants(ienum, prefix_length);5112}5113}51145115for (int i = 0; i < Variant::VARIANT_MAX; i++) {5116if (i == Variant::OBJECT) {5117continue;5118}51195120const Variant::Type type = Variant::Type(i);51215122List<StringName> enum_names;5123Variant::get_enums_for_type(type, &enum_names);51245125for (const StringName &enum_name : enum_names) {5126TypeInterface enum_itype;5127enum_itype.is_enum = true;5128enum_itype.name = Variant::get_type_name(type) + "." + enum_name;5129enum_itype.cname = enum_itype.name;5130enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);5131TypeInterface::postsetup_enum_type(enum_itype);5132enum_types.insert(enum_itype.cname, enum_itype);5133}5134}5135}51365137bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) {5138// Compare p_imethod with all the methods already registered in p_itype.5139for (const MethodInterface &method : p_itype.methods) {5140if (method.proxy_name == p_imethod.proxy_name) {5141if (_method_has_conflicting_signature(p_imethod, method)) {5142return true;5143}5144}5145}51465147return false;5148}51495150bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) {5151// Check if a method already exists in p_itype with a method signature that would conflict with p_imethod.5152// The return type is ignored because only changing the return type is not enough to avoid conflicts.5153// The const keyword is also ignored since it doesn't generate different C# code.51545155if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) {5156// Different argument count, so no conflict.5157return false;5158}51595160List<BindingsGenerator::ArgumentInterface>::ConstIterator left_itr = p_imethod_left.arguments.begin();5161List<BindingsGenerator::ArgumentInterface>::ConstIterator right_itr = p_imethod_right.arguments.begin();5162for (; left_itr != p_imethod_left.arguments.end(); ++left_itr, ++right_itr) {5163const ArgumentInterface &iarg_left = *left_itr;5164const ArgumentInterface &iarg_right = *right_itr;51655166if (iarg_left.type.cname != iarg_right.type.cname) {5167// Different types for arguments in the same position, so no conflict.5168return false;5169}51705171if (iarg_left.def_param_mode != iarg_right.def_param_mode) {5172// If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T'5173// and will not create a conflict.5174if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) {5175return false;5176}5177}5178}51795180return true;5181}51825183void BindingsGenerator::_initialize_blacklisted_methods() {5184blacklisted_methods["Object"].push_back("to_string"); // there is already ToString5185blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead5186blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)5187}51885189void BindingsGenerator::_initialize_compat_singletons() {5190compat_singletons.insert("EditorInterface");5191}51925193void BindingsGenerator::_log(const char *p_format, ...) {5194if (log_print_enabled) {5195va_list list;51965197va_start(list, p_format);5198OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data());5199va_end(list);5200}5201}52025203void BindingsGenerator::_initialize() {5204initialized = false;52055206EditorHelp::generate_doc(false);52075208enum_types.clear();52095210_initialize_blacklisted_methods();52115212_initialize_compat_singletons();52135214bool obj_type_ok = _populate_object_type_interfaces();5215ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");52165217_populate_builtin_type_interfaces();52185219_populate_global_constants();52205221// Generate internal calls (after populating type interfaces and global constants)52225223for (const KeyValue<StringName, TypeInterface> &E : obj_types) {5224const TypeInterface &itype = E.value;5225Error err = _populate_method_icalls_table(itype);5226ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name);5227}52285229initialized = true;5230}52315232static String generate_all_glue_option = "--generate-mono-glue";52335234static void handle_cmdline_options(String glue_dir_path) {5235BindingsGenerator bindings_generator;5236bindings_generator.set_log_print_enabled(true);52375238if (!bindings_generator.is_initialized()) {5239ERR_PRINT("Failed to initialize the bindings generator");5240return;5241}52425243CRASH_COND(glue_dir_path.is_empty());52445245if (bindings_generator.generate_cs_api(glue_dir_path.path_join(API_SOLUTION_NAME)) != OK) {5246ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API.");5247}5248}52495250static void cleanup_and_exit_godot() {5251// Exit once done.5252Main::cleanup(true);5253::exit(0);5254}52555256void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {5257String glue_dir_path;52585259const List<String>::Element *elem = p_cmdline_args.front();52605261while (elem) {5262if (elem->get() == generate_all_glue_option) {5263const List<String>::Element *path_elem = elem->next();52645265if (path_elem) {5266glue_dir_path = path_elem->get();5267elem = elem->next();5268} else {5269ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");5270// Exit once done with invalid command line arguments.5271cleanup_and_exit_godot();5272}52735274break;5275}52765277elem = elem->next();5278}52795280if (glue_dir_path.length()) {5281if (Engine::get_singleton()->is_editor_hint() ||5282Engine::get_singleton()->is_project_manager_hint()) {5283handle_cmdline_options(glue_dir_path);5284} else {5285// Running from a project folder, which doesn't make sense and crashes.5286ERR_PRINT(generate_all_glue_option + ": Cannot generate Mono glue while running a game project. Change current directory or enable --editor.");5287}5288// Exit once done.5289cleanup_and_exit_godot();5290}5291}52925293#endif // DEBUG_ENABLED529452955296