Path: blob/master/modules/mono/editor/bindings_generator.cpp
20941 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 (!_validate_api_type(target_itype, p_itype)) {276// If the target member is referenced from a type with a different API level, we can't reference it.277_append_text_undeclared(output, link_target);278} else if (link_tag == "method") {279_append_text_method(output, target_itype, target_cname, link_target, link_target_parts);280} else if (link_tag == "constructor") {281// TODO: Support constructors?282_append_text_undeclared(output, link_target);283} else if (link_tag == "operator") {284// TODO: Support operators?285_append_text_undeclared(output, link_target);286} else if (link_tag == "member") {287_append_text_member(output, target_itype, target_cname, link_target, link_target_parts);288} else if (link_tag == "signal") {289_append_text_signal(output, target_itype, target_cname, link_target, link_target_parts);290} else if (link_tag == "enum") {291_append_text_enum(output, target_itype, target_cname, link_target, link_target_parts);292} else if (link_tag == "constant") {293_append_text_constant(output, target_itype, target_cname, link_target, link_target_parts);294} else if (link_tag == "param") {295_append_text_param(output, link_target);296} else if (link_tag == "theme_item") {297// We do not declare theme_items in any way in C#, so there is nothing to reference.298_append_text_undeclared(output, link_target);299}300301pos = brk_end + 1;302} else if (doc->class_list.has(tag)) {303if (tag == "Array" || tag == "Dictionary") {304output.append("'" BINDINGS_NAMESPACE_COLLECTIONS ".");305output.append(tag);306output.append("'");307} else if (tag == "bool" || tag == "int") {308output.append(tag);309} else if (tag == "float") {310output.append(311#ifdef REAL_T_IS_DOUBLE312"double"313#else314"float"315#endif316);317} else if (tag == "Variant") {318output.append("'Godot.Variant'");319} else if (tag == "String") {320output.append("string");321} else if (tag == "Nil") {322output.append("null");323} else if (tag.begins_with("@")) {324// @GlobalScope, @GDScript, etc.325output.append("'" + tag + "'");326} else if (tag == "PackedByteArray") {327output.append("byte[]");328} else if (tag == "PackedInt32Array") {329output.append("int[]");330} else if (tag == "PackedInt64Array") {331output.append("long[]");332} else if (tag == "PackedFloat32Array") {333output.append("float[]");334} else if (tag == "PackedFloat64Array") {335output.append("double[]");336} else if (tag == "PackedStringArray") {337output.append("string[]");338} else if (tag == "PackedVector2Array") {339output.append("'" BINDINGS_NAMESPACE ".Vector2[]'");340} else if (tag == "PackedVector3Array") {341output.append("'" BINDINGS_NAMESPACE ".Vector3[]'");342} else if (tag == "PackedColorArray") {343output.append("'" BINDINGS_NAMESPACE ".Color[]'");344} else if (tag == "PackedVector4Array") {345output.append("'" BINDINGS_NAMESPACE ".Vector4[]'");346} else {347const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));348349if (!target_itype) {350target_itype = _get_type_or_null(TypeReference("_" + tag));351}352353if (target_itype) {354output.append("'" + target_itype->proxy_name + "'");355} else {356ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");357output.append("'" + tag + "'");358}359}360361pos = brk_end + 1;362} else if (tag == "b") {363// Bold is not supported.364pos = brk_end + 1;365tag_stack.push_front(tag);366} else if (tag == "i") {367// Italic is not supported.368pos = brk_end + 1;369tag_stack.push_front(tag);370} else if (tag == "code" || tag.begins_with("code ")) {371code_tag = true;372pos = brk_end + 1;373tag_stack.push_front("code");374} else if (tag == "kbd") {375// Keyboard combinations are not supported.376pos = brk_end + 1;377tag_stack.push_front(tag);378} else if (tag == "center") {379// Center alignment is not supported.380pos = brk_end + 1;381tag_stack.push_front(tag);382} else if (tag == "br") {383// Break is not supported.384pos = brk_end + 1;385tag_stack.push_front(tag);386} else if (tag == "u") {387// Underline is not supported.388pos = brk_end + 1;389tag_stack.push_front(tag);390} else if (tag == "s") {391// Strikethrough is not supported.392pos = brk_end + 1;393tag_stack.push_front(tag);394} else if (tag == "url") {395int end = bbcode.find_char('[', brk_end);396if (end == -1) {397end = bbcode.length();398}399String url = bbcode.substr(brk_end + 1, end - brk_end - 1);400// Not supported. Just append the url.401output.append(url);402403pos = brk_end + 1;404tag_stack.push_front(tag);405} else if (tag.begins_with("url=")) {406String url = tag.substr(4);407// Not supported. Just append the url.408output.append(url);409410pos = brk_end + 1;411tag_stack.push_front("url");412} else if (tag == "img") {413int end = bbcode.find_char('[', brk_end);414if (end == -1) {415end = bbcode.length();416}417String image = bbcode.substr(brk_end + 1, end - brk_end - 1);418419// Not supported. Just append the bbcode.420output.append("[img]");421output.append(image);422output.append("[/img]");423424pos = end;425tag_stack.push_front(tag);426} else if (tag.begins_with("color=")) {427// Not supported.428pos = brk_end + 1;429tag_stack.push_front("color");430} else if (tag.begins_with("font=")) {431// Not supported.432pos = brk_end + 1;433tag_stack.push_front("font");434} else {435// Ignore unrecognized tag.436output.append("[");437pos = brk_pos + 1;438}439}440441return output.as_string();442}443444String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {445// Based on the version in EditorHelp.446447if (p_bbcode.is_empty()) {448return String();449}450451DocTools *doc = EditorHelp::get_doc_data();452453String bbcode = p_bbcode;454455StringBuilder xml_output;456457xml_output.append("<para>");458459List<String> tag_stack;460bool code_tag = false;461bool line_del = false;462463int pos = 0;464while (pos < bbcode.length()) {465int brk_pos = bbcode.find_char('[', pos);466467if (brk_pos < 0) {468brk_pos = bbcode.length();469}470471if (brk_pos > pos) {472if (!line_del) {473String text = bbcode.substr(pos, brk_pos - pos);474if (code_tag || tag_stack.size() > 0) {475xml_output.append(text.xml_escape());476} else {477Vector<String> lines = text.split("\n");478for (int i = 0; i < lines.size(); i++) {479if (i != 0) {480xml_output.append("<para>");481}482483xml_output.append(lines[i].xml_escape());484485if (i != lines.size() - 1) {486xml_output.append("</para>\n");487}488}489}490}491}492493if (brk_pos == bbcode.length()) {494// Nothing else to add.495break;496}497498int brk_end = bbcode.find_char(']', brk_pos + 1);499500if (brk_end == -1) {501if (!line_del) {502String text = bbcode.substr(brk_pos);503if (code_tag || tag_stack.size() > 0) {504xml_output.append(text.xml_escape());505} else {506Vector<String> lines = text.split("\n");507for (int i = 0; i < lines.size(); i++) {508if (i != 0) {509xml_output.append("<para>");510}511512xml_output.append(lines[i].xml_escape());513514if (i != lines.size() - 1) {515xml_output.append("</para>\n");516}517}518}519}520521break;522}523524String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);525526if (tag.begins_with("/")) {527bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);528529if (!tag_ok) {530if (!line_del) {531xml_output.append("[");532}533pos = brk_pos + 1;534continue;535}536537tag_stack.pop_front();538pos = brk_end + 1;539code_tag = false;540541if (tag == "/url") {542xml_output.append("</a>");543} else if (tag == "/code") {544xml_output.append("</c>");545} else if (tag == "/codeblock") {546xml_output.append("</code>");547} else if (tag == "/b") {548xml_output.append("</b>");549} else if (tag == "/i") {550xml_output.append("</i>");551} else if (tag == "/csharp") {552xml_output.append("</code>");553line_del = true;554} else if (tag == "/codeblocks") {555line_del = false;556}557} else if (code_tag) {558xml_output.append("[");559pos = brk_pos + 1;560} 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 ")) {561const int tag_end = tag.find_char(' ');562const String link_tag = tag.substr(0, tag_end);563const String link_target = tag.substr(tag_end + 1).lstrip(" ");564565const Vector<String> link_target_parts = link_target.split(".");566567if (link_target_parts.is_empty() || link_target_parts.size() > 2) {568ERR_PRINT("Invalid reference format: '" + tag + "'.");569570xml_output.append("<c>");571xml_output.append(tag);572xml_output.append("</c>");573574pos = brk_end + 1;575continue;576}577578const TypeInterface *target_itype;579StringName target_cname;580581if (link_target_parts.size() == 2) {582target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));583if (!target_itype) {584target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));585}586target_cname = link_target_parts[1];587} else {588target_itype = p_itype;589target_cname = link_target_parts[0];590}591592if (!_validate_api_type(target_itype, p_itype)) {593// If the target member is referenced from a type with a different API level, we can't reference it.594_append_xml_undeclared(xml_output, link_target);595} else if (link_tag == "method") {596_append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);597} else if (link_tag == "constructor") {598// TODO: Support constructors?599_append_xml_undeclared(xml_output, link_target);600} else if (link_tag == "operator") {601// TODO: Support operators?602_append_xml_undeclared(xml_output, link_target);603} else if (link_tag == "member") {604_append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);605} else if (link_tag == "signal") {606_append_xml_signal(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);607} else if (link_tag == "enum") {608_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);609} else if (link_tag == "constant") {610_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);611} else if (link_tag == "param") {612_append_xml_param(xml_output, link_target, p_is_signal);613} else if (link_tag == "theme_item") {614// We do not declare theme_items in any way in C#, so there is nothing to reference.615_append_xml_undeclared(xml_output, link_target);616}617618pos = brk_end + 1;619} else if (doc->class_list.has(tag)) {620if (tag == "Array" || tag == "Dictionary") {621xml_output.append("<see cref=\"" BINDINGS_NAMESPACE_COLLECTIONS ".");622xml_output.append(tag);623xml_output.append("\"/>");624} else if (tag == "bool" || tag == "int") {625xml_output.append("<see cref=\"");626xml_output.append(tag);627xml_output.append("\"/>");628} else if (tag == "float") {629xml_output.append("<see cref=\""630#ifdef REAL_T_IS_DOUBLE631"double"632#else633"float"634#endif635"\"/>");636} else if (tag == "Variant") {637xml_output.append("<see cref=\"Godot.Variant\"/>");638} else if (tag == "String") {639xml_output.append("<see cref=\"string\"/>");640} else if (tag == "Nil") {641xml_output.append("<see langword=\"null\"/>");642} else if (tag.begins_with("@")) {643// @GlobalScope, @GDScript, etc.644xml_output.append("<c>");645xml_output.append(tag);646xml_output.append("</c>");647} else if (tag == "PackedByteArray") {648xml_output.append("<see cref=\"byte\"/>[]");649} else if (tag == "PackedInt32Array") {650xml_output.append("<see cref=\"int\"/>[]");651} else if (tag == "PackedInt64Array") {652xml_output.append("<see cref=\"long\"/>[]");653} else if (tag == "PackedFloat32Array") {654xml_output.append("<see cref=\"float\"/>[]");655} else if (tag == "PackedFloat64Array") {656xml_output.append("<see cref=\"double\"/>[]");657} else if (tag == "PackedStringArray") {658xml_output.append("<see cref=\"string\"/>[]");659} else if (tag == "PackedVector2Array") {660xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>[]");661} else if (tag == "PackedVector3Array") {662xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>[]");663} else if (tag == "PackedColorArray") {664xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>[]");665} else if (tag == "PackedVector4Array") {666xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector4\"/>[]");667} else {668const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));669670if (!target_itype) {671target_itype = _get_type_or_null(TypeReference("_" + tag));672}673674if (target_itype) {675if (!_validate_api_type(target_itype, p_itype)) {676_append_xml_undeclared(xml_output, target_itype->proxy_name);677} else {678xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");679xml_output.append(target_itype->proxy_name);680xml_output.append("\"/>");681}682} else {683ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");684685xml_output.append("<c>");686xml_output.append(tag);687xml_output.append("</c>");688}689}690691pos = brk_end + 1;692} else if (tag == "b") {693xml_output.append("<b>");694695pos = brk_end + 1;696tag_stack.push_front(tag);697} else if (tag == "i") {698xml_output.append("<i>");699700pos = brk_end + 1;701tag_stack.push_front(tag);702} else if (tag == "code" || tag.begins_with("code ")) {703int end = bbcode.find_char('[', brk_end);704if (end == -1) {705end = bbcode.length();706}707String code = bbcode.substr(brk_end + 1, end - brk_end - 1);708if (langword_check.has(code)) {709xml_output.append("<see langword=\"");710xml_output.append(code);711xml_output.append("\"/>");712713pos = brk_end + code.length() + 8;714} else {715xml_output.append("<c>");716717code_tag = true;718pos = brk_end + 1;719tag_stack.push_front("code");720}721} else if (tag == "codeblock" || tag.begins_with("codeblock ")) {722xml_output.append("<code>");723724code_tag = true;725pos = brk_end + 1;726tag_stack.push_front("codeblock");727} else if (tag == "codeblocks") {728line_del = true;729pos = brk_end + 1;730tag_stack.push_front(tag);731} else if (tag == "csharp" || tag.begins_with("csharp ")) {732xml_output.append("<code>");733734line_del = false;735code_tag = true;736pos = brk_end + 1;737tag_stack.push_front("csharp");738} else if (tag == "kbd") {739// Keyboard combinations are not supported in xml comments.740pos = brk_end + 1;741tag_stack.push_front(tag);742} else if (tag == "center") {743// Center alignment is not supported in xml comments.744pos = brk_end + 1;745tag_stack.push_front(tag);746} else if (tag == "br") {747xml_output.append("\n"); // FIXME: Should use <para> instead. Luckily this tag isn't used for now.748pos = brk_end + 1;749} else if (tag == "u") {750// Underline is not supported in Rider xml comments.751pos = brk_end + 1;752tag_stack.push_front(tag);753} else if (tag == "s") {754// Strikethrough is not supported in xml comments.755pos = brk_end + 1;756tag_stack.push_front(tag);757} else if (tag == "url") {758int end = bbcode.find_char('[', brk_end);759if (end == -1) {760end = bbcode.length();761}762String url = bbcode.substr(brk_end + 1, end - brk_end - 1);763xml_output.append("<a href=\"");764xml_output.append(url);765xml_output.append("\">");766xml_output.append(url);767768pos = brk_end + 1;769tag_stack.push_front(tag);770} else if (tag.begins_with("url=")) {771String url = tag.substr(4);772xml_output.append("<a href=\"");773xml_output.append(url);774xml_output.append("\">");775776pos = brk_end + 1;777tag_stack.push_front("url");778} else if (tag == "img") {779int end = bbcode.find_char('[', brk_end);780if (end == -1) {781end = bbcode.length();782}783String image = bbcode.substr(brk_end + 1, end - brk_end - 1);784785// Not supported. Just append the bbcode.786xml_output.append("[img]");787xml_output.append(image);788xml_output.append("[/img]");789790pos = end;791tag_stack.push_front(tag);792} else if (tag.begins_with("color=")) {793// Not supported.794pos = brk_end + 1;795tag_stack.push_front("color");796} else if (tag.begins_with("font=")) {797// Not supported.798pos = brk_end + 1;799tag_stack.push_front("font");800} else {801if (!line_del) {802// Ignore unrecognized tag.803xml_output.append("[");804}805pos = brk_pos + 1;806}807}808809xml_output.append("</para>");810811return xml_output.as_string();812}813814void 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) {815if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {816if (OS::get_singleton()->is_stdout_verbose()) {817OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());818}819820// TODO Map what we can821_append_text_undeclared(p_output, p_link_target);822} else if (!p_target_itype || !p_target_itype->is_object_type) {823if (OS::get_singleton()->is_stdout_verbose()) {824if (p_target_itype) {825OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());826} else {827OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());828}829}830831// TODO Map what we can832_append_text_undeclared(p_output, p_link_target);833} else {834if (p_target_cname == "_init") {835// The _init method is not declared in C#, reference the constructor instead836p_output.append("'new " BINDINGS_NAMESPACE ".");837p_output.append(p_target_itype->proxy_name);838p_output.append("()'");839} else if (p_target_cname == "to_string") {840// C# uses the built-in object.ToString() method, reference that instead.841p_output.append("'object.ToString()'");842} else {843const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);844845if (target_imethod) {846p_output.append("'" BINDINGS_NAMESPACE ".");847p_output.append(p_target_itype->proxy_name);848p_output.append(".");849p_output.append(target_imethod->proxy_name);850p_output.append("(");851bool first_key = true;852for (const ArgumentInterface &iarg : target_imethod->arguments) {853const TypeInterface *arg_type = _get_type_or_null(iarg.type);854855if (first_key) {856first_key = false;857} else {858p_output.append(", ");859}860if (!arg_type) {861ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");862p_output.append(iarg.type.cname);863continue;864}865if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {866p_output.append("Nullable<");867}868String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);869p_output.append(arg_cs_type.replacen("params ", ""));870if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {871p_output.append(">");872}873}874p_output.append(")'");875} else {876if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {877ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");878}879880_append_text_undeclared(p_output, p_link_target);881}882}883}884}885886void 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) {887if (p_link_target.contains_char('/')) {888// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.889_append_text_undeclared(p_output, p_link_target);890} else if (!p_target_itype || !p_target_itype->is_object_type) {891if (OS::get_singleton()->is_stdout_verbose()) {892if (p_target_itype) {893OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());894} else {895OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());896}897}898899// TODO Map what we can900_append_text_undeclared(p_output, p_link_target);901} else {902const TypeInterface *current_itype = p_target_itype;903const PropertyInterface *target_iprop = nullptr;904905while (target_iprop == nullptr && current_itype != nullptr) {906target_iprop = current_itype->find_property_by_name(p_target_cname);907if (target_iprop == nullptr) {908current_itype = _get_type_or_null(TypeReference(current_itype->base_name));909}910}911912if (target_iprop) {913p_output.append("'" BINDINGS_NAMESPACE ".");914p_output.append(current_itype->proxy_name);915p_output.append(".");916p_output.append(target_iprop->proxy_name);917p_output.append("'");918} else {919if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {920ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");921}922923_append_text_undeclared(p_output, p_link_target);924}925}926}927928void 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) {929if (!p_target_itype || !p_target_itype->is_object_type) {930if (OS::get_singleton()->is_stdout_verbose()) {931if (p_target_itype) {932OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());933} else {934OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());935}936}937938// TODO Map what we can939_append_text_undeclared(p_output, p_link_target);940} else {941const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);942943if (target_isignal) {944p_output.append("'" BINDINGS_NAMESPACE ".");945p_output.append(p_target_itype->proxy_name);946p_output.append(".");947p_output.append(target_isignal->proxy_name);948p_output.append("'");949} else {950if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {951ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");952}953954_append_text_undeclared(p_output, p_link_target);955}956}957}958959void 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) {960const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);961962HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);963964if (!enum_match && search_cname != p_target_cname) {965enum_match = enum_types.find(p_target_cname);966}967968if (enum_match) {969const TypeInterface &target_enum_itype = enum_match->value;970971p_output.append("'" BINDINGS_NAMESPACE ".");972p_output.append(target_enum_itype.proxy_name); // Includes nesting class if any973p_output.append("'");974} else {975if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_target_cname)) {976ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");977}978979_append_text_undeclared(p_output, p_link_target);980}981}982983void 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) {984if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {985_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);986} else if (!p_target_itype || !p_target_itype->is_object_type) {987// Search in @GlobalScope as a last resort if no class was specified988if (p_link_target_parts.size() == 1) {989_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);990return;991}992993if (OS::get_singleton()->is_stdout_verbose()) {994if (p_target_itype) {995OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());996} else {997OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());998}999}10001001// TODO Map what we can1002_append_text_undeclared(p_output, p_link_target);1003} else {1004// Try to find the constant in the current class1005if (p_target_itype->is_singleton_instance) {1006// Constants and enums are declared in the static singleton class.1007p_target_itype = &obj_types[p_target_itype->cname];1008}10091010const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);10111012if (target_iconst) {1013// Found constant in current class1014p_output.append("'" BINDINGS_NAMESPACE ".");1015p_output.append(p_target_itype->proxy_name);1016p_output.append(".");1017p_output.append(target_iconst->proxy_name);1018p_output.append("'");1019} else {1020// Try to find as enum constant in the current class1021const EnumInterface *target_ienum = nullptr;10221023for (const EnumInterface &ienum : p_target_itype->enums) {1024target_ienum = &ienum;1025target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1026if (target_iconst) {1027break;1028}1029}10301031if (target_iconst) {1032p_output.append("'" BINDINGS_NAMESPACE ".");1033p_output.append(p_target_itype->proxy_name);1034p_output.append(".");1035p_output.append(target_ienum->proxy_name);1036p_output.append(".");1037p_output.append(target_iconst->proxy_name);1038p_output.append("'");1039} else if (p_link_target_parts.size() == 1) {1040// Also search in @GlobalScope as a last resort if no class was specified1041_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);1042} else {1043if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {1044ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");1045}10461047_append_xml_undeclared(p_output, p_link_target);1048}1049}1050}1051}10521053void BindingsGenerator::_append_text_constant_in_global_scope(StringBuilder &p_output, const String &p_target_cname, const String &p_link_target) {1054// Try to find as a global constant1055const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);10561057if (target_iconst) {1058// Found global constant1059p_output.append("'" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");1060p_output.append(target_iconst->proxy_name);1061p_output.append("'");1062} else {1063// Try to find as global enum constant1064const EnumInterface *target_ienum = nullptr;10651066for (const EnumInterface &ienum : global_enums) {1067target_ienum = &ienum;1068target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1069if (target_iconst) {1070break;1071}1072}10731074if (target_iconst) {1075p_output.append("'" BINDINGS_NAMESPACE ".");1076p_output.append(target_ienum->proxy_name);1077p_output.append(".");1078p_output.append(target_iconst->proxy_name);1079p_output.append("'");1080} else {1081ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");1082_append_text_undeclared(p_output, p_link_target);1083}1084}1085}10861087void BindingsGenerator::_append_text_param(StringBuilder &p_output, const String &p_link_target) {1088const String link_target = snake_to_camel_case(p_link_target);1089p_output.append("'" + link_target + "'");1090}10911092void BindingsGenerator::_append_text_undeclared(StringBuilder &p_output, const String &p_link_target) {1093p_output.append("'" + p_link_target + "'");1094}10951096void 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) {1097if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {1098if (OS::get_singleton()->is_stdout_verbose()) {1099OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());1100}11011102// TODO Map what we can1103_append_xml_undeclared(p_xml_output, p_link_target);1104} else if (!p_target_itype || !p_target_itype->is_object_type) {1105if (OS::get_singleton()->is_stdout_verbose()) {1106if (p_target_itype) {1107OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1108} else {1109OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());1110}1111}11121113// TODO Map what we can1114_append_xml_undeclared(p_xml_output, p_link_target);1115} else {1116if (p_target_cname == "_init") {1117// The _init method is not declared in C#, reference the constructor instead.1118p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1119p_xml_output.append(p_target_itype->proxy_name);1120p_xml_output.append(".");1121p_xml_output.append(p_target_itype->proxy_name);1122p_xml_output.append("()\"/>");1123} else if (p_target_cname == "to_string") {1124// C# uses the built-in object.ToString() method, reference that instead.1125p_xml_output.append("<see cref=\"object.ToString()\"/>");1126} else {1127const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);11281129if (target_imethod) {1130const String method_name = p_target_itype->proxy_name + "." + target_imethod->proxy_name;1131if (!_validate_api_type(p_target_itype, p_source_itype)) {1132_append_xml_undeclared(p_xml_output, method_name);1133} else {1134p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1135p_xml_output.append(method_name);1136p_xml_output.append("(");1137bool first_key = true;1138for (const ArgumentInterface &iarg : target_imethod->arguments) {1139const TypeInterface *arg_type = _get_type_or_null(iarg.type);11401141if (first_key) {1142first_key = false;1143} else {1144p_xml_output.append(", ");1145}1146if (!arg_type) {1147ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");1148p_xml_output.append(iarg.type.cname);1149continue;1150}1151if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {1152p_xml_output.append("Nullable{");1153}1154String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);1155p_xml_output.append(arg_cs_type.replacen("<", "{").replacen(">", "}").replacen("params ", ""));1156if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {1157p_xml_output.append("}");1158}1159}1160p_xml_output.append(")\"/>");1161}1162} else {1163if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {1164ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");1165}11661167_append_xml_undeclared(p_xml_output, p_link_target);1168}1169}1170}1171}11721173void 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) {1174if (p_link_target.contains_char('/')) {1175// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.1176_append_xml_undeclared(p_xml_output, p_link_target);1177} else if (!p_target_itype || !p_target_itype->is_object_type) {1178if (OS::get_singleton()->is_stdout_verbose()) {1179if (p_target_itype) {1180OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1181} else {1182OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());1183}1184}11851186// TODO Map what we can1187_append_xml_undeclared(p_xml_output, p_link_target);1188} else {1189const TypeInterface *current_itype = p_target_itype;1190const PropertyInterface *target_iprop = nullptr;11911192while (target_iprop == nullptr && current_itype != nullptr) {1193target_iprop = current_itype->find_property_by_name(p_target_cname);1194if (target_iprop == nullptr) {1195current_itype = _get_type_or_null(TypeReference(current_itype->base_name));1196}1197}11981199if (target_iprop) {1200const String member_name = current_itype->proxy_name + "." + target_iprop->proxy_name;1201if (!_validate_api_type(p_target_itype, p_source_itype)) {1202_append_xml_undeclared(p_xml_output, member_name);1203} else {1204p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1205p_xml_output.append(member_name);1206p_xml_output.append("\"/>");1207}1208} else {1209if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {1210ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");1211}12121213_append_xml_undeclared(p_xml_output, p_link_target);1214}1215}1216}12171218void 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) {1219if (!p_target_itype || !p_target_itype->is_object_type) {1220if (OS::get_singleton()->is_stdout_verbose()) {1221if (p_target_itype) {1222OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1223} else {1224OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());1225}1226}12271228// TODO Map what we can1229_append_xml_undeclared(p_xml_output, p_link_target);1230} else {1231const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);12321233if (target_isignal) {1234const String signal_name = p_target_itype->proxy_name + "." + target_isignal->proxy_name;1235if (!_validate_api_type(p_target_itype, p_source_itype)) {1236_append_xml_undeclared(p_xml_output, signal_name);1237} else {1238p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1239p_xml_output.append(signal_name);1240p_xml_output.append("\"/>");1241}1242} else {1243if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {1244ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");1245}12461247_append_xml_undeclared(p_xml_output, p_link_target);1248}1249}1250}12511252void 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) {1253const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);12541255HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);12561257if (!enum_match && search_cname != p_target_cname) {1258enum_match = enum_types.find(p_target_cname);1259}12601261if (enum_match) {1262const TypeInterface &target_enum_itype = enum_match->value;12631264if (!_validate_api_type(p_target_itype, p_source_itype)) {1265_append_xml_undeclared(p_xml_output, target_enum_itype.proxy_name);1266} else {1267p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1268p_xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any1269p_xml_output.append("\"/>");1270}1271} else {1272if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_target_cname)) {1273ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");1274}12751276_append_xml_undeclared(p_xml_output, p_link_target);1277}1278}12791280void 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) {1281if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {1282_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1283} else if (!p_target_itype || !p_target_itype->is_object_type) {1284// Search in @GlobalScope as a last resort if no class was specified1285if (p_link_target_parts.size() == 1) {1286_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1287return;1288}12891290if (OS::get_singleton()->is_stdout_verbose()) {1291if (p_target_itype) {1292OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());1293} else {1294OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());1295}1296}12971298// TODO Map what we can1299_append_xml_undeclared(p_xml_output, p_link_target);1300} else {1301// Try to find the constant in the current class1302if (p_target_itype->is_singleton_instance) {1303// Constants and enums are declared in the static singleton class.1304p_target_itype = &obj_types[p_target_itype->cname];1305}13061307const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);13081309if (target_iconst) {1310// Found constant in current class1311p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1312p_xml_output.append(p_target_itype->proxy_name);1313p_xml_output.append(".");1314p_xml_output.append(target_iconst->proxy_name);1315p_xml_output.append("\"/>");1316} else {1317// Try to find as enum constant in the current class1318const EnumInterface *target_ienum = nullptr;13191320for (const EnumInterface &ienum : p_target_itype->enums) {1321target_ienum = &ienum;1322target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1323if (target_iconst) {1324break;1325}1326}13271328if (target_iconst) {1329p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1330p_xml_output.append(p_target_itype->proxy_name);1331p_xml_output.append(".");1332p_xml_output.append(target_ienum->proxy_name);1333p_xml_output.append(".");1334p_xml_output.append(target_iconst->proxy_name);1335p_xml_output.append("\"/>");1336} else if (p_link_target_parts.size() == 1) {1337// Also search in @GlobalScope as a last resort if no class was specified1338_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);1339} else {1340if (!p_target_itype->is_intentionally_ignored(p_target_cname)) {1341ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");1342}13431344_append_xml_undeclared(p_xml_output, p_link_target);1345}1346}1347}1348}13491350void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target) {1351// Try to find as a global constant1352const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);13531354if (target_iconst) {1355// Found global constant1356p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");1357p_xml_output.append(target_iconst->proxy_name);1358p_xml_output.append("\"/>");1359} else {1360// Try to find as global enum constant1361const EnumInterface *target_ienum = nullptr;13621363for (const EnumInterface &ienum : global_enums) {1364target_ienum = &ienum;1365target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);1366if (target_iconst) {1367break;1368}1369}13701371if (target_iconst) {1372p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");1373p_xml_output.append(target_ienum->proxy_name);1374p_xml_output.append(".");1375p_xml_output.append(target_iconst->proxy_name);1376p_xml_output.append("\"/>");1377} else {1378ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");1379_append_xml_undeclared(p_xml_output, p_link_target);1380}1381}1382}13831384void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {1385const String link_target = snake_to_camel_case(p_link_target);13861387if (!p_is_signal) {1388p_xml_output.append("<paramref name=\"");1389p_xml_output.append(link_target);1390p_xml_output.append("\"/>");1391} else {1392// Documentation in C# is added to an event, not the delegate itself;1393// as such, we treat these parameters as codeblocks instead.1394// See: https://github.com/godotengine/godot/pull/655291395_append_xml_undeclared(p_xml_output, link_target);1396}1397}13981399void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {1400p_xml_output.append("<c>");1401p_xml_output.append(p_link_target);1402p_xml_output.append("</c>");1403}14041405bool BindingsGenerator::_validate_api_type(const TypeInterface *p_target_itype, const TypeInterface *p_source_itype) {1406static constexpr const char *api_types[5] = {1407"Core",1408"Editor",1409"Extension",1410"Editor Extension",1411"None",1412};14131414const ClassDB::APIType target_api = p_target_itype ? p_target_itype->api_type : ClassDB::API_NONE;1415ERR_FAIL_INDEX_V((int)target_api, 5, false);1416const ClassDB::APIType source_api = p_source_itype ? p_source_itype->api_type : ClassDB::API_NONE;1417ERR_FAIL_INDEX_V((int)source_api, 5, false);1418bool validate = false;14191420switch (target_api) {1421case ClassDB::API_NONE:1422case ClassDB::API_CORE:1423default:1424validate = true;1425break;1426case ClassDB::API_EDITOR:1427validate = source_api == ClassDB::API_EDITOR || source_api == ClassDB::API_EDITOR_EXTENSION;1428break;1429case ClassDB::API_EXTENSION:1430validate = source_api == ClassDB::API_EXTENSION || source_api == ClassDB::API_EDITOR_EXTENSION;1431break;1432case ClassDB::API_EDITOR_EXTENSION:1433validate = source_api == ClassDB::API_EDITOR_EXTENSION;1434break;1435}1436if (!validate) {1437const String target_name = p_target_itype ? p_target_itype->proxy_name : "@GlobalScope";1438const String source_name = p_source_itype ? p_source_itype->proxy_name : "@GlobalScope";1439WARN_PRINT(vformat("Type '%s' has API level '%s'; it cannot be referenced by type '%s' with API level '%s'.",1440target_name, api_types[target_api], source_name, api_types[source_api]));1441}1442return validate;1443}14441445int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {1446CRASH_COND(p_ienum.constants.is_empty());14471448const ConstantInterface &front_iconstant = p_ienum.constants.front()->get();1449Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true);1450int candidate_len = front_parts.size() - 1;14511452if (candidate_len == 0) {1453return 0;1454}14551456for (const ConstantInterface &iconstant : p_ienum.constants) {1457Vector<String> parts = iconstant.name.split("_", /* p_allow_empty: */ true);14581459int i;1460for (i = 0; i < candidate_len && i < parts.size(); i++) {1461if (front_parts[i] != parts[i]) {1462// HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT').1463bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS")));1464if (!hardcoded_exc) {1465break;1466}1467}1468}1469candidate_len = i;14701471if (candidate_len == 0) {1472return 0;1473}1474}14751476return candidate_len;1477}14781479void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) {1480if (p_prefix_length > 0) {1481for (ConstantInterface &iconstant : p_ienum.constants) {1482int curr_prefix_length = p_prefix_length;14831484String constant_name = iconstant.name;14851486Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true);14871488if (parts.size() <= curr_prefix_length) {1489continue;1490}14911492if (is_digit(parts[curr_prefix_length][0])) {1493// The name of enum constants may begin with a numeric digit when strip from the enum prefix,1494// so we make the prefix for this constant one word shorter in those cases.1495for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {1496if (!is_digit(parts[curr_prefix_length][0])) {1497break;1498}1499}1500}15011502constant_name = "";1503for (int i = curr_prefix_length; i < parts.size(); i++) {1504if (i > curr_prefix_length) {1505constant_name += "_";1506}1507constant_name += parts[i];1508}15091510iconstant.proxy_name = snake_to_pascal_case(constant_name, true);1511}1512}1513}15141515Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_itype) {1516for (const MethodInterface &imethod : p_itype.methods) {1517if (imethod.is_virtual) {1518continue;1519}15201521const TypeInterface *return_type = _get_type_or_null(imethod.return_type);1522ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");15231524String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind";15251526if (!imethod.is_static) {1527im_unique_sig += ",CallInstance";1528}15291530// Get arguments information1531for (const ArgumentInterface &iarg : imethod.arguments) {1532const TypeInterface *arg_type = _get_type_or_null(iarg.type);1533ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");15341535im_unique_sig += ",";1536im_unique_sig += get_arg_unique_sig(*arg_type);1537}15381539// godot_icall_{argc}_{icallcount}1540String icall_method = ICALL_PREFIX;1541icall_method += itos(imethod.arguments.size());1542icall_method += "_";1543icall_method += itos(method_icalls.size());15441545InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_unique_sig);15461547im_icall.is_vararg = imethod.is_vararg;1548im_icall.is_static = imethod.is_static;1549im_icall.return_type = imethod.return_type;15501551for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) {1552im_icall.argument_types.push_back(F->get().type);1553}15541555List<InternalCall>::Element *match = method_icalls.find(im_icall);15561557if (match) {1558if (p_itype.api_type != ClassDB::API_EDITOR) {1559match->get().editor_only = false;1560}1561method_icalls_map.insert(&imethod, &match->get());1562} else {1563List<InternalCall>::Element *added = method_icalls.push_back(im_icall);1564method_icalls_map.insert(&imethod, &added->get());1565}1566}15671568return OK;1569}15701571void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) {1572p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");1573p_output.append("using System;\n\n");1574// The class where we put the extensions doesn't matter, so just use "GD".1575p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{");15761577#define ARRAY_IS_EMPTY(m_type) \1578p_output.append("\n" INDENT1 "/// <summary>\n"); \1579p_output.append(INDENT1 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \1580p_output.append(INDENT1 "/// </summary>\n"); \1581p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \1582p_output.append(INDENT1 "/// <returns>Whether or not the array is empty.</returns>\n"); \1583p_output.append(INDENT1 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \1584p_output.append(OPEN_BLOCK_L1); \1585p_output.append(INDENT2 "return instance == null || instance.Length == 0;\n"); \1586p_output.append(INDENT1 CLOSE_BLOCK);15871588#define ARRAY_JOIN(m_type) \1589p_output.append("\n" INDENT1 "/// <summary>\n"); \1590p_output.append(INDENT1 "/// Converts this " #m_type " array to a string delimited by the given string.\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 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \1594p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \1595p_output.append(INDENT1 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \1596p_output.append(OPEN_BLOCK_L1); \1597p_output.append(INDENT2 "return String.Join(delimiter, instance);\n"); \1598p_output.append(INDENT1 CLOSE_BLOCK);15991600#define ARRAY_STRINGIFY(m_type) \1601p_output.append("\n" INDENT1 "/// <summary>\n"); \1602p_output.append(INDENT1 "/// Converts this " #m_type " array to a string with brackets.\n"); \1603p_output.append(INDENT1 "/// </summary>\n"); \1604p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \1605p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \1606p_output.append(INDENT1 "public static string Stringify(this " #m_type "[] instance)\n"); \1607p_output.append(OPEN_BLOCK_L1); \1608p_output.append(INDENT2 "return \"[\" + instance.Join() + \"]\";\n"); \1609p_output.append(INDENT1 CLOSE_BLOCK);16101611#define ARRAY_ALL(m_type) \1612ARRAY_IS_EMPTY(m_type) \1613ARRAY_JOIN(m_type) \1614ARRAY_STRINGIFY(m_type)16151616ARRAY_ALL(byte);1617ARRAY_ALL(int);1618ARRAY_ALL(long);1619ARRAY_ALL(float);1620ARRAY_ALL(double);1621ARRAY_ALL(string);1622ARRAY_ALL(Color);1623ARRAY_ALL(Vector2);1624ARRAY_ALL(Vector2I);1625ARRAY_ALL(Vector3);1626ARRAY_ALL(Vector3I);1627ARRAY_ALL(Vector4);1628ARRAY_ALL(Vector4I);16291630#undef ARRAY_ALL1631#undef ARRAY_IS_EMPTY1632#undef ARRAY_JOIN1633#undef ARRAY_STRINGIFY16341635p_output.append(CLOSE_BLOCK); // End of GD class.1636}16371638void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {1639// Constants (in partial GD class)16401641p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");16421643p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" OPEN_BLOCK);16441645for (const ConstantInterface &iconstant : global_constants) {1646if (iconstant.const_doc && iconstant.const_doc->description.size()) {1647String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);1648Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();16491650if (summary_lines.size()) {1651p_output.append(MEMBER_BEGIN "/// <summary>\n");16521653for (int i = 0; i < summary_lines.size(); i++) {1654p_output.append(INDENT1 "/// ");1655p_output.append(summary_lines[i]);1656p_output.append("\n");1657}16581659p_output.append(INDENT1 "/// </summary>");1660}1661}16621663p_output.append(MEMBER_BEGIN "public const long ");1664p_output.append(iconstant.proxy_name);1665p_output.append(" = ");1666p_output.append(itos(iconstant.value));1667p_output.append(";");1668}16691670if (!global_constants.is_empty()) {1671p_output.append("\n");1672}16731674p_output.append(CLOSE_BLOCK); // end of GD class16751676// Enums16771678for (const EnumInterface &ienum : global_enums) {1679CRASH_COND(ienum.constants.is_empty());16801681String enum_proxy_name = ienum.proxy_name;16821683bool enum_in_static_class = false;16841685if (enum_proxy_name.find_char('.') > 0) {1686enum_in_static_class = true;1687String enum_class_name = enum_proxy_name.get_slicec('.', 0);1688enum_proxy_name = enum_proxy_name.get_slicec('.', 1);16891690CRASH_COND(enum_class_name != "Variant"); // Hard-coded...16911692_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());16931694p_output << "\npublic partial struct " << enum_class_name << "\n" OPEN_BLOCK;1695}16961697const String maybe_indent = !enum_in_static_class ? "" : INDENT1;16981699if (ienum.is_flags) {1700p_output << "\n"1701<< maybe_indent << "[System.Flags]";1702}17031704p_output << "\n"1705<< maybe_indent << "public enum " << enum_proxy_name << " : long"1706<< "\n"1707<< maybe_indent << OPEN_BLOCK;17081709for (const ConstantInterface &iconstant : ienum.constants) {1710if (iconstant.const_doc && iconstant.const_doc->description.size()) {1711String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);1712Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();17131714if (summary_lines.size()) {1715p_output << maybe_indent << INDENT1 "/// <summary>\n";17161717for (int i = 0; i < summary_lines.size(); i++) {1718p_output << maybe_indent << INDENT1 "/// " << summary_lines[i] << "\n";1719}17201721p_output << maybe_indent << INDENT1 "/// </summary>\n";1722}1723}17241725p_output << maybe_indent << INDENT11726<< iconstant.proxy_name1727<< " = "1728<< itos(iconstant.value)1729<< ",\n";1730}17311732p_output << maybe_indent << CLOSE_BLOCK;17331734if (enum_in_static_class) {1735p_output << CLOSE_BLOCK;1736}1737}1738}17391740Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {1741ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);17421743Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1744ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);17451746if (!DirAccess::exists(p_proj_dir)) {1747Error err = da->make_dir_recursive(p_proj_dir);1748ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");1749}17501751da->change_dir(p_proj_dir);1752da->make_dir("Generated");1753da->make_dir("Generated/GodotObjects");17541755String base_gen_dir = Path::join(p_proj_dir, "Generated");1756String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");17571758Vector<String> compile_items;17591760// Generate source file for global scope constants and enums1761{1762StringBuilder constants_source;1763_generate_global_constants(constants_source);1764String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");1765Error save_err = _save_file(output_file, constants_source);1766if (save_err != OK) {1767return save_err;1768}17691770compile_items.push_back(output_file);1771}17721773// Generate source file for array extensions1774{1775StringBuilder extensions_source;1776_generate_array_extensions(extensions_source);1777String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_extensions.cs");1778Error save_err = _save_file(output_file, extensions_source);1779if (save_err != OK) {1780return save_err;1781}17821783compile_items.push_back(output_file);1784}17851786for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1787const TypeInterface &itype = E.value;17881789if (itype.api_type == ClassDB::API_EDITOR) {1790continue;1791}17921793String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");1794Error err = _generate_cs_type(itype, output_file);17951796if (err == ERR_SKIP) {1797continue;1798}17991800if (err != OK) {1801return err;1802}18031804compile_items.push_back(output_file);1805}18061807// Generate source file for built-in type constructor dictionary.18081809{1810StringBuilder cs_built_in_ctors_content;18111812cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1813cs_built_in_ctors_content.append("using System;\n"1814"using System.Collections.Generic;\n"1815"\n");1816cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{");18171818cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");18191820cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n");1821cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1822cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n");1823cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n");1824cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n");1825cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);18261827cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n");1828cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1829cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n");18301831for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1832const TypeInterface &itype = E.value;18331834if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) {1835continue;1836}18371838if (itype.is_deprecated) {1839cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");1840}18411842cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\"");1843cs_built_in_ctors_content.append(itype.name);1844cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");1845cs_built_in_ctors_content.append(itype.proxy_name);1846if (itype.is_singleton && !itype.is_compat_singleton) {1847cs_built_in_ctors_content.append("Instance");1848}1849cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");18501851if (itype.is_deprecated) {1852cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");1853}1854}18551856cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);18571858cs_built_in_ctors_content.append(CLOSE_BLOCK);18591860String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs");1861Error err = _save_file(constructors_file, cs_built_in_ctors_content);18621863if (err != OK) {1864return err;1865}18661867compile_items.push_back(constructors_file);1868}18691870// Generate native calls18711872StringBuilder cs_icalls_content;18731874cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1875cs_icalls_content.append("using System;\n"1876"using System.Diagnostics.CodeAnalysis;\n"1877"using System.Runtime.InteropServices;\n"1878"using Godot.NativeInterop;\n"1879"\n");1880cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");1881cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");1882cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");1883cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");1884cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS "\n{");18851886cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = ");1887cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_CORE)) + ";\n");18881889cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");18901891for (const InternalCall &icall : method_icalls) {1892if (icall.editor_only) {1893continue;1894}1895Error err = _generate_cs_native_calls(icall, cs_icalls_content);1896if (err != OK) {1897return err;1898}1899}19001901cs_icalls_content.append(CLOSE_BLOCK);19021903String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs");19041905Error err = _save_file(internal_methods_file, cs_icalls_content);1906if (err != OK) {1907return err;1908}19091910compile_items.push_back(internal_methods_file);19111912// Generate GeneratedIncludes.props19131914StringBuilder includes_props_content;1915includes_props_content.append("<Project>\n"1916" <ItemGroup>\n");19171918for (int i = 0; i < compile_items.size(); i++) {1919String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');1920includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");1921}19221923includes_props_content.append(" </ItemGroup>\n"1924"</Project>\n");19251926String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");19271928err = _save_file(includes_props_file, includes_props_content);1929if (err != OK) {1930return err;1931}19321933return OK;1934}19351936Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {1937ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);19381939Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);1940ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);19411942if (!DirAccess::exists(p_proj_dir)) {1943Error err = da->make_dir_recursive(p_proj_dir);1944ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);1945}19461947da->change_dir(p_proj_dir);1948da->make_dir("Generated");1949da->make_dir("Generated/GodotObjects");19501951String base_gen_dir = Path::join(p_proj_dir, "Generated");1952String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");19531954Vector<String> compile_items;19551956for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1957const TypeInterface &itype = E.value;19581959if (itype.api_type != ClassDB::API_EDITOR) {1960continue;1961}19621963String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");1964Error err = _generate_cs_type(itype, output_file);19651966if (err == ERR_SKIP) {1967continue;1968}19691970if (err != OK) {1971return err;1972}19731974compile_items.push_back(output_file);1975}19761977// Generate source file for editor type constructor dictionary.19781979{1980StringBuilder cs_built_in_ctors_content;19811982cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");1983cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{");19841985cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n");1986cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);1987cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");19881989for (const KeyValue<StringName, TypeInterface> &E : obj_types) {1990const TypeInterface &itype = E.value;19911992if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) {1993continue;1994}19951996if (itype.is_deprecated) {1997cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");1998}19992000cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\"");2001cs_built_in_ctors_content.append(itype.name);2002cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");2003cs_built_in_ctors_content.append(itype.proxy_name);2004if (itype.is_singleton && !itype.is_compat_singleton) {2005cs_built_in_ctors_content.append("Instance");2006}2007cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");20082009if (itype.is_deprecated) {2010cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");2011}2012}20132014cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);20152016cs_built_in_ctors_content.append(CLOSE_BLOCK);20172018String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs");2019Error err = _save_file(constructors_file, cs_built_in_ctors_content);20202021if (err != OK) {2022return err;2023}20242025compile_items.push_back(constructors_file);2026}20272028// Generate native calls20292030StringBuilder cs_icalls_content;20312032cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");2033cs_icalls_content.append("using System;\n"2034"using System.Diagnostics.CodeAnalysis;\n"2035"using System.Runtime.InteropServices;\n"2036"using Godot.NativeInterop;\n"2037"\n");2038cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");2039cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");2040cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");2041cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");2042cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" OPEN_BLOCK);20432044cs_icalls_content.append(INDENT1 "internal static ulong godot_api_hash = ");2045cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_EDITOR)) + ";\n");20462047cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");20482049cs_icalls_content.append("\n");20502051for (const InternalCall &icall : method_icalls) {2052if (!icall.editor_only) {2053continue;2054}2055Error err = _generate_cs_native_calls(icall, cs_icalls_content);2056if (err != OK) {2057return err;2058}2059}20602061cs_icalls_content.append(CLOSE_BLOCK);20622063String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");20642065Error err = _save_file(internal_methods_file, cs_icalls_content);2066if (err != OK) {2067return err;2068}20692070compile_items.push_back(internal_methods_file);20712072// Generate GeneratedIncludes.props20732074StringBuilder includes_props_content;2075includes_props_content.append("<Project>\n"2076" <ItemGroup>\n");20772078for (int i = 0; i < compile_items.size(); i++) {2079String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');2080includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");2081}20822083includes_props_content.append(" </ItemGroup>\n"2084"</Project>\n");20852086String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");20872088err = _save_file(includes_props_file, includes_props_content);2089if (err != OK) {2090return err;2091}20922093return OK;2094}20952096Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {2097ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);20982099String output_dir = Path::abspath(Path::realpath(p_output_dir));21002101Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);2102ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);21032104if (!DirAccess::exists(output_dir)) {2105Error err = da->make_dir_recursive(output_dir);2106ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);2107}21082109Error proj_err;21102111// Generate GodotSharp source files21122113String core_proj_dir = output_dir.path_join(CORE_API_ASSEMBLY_NAME);21142115proj_err = generate_cs_core_project(core_proj_dir);2116if (proj_err != OK) {2117ERR_PRINT("Generation of the Core API C# project failed.");2118return proj_err;2119}21202121// Generate GodotSharpEditor source files21222123String editor_proj_dir = output_dir.path_join(EDITOR_API_ASSEMBLY_NAME);21242125proj_err = generate_cs_editor_project(editor_proj_dir);2126if (proj_err != OK) {2127ERR_PRINT("Generation of the Editor API C# project failed.");2128return proj_err;2129}21302131_log("The Godot API sources were successfully generated\n");21322133return OK;2134}21352136// FIXME: There are some members that hide other inherited members.2137// - In the case of both members being the same kind, the new one must be declared2138// explicitly as 'new' to avoid the warning (and we must print a message about it).2139// - In the case of both members being of a different kind, then the new one must2140// be renamed to avoid the name collision (and we must print a warning about it).2141// - Csc warning e.g.:2142// ObjectType/LineEdit.cs(140,38): warning CS0108: 'LineEdit.FocusMode' hides inherited member 'Control.FocusMode'. Use the new keyword if hiding was intended.2143Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) {2144CRASH_COND(!itype.is_object_type);21452146bool is_derived_type = itype.base_name != StringName();21472148if (!is_derived_type) {2149// Some GodotObject assertions2150CRASH_COND(itype.cname != name_cache.type_Object);2151CRASH_COND(!itype.is_instantiable);2152CRASH_COND(itype.api_type != ClassDB::API_CORE);2153CRASH_COND(itype.is_ref_counted);2154CRASH_COND(itype.is_singleton);2155}21562157_log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data());21582159StringBuilder output;21602161output.append("namespace " BINDINGS_NAMESPACE ";\n\n");21622163output.append("using System;\n"); // IntPtr2164output.append("using System.ComponentModel;\n"); // EditorBrowsable2165output.append("using System.Diagnostics;\n"); // DebuggerBrowsable2166output.append("using Godot.NativeInterop;\n");21672168output.append("\n#nullable disable\n");21692170const DocData::ClassDoc *class_doc = itype.class_doc;21712172if (class_doc && class_doc->description.size()) {2173String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype);2174Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();21752176if (summary_lines.size()) {2177output.append("/// <summary>\n");21782179for (int i = 0; i < summary_lines.size(); i++) {2180output.append("/// ");2181output.append(summary_lines[i]);2182output.append("\n");2183}21842185output.append("/// </summary>\n");2186}2187}21882189if (itype.is_deprecated) {2190output.append("[Obsolete(\"");2191output.append(bbcode_to_text(itype.deprecation_message, &itype));2192output.append("\")]\n");2193}21942195// We generate a `GodotClassName` attribute if the engine class name is not the same as the2196// generated C# class name. This allows introspection code to find the name associated with2197// the class. If the attribute is not present, the C# class name can be used instead.2198if (itype.name != itype.proxy_name) {2199output << "[GodotClassName(\"" << itype.name << "\")]\n";2200}22012202output.append("public ");2203if (itype.is_singleton) {2204output.append("static partial class ");2205} else {2206// Even if the class is not instantiable, we can't declare it abstract because2207// the engine can still instantiate them and return them via the scripting API.2208// Example: `SceneTreeTimer` returned from `SceneTree.create_timer`.2209// See the reverted commit: ef5672d3f94a7321ed779c922088bb72adbb15212210output.append("partial class ");2211}2212output.append(itype.proxy_name);22132214if (is_derived_type && !itype.is_singleton) {2215if (obj_types.has(itype.base_name)) {2216TypeInterface base_type = obj_types[itype.base_name];2217output.append(" : ");2218output.append(base_type.proxy_name);2219if (base_type.is_singleton) {2220// If the type is a singleton, use the instance type.2221output.append(CS_SINGLETON_INSTANCE_SUFFIX);2222}2223} else {2224ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");2225return ERR_INVALID_DATA;2226}2227}22282229output.append("\n{");22302231// Add constants22322233for (const ConstantInterface &iconstant : itype.constants) {2234if (iconstant.const_doc && iconstant.const_doc->description.size()) {2235String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);2236Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();22372238if (summary_lines.size()) {2239output.append(MEMBER_BEGIN "/// <summary>\n");22402241for (int i = 0; i < summary_lines.size(); i++) {2242output.append(INDENT1 "/// ");2243output.append(summary_lines[i]);2244output.append("\n");2245}22462247output.append(INDENT1 "/// </summary>");2248}2249}22502251if (iconstant.is_deprecated) {2252output.append(MEMBER_BEGIN "[Obsolete(\"");2253output.append(bbcode_to_text(iconstant.deprecation_message, &itype));2254output.append("\")]");2255}22562257output.append(MEMBER_BEGIN "public const long ");2258output.append(iconstant.proxy_name);2259output.append(" = ");2260output.append(itos(iconstant.value));2261output.append(";");2262}22632264if (itype.constants.size()) {2265output.append("\n");2266}22672268// Add enums22692270for (const EnumInterface &ienum : itype.enums) {2271ERR_FAIL_COND_V(ienum.constants.is_empty(), ERR_BUG);22722273if (ienum.is_flags) {2274output.append(MEMBER_BEGIN "[System.Flags]");2275}22762277output.append(MEMBER_BEGIN "public enum ");2278output.append(ienum.proxy_name);2279output.append(" : long");2280output.append(MEMBER_BEGIN OPEN_BLOCK);22812282const ConstantInterface &last = ienum.constants.back()->get();2283for (const ConstantInterface &iconstant : ienum.constants) {2284if (iconstant.const_doc && iconstant.const_doc->description.size()) {2285String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);2286Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();22872288if (summary_lines.size()) {2289output.append(INDENT2 "/// <summary>\n");22902291for (int i = 0; i < summary_lines.size(); i++) {2292output.append(INDENT2 "/// ");2293output.append(summary_lines[i]);2294output.append("\n");2295}22962297output.append(INDENT2 "/// </summary>\n");2298}2299}23002301if (iconstant.is_deprecated) {2302output.append(INDENT2 "[Obsolete(\"");2303output.append(bbcode_to_text(iconstant.deprecation_message, &itype));2304output.append("\")]\n");2305}23062307output.append(INDENT2);2308output.append(iconstant.proxy_name);2309output.append(" = ");2310output.append(itos(iconstant.value));2311output.append(&iconstant != &last ? ",\n" : "\n");2312}23132314output.append(INDENT1 CLOSE_BLOCK);2315}23162317// Add properties23182319for (const PropertyInterface &iprop : itype.properties) {2320Error prop_err = _generate_cs_property(itype, iprop, output);2321ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,2322"Failed to generate property '" + iprop.cname.operator String() +2323"' for class '" + itype.name + "'.");2324}23252326// Add native name static field and cached type.23272328if (is_derived_type && !itype.is_singleton) {2329output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";2330}23312332output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");2333output.append(itype.name);2334output.append("\";\n");23352336if (itype.is_singleton || itype.is_compat_singleton) {2337// Add the Singleton static property.23382339String instance_type_name;23402341if (itype.is_singleton) {2342StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX;2343instance_type_name = obj_types.has(instance_name)2344? obj_types[instance_name].proxy_name2345: "GodotObject";2346} else {2347instance_type_name = itype.proxy_name;2348}23492350output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n");23512352output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n"2353<< INDENT2 "singleton \?\?= (" + instance_type_name + ")"2354<< C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n";2355}23562357if (!itype.is_singleton) {2358// IMPORTANT: We also generate the static fields for GodotObject instead of declaring2359// them manually in the `GodotObject.base.cs` partial class declaration, because they're2360// required by other static fields in this generated partial class declaration.2361// Static fields are initialized in order of declaration, but when they're in different2362// partial class declarations then it becomes harder to tell (Rider warns about this).23632364if (itype.is_instantiable) {2365// Add native constructor static field23662367output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2368<< INDENT1 "private static readonly unsafe delegate* unmanaged<godot_bool, IntPtr> "2369<< CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR2370<< "(" BINDINGS_NATIVE_NAME_FIELD ");\n";2371}23722373if (is_derived_type) {2374// Add default constructor2375if (itype.is_instantiable) {2376output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this("2377<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12378<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2379<< INDENT3 "ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", "2380<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2381<< (itype.is_ref_counted ? "true" : "false") << ");\n"2382<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;2383} else {2384// Hide the constructor2385output << MEMBER_BEGIN "internal " << itype.proxy_name << "() : this("2386<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12387<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2388<< INDENT3 "ConstructAndInitialize(null, "2389<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2390<< (itype.is_ref_counted ? "true" : "false") << ");\n"2391<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;2392}23932394output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this("2395<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L12396<< INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n"2397<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK2398<< INDENT3 "ConstructAndInitialize(null, "2399<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "2400<< (itype.is_ref_counted ? "true" : "false") << ");\n"2401<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;24022403// Add.. em.. trick constructor. Sort of.2404output.append(MEMBER_BEGIN "internal ");2405output.append(itype.proxy_name);2406output.append("(bool " CS_PARAM_MEMORYOWN ") : base(" CS_PARAM_MEMORYOWN ") { }\n");2407}2408}24092410// Methods24112412int method_bind_count = 0;2413for (const MethodInterface &imethod : itype.methods) {2414Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);2415ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2416"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");2417if (imethod.is_internal) {2418// No need to generate span overloads for internal methods.2419continue;2420}24212422method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);2423ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2424"Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");2425}24262427// Signals24282429for (const SignalInterface &isignal : itype.signals_) {2430Error method_err = _generate_cs_signal(itype, isignal, output);2431ERR_FAIL_COND_V_MSG(method_err != OK, method_err,2432"Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'.");2433}24342435// Script members look-up24362437if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) {2438// Generate method names cache fields24392440for (const MethodInterface &imethod : itype.methods) {2441if (!imethod.is_virtual) {2442continue;2443}24442445output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"2446<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2447<< INDENT1 "private static readonly StringName "2448<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2449<< " = \"" << imethod.proxy_name << "\";\n";2450}24512452// Generate signal names cache fields24532454for (const SignalInterface &isignal : itype.signals_) {2455output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"2456<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"2457<< INDENT1 "private static readonly StringName "2458<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name2459<< " = \"" << isignal.proxy_name << "\";\n";2460}24612462// TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method24632464// Generate InvokeGodotClassMethod24652466output << MEMBER_BEGIN "/// <summary>\n"2467<< INDENT1 "/// Invokes the method with the given name, using the given arguments.\n"2468<< INDENT1 "/// This method is used by Godot to invoke methods from the engine side.\n"2469<< INDENT1 "/// Do not call or override this method.\n"2470<< INDENT1 "/// </summary>\n"2471<< INDENT1 "/// <param name=\"method\">Name of the method to invoke.</param>\n"2472<< INDENT1 "/// <param name=\"args\">Arguments to use with the invoked method.</param>\n"2473<< INDENT1 "/// <param name=\"ret\">Value returned by the invoked method.</param>\n";24742475// Avoid raising diagnostics because of calls to obsolete methods.2476output << "#pragma warning disable CS0618 // Member is obsolete\n";24772478output << INDENT1 "protected internal " << (is_derived_type ? "override" : "virtual")2479<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "2480<< "NativeVariantPtrArgs args, out godot_variant ret)\n"2481<< INDENT1 "{\n";24822483for (const MethodInterface &imethod : itype.methods) {2484if (!imethod.is_virtual) {2485continue;2486}24872488// We also call HasGodotClassMethod to ensure the method is overridden and avoid calling2489// the stub implementation. This solution adds some extra overhead to calls, but it's2490// much simpler than other solutions. This won't be a problem once we move to function2491// pointers of generated wrappers for each method, as lookup will only happen once.24922493// We check both native names (snake_case) and proxy names (PascalCase)2494output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2495<< " || method == MethodName." << imethod.proxy_name2496<< ") && args.Count == " << itos(imethod.arguments.size())2497<< " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"2498<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"2499<< INDENT2 "{\n";25002501if (imethod.return_type.cname != name_cache.type_void) {2502output << INDENT3 "var callRet = ";2503} else {2504output << INDENT3;2505}25062507output << imethod.proxy_name << "(";25082509int i = 0;2510for (List<BindingsGenerator::ArgumentInterface>::ConstIterator itr = imethod.arguments.begin(); itr != imethod.arguments.end(); ++itr, ++i) {2511const ArgumentInterface &iarg = *itr;25122513const TypeInterface *arg_type = _get_type_or_null(iarg.type);2514ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");25152516if (i != 0) {2517output << ", ";2518}25192520if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {2521String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);25222523output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name) << ")";2524} else {2525output << sformat(arg_type->cs_variant_to_managed,2526"args[" + itos(i) + "]", arg_type->cs_type, arg_type->name);2527}2528}25292530output << ");\n";25312532if (imethod.return_type.cname != name_cache.type_void) {2533const TypeInterface *return_type = _get_type_or_null(imethod.return_type);2534ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");25352536output << INDENT3 "ret = "2537<< sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name)2538<< ";\n"2539<< INDENT3 "return true;\n";2540} else {2541output << INDENT3 "ret = default;\n"2542<< INDENT3 "return true;\n";2543}25442545output << INDENT2 "}\n";2546}25472548if (is_derived_type) {2549output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n";2550} else {2551output << INDENT2 "ret = default;\n"2552<< INDENT2 "return false;\n";2553}25542555output << INDENT1 "}\n";25562557output << "#pragma warning restore CS0618\n";25582559// Generate HasGodotClassMethod25602561output << MEMBER_BEGIN "/// <summary>\n"2562<< INDENT1 "/// Check if the type contains a method with the given name.\n"2563<< INDENT1 "/// This method is used by Godot to check if a method exists before invoking it.\n"2564<< INDENT1 "/// Do not call or override this method.\n"2565<< INDENT1 "/// </summary>\n"2566<< INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n";25672568output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")2569<< " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n"2570<< INDENT1 "{\n";25712572for (const MethodInterface &imethod : itype.methods) {2573if (!imethod.is_virtual) {2574continue;2575}25762577// We check for native names (snake_case). If we detect one, we call HasGodotClassMethod2578// again, but this time with the respective proxy name (PascalCase). It's the job of2579// user derived classes to override the method and check for those. Our C# source2580// generators take care of generating those override methods.2581output << INDENT2 "if (method == MethodName." << imethod.proxy_name2582<< ")\n" INDENT2 "{\n"2583<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "("2584<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name2585<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"2586<< INDENT4 "return true;\n"2587<< INDENT3 "}\n" INDENT2 "}\n";2588}25892590if (is_derived_type) {2591output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n";2592} else {2593output << INDENT2 "return false;\n";2594}25952596output << INDENT1 "}\n";25972598// Generate HasGodotClassSignal25992600output << MEMBER_BEGIN "/// <summary>\n"2601<< INDENT1 "/// Check if the type contains a signal with the given name.\n"2602<< INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n"2603<< INDENT1 "/// Do not call or override this method.\n"2604<< INDENT1 "/// </summary>\n"2605<< INDENT1 "/// <param name=\"signal\">Name of the signal to check for.</param>\n";26062607output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")2608<< " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n"2609<< INDENT1 "{\n";26102611for (const SignalInterface &isignal : itype.signals_) {2612// We check for native names (snake_case). If we detect one, we call HasGodotClassSignal2613// again, but this time with the respective proxy name (PascalCase). It's the job of2614// user derived classes to override the method and check for those. Our C# source2615// generators take care of generating those override methods.2616output << INDENT2 "if (signal == SignalName." << isignal.proxy_name2617<< ")\n" INDENT2 "{\n"2618<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_SIGNAL "("2619<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name2620<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"2621<< INDENT4 "return true;\n"2622<< INDENT3 "}\n" INDENT2 "}\n";2623}26242625if (is_derived_type) {2626output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(signal);\n";2627} else {2628output << INDENT2 "return false;\n";2629}26302631output << INDENT1 "}\n";2632}26332634//Generate StringName for all class members2635bool is_inherit = !itype.is_singleton && obj_types.has(itype.base_name);2636//PropertyName2637output << MEMBER_BEGIN "/// <summary>\n"2638<< INDENT1 "/// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n"2639<< INDENT1 "/// </summary>\n";2640if (is_inherit) {2641output << INDENT1 "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName";2642} else {2643output << INDENT1 "public class PropertyName";2644}2645output << "\n"2646<< INDENT1 "{\n";2647for (const PropertyInterface &iprop : itype.properties) {2648output << INDENT2 "/// <summary>\n"2649<< INDENT2 "/// Cached name for the '" << iprop.cname << "' property.\n"2650<< INDENT2 "/// </summary>\n"2651<< INDENT2 "public static "2652<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".PropertyName." + iprop.proxy_name) ? "new " : "")2653<< "readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n";2654}2655output << INDENT1 "}\n";2656//MethodName2657output << MEMBER_BEGIN "/// <summary>\n"2658<< INDENT1 "/// Cached StringNames for the methods contained in this class, for fast lookup.\n"2659<< INDENT1 "/// </summary>\n";2660if (is_inherit) {2661output << INDENT1 "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName";2662} else {2663output << INDENT1 "public class MethodName";2664}2665output << "\n"2666<< INDENT1 "{\n";2667HashMap<String, StringName> method_names;2668for (const MethodInterface &imethod : itype.methods) {2669if (method_names.has(imethod.proxy_name)) {2670ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value.");2671continue;2672}2673method_names[imethod.proxy_name] = imethod.cname;2674output << INDENT2 "/// <summary>\n"2675<< INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n"2676<< INDENT2 "/// </summary>\n"2677<< INDENT2 "public static "2678<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".MethodName." + imethod.proxy_name) ? "new " : "")2679<< "readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n";2680}2681output << INDENT1 "}\n";2682//SignalName2683output << MEMBER_BEGIN "/// <summary>\n"2684<< INDENT1 "/// Cached StringNames for the signals contained in this class, for fast lookup.\n"2685<< INDENT1 "/// </summary>\n";2686if (is_inherit) {2687output << INDENT1 "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName";2688} else {2689output << INDENT1 "public class SignalName";2690}2691output << "\n"2692<< INDENT1 "{\n";2693for (const SignalInterface &isignal : itype.signals_) {2694output << INDENT2 "/// <summary>\n"2695<< INDENT2 "/// Cached name for the '" << isignal.cname << "' signal.\n"2696<< INDENT2 "/// </summary>\n"2697<< INDENT2 "public static "2698<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".SignalName." + isignal.proxy_name) ? "new " : "")2699<< "readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n";2700}2701output << INDENT1 "}\n";27022703output.append(CLOSE_BLOCK /* class */);27042705return _save_file(p_output_file, output);2706}27072708Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) {2709const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter);27102711// Search it in base types too2712const TypeInterface *current_type = &p_itype;2713while (!setter && current_type->base_name != StringName()) {2714HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);2715ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");2716current_type = &base_match->value;2717setter = current_type->find_method_by_name(p_iprop.setter);2718}27192720const MethodInterface *getter = p_itype.find_method_by_name(p_iprop.getter);27212722// Search it in base types too2723current_type = &p_itype;2724while (!getter && current_type->base_name != StringName()) {2725HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);2726ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");2727current_type = &base_match->value;2728getter = current_type->find_method_by_name(p_iprop.getter);2729}27302731ERR_FAIL_COND_V(!setter && !getter, ERR_BUG);27322733if (setter) {2734int setter_argc = p_iprop.index != -1 ? 2 : 1;2735ERR_FAIL_COND_V(setter->arguments.size() != setter_argc, ERR_BUG);2736}27372738if (getter) {2739int getter_argc = p_iprop.index != -1 ? 1 : 0;2740ERR_FAIL_COND_V(getter->arguments.size() != getter_argc, ERR_BUG);2741}27422743if (getter && setter) {2744const ArgumentInterface &setter_first_arg = setter->arguments.back()->get();2745if (getter->return_type.cname != setter_first_arg.type.cname) {2746ERR_FAIL_V_MSG(ERR_BUG,2747"Return type from getter doesn't match first argument of setter for property: '" +2748p_itype.name + "." + String(p_iprop.cname) + "'.");2749}2750}27512752const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;27532754const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name);2755ERR_FAIL_NULL_V_MSG(prop_itype, ERR_BUG, "Property type '" + proptype_name.cname + "' was not found.");27562757ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,2758"Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'.");27592760if (p_itype.api_type == ClassDB::API_CORE) {2761ERR_FAIL_COND_V_MSG(prop_itype->api_type == ClassDB::API_EDITOR, ERR_BUG,2762"Property '" + p_itype.name + "." + String(p_iprop.cname) + "' has type '" + prop_itype->name +2763"' from the editor API. Core API cannot have dependencies on the editor API.");2764}27652766if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) {2767String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype);2768Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();27692770if (summary_lines.size()) {2771p_output.append(MEMBER_BEGIN "/// <summary>\n");27722773for (int i = 0; i < summary_lines.size(); i++) {2774p_output.append(INDENT1 "/// ");2775p_output.append(summary_lines[i]);2776p_output.append("\n");2777}27782779p_output.append(INDENT1 "/// </summary>");2780}2781}27822783if (p_iprop.is_deprecated) {2784p_output.append(MEMBER_BEGIN "[Obsolete(\"");2785p_output.append(bbcode_to_text(p_iprop.deprecation_message, &p_itype));2786p_output.append("\")]");2787}27882789if (p_iprop.is_hidden) {2790p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");2791// Deprecated PROPERTY_USAGE_INTERNAL properties appear as hidden to C# and may call deprecated getter/setter functions.2792p_output.append("\n#pragma warning disable CS0618 // Type or member is obsolete.");2793}27942795p_output.append(MEMBER_BEGIN "public ");27962797if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) {2798p_output.append("new ");2799}28002801if (p_itype.is_singleton) {2802p_output.append("static ");2803}28042805String prop_cs_type = prop_itype->cs_type + _get_generic_type_parameters(*prop_itype, proptype_name.generic_type_parameters);28062807p_output.append(prop_cs_type);2808p_output.append(" ");2809p_output.append(p_iprop.proxy_name);2810p_output.append("\n" OPEN_BLOCK_L1);28112812if (getter) {2813p_output.append(INDENT2 "get\n" OPEN_BLOCK_L2 INDENT3);28142815p_output.append("return ");2816p_output.append(getter->proxy_name + "(");2817if (p_iprop.index != -1) {2818const ArgumentInterface &idx_arg = getter->arguments.front()->get();2819if (idx_arg.type.cname != name_cache.type_int) {2820// Assume the index parameter is an enum2821const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);2822CRASH_COND(idx_arg_type == nullptr);2823p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + ")");2824} else {2825p_output.append(itos(p_iprop.index));2826}2827}2828p_output.append(");\n" CLOSE_BLOCK_L2);2829}28302831if (setter) {2832p_output.append(INDENT2 "set\n" OPEN_BLOCK_L2 INDENT3);28332834p_output.append(setter->proxy_name + "(");2835if (p_iprop.index != -1) {2836const ArgumentInterface &idx_arg = setter->arguments.front()->get();2837if (idx_arg.type.cname != name_cache.type_int) {2838// Assume the index parameter is an enum2839const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);2840CRASH_COND(idx_arg_type == nullptr);2841p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + "), ");2842} else {2843p_output.append(itos(p_iprop.index) + ", ");2844}2845}2846p_output.append("value);\n" CLOSE_BLOCK_L2);2847}28482849p_output.append(CLOSE_BLOCK_L1);28502851if (p_iprop.is_hidden) {2852p_output.append("#pragma warning restore CS0618 // Type or member is obsolete.\n");2853}28542855return OK;2856}28572858Error 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) {2859const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);2860ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");28612862ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,2863"Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'.");28642865if (p_itype.api_type == ClassDB::API_CORE) {2866ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG,2867"Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name +2868"' from the editor API. Core API cannot have dependencies on the editor API.");2869}28702871if (p_imethod.is_virtual && p_use_span) {2872return OK;2873}28742875bool has_span_argument = false;28762877if (p_use_span) {2878if (p_imethod.is_vararg) {2879has_span_argument = true;2880} else {2881for (const ArgumentInterface &iarg : p_imethod.arguments) {2882const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);2883ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");28842885if (arg_type->is_span_compatible) {2886has_span_argument = true;2887break;2888}2889}2890}28912892if (has_span_argument) {2893// Span overloads use the same method bind as the array overloads.2894// Since both overloads are generated one after the other, we can decrease the count here2895// to ensure the span overload uses the same method bind.2896p_method_bind_count--;2897}2898}28992900String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);29012902String arguments_sig;2903StringBuilder cs_in_statements;2904bool cs_in_expr_is_unsafe = false;29052906String icall_params = method_bind_field;29072908if (!p_imethod.is_static) {2909String self_reference = "this";2910if (p_itype.is_singleton) {2911self_reference = CS_PROPERTY_SINGLETON;2912}29132914if (p_itype.cs_in.size()) {2915cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference,2916String(), String(), String(), INDENT2);2917}29182919icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference);2920}29212922StringBuilder default_args_doc;29232924// Retrieve information from the arguments2925const ArgumentInterface &first = p_imethod.arguments.front()->get();2926for (const ArgumentInterface &iarg : p_imethod.arguments) {2927const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);2928ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");29292930ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,2931"Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");29322933if (p_itype.api_type == ClassDB::API_CORE) {2934ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,2935"Argument '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "' has type '" +2936arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");2937}29382939if (iarg.default_argument.size()) {2940CRASH_COND_MSG(!_arg_default_value_is_assignable_to_type(iarg.def_param_value, *arg_type),2941"Invalid default value for parameter '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");2942}29432944String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);29452946bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;29472948// Add the current arguments to the signature2949// If the argument has a default value which is not a constant, we will make it Nullable2950{2951if (&iarg != &first) {2952arguments_sig += ", ";2953}29542955if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2956arguments_sig += "Nullable<";2957}29582959if (use_span_for_arg) {2960arguments_sig += arg_type->c_type_in;2961} else {2962arguments_sig += arg_cs_type;2963}29642965if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2966arguments_sig += "> ";2967} else {2968arguments_sig += " ";2969}29702971arguments_sig += iarg.name;29722973if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {2974if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {2975arguments_sig += " = null";2976} else {2977arguments_sig += " = " + sformat(iarg.default_argument, arg_type->cs_type);2978}2979}2980}29812982icall_params += ", ";29832984if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {2985// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:2986// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;2987String arg_or_defval_local = iarg.name;2988arg_or_defval_local += "OrDefVal";29892990cs_in_statements << INDENT2 << arg_cs_type << " " << arg_or_defval_local << " = " << iarg.name;29912992if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {2993cs_in_statements << ".HasValue ? ";2994} else {2995cs_in_statements << " != null ? ";2996}29972998cs_in_statements << iarg.name;29993000if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {3001cs_in_statements << ".Value : ";3002} else {3003cs_in_statements << " : ";3004}30053006String cs_type = arg_cs_type;3007if (cs_type.ends_with("[]")) {3008cs_type = cs_type.substr(0, cs_type.length() - 2);3009}30103011String def_arg = sformat(iarg.default_argument, cs_type);30123013cs_in_statements << def_arg << ";\n";30143015if (arg_type->cs_in.size()) {3016cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, arg_or_defval_local,3017String(), String(), String(), INDENT2);3018}30193020if (arg_type->cs_in_expr.is_empty()) {3021icall_params += arg_or_defval_local;3022} else {3023icall_params += sformat(arg_type->cs_in_expr, arg_or_defval_local, arg_type->c_type);3024}30253026// Apparently the name attribute must not include the @3027String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1) : iarg.name;3028// Escape < and > in the attribute default value3029String param_def_arg = def_arg.replacen("<", "<").replacen(">", ">");30303031default_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>");3032} else {3033if (arg_type->cs_in.size()) {3034cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, iarg.name,3035String(), String(), String(), INDENT2);3036}30373038icall_params += arg_type->cs_in_expr.is_empty() ? iarg.name : sformat(arg_type->cs_in_expr, iarg.name, arg_type->c_type);3039}30403041cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;3042}30433044if (p_use_span && !has_span_argument) {3045return OK;3046}30473048// Collect caller name for MethodBind3049if (p_imethod.is_vararg) {3050icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";3051}30523053// Generate method3054{3055if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {3056p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"3057<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";30583059if (p_itype.is_singleton) {3060// Singletons are static classes. They don't derive GodotObject,3061// so we need to specify the type to call the static method.3062p_output << "GodotObject.";3063}30643065p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName."3066<< p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul"3067<< ");\n";3068}30693070if (p_imethod.method_doc && p_imethod.method_doc->description.size()) {3071String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype);3072Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();30733074if (summary_lines.size()) {3075p_output.append(MEMBER_BEGIN "/// <summary>\n");30763077for (int i = 0; i < summary_lines.size(); i++) {3078p_output.append(INDENT1 "/// ");3079p_output.append(summary_lines[i]);3080p_output.append("\n");3081}30823083p_output.append(INDENT1 "/// </summary>");3084}3085}30863087if (default_args_doc.get_string_length()) {3088p_output.append(default_args_doc.as_string());3089}30903091if (p_imethod.is_deprecated) {3092p_output.append(MEMBER_BEGIN "[Obsolete(\"");3093p_output.append(bbcode_to_text(p_imethod.deprecation_message, &p_itype));3094p_output.append("\")]");3095}30963097if (p_imethod.is_hidden) {3098p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");3099}31003101p_output.append(MEMBER_BEGIN);3102p_output.append(p_imethod.is_internal ? "internal " : "public ");31033104if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_imethod.proxy_name)) {3105p_output.append("new ");3106}31073108if (p_itype.is_singleton || p_imethod.is_static) {3109p_output.append("static ");3110} else if (p_imethod.is_virtual) {3111p_output.append("virtual ");3112}31133114if (cs_in_expr_is_unsafe) {3115p_output.append("unsafe ");3116}31173118String return_cs_type = return_type->cs_type + _get_generic_type_parameters(*return_type, p_imethod.return_type.generic_type_parameters);31193120p_output.append(return_cs_type + " ");3121p_output.append(p_imethod.proxy_name + "(");3122p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L1);31233124if (p_imethod.is_virtual) {3125// Godot virtual method must be overridden, therefore we return a default value by default.31263127if (return_type->cname == name_cache.type_void) {3128p_output.append(CLOSE_BLOCK_L1);3129} else {3130p_output.append(INDENT2 "return default;\n" CLOSE_BLOCK_L1);3131}31323133return OK; // Won't increment method bind count3134}31353136if (p_imethod.requires_object_call) {3137// Fallback to Godot's object.Call(string, params)31383139p_output.append(INDENT2 CS_METHOD_CALL "(");3140p_output.append("MethodName." + p_imethod.proxy_name);31413142for (const ArgumentInterface &iarg : p_imethod.arguments) {3143p_output.append(", ");3144p_output.append(iarg.name);3145}31463147p_output.append(");\n" CLOSE_BLOCK_L1);31483149return OK; // Won't increment method bind count3150}31513152HashMap<const MethodInterface *, const InternalCall *>::ConstIterator match = method_icalls_map.find(&p_imethod);3153ERR_FAIL_NULL_V(match, ERR_BUG);31543155const InternalCall *im_icall = match->value;31563157String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS;3158im_call += ".";3159im_call += im_icall->name;31603161if (p_imethod.arguments.size() && cs_in_statements.get_string_length() > 0) {3162p_output.append(cs_in_statements.as_string());3163}31643165if (return_type->cname == name_cache.type_void) {3166p_output << INDENT2 << im_call << "(" << icall_params << ");\n";3167} else if (return_type->cs_out.is_empty()) {3168p_output << INDENT2 "return " << im_call << "(" << icall_params << ");\n";3169} else {3170p_output.append(sformat(return_type->cs_out, im_call, icall_params,3171return_cs_type, return_type->c_type_out, String(), INDENT2));3172p_output.append("\n");3173}31743175p_output.append(CLOSE_BLOCK_L1);3176}31773178p_method_bind_count++;31793180return OK;3181}31823183Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {3184String arguments_sig;31853186// Retrieve information from the arguments3187const ArgumentInterface &first = p_isignal.arguments.front()->get();3188for (const ArgumentInterface &iarg : p_isignal.arguments) {3189const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);3190ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");31913192ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,3193"Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'.");31943195if (p_itype.api_type == ClassDB::API_CORE) {3196ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,3197"Argument '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "' has type '" +3198arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");3199}32003201// Add the current arguments to the signature32023203if (&iarg != &first) {3204arguments_sig += ", ";3205}32063207String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);32083209arguments_sig += arg_cs_type;3210arguments_sig += " ";3211arguments_sig += iarg.name;3212}32133214// Generate signal3215{3216bool is_parameterless = p_isignal.arguments.is_empty();32173218// Delegate name is [SignalName]EventHandler3219String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";32203221if (!is_parameterless) {3222p_output.append(MEMBER_BEGIN "/// <summary>\n");3223p_output.append(INDENT1 "/// ");3224p_output.append("Represents the method that handles the ");3225p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");3226p_output.append(" event of a ");3227p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");3228p_output.append(" class.\n");3229p_output.append(INDENT1 "/// </summary>");32303231// Generate delegate3232if (p_isignal.is_deprecated) {3233p_output.append(MEMBER_BEGIN "[Obsolete(\"");3234p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3235p_output.append("\")]");3236}3237p_output.append(MEMBER_BEGIN "public delegate void ");3238p_output.append(delegate_name);3239p_output.append("(");3240p_output.append(arguments_sig);3241p_output.append(");\n");32423243// Generate Callable trampoline for the delegate3244if (p_isignal.is_deprecated) {3245p_output.append(MEMBER_BEGIN "[Obsolete(\"");3246p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3247p_output.append("\")]");3248}3249p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline"3250<< "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"3251<< INDENT1 "{\n"3252<< INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"3253<< INDENT2 "((" << delegate_name << ")delegateObj)(";32543255int idx = 0;3256for (const ArgumentInterface &iarg : p_isignal.arguments) {3257const TypeInterface *arg_type = _get_type_or_null(iarg.type);3258ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");32593260if (idx != 0) {3261p_output << ", ";3262}32633264if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {3265String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);32663267p_output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name) << ")";3268} else {3269p_output << sformat(arg_type->cs_variant_to_managed,3270"args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name);3271}32723273idx++;3274}32753276p_output << ");\n"3277<< INDENT2 "ret = default;\n"3278<< INDENT1 "}\n";3279}32803281if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {3282String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);3283Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();32843285if (summary_lines.size()) {3286p_output.append(MEMBER_BEGIN "/// <summary>\n");32873288for (int i = 0; i < summary_lines.size(); i++) {3289p_output.append(INDENT1 "/// ");3290p_output.append(summary_lines[i]);3291p_output.append("\n");3292}32933294p_output.append(INDENT1 "/// </summary>");3295}3296}32973298// TODO:3299// Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded?3300// If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here.33013302// Generate event3303if (p_isignal.is_deprecated) {3304p_output.append(MEMBER_BEGIN "[Obsolete(\"");3305p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3306p_output.append("\")]");3307}3308p_output.append(MEMBER_BEGIN "public ");33093310if (p_itype.is_singleton) {3311p_output.append("static ");3312}33133314if (!is_parameterless) {3315// `unsafe` is needed for taking the trampoline's function pointer3316p_output << "unsafe ";3317}33183319p_output.append("event ");3320p_output.append(delegate_name);3321p_output.append(" ");3322p_output.append(p_isignal.proxy_name);3323p_output.append("\n" OPEN_BLOCK_L1 INDENT2);33243325if (p_itype.is_singleton) {3326p_output.append("add => " CS_PROPERTY_SINGLETON ".Connect(SignalName.");3327} else {3328p_output.append("add => Connect(SignalName.");3329}33303331if (is_parameterless) {3332// Delegate type is Action. No need for custom trampoline.3333p_output << p_isignal.proxy_name << ", Callable.From(value));\n";3334} else {3335p_output << p_isignal.proxy_name3336<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";3337}33383339if (p_itype.is_singleton) {3340p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName.");3341} else {3342p_output.append(INDENT2 "remove => Disconnect(SignalName.");3343}33443345if (is_parameterless) {3346// Delegate type is Action. No need for custom trampoline.3347p_output << p_isignal.proxy_name << ", Callable.From(value));\n";3348} else {3349p_output << p_isignal.proxy_name3350<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";3351}33523353p_output.append(CLOSE_BLOCK_L1);33543355// Generate EmitSignal{EventName} method to raise the event.3356if (!p_itype.is_singleton) {3357if (p_isignal.is_deprecated) {3358p_output.append(MEMBER_BEGIN "[Obsolete(\"");3359p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));3360p_output.append("\")]");3361}3362p_output.append(MEMBER_BEGIN "protected void ");3363p_output << "EmitSignal" << p_isignal.proxy_name;3364if (is_parameterless) {3365p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);3366p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";3367p_output.append(CLOSE_BLOCK_L1);3368} else {3369p_output.append("(");33703371StringBuilder cs_emitsignal_params;33723373int idx = 0;3374for (const ArgumentInterface &iarg : p_isignal.arguments) {3375const TypeInterface *arg_type = _get_type_or_null(iarg.type);3376ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");33773378if (idx != 0) {3379p_output << ", ";3380cs_emitsignal_params << ", ";3381}33823383String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);33843385p_output << arg_cs_type << " " << iarg.name;33863387if (arg_type->is_enum) {3388cs_emitsignal_params << "(long)";3389}33903391cs_emitsignal_params << iarg.name;33923393idx++;3394}33953396p_output.append(")\n" OPEN_BLOCK_L1 INDENT2);3397p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ", " << cs_emitsignal_params << ");\n";3398p_output.append(CLOSE_BLOCK_L1);3399}3400}3401}34023403return OK;3404}34053406Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output) {3407bool ret_void = p_icall.return_type.cname == name_cache.type_void;34083409const TypeInterface *return_type = _get_type_or_null(p_icall.return_type);3410ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_icall.return_type.cname + "' was not found.");34113412StringBuilder c_func_sig;3413StringBuilder c_in_statements;3414StringBuilder c_args_var_content;34153416c_func_sig << "IntPtr " CS_PARAM_METHODBIND;34173418if (!p_icall.is_static) {3419c_func_sig += ", IntPtr " CS_PARAM_INSTANCE;3420}34213422// Get arguments information3423int i = 0;3424for (const TypeReference &arg_type_ref : p_icall.argument_types) {3425const TypeInterface *arg_type = _get_type_or_null(arg_type_ref);3426ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + arg_type_ref.cname + "' was not found.");34273428String c_param_name = "arg" + itos(i + 1);34293430if (p_icall.is_vararg) {3431if (i < p_icall.get_arguments_count() - 1) {3432String c_in_vararg = arg_type->c_in_vararg;34333434if (arg_type->is_object_type) {3435c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObjectPtr(%1);\n";3436}34373438ERR_FAIL_COND_V_MSG(c_in_vararg.is_empty(), ERR_BUG,3439"VarArg support not implemented for parameter type: " + arg_type->name);34403441c_in_statements3442<< sformat(c_in_vararg, return_type->c_type, c_param_name,3443String(), String(), String(), INDENT3)3444<< INDENT3 C_LOCAL_PTRCALL_ARGS "[" << itos(i)3445<< "] = new IntPtr(&" << c_param_name << "_in);\n";3446}3447} else {3448if (i > 0) {3449c_args_var_content << ", ";3450}3451if (arg_type->c_in.size()) {3452c_in_statements << sformat(arg_type->c_in, arg_type->c_type, c_param_name,3453String(), String(), String(), INDENT2);3454}3455c_args_var_content << sformat(arg_type->c_arg_in, c_param_name);3456}34573458c_func_sig << ", " << arg_type->c_type_in << " " << c_param_name;34593460i++;3461}34623463// Collect caller name for MethodBind3464if (p_icall.is_vararg) {3465c_func_sig << ", godot_string_name caller";3466}34673468String icall_method = p_icall.name;34693470// Generate icall function34713472r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " "3473<< icall_method << "(" << c_func_sig.as_string() << ")\n" OPEN_BLOCK_L1;34743475if (!p_icall.is_static) {3476r_output << INDENT2 "ExceptionUtils.ThrowIfNullPtr(" CS_PARAM_INSTANCE ");\n";3477}34783479if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) {3480String ptrcall_return_type;3481String initialization;34823483if (return_type->is_object_type) {3484ptrcall_return_type = return_type->is_ref_counted ? "godot_ref" : return_type->c_type;3485initialization = " = default";3486} else {3487ptrcall_return_type = return_type->c_type;3488}34893490r_output << INDENT2;34913492if (return_type->is_ref_counted || return_type->c_type_is_disposable_struct) {3493r_output << "using ";34943495if (initialization.is_empty()) {3496initialization = " = default";3497}3498} else if (return_type->c_ret_needs_default_initialization) {3499initialization = " = default";3500}35013502r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n";3503}35043505String argc_str = itos(p_icall.get_arguments_count());35063507auto generate_call_and_return_stmts = [&](const char *base_indent) {3508if (p_icall.is_vararg) {3509// MethodBind Call3510r_output << base_indent;35113512// VarArg methods always return Variant, but there are some cases in which MethodInfo provides3513// a specific return type. We trust this information is valid. We need a temporary local to keep3514// the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr,3515// it could be deleted too early. This is the case with GDScript.new() which returns OBJECT.3516// Alternatively, we could just return Variant, but that would result in a worse API.35173518if (!ret_void) {3519if (return_type->cname != name_cache.type_Variant) {3520// Usually the return value takes ownership, but in this case the variant is only used3521// for conversion to another return type. As such, the local variable takes ownership.3522r_output << "using godot_variant " << C_LOCAL_VARARG_RET " = ";3523} else {3524// Variant's [c_out] takes ownership of the variant value3525r_output << "godot_variant " << C_LOCAL_RET " = ";3526}3527}35283529r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("3530<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3531<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")3532<< ", total_length, out godot_variant_call_error vcall_error);\n";35333534r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"3535<< ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3536<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")3537<< ", total_length, vcall_error);\n";35383539if (!ret_void) {3540if (return_type->cname != name_cache.type_Variant) {3541if (return_type->cname == name_cache.enum_Error) {3542r_output << base_indent << C_LOCAL_RET " = VariantUtils.ConvertToInt64(" C_LOCAL_VARARG_RET ");\n";3543} else {3544// TODO: Use something similar to c_in_vararg (see usage above, with error if not implemented)3545CRASH_NOW_MSG("Custom VarArg return type not implemented: " + return_type->name);3546r_output << base_indent << C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n";3547}3548}3549}3550} else {3551// MethodBind PtrCall3552r_output << base_indent << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_ptrcall("3553<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)3554<< ", " << (p_icall.get_arguments_count() ? C_LOCAL_PTRCALL_ARGS : "null")3555<< ", " << (!ret_void ? "&" C_LOCAL_RET ");\n" : "null);\n");3556}35573558// Return statement35593560if (!ret_void) {3561if (return_type->c_out.is_empty()) {3562r_output << base_indent << "return " C_LOCAL_RET ";\n";3563} else {3564r_output << sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET,3565return_type->name, String(), String(), base_indent);3566}3567}3568};35693570if (p_icall.get_arguments_count()) {3571if (p_icall.is_vararg) {3572String vararg_arg = "arg" + argc_str;3573String real_argc_str = itos(p_icall.get_arguments_count() - 1); // Arguments count without vararg35743575p_icall.get_arguments_count();35763577r_output << INDENT2 "int vararg_length = " << vararg_arg << ".Length;\n"3578<< INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n";35793580r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n"3581<< INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold] :\n"3582<< INDENT3 "new godot_variant.movable[vararg_length];\n";35833584r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n"3585<< INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n"3586<< INDENT3 "new IntPtr[total_length];\n";35873588r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n"3589<< INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = "3590"&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n"3591<< OPEN_BLOCK_L2;35923593r_output << c_in_statements.as_string();35943595r_output << INDENT3 "for (int i = 0; i < vararg_length; i++)\n" OPEN_BLOCK_L33596<< INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n"3597<< INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n"3598<< CLOSE_BLOCK_L3;35993600generate_call_and_return_stmts(INDENT3);36013602r_output << CLOSE_BLOCK_L2;3603} else {3604r_output << c_in_statements.as_string();36053606r_output << INDENT2 "void** " C_LOCAL_PTRCALL_ARGS " = stackalloc void*["3607<< argc_str << "] { " << c_args_var_content.as_string() << " };\n";36083609generate_call_and_return_stmts(INDENT2);3610}3611} else {3612generate_call_and_return_stmts(INDENT2);3613}36143615r_output << CLOSE_BLOCK_L1;36163617return OK;3618}36193620Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) {3621Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);3622ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'.");36233624file->store_string(p_content.as_string());36253626return OK;3627}36283629const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(const TypeReference &p_typeref) {3630HashMap<StringName, TypeInterface>::ConstIterator builtin_type_match = builtin_types.find(p_typeref.cname);36313632if (builtin_type_match) {3633return &builtin_type_match->value;3634}36353636HashMap<StringName, TypeInterface>::ConstIterator obj_type_match = obj_types.find(p_typeref.cname);36373638if (obj_type_match) {3639return &obj_type_match->value;3640}36413642if (p_typeref.is_enum) {3643HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(p_typeref.cname);36443645if (enum_match) {3646return &enum_match->value;3647}36483649// Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.3650HashMap<StringName, TypeInterface>::ConstIterator int_match = builtin_types.find(name_cache.type_int);3651ERR_FAIL_NULL_V(int_match, nullptr);3652return &int_match->value;3653}36543655return nullptr;3656}36573658const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) {3659const TypeInterface *itype = _get_type_or_null(p_typeref);3660if (itype == nullptr) {3661return nullptr;3662}36633664if (itype->is_singleton) {3665StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX;3666itype = &obj_types.find(instance_type_name)->value;3667}36683669return itype;3670}36713672const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) {3673if (p_generic_type_parameters.is_empty()) {3674return "";3675}36763677ERR_FAIL_COND_V_MSG(p_itype.type_parameter_count != p_generic_type_parameters.size(), "",3678"Generic type parameter count mismatch for type '" + p_itype.name + "'." +3679" Found " + itos(p_generic_type_parameters.size()) + ", but requires " +3680itos(p_itype.type_parameter_count) + ".");36813682int i = 0;3683String params = "<";3684for (const TypeReference ¶m_type : p_generic_type_parameters) {3685const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type);3686ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found.");36873688ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "",3689"Generic type parameter is a singleton: '" + param_itype->name + "'.");36903691if (p_itype.api_type == ClassDB::API_CORE) {3692ERR_FAIL_COND_V_MSG(param_itype->api_type == ClassDB::API_EDITOR, "",3693"Generic type parameter '" + param_itype->name + "' has type from the editor API." +3694" Core API cannot have dependencies on the editor API.");3695}36963697params += param_itype->cs_type;3698if (i < p_generic_type_parameters.size() - 1) {3699params += ", ";3700}37013702i++;3703}3704params += ">";37053706return params;3707}37083709StringName BindingsGenerator::_get_type_name_from_meta(Variant::Type p_type, GodotTypeInfo::Metadata p_meta) {3710if (p_type == Variant::INT) {3711return _get_int_type_name_from_meta(p_meta);3712} else if (p_type == Variant::FLOAT) {3713return _get_float_type_name_from_meta(p_meta);3714} else {3715return Variant::get_type_name(p_type);3716}3717}37183719StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {3720switch (p_meta) {3721case GodotTypeInfo::METADATA_INT_IS_INT8:3722return "sbyte";3723break;3724case GodotTypeInfo::METADATA_INT_IS_INT16:3725return "short";3726break;3727case GodotTypeInfo::METADATA_INT_IS_INT32:3728return "int";3729break;3730case GodotTypeInfo::METADATA_INT_IS_INT64:3731return "long";3732break;3733case GodotTypeInfo::METADATA_INT_IS_UINT8:3734return "byte";3735break;3736case GodotTypeInfo::METADATA_INT_IS_UINT16:3737return "ushort";3738break;3739case GodotTypeInfo::METADATA_INT_IS_UINT32:3740return "uint";3741break;3742case GodotTypeInfo::METADATA_INT_IS_UINT64:3743return "ulong";3744break;3745case GodotTypeInfo::METADATA_INT_IS_CHAR16:3746return "char";3747break;3748case GodotTypeInfo::METADATA_INT_IS_CHAR32:3749// To prevent breaking compatibility, C# bindings need to keep using `long`.3750return "long";3751default:3752// Assume INT643753return "long";3754}3755}37563757StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {3758switch (p_meta) {3759case GodotTypeInfo::METADATA_REAL_IS_FLOAT:3760return "float";3761break;3762case GodotTypeInfo::METADATA_REAL_IS_DOUBLE:3763return "double";3764break;3765default:3766// Assume FLOAT643767return "double";3768}3769}37703771bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant &p_val, const TypeInterface &p_arg_type) {3772if (p_arg_type.name == name_cache.type_Variant) {3773// Variant can take anything3774return true;3775}37763777switch (p_val.get_type()) {3778case Variant::NIL:3779return p_arg_type.is_object_type ||3780name_cache.is_nullable_type(p_arg_type.name);3781case Variant::BOOL:3782return p_arg_type.name == name_cache.type_bool;3783case Variant::INT:3784return p_arg_type.name == name_cache.type_sbyte ||3785p_arg_type.name == name_cache.type_short ||3786p_arg_type.name == name_cache.type_int ||3787p_arg_type.name == name_cache.type_byte ||3788p_arg_type.name == name_cache.type_ushort ||3789p_arg_type.name == name_cache.type_uint ||3790p_arg_type.name == name_cache.type_long ||3791p_arg_type.name == name_cache.type_ulong ||3792p_arg_type.name == name_cache.type_float ||3793p_arg_type.name == name_cache.type_double ||3794p_arg_type.is_enum;3795case Variant::FLOAT:3796return p_arg_type.name == name_cache.type_float ||3797p_arg_type.name == name_cache.type_double;3798case Variant::STRING:3799case Variant::STRING_NAME:3800return p_arg_type.name == name_cache.type_String ||3801p_arg_type.name == name_cache.type_StringName ||3802p_arg_type.name == name_cache.type_NodePath;3803case Variant::NODE_PATH:3804return p_arg_type.name == name_cache.type_NodePath;3805case Variant::TRANSFORM2D:3806case Variant::TRANSFORM3D:3807case Variant::BASIS:3808case Variant::QUATERNION:3809case Variant::PLANE:3810case Variant::AABB:3811case Variant::COLOR:3812case Variant::VECTOR2:3813case Variant::RECT2:3814case Variant::VECTOR3:3815case Variant::VECTOR4:3816case Variant::PROJECTION:3817case Variant::RID:3818case Variant::PACKED_BYTE_ARRAY:3819case Variant::PACKED_INT32_ARRAY:3820case Variant::PACKED_INT64_ARRAY:3821case Variant::PACKED_FLOAT32_ARRAY:3822case Variant::PACKED_FLOAT64_ARRAY:3823case Variant::PACKED_STRING_ARRAY:3824case Variant::PACKED_VECTOR2_ARRAY:3825case Variant::PACKED_VECTOR3_ARRAY:3826case Variant::PACKED_VECTOR4_ARRAY:3827case Variant::PACKED_COLOR_ARRAY:3828case Variant::CALLABLE:3829case Variant::SIGNAL:3830return p_arg_type.name == Variant::get_type_name(p_val.get_type());3831case Variant::ARRAY:3832return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Array_generic;3833case Variant::DICTIONARY:3834return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Dictionary_generic;3835case Variant::OBJECT:3836return p_arg_type.is_object_type;3837case Variant::VECTOR2I:3838return p_arg_type.name == name_cache.type_Vector2 ||3839p_arg_type.name == Variant::get_type_name(p_val.get_type());3840case Variant::RECT2I:3841return p_arg_type.name == name_cache.type_Rect2 ||3842p_arg_type.name == Variant::get_type_name(p_val.get_type());3843case Variant::VECTOR3I:3844return p_arg_type.name == name_cache.type_Vector3 ||3845p_arg_type.name == Variant::get_type_name(p_val.get_type());3846case Variant::VECTOR4I:3847return p_arg_type.name == name_cache.type_Vector4 ||3848p_arg_type.name == Variant::get_type_name(p_val.get_type());3849case Variant::VARIANT_MAX:3850CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type()));3851break;3852}38533854return false;3855}38563857bool method_has_ptr_parameter(MethodInfo p_method_info) {3858if (p_method_info.return_val.type == Variant::INT && p_method_info.return_val.hint == PROPERTY_HINT_INT_IS_POINTER) {3859return true;3860}3861for (PropertyInfo arg : p_method_info.arguments) {3862if (arg.type == Variant::INT && arg.hint == PROPERTY_HINT_INT_IS_POINTER) {3863return true;3864}3865}3866return false;3867}38683869struct SortMethodWithHashes {3870_FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const {3871return p_a.first < p_b.first;3872}3873};38743875bool BindingsGenerator::_populate_object_type_interfaces() {3876obj_types.clear();38773878LocalVector<StringName> class_list;3879ClassDB::get_class_list(class_list);38803881for (const StringName &type_cname : class_list) {3882ClassDB::APIType api_type = ClassDB::get_api_type(type_cname);38833884if (api_type == ClassDB::API_NONE) {3885continue;3886}38873888if (ignored_types.has(type_cname)) {3889_log("Ignoring type '%s' because it's in the list of ignored types\n", String(type_cname).utf8().get_data());3890continue;3891}38923893if (!ClassDB::is_class_exposed(type_cname)) {3894_log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data());3895continue;3896}38973898if (!ClassDB::is_class_enabled(type_cname)) {3899_log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data());3900continue;3901}39023903ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname);39043905TypeInterface itype = TypeInterface::create_object_type(type_cname, pascal_to_pascal_case(type_cname), api_type);39063907itype.base_name = ClassDB::get_parent_class(type_cname);3908itype.is_singleton = Engine::get_singleton()->has_singleton(type_cname);3909itype.is_instantiable = class_info->creation_func && !itype.is_singleton;3910itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);3911itype.memory_own = itype.is_ref_counted;39123913if (itype.class_doc) {3914itype.is_deprecated = itype.class_doc->is_deprecated;3915itype.deprecation_message = itype.class_doc->deprecated_message;39163917if (itype.is_deprecated && itype.deprecation_message.is_empty()) {3918WARN_PRINT("An empty deprecation message is discouraged. Type: '" + itype.proxy_name + "'.");3919itype.deprecation_message = "This class is deprecated.";3920}3921}39223923if (itype.is_singleton && compat_singletons.has(itype.cname)) {3924itype.is_singleton = false;3925itype.is_compat_singleton = true;3926}39273928itype.c_out = "%5return ";3929itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;3930itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";39313932itype.cs_type = itype.proxy_name;39333934itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";39353936itype.cs_out = "%5return (%2)%0(%1);";39373938itype.c_arg_in = "&%s";3939itype.c_type = "IntPtr";3940itype.c_type_in = itype.c_type;3941itype.c_type_out = "GodotObject";39423943// Populate properties39443945List<PropertyInfo> property_list;3946ClassDB::get_property_list(type_cname, &property_list, true);39473948HashMap<StringName, StringName> accessor_methods;39493950for (const PropertyInfo &property : property_list) {3951if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {3952continue;3953}39543955if (property.name.contains_char('/')) {3956// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.3957continue;3958}39593960PropertyInterface iprop;3961iprop.cname = property.name;3962iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);3963iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);39643965// If the property is internal hide it; otherwise, hide the getter and setter.3966if (property.usage & PROPERTY_USAGE_INTERNAL) {3967iprop.is_hidden = true;3968} else {3969if (iprop.setter != StringName()) {3970accessor_methods[iprop.setter] = iprop.cname;3971}3972if (iprop.getter != StringName()) {3973accessor_methods[iprop.getter] = iprop.cname;3974}3975}39763977bool valid = false;3978iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);3979ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");39803981iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));39823983// Prevent the property and its enclosing type from sharing the same name3984if (iprop.proxy_name == itype.proxy_name) {3985_log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n",3986iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data());39873988iprop.proxy_name += "_";3989}39903991iprop.prop_doc = nullptr;39923993for (int i = 0; i < itype.class_doc->properties.size(); i++) {3994const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i];39953996if (prop_doc.name == iprop.cname) {3997iprop.prop_doc = &prop_doc;3998break;3999}4000}40014002if (iprop.prop_doc) {4003iprop.is_deprecated = iprop.prop_doc->is_deprecated;4004iprop.deprecation_message = iprop.prop_doc->deprecated_message;40054006if (iprop.is_deprecated && iprop.deprecation_message.is_empty()) {4007WARN_PRINT("An empty deprecation message is discouraged. Property: '" + itype.proxy_name + "." + iprop.proxy_name + "'.");4008iprop.deprecation_message = "This property is deprecated.";4009}4010}40114012itype.properties.push_back(iprop);4013}40144015// Populate methods40164017List<MethodInfo> virtual_method_list;4018ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true);40194020List<Pair<MethodInfo, uint32_t>> method_list_with_hashes;4021ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true);4022method_list_with_hashes.sort_custom<SortMethodWithHashes>();40234024List<MethodInterface> compat_methods;4025for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) {4026const MethodInfo &method_info = E.first;4027const uint32_t hash = E.second;40284029if (method_info.name.is_empty()) {4030continue;4031}40324033String cname = method_info.name;40344035if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) {4036continue;4037}40384039if (method_has_ptr_parameter(method_info)) {4040// Pointers are not supported.4041itype.ignored_members.insert(method_info.name);4042continue;4043}40444045MethodInterface imethod;4046imethod.name = method_info.name;4047imethod.cname = cname;4048imethod.hash = hash;40494050if (method_info.flags & METHOD_FLAG_STATIC) {4051imethod.is_static = true;4052}40534054if (method_info.flags & METHOD_FLAG_VIRTUAL) {4055imethod.is_virtual = true;4056itype.has_virtual_methods = true;4057}40584059PropertyInfo return_info = method_info.return_val;40604061MethodBind *m = nullptr;40624063if (!imethod.is_virtual) {4064bool method_exists = false;4065m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat);40664067if (unlikely(!method_exists)) {4068ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4069"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");4070}4071}40724073imethod.is_vararg = m && m->is_vararg();40744075if (!m && !imethod.is_virtual) {4076ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,4077"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");40784079// A virtual method without the virtual flag. This is a special case.40804081// There is no method bind, so let's fallback to Godot's object.Call(string, params)4082imethod.requires_object_call = true;40834084// The method Object.free is registered as a virtual method, but without the virtual flag.4085// This is because this method is not supposed to be overridden, but called.4086// We assume the return type is void.4087imethod.return_type.cname = name_cache.type_void;40884089// Actually, more methods like this may be added in the future, which could return4090// something different. Let's put this check to notify us if that ever happens.4091if (itype.cname != name_cache.type_Object || imethod.name != "free") {4092WARN_PRINT("Notification: New unexpected virtual non-overridable method found."4093" We only expected Object.free, but found '" +4094itype.name + "." + imethod.name + "'.");4095}4096} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4097imethod.return_type.cname = return_info.class_name;4098imethod.return_type.is_enum = true;4099} else if (return_info.class_name != StringName()) {4100imethod.return_type.cname = return_info.class_name;41014102bool bad_reference_hint = !imethod.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&4103ClassDB::is_parent_class(return_info.class_name, name_cache.type_RefCounted);4104ERR_FAIL_COND_V_MSG(bad_reference_hint, false,4105String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +4106" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");4107} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {4108imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4109imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));4110} else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4111imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";4112Vector<String> split = return_info.hint_string.split(";");4113imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));4114imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));4115} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {4116imethod.return_type.cname = return_info.hint_string;4117} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {4118imethod.return_type.cname = name_cache.type_Variant;4119} else if (return_info.type == Variant::NIL) {4120imethod.return_type.cname = name_cache.type_void;4121} else {4122imethod.return_type.cname = _get_type_name_from_meta(return_info.type, m ? m->get_argument_meta(-1) : (GodotTypeInfo::Metadata)method_info.return_val_metadata);4123}41244125for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4126const PropertyInfo &arginfo = method_info.arguments[idx];41274128String orig_arg_name = arginfo.name;41294130ArgumentInterface iarg;4131iarg.name = orig_arg_name;41324133if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4134iarg.type.cname = arginfo.class_name;4135iarg.type.is_enum = true;4136} else if (arginfo.class_name != StringName()) {4137iarg.type.cname = arginfo.class_name;4138} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4139iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4140iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4141} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4142iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4143Vector<String> split = arginfo.hint_string.split(";");4144iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4145iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4146} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4147iarg.type.cname = arginfo.hint_string;4148} else if (arginfo.type == Variant::NIL) {4149iarg.type.cname = name_cache.type_Variant;4150} else {4151iarg.type.cname = _get_type_name_from_meta(arginfo.type, m ? m->get_argument_meta(idx) : (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4152}41534154iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));41554156if (m && m->has_default_argument(idx)) {4157bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(idx), iarg);4158ERR_FAIL_COND_V_MSG(!defval_ok, false,4159"Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");4160}41614162imethod.add_argument(iarg);4163}41644165if (imethod.is_vararg) {4166ArgumentInterface ivararg;4167ivararg.type.cname = name_cache.type_VarArg;4168ivararg.name = "@args";4169imethod.add_argument(ivararg);4170}41714172imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name));41734174// Prevent the method and its enclosing type from sharing the same name4175if (imethod.proxy_name == itype.proxy_name) {4176_log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n",4177imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data());41784179imethod.proxy_name += "_";4180}41814182HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname);4183if (accessor) {4184// We only hide an accessor method if it's in the same class as the property.4185// It's easier this way, but also we don't know if an accessor method in a different class4186// could have other purposes, so better leave those untouched.4187imethod.is_hidden = true;4188}41894190if (itype.class_doc) {4191for (int i = 0; i < itype.class_doc->methods.size(); i++) {4192if (itype.class_doc->methods[i].name == imethod.name) {4193imethod.method_doc = &itype.class_doc->methods[i];4194break;4195}4196}4197}41984199if (imethod.method_doc) {4200imethod.is_deprecated = imethod.method_doc->is_deprecated;4201imethod.deprecation_message = imethod.method_doc->deprecated_message;42024203if (imethod.is_deprecated && imethod.deprecation_message.is_empty()) {4204WARN_PRINT("An empty deprecation message is discouraged. Method: '" + itype.proxy_name + "." + imethod.proxy_name + "'.");4205imethod.deprecation_message = "This method is deprecated.";4206}4207}42084209ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,4210"Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");42114212// Compat methods aren't added to the type yet, they need to be checked for conflicts4213// after all the non-compat methods have been added. The compat methods are added in4214// reverse so the most recently added ones take precedence over older compat methods.4215if (imethod.is_compat) {4216// If the method references deprecated types, mark the method as deprecated as well.4217for (const ArgumentInterface &iarg : imethod.arguments) {4218String arg_type_name = iarg.type.cname;4219String doc_name = arg_type_name.begins_with("_") ? arg_type_name.substr(1) : arg_type_name;4220const DocData::ClassDoc &class_doc = EditorHelp::get_doc_data()->class_list[doc_name];4221if (class_doc.is_deprecated) {4222imethod.is_deprecated = true;4223imethod.deprecation_message = "This method overload is deprecated.";4224break;4225}4226}42274228imethod.is_hidden = true;4229compat_methods.push_front(imethod);4230continue;4231}42324233// Methods starting with an underscore are ignored unless they're used as a property setter or getter4234if (!imethod.is_virtual && imethod.name[0] == '_') {4235for (const PropertyInterface &iprop : itype.properties) {4236if (iprop.setter == imethod.name || iprop.getter == imethod.name) {4237imethod.is_internal = true;4238itype.methods.push_back(imethod);4239break;4240}4241}4242} else {4243itype.methods.push_back(imethod);4244}4245}42464247// Add compat methods that don't conflict with other methods in the type.4248for (const MethodInterface &imethod : compat_methods) {4249if (_method_has_conflicting_signature(imethod, itype)) {4250WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored.");4251continue;4252}4253itype.methods.push_back(imethod);4254}42554256// Populate signals42574258const AHashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;42594260for (const KeyValue<StringName, MethodInfo> &E : signal_map) {4261SignalInterface isignal;42624263const MethodInfo &method_info = E.value;42644265if (method_info.name.begins_with("_")) {4266// Signals starting with an underscore are internal and not meant to be exposed.4267continue;4268}42694270isignal.name = method_info.name;4271isignal.cname = method_info.name;42724273for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {4274const PropertyInfo &arginfo = method_info.arguments[idx];42754276String orig_arg_name = arginfo.name;42774278ArgumentInterface iarg;4279iarg.name = orig_arg_name;42804281if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {4282iarg.type.cname = arginfo.class_name;4283iarg.type.is_enum = true;4284} else if (arginfo.class_name != StringName()) {4285iarg.type.cname = arginfo.class_name;4286} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {4287iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4288iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));4289} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {4290iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";4291Vector<String> split = arginfo.hint_string.split(";");4292iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));4293iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));4294} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {4295iarg.type.cname = arginfo.hint_string;4296} else if (arginfo.type == Variant::NIL) {4297iarg.type.cname = name_cache.type_Variant;4298} else {4299iarg.type.cname = _get_type_name_from_meta(arginfo.type, (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));4300}43014302iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));43034304isignal.add_argument(iarg);4305}43064307isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name));43084309// Prevent the signal and its enclosing type from sharing the same name4310if (isignal.proxy_name == itype.proxy_name) {4311_log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n",4312isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data());43134314isignal.proxy_name += "_";4315}43164317if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) {4318// ClassDB allows signal names that conflict with method or property names.4319// While registering a signal with a conflicting name is considered wrong,4320// it may still happen and it may take some time until someone fixes the name.4321// We can't allow the bindings to be in a broken state while we wait for a fix;4322// that's why we must handle this possibility by renaming the signal.4323isignal.proxy_name += "Signal";4324}43254326if (itype.class_doc) {4327for (int i = 0; i < itype.class_doc->signals.size(); i++) {4328const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i];4329if (signal_doc.name == isignal.name) {4330isignal.method_doc = &signal_doc;4331break;4332}4333}4334}43354336if (isignal.method_doc) {4337isignal.is_deprecated = isignal.method_doc->is_deprecated;4338isignal.deprecation_message = isignal.method_doc->deprecated_message;43394340if (isignal.is_deprecated && isignal.deprecation_message.is_empty()) {4341WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + itype.proxy_name + "." + isignal.proxy_name + "'.");4342isignal.deprecation_message = "This signal is deprecated.";4343}4344}43454346itype.signals_.push_back(isignal);4347}43484349// Populate enums and constants43504351List<String> constants;4352ClassDB::get_integer_constant_list(type_cname, &constants, true);43534354const HashMap<StringName, ClassDB::ClassInfo::EnumInfo> &enum_map = class_info->enum_map;43554356for (const KeyValue<StringName, ClassDB::ClassInfo::EnumInfo> &E : enum_map) {4357StringName enum_proxy_cname = E.key;4358String enum_proxy_name = pascal_to_pascal_case(enum_proxy_cname.operator String());4359if (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)) {4360// In case the enum name conflicts with other PascalCase members,4361// we append 'Enum' to the enum name in those cases.4362// We have several conflicts between enums and PascalCase properties.4363enum_proxy_name += "Enum";4364enum_proxy_cname = StringName(enum_proxy_name);4365}4366EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);4367const List<StringName> &enum_constants = E.value.constants;4368for (const StringName &constant_cname : enum_constants) {4369String constant_name = constant_cname.operator String();4370int64_t *value = class_info->constant_map.getptr(constant_cname);4371ERR_FAIL_NULL_V(value, false);4372constants.erase(constant_name);43734374ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);43754376iconstant.const_doc = nullptr;4377for (int i = 0; i < itype.class_doc->constants.size(); i++) {4378const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];43794380if (const_doc.name == iconstant.name) {4381iconstant.const_doc = &const_doc;4382break;4383}4384}43854386if (iconstant.const_doc) {4387iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4388iconstant.deprecation_message = iconstant.const_doc->deprecated_message;43894390if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4391WARN_PRINT("An empty deprecation message is discouraged. Enum member: '" + itype.proxy_name + "." + ienum.proxy_name + "." + iconstant.proxy_name + "'.");4392iconstant.deprecation_message = "This enum member is deprecated.";4393}4394}43954396ienum.constants.push_back(iconstant);4397}43984399int prefix_length = _determine_enum_prefix(ienum);44004401_apply_prefix_to_enum_constants(ienum, prefix_length);44024403itype.enums.push_back(ienum);44044405TypeInterface enum_itype;4406enum_itype.is_enum = true;4407enum_itype.name = itype.name + "." + String(E.key);4408enum_itype.cname = StringName(enum_itype.name);4409enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name;4410TypeInterface::postsetup_enum_type(enum_itype);4411enum_types.insert(enum_itype.cname, enum_itype);4412}44134414for (const String &constant_name : constants) {4415int64_t *value = class_info->constant_map.getptr(StringName(constant_name));4416ERR_FAIL_NULL_V(value, false);44174418String constant_proxy_name = snake_to_pascal_case(constant_name, true);44194420if (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)) {4421// In case the constant name conflicts with other PascalCase members,4422// we append 'Constant' to the constant name in those cases.4423constant_proxy_name += "Constant";4424}44254426ConstantInterface iconstant(constant_name, constant_proxy_name, *value);44274428iconstant.const_doc = nullptr;4429for (int i = 0; i < itype.class_doc->constants.size(); i++) {4430const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];44314432if (const_doc.name == iconstant.name) {4433iconstant.const_doc = &const_doc;4434break;4435}4436}44374438if (iconstant.const_doc) {4439iconstant.is_deprecated = iconstant.const_doc->is_deprecated;4440iconstant.deprecation_message = iconstant.const_doc->deprecated_message;44414442if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {4443WARN_PRINT("An empty deprecation message is discouraged. Constant: '" + itype.proxy_name + "." + iconstant.proxy_name + "'.");4444iconstant.deprecation_message = "This constant is deprecated.";4445}4446}44474448itype.constants.push_back(iconstant);4449}44504451obj_types.insert(itype.cname, itype);44524453if (itype.is_singleton) {4454// Add singleton instance type.4455itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;4456itype.is_singleton = false;4457itype.is_singleton_instance = true;44584459// Remove constants and enums, those will remain in the static class.4460itype.constants.clear();4461itype.enums.clear();44624463obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);4464}4465}44664467return true;4468}44694470static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {4471return String::num_real(p_vec2.x, true) + "f, " +4472String::num_real(p_vec2.y, true) + "f";4473}44744475static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {4476return String::num_real(p_vec3.x, true) + "f, " +4477String::num_real(p_vec3.y, true) + "f, " +4478String::num_real(p_vec3.z, true) + "f";4479}44804481static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {4482return String::num_real(p_vec4.x, true) + "f, " +4483String::num_real(p_vec4.y, true) + "f, " +4484String::num_real(p_vec4.z, true) + "f, " +4485String::num_real(p_vec4.w, true) + "f";4486}44874488static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {4489return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);4490}44914492static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {4493return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);4494}44954496static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {4497return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);4498}44994500static String _get_color_cs_ctor_args(const Color &p_color) {4501return String::num(p_color.r, 4) + "f, " +4502String::num(p_color.g, 4) + "f, " +4503String::num(p_color.b, 4) + "f, " +4504String::num(p_color.a, 4) + "f";4505}45064507bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {4508r_iarg.def_param_value = p_val;45094510switch (p_val.get_type()) {4511case Variant::NIL:4512// Either Object type or Variant4513r_iarg.default_argument = "default";4514break;4515// Atomic types4516case Variant::BOOL:4517r_iarg.default_argument = bool(p_val) ? "true" : "false";4518break;4519case Variant::INT:4520if (r_iarg.type.cname != name_cache.type_int) {4521r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";4522} else {4523r_iarg.default_argument = p_val.operator String();4524}4525break;4526case Variant::FLOAT:4527r_iarg.default_argument = p_val.operator String();45284529if (r_iarg.type.cname == name_cache.type_float) {4530r_iarg.default_argument += "f";4531}4532break;4533case Variant::STRING:4534case Variant::STRING_NAME:4535case Variant::NODE_PATH:4536if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {4537if (r_iarg.default_argument.length() > 0) {4538r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";4539r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4540} else {4541// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.4542r_iarg.default_argument = "null";4543}4544} else {4545CRASH_COND(r_iarg.type.cname != name_cache.type_String);4546r_iarg.default_argument = "\"" + p_val.operator String() + "\"";4547}4548break;4549case Variant::PLANE: {4550Plane plane = p_val.operator Plane();4551r_iarg.default_argument = "new Plane(new Vector3(" +4552_get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";4553r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4554} break;4555case Variant::AABB: {4556AABB aabb = p_val.operator ::AABB();4557r_iarg.default_argument = "new Aabb(new Vector3(" +4558_get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +4559_get_vector3_cs_ctor_args(aabb.size) + "))";4560r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4561} break;4562case Variant::RECT2: {4563Rect2 rect = p_val.operator Rect2();4564r_iarg.default_argument = "new Rect2(new Vector2(" +4565_get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +4566_get_vector2_cs_ctor_args(rect.size) + "))";4567r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4568} break;4569case Variant::RECT2I: {4570Rect2i rect = p_val.operator Rect2i();4571r_iarg.default_argument = "new Rect2I(new Vector2I(" +4572_get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +4573_get_vector2i_cs_ctor_args(rect.size) + "))";4574r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4575} break;4576case Variant::COLOR:4577r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";4578r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4579break;4580case Variant::VECTOR2:4581r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";4582r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4583break;4584case Variant::VECTOR2I:4585r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";4586r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4587break;4588case Variant::VECTOR3:4589r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";4590r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4591break;4592case Variant::VECTOR3I:4593r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";4594r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4595break;4596case Variant::VECTOR4:4597r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";4598r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4599break;4600case Variant::VECTOR4I:4601r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";4602r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4603break;4604case Variant::OBJECT:4605ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4606"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");46074608r_iarg.default_argument = "null";4609break;4610case Variant::DICTIONARY:4611ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false,4612"Default value of type 'Dictionary' must be an empty dictionary.");4613// The [cs_in] expression already interprets null values as empty dictionaries.4614r_iarg.default_argument = "null";4615r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4616break;4617case Variant::RID:4618ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,4619"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");46204621ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4622"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");46234624r_iarg.default_argument = "default";4625break;4626case Variant::ARRAY:4627ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false,4628"Default value of type 'Array' must be an empty array.");4629// The [cs_in] expression already interprets null values as empty arrays.4630r_iarg.default_argument = "null";4631r_iarg.def_param_mode = ArgumentInterface::CONSTANT;4632break;4633case Variant::PACKED_BYTE_ARRAY:4634case Variant::PACKED_INT32_ARRAY:4635case Variant::PACKED_INT64_ARRAY:4636case Variant::PACKED_FLOAT32_ARRAY:4637case Variant::PACKED_FLOAT64_ARRAY:4638case Variant::PACKED_STRING_ARRAY:4639case Variant::PACKED_VECTOR2_ARRAY:4640case Variant::PACKED_VECTOR3_ARRAY:4641case Variant::PACKED_VECTOR4_ARRAY:4642case Variant::PACKED_COLOR_ARRAY:4643r_iarg.default_argument = "Array.Empty<%s>()";4644r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;4645break;4646case Variant::TRANSFORM2D: {4647Transform2D transform = p_val.operator Transform2D();4648if (transform == Transform2D()) {4649r_iarg.default_argument = "Transform2D.Identity";4650} else {4651r_iarg.default_argument = "new Transform2D(new Vector2(" +4652_get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +4653_get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +4654_get_vector2_cs_ctor_args(transform.columns[2]) + "))";4655}4656r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4657} break;4658case Variant::TRANSFORM3D: {4659Transform3D transform = p_val.operator Transform3D();4660if (transform == Transform3D()) {4661r_iarg.default_argument = "Transform3D.Identity";4662} else {4663Basis basis = transform.basis;4664r_iarg.default_argument = "new Transform3D(new Vector3(" +4665_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4666_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4667_get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +4668_get_vector3_cs_ctor_args(transform.origin) + "))";4669}4670r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4671} break;4672case Variant::PROJECTION: {4673Projection projection = p_val.operator Projection();4674if (projection == Projection()) {4675r_iarg.default_argument = "Projection.Identity";4676} else {4677r_iarg.default_argument = "new Projection(new Vector4(" +4678_get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +4679_get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +4680_get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +4681_get_vector4_cs_ctor_args(projection.columns[3]) + "))";4682}4683r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4684} break;4685case Variant::BASIS: {4686Basis basis = p_val.operator Basis();4687if (basis == Basis()) {4688r_iarg.default_argument = "Basis.Identity";4689} else {4690r_iarg.default_argument = "new Basis(new Vector3(" +4691_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +4692_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +4693_get_vector3_cs_ctor_args(basis.get_column(2)) + "))";4694}4695r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4696} break;4697case Variant::QUATERNION: {4698Quaternion quaternion = p_val.operator Quaternion();4699if (quaternion == Quaternion()) {4700r_iarg.default_argument = "Quaternion.Identity";4701} else {4702r_iarg.default_argument = "new Quaternion(" +4703String::num_real(quaternion.x, false) + "f, " +4704String::num_real(quaternion.y, false) + "f, " +4705String::num_real(quaternion.z, false) + "f, " +4706String::num_real(quaternion.w, false) + "f)";4707}4708r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4709} break;4710case Variant::CALLABLE:4711ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Callable, false,4712"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Callable) + "'.");4713ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4714"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4715r_iarg.default_argument = "default";4716break;4717case Variant::SIGNAL:4718ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Signal, false,4719"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Signal) + "'.");4720ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,4721"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");4722r_iarg.default_argument = "default";4723break;4724case Variant::VARIANT_MAX:4725ERR_FAIL_V_MSG(false, "Unexpected Variant type: " + itos(p_val.get_type()));4726break;4727}47284729if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") {4730r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;4731}47324733return true;4734}47354736void BindingsGenerator::_populate_builtin_type_interfaces() {4737builtin_types.clear();47384739TypeInterface itype;47404741#define INSERT_STRUCT_TYPE(m_type, m_proxy_name) \4742{ \4743itype = TypeInterface::create_value_type(String(#m_type), String(#m_proxy_name)); \4744itype.cs_in_expr = "&%0"; \4745itype.cs_in_expr_is_unsafe = true; \4746builtin_types.insert(itype.cname, itype); \4747}47484749INSERT_STRUCT_TYPE(Vector2, Vector2)4750INSERT_STRUCT_TYPE(Vector2i, Vector2I)4751INSERT_STRUCT_TYPE(Rect2, Rect2)4752INSERT_STRUCT_TYPE(Rect2i, Rect2I)4753INSERT_STRUCT_TYPE(Transform2D, Transform2D)4754INSERT_STRUCT_TYPE(Vector3, Vector3)4755INSERT_STRUCT_TYPE(Vector3i, Vector3I)4756INSERT_STRUCT_TYPE(Basis, Basis)4757INSERT_STRUCT_TYPE(Quaternion, Quaternion)4758INSERT_STRUCT_TYPE(Transform3D, Transform3D)4759INSERT_STRUCT_TYPE(AABB, Aabb)4760INSERT_STRUCT_TYPE(Color, Color)4761INSERT_STRUCT_TYPE(Plane, Plane)4762INSERT_STRUCT_TYPE(Vector4, Vector4)4763INSERT_STRUCT_TYPE(Vector4i, Vector4I)4764INSERT_STRUCT_TYPE(Projection, Projection)47654766#undef INSERT_STRUCT_TYPE47674768// bool4769itype = TypeInterface::create_value_type(String("bool"));4770itype.cs_in_expr = "%0.ToGodotBool()";4771itype.cs_out = "%5return %0(%1).ToBool();";4772itype.c_type = "godot_bool";4773itype.c_type_in = itype.c_type;4774itype.c_type_out = itype.c_type;4775itype.c_arg_in = "&%s";4776itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n";4777builtin_types.insert(itype.cname, itype);47784779// Integer types4780{4781// C interface for 'uint32_t' is the same as that of enums. Remember to apply4782// any of the changes done here to 'TypeInterface::postsetup_enum_type' as well.4783#define INSERT_INT_TYPE(m_name, m_int_struct_name) \4784{ \4785itype = TypeInterface::create_value_type(String(m_name)); \4786if (itype.name != "long" && itype.name != "ulong") { \4787itype.c_in = "%5%0 %1_in = %1;\n"; \4788itype.c_out = "%5return (%0)(%1);\n"; \4789itype.c_type = "long"; \4790itype.c_arg_in = "&%s_in"; \4791} else { \4792itype.c_arg_in = "&%s"; \4793} \4794itype.c_type_in = itype.name; \4795itype.c_type_out = itype.name; \4796itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \4797builtin_types.insert(itype.cname, itype); \4798}47994800// The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type'48014802INSERT_INT_TYPE("sbyte", "Int8");4803INSERT_INT_TYPE("short", "Int16");4804INSERT_INT_TYPE("int", "Int32");4805INSERT_INT_TYPE("long", "Int64");4806INSERT_INT_TYPE("byte", "UInt8");4807INSERT_INT_TYPE("ushort", "UInt16");4808INSERT_INT_TYPE("uint", "UInt32");4809INSERT_INT_TYPE("ulong", "UInt64");48104811#undef INSERT_INT_TYPE4812}48134814// Floating point types4815{4816// float4817itype = TypeInterface();4818itype.name = "float";4819itype.cname = itype.name;4820itype.proxy_name = "float";4821itype.cs_type = itype.proxy_name;4822{4823// The expected type for 'float' in ptrcall is 'double'4824itype.c_in = "%5%0 %1_in = %1;\n";4825itype.c_out = "%5return (%0)%1;\n";4826itype.c_type = "double";4827itype.c_arg_in = "&%s_in";4828}4829itype.c_type_in = itype.proxy_name;4830itype.c_type_out = itype.proxy_name;4831itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4832builtin_types.insert(itype.cname, itype);48334834// double4835itype = TypeInterface();4836itype.name = "double";4837itype.cname = itype.name;4838itype.proxy_name = "double";4839itype.cs_type = itype.proxy_name;4840itype.c_type = "double";4841itype.c_arg_in = "&%s";4842itype.c_type_in = itype.proxy_name;4843itype.c_type_out = itype.proxy_name;4844itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";4845builtin_types.insert(itype.cname, itype);4846}48474848// String4849itype = TypeInterface();4850itype.name = "String";4851itype.cname = itype.name;4852itype.proxy_name = "string";4853itype.cs_type = itype.proxy_name;4854itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n";4855itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n";4856itype.c_arg_in = "&%s_in";4857itype.c_type = "godot_string";4858itype.c_type_in = itype.cs_type;4859itype.c_type_out = itype.cs_type;4860itype.c_type_is_disposable_struct = true;4861itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n";4862builtin_types.insert(itype.cname, itype);48634864// StringName4865itype = TypeInterface();4866itype.name = "StringName";4867itype.cname = itype.name;4868itype.proxy_name = "StringName";4869itype.cs_type = itype.proxy_name;4870itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4871// Cannot pass null StringName to ptrcall4872itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4873itype.c_arg_in = "&%s";4874itype.c_type = "godot_string_name";4875itype.c_type_in = itype.c_type;4876itype.c_type_out = itype.cs_type;4877itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n";4878itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4879itype.c_ret_needs_default_initialization = true;4880builtin_types.insert(itype.cname, itype);48814882// NodePath4883itype = TypeInterface();4884itype.name = "NodePath";4885itype.cname = itype.name;4886itype.proxy_name = "NodePath";4887itype.cs_type = itype.proxy_name;4888itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";4889// Cannot pass null NodePath to ptrcall4890itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";4891itype.c_arg_in = "&%s";4892itype.c_type = "godot_node_path";4893itype.c_type_in = itype.c_type;4894itype.c_type_out = itype.cs_type;4895itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4896itype.c_ret_needs_default_initialization = true;4897builtin_types.insert(itype.cname, itype);48984899// RID4900itype = TypeInterface();4901itype.name = "RID";4902itype.cname = itype.name;4903itype.proxy_name = "Rid";4904itype.cs_type = itype.proxy_name;4905itype.c_arg_in = "&%s";4906itype.c_type = itype.cs_type;4907itype.c_type_in = itype.c_type;4908itype.c_type_out = itype.c_type;4909builtin_types.insert(itype.cname, itype);49104911// Variant4912itype = TypeInterface();4913itype.name = "Variant";4914itype.cname = itype.name;4915itype.proxy_name = "Variant";4916itype.cs_type = itype.proxy_name;4917itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n";4918itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n";4919itype.c_arg_in = "&%s_in";4920itype.c_type = "godot_variant";4921itype.c_type_in = itype.cs_type;4922itype.c_type_out = itype.cs_type;4923itype.c_type_is_disposable_struct = false; // [c_out] takes ownership4924itype.c_ret_needs_default_initialization = true;4925builtin_types.insert(itype.cname, itype);49264927// Callable4928itype = TypeInterface::create_value_type(String("Callable"));4929itype.cs_in_expr = "%0";4930itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n";4931itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %1);\n";4932itype.c_arg_in = "&%s_in";4933itype.c_type = "godot_callable";4934itype.c_type_in = "in " + itype.cs_type;4935itype.c_type_out = itype.cs_type;4936itype.c_type_is_disposable_struct = true;4937builtin_types.insert(itype.cname, itype);49384939// Signal4940itype = TypeInterface();4941itype.name = "Signal";4942itype.cname = itype.name;4943itype.proxy_name = "Signal";4944itype.cs_type = itype.proxy_name;4945itype.cs_in_expr = "%0";4946itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n";4947itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(in %1);\n";4948itype.c_arg_in = "&%s_in";4949itype.c_type = "godot_signal";4950itype.c_type_in = "in " + itype.cs_type;4951itype.c_type_out = itype.cs_type;4952itype.c_type_is_disposable_struct = true;4953builtin_types.insert(itype.cname, itype);49544955// VarArg (fictitious type to represent variable arguments)4956itype = TypeInterface();4957itype.name = "VarArg";4958itype.cname = itype.name;4959itype.proxy_name = "ReadOnlySpan<Variant>";4960itype.cs_type = "params Variant[]";4961itype.cs_in_expr = "%0";4962// c_type, c_in and c_arg_in are hard-coded in the generator.4963// c_out and c_type_out are not applicable to VarArg.4964itype.c_arg_in = "&%s_in";4965itype.c_type_in = "ReadOnlySpan<Variant>";4966itype.is_span_compatible = true;4967builtin_types.insert(itype.cname, itype);49684969#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \4970{ \4971itype = TypeInterface(); \4972itype.name = #m_name; \4973itype.cname = itype.name; \4974itype.proxy_name = #m_proxy_t "[]"; \4975itype.cs_type = itype.proxy_name; \4976itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \4977itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \4978itype.c_arg_in = "&%s_in"; \4979itype.c_type = #m_managed_type; \4980itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \4981itype.c_type_out = itype.proxy_name; \4982itype.c_type_is_disposable_struct = true; \4983itype.is_span_compatible = true; \4984builtin_types.insert(itype.name, itype); \4985}49864987#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t)49884989INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int);4990INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long);4991INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte);49924993INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float);4994INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double);49954996INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string);49974998INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color);4999INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2);5000INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3);5001INSERT_ARRAY(PackedVector4Array, godot_packed_vector4_array, Vector4);50025003#undef INSERT_ARRAY50045005// Array5006itype = TypeInterface();5007itype.name = "Array";5008itype.cname = itype.name;5009itype.proxy_name = itype.name;5010itype.type_parameter_count = 1;5011itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;5012itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";5013itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";5014itype.c_arg_in = "&%s";5015itype.c_type = "godot_array";5016itype.c_type_in = itype.c_type;5017itype.c_type_out = itype.cs_type;5018itype.c_type_is_disposable_struct = false; // [c_out] takes ownership5019itype.c_ret_needs_default_initialization = true;5020builtin_types.insert(itype.cname, itype);50215022// Array_@generic5023// Reuse Array's itype5024itype.name = "Array_@generic";5025itype.cname = itype.name;5026itype.cs_out = "%5return new %2(%0(%1));";5027// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5028itype.cs_variant_to_managed = "VariantUtils.ConvertToArray(%0)";5029itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)";5030builtin_types.insert(itype.cname, itype);50315032// Dictionary5033itype = TypeInterface();5034itype.name = "Dictionary";5035itype.cname = itype.name;5036itype.proxy_name = itype.name;5037itype.type_parameter_count = 2;5038itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;5039itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";5040itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";5041itype.c_arg_in = "&%s";5042itype.c_type = "godot_dictionary";5043itype.c_type_in = itype.c_type;5044itype.c_type_out = itype.cs_type;5045itype.c_type_is_disposable_struct = false; // [c_out] takes ownership5046itype.c_ret_needs_default_initialization = true;5047builtin_types.insert(itype.cname, itype);50485049// Dictionary_@generic5050// Reuse Dictionary's itype5051itype.name = "Dictionary_@generic";5052itype.cname = itype.name;5053itype.cs_out = "%5return new %2(%0(%1));";5054// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case5055itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionary(%0)";5056itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)";5057builtin_types.insert(itype.cname, itype);50585059// void (fictitious type to represent the return type of methods that do not return anything)5060itype = TypeInterface();5061itype.name = "void";5062itype.cname = itype.name;5063itype.proxy_name = itype.name;5064itype.cs_type = itype.proxy_name;5065itype.c_type = itype.proxy_name;5066itype.c_type_in = itype.c_type;5067itype.c_type_out = itype.c_type;5068builtin_types.insert(itype.cname, itype);5069}50705071void BindingsGenerator::_populate_global_constants() {5072int global_constants_count = CoreConstants::get_global_constant_count();50735074if (global_constants_count > 0) {5075HashMap<String, DocData::ClassDoc>::Iterator match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");50765077CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData.");50785079const DocData::ClassDoc &global_scope_doc = match->value;50805081for (int i = 0; i < global_constants_count; i++) {5082String constant_name = CoreConstants::get_global_constant_name(i);50835084const DocData::ConstantDoc *const_doc = nullptr;5085for (int j = 0; j < global_scope_doc.constants.size(); j++) {5086const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];50875088if (curr_const_doc.name == constant_name) {5089const_doc = &curr_const_doc;5090break;5091}5092}50935094int64_t constant_value = CoreConstants::get_global_constant_value(i);5095StringName enum_name = CoreConstants::get_global_constant_enum(i);50965097ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);5098iconstant.const_doc = const_doc;50995100if (enum_name != StringName()) {5101EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));5102List<EnumInterface>::Element *enum_match = global_enums.find(ienum);5103if (enum_match) {5104enum_match->get().constants.push_back(iconstant);5105} else {5106ienum.constants.push_back(iconstant);5107global_enums.push_back(ienum);5108}5109} else {5110global_constants.push_back(iconstant);5111}5112}51135114for (EnumInterface &ienum : global_enums) {5115TypeInterface enum_itype;5116enum_itype.is_enum = true;5117enum_itype.name = ienum.cname.operator String();5118enum_itype.cname = ienum.cname;5119enum_itype.proxy_name = ienum.proxy_name;5120TypeInterface::postsetup_enum_type(enum_itype);5121enum_types.insert(enum_itype.cname, enum_itype);51225123int prefix_length = _determine_enum_prefix(ienum);51245125// HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.5126if (ienum.cname == name_cache.enum_Error) {5127if (prefix_length > 0) { // Just in case it ever changes5128ERR_PRINT("Prefix for enum '" _STR(Error) "' is not empty.");5129}51305131prefix_length = 1; // 'ERR_'5132}51335134_apply_prefix_to_enum_constants(ienum, prefix_length);5135}5136}51375138for (int i = 0; i < Variant::VARIANT_MAX; i++) {5139if (i == Variant::OBJECT) {5140continue;5141}51425143const Variant::Type type = Variant::Type(i);51445145List<StringName> enum_names;5146Variant::get_enums_for_type(type, &enum_names);51475148for (const StringName &enum_name : enum_names) {5149TypeInterface enum_itype;5150enum_itype.is_enum = true;5151enum_itype.name = Variant::get_type_name(type) + "." + enum_name;5152enum_itype.cname = enum_itype.name;5153enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);5154TypeInterface::postsetup_enum_type(enum_itype);5155enum_types.insert(enum_itype.cname, enum_itype);5156}5157}5158}51595160bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) {5161// Compare p_imethod with all the methods already registered in p_itype.5162for (const MethodInterface &method : p_itype.methods) {5163if (method.proxy_name == p_imethod.proxy_name) {5164if (_method_has_conflicting_signature(p_imethod, method)) {5165return true;5166}5167}5168}51695170return false;5171}51725173bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) {5174// Check if a method already exists in p_itype with a method signature that would conflict with p_imethod.5175// The return type is ignored because only changing the return type is not enough to avoid conflicts.5176// The const keyword is also ignored since it doesn't generate different C# code.51775178if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) {5179// Different argument count, so no conflict.5180return false;5181}51825183List<BindingsGenerator::ArgumentInterface>::ConstIterator left_itr = p_imethod_left.arguments.begin();5184List<BindingsGenerator::ArgumentInterface>::ConstIterator right_itr = p_imethod_right.arguments.begin();5185for (; left_itr != p_imethod_left.arguments.end(); ++left_itr, ++right_itr) {5186const ArgumentInterface &iarg_left = *left_itr;5187const ArgumentInterface &iarg_right = *right_itr;51885189if (iarg_left.type.cname != iarg_right.type.cname) {5190// Different types for arguments in the same position, so no conflict.5191return false;5192}51935194if (iarg_left.def_param_mode != iarg_right.def_param_mode) {5195// If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T'5196// and will not create a conflict.5197if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) {5198return false;5199}5200}5201}52025203return true;5204}52055206void BindingsGenerator::_initialize_blacklisted_methods() {5207blacklisted_methods["Object"].push_back("to_string"); // there is already ToString5208blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead5209blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)5210}52115212void BindingsGenerator::_initialize_compat_singletons() {5213compat_singletons.insert("EditorInterface");5214}52155216void BindingsGenerator::_log(const char *p_format, ...) {5217if (log_print_enabled) {5218va_list list;52195220va_start(list, p_format);5221OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data());5222va_end(list);5223}5224}52255226void BindingsGenerator::_initialize() {5227initialized = false;52285229EditorHelp::generate_doc(false);52305231enum_types.clear();52325233_initialize_blacklisted_methods();52345235_initialize_compat_singletons();52365237bool obj_type_ok = _populate_object_type_interfaces();5238ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");52395240_populate_builtin_type_interfaces();52415242_populate_global_constants();52435244// Generate internal calls (after populating type interfaces and global constants)52455246for (const KeyValue<StringName, TypeInterface> &E : obj_types) {5247const TypeInterface &itype = E.value;5248Error err = _populate_method_icalls_table(itype);5249ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name);5250}52515252initialized = true;5253}52545255static String generate_all_glue_option = "--generate-mono-glue";52565257static void handle_cmdline_options(String glue_dir_path) {5258BindingsGenerator bindings_generator;5259bindings_generator.set_log_print_enabled(true);52605261if (!bindings_generator.is_initialized()) {5262ERR_PRINT("Failed to initialize the bindings generator");5263return;5264}52655266CRASH_COND(glue_dir_path.is_empty());52675268if (bindings_generator.generate_cs_api(glue_dir_path.path_join(API_SOLUTION_NAME)) != OK) {5269ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API.");5270}5271}52725273static void cleanup_and_exit_godot() {5274// Exit once done.5275Main::cleanup(true);5276::exit(0);5277}52785279void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {5280String glue_dir_path;52815282const List<String>::Element *elem = p_cmdline_args.front();52835284while (elem) {5285if (elem->get() == generate_all_glue_option) {5286const List<String>::Element *path_elem = elem->next();52875288if (path_elem) {5289glue_dir_path = path_elem->get();5290elem = elem->next();5291} else {5292ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");5293// Exit once done with invalid command line arguments.5294cleanup_and_exit_godot();5295}52965297break;5298}52995300elem = elem->next();5301}53025303if (glue_dir_path.length()) {5304if (Engine::get_singleton()->is_editor_hint() ||5305Engine::get_singleton()->is_project_manager_hint()) {5306handle_cmdline_options(glue_dir_path);5307} else {5308// Running from a project folder, which doesn't make sense and crashes.5309ERR_PRINT(generate_all_glue_option + ": Cannot generate Mono glue while running a game project. Change current directory or enable --editor.");5310}5311// Exit once done.5312cleanup_and_exit_godot();5313}5314}53155316#endif // DEBUG_ENABLED531753185319