summaryrefslogtreecommitdiffstats
path: root/modules/mono/editor
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono/editor')
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs133
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs133
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs106
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs148
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs134
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs46
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs176
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs (renamed from modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs)195
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs19
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs527
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs293
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs23
-rw-r--r--modules/mono/editor/bindings_generator.cpp7
19 files changed, 1711 insertions, 246 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
new file mode 100644
index 0000000000..7b106ef63c
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+#pragma warning disable CS0169
+#pragma warning disable CS0414
+
+namespace Godot.SourceGenerators.Sample
+{
+ [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")]
+ [SuppressMessage("ReSharper", "RedundantNameQualifier")]
+ [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public partial class ExportedFields : Godot.Object
+ {
+ [Export] private Boolean field_Boolean = true;
+ [Export] private Char field_Char = 'f';
+ [Export] private SByte field_SByte = 10;
+ [Export] private Int16 field_Int16 = 10;
+ [Export] private Int32 field_Int32 = 10;
+ [Export] private Int64 field_Int64 = 10;
+ [Export] private Byte field_Byte = 10;
+ [Export] private UInt16 field_UInt16 = 10;
+ [Export] private UInt32 field_UInt32 = 10;
+ [Export] private UInt64 field_UInt64 = 10;
+ [Export] private Single field_Single = 10;
+ [Export] private Double field_Double = 10;
+ [Export] private String field_String = "foo";
+
+ // Godot structs
+ [Export] private Vector2 field_Vector2 = new(10f, 10f);
+ [Export] private Vector2i field_Vector2i = Vector2i.Up;
+ [Export] private Rect2 field_Rect2 = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
+ [Export] private Rect2i field_Rect2i = new(new Vector2i(10, 10), new Vector2i(10, 10));
+ [Export] private Transform2D field_Transform2D = Transform2D.Identity;
+ [Export] private Vector3 field_Vector3 = new(10f, 10f, 10f);
+ [Export] private Vector3i field_Vector3i = Vector3i.Back;
+ [Export] private Basis field_Basis = new Basis(Quaternion.Identity);
+ [Export] private Quaternion field_Quaternion = new Quaternion(Basis.Identity);
+ [Export] private Transform3D field_Transform3D = Transform3D.Identity;
+ [Export] private Vector4 field_Vector4 = new(10f, 10f, 10f, 10f);
+ [Export] private Vector4i field_Vector4i = Vector4i.One;
+ [Export] private Projection field_Projection = Projection.Identity;
+ [Export] private AABB field_AABB = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
+ [Export] private Color field_Color = Colors.Aquamarine;
+ [Export] private Plane field_Plane = Plane.PlaneXZ;
+ [Export] private Callable field_Callable = new Callable(Engine.GetMainLoop(), "_process");
+ [Export] private SignalInfo field_SignalInfo = new SignalInfo(Engine.GetMainLoop(), "property_list_changed");
+
+ // Enums
+ [SuppressMessage("ReSharper", "UnusedMember.Local")]
+ enum MyEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyEnum field_Enum = MyEnum.C;
+
+ [Flags]
+ [SuppressMessage("ReSharper", "UnusedMember.Local")]
+ enum MyFlagsEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyFlagsEnum field_FlagsEnum = MyFlagsEnum.C;
+
+ // Arrays
+ [Export] private Byte[] field_ByteArray = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int32[] field_Int32Array = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int64[] field_Int64Array = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Single[] field_SingleArray = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ [Export] private Double[] field_DoubleArray = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ [Export] private String[] field_StringArray = { "foo", "bar" };
+ [Export(PropertyHint.Enum, "A,B,C")] private String[] field_StringArrayEnum = { "foo", "bar" };
+ [Export] private Vector2[] field_Vector2Array = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
+ [Export] private Vector3[] field_Vector3Array = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
+ [Export] private Color[] field_ColorArray = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
+ [Export] private Godot.Object[] field_GodotObjectOrDerivedArray = { null };
+ [Export] private object[] field_SystemObjectArray = { 0, 1f, 2d, "foo", Vector3i.Up };
+
+ // Generics
+ [Export] private Godot.Collections.Dictionary<string, string> field_GodotGenericDictionary =
+ new Godot.Collections.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private Godot.Collections.Array<string> field_GodotGenericArray =
+ new Godot.Collections.Array<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.Dictionary<string, string> field_SystemGenericDictionary =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.List<string> field_SystemGenericList =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IDictionary<string, string> field_GenericIDictionary =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.ICollection<string> field_GenericICollection =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IEnumerable<string> field_GenericIEnumerable =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ // Variant
+ [Export] private object field_SystemObject = "foo";
+
+ // Classes
+ [Export] private Godot.Object field_GodotObjectOrDerived;
+ [Export] private Godot.Texture field_GodotResourceTexture;
+ [Export] private StringName field_StringName = new StringName("foo");
+ [Export] private NodePath field_NodePath = new NodePath("foo");
+ [Export] private RID field_RID;
+
+ [Export] private Godot.Collections.Dictionary field_GodotDictionary =
+ new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private Godot.Collections.Array field_GodotArray =
+ new() { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IDictionary field_IDictionary =
+ new System.Collections.Generic.Dictionary<object, object>
+ { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private System.Collections.ICollection field_ICollection =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IEnumerable field_IEnumerable =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
new file mode 100644
index 0000000000..71025c1d43
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+#pragma warning disable CS0169
+#pragma warning disable CS0414
+
+namespace Godot.SourceGenerators.Sample
+{
+ [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")]
+ [SuppressMessage("ReSharper", "RedundantNameQualifier")]
+ [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public partial class ExportedProperties : Godot.Object
+ {
+ [Export] private Boolean property_Boolean { get; set; } = true;
+ [Export] private Char property_Char { get; set; } = 'f';
+ [Export] private SByte property_SByte { get; set; } = 10;
+ [Export] private Int16 property_Int16 { get; set; } = 10;
+ [Export] private Int32 property_Int32 { get; set; } = 10;
+ [Export] private Int64 property_Int64 { get; set; } = 10;
+ [Export] private Byte property_Byte { get; set; } = 10;
+ [Export] private UInt16 property_UInt16 { get; set; } = 10;
+ [Export] private UInt32 property_UInt32 { get; set; } = 10;
+ [Export] private UInt64 property_UInt64 { get; set; } = 10;
+ [Export] private Single property_Single { get; set; } = 10;
+ [Export] private Double property_Double { get; set; } = 10;
+ [Export] private String property_String { get; set; } = "foo";
+
+ // Godot structs
+ [Export] private Vector2 property_Vector2 { get; set; } = new(10f, 10f);
+ [Export] private Vector2i property_Vector2i { get; set; } = Vector2i.Up;
+ [Export] private Rect2 property_Rect2 { get; set; } = new(new Vector2(10f, 10f), new Vector2(10f, 10f));
+ [Export] private Rect2i property_Rect2i { get; set; } = new(new Vector2i(10, 10), new Vector2i(10, 10));
+ [Export] private Transform2D property_Transform2D { get; set; } = Transform2D.Identity;
+ [Export] private Vector3 property_Vector3 { get; set; } = new(10f, 10f, 10f);
+ [Export] private Vector3i property_Vector3i { get; set; } = Vector3i.Back;
+ [Export] private Basis property_Basis { get; set; } = new Basis(Quaternion.Identity);
+ [Export] private Quaternion property_Quaternion { get; set; } = new Quaternion(Basis.Identity);
+ [Export] private Transform3D property_Transform3D { get; set; } = Transform3D.Identity;
+ [Export] private Vector4 property_Vector4 { get; set; } = new(10f, 10f, 10f, 10f);
+ [Export] private Vector4i property_Vector4i { get; set; } = Vector4i.One;
+ [Export] private Projection property_Projection { get; set; } = Projection.Identity;
+ [Export] private AABB property_AABB { get; set; } = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f));
+ [Export] private Color property_Color { get; set; } = Colors.Aquamarine;
+ [Export] private Plane property_Plane { get; set; } = Plane.PlaneXZ;
+ [Export] private Callable property_Callable { get; set; } = new Callable(Engine.GetMainLoop(), "_process");
+ [Export] private SignalInfo property_SignalInfo { get; set; } = new SignalInfo(Engine.GetMainLoop(), "property_list_changed");
+
+ // Enums
+ [SuppressMessage("ReSharper", "UnusedMember.Local")]
+ enum MyEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyEnum property_Enum { get; set; } = MyEnum.C;
+
+ [Flags]
+ [SuppressMessage("ReSharper", "UnusedMember.Local")]
+ enum MyFlagsEnum
+ {
+ A,
+ B,
+ C
+ }
+
+ [Export] private MyFlagsEnum property_FlagsEnum { get; set; } = MyFlagsEnum.C;
+
+ // Arrays
+ [Export] private Byte[] property_ByteArray { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int32[] property_Int32Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Int64[] property_Int64Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 };
+ [Export] private Single[] property_SingleArray { get; set; } = { 0f, 1f, 2f, 3f, 4f, 5f, 6f };
+ [Export] private Double[] property_DoubleArray { get; set; } = { 0d, 1d, 2d, 3d, 4d, 5d, 6d };
+ [Export] private String[] property_StringArray { get; set; } = { "foo", "bar" };
+ [Export(PropertyHint.Enum, "A,B,C")] private String[] property_StringArrayEnum { get; set; } = { "foo", "bar" };
+ [Export] private Vector2[] property_Vector2Array { get; set; } = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
+ [Export] private Vector3[] property_Vector3Array { get; set; } = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right };
+ [Export] private Color[] property_ColorArray { get; set; } = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige };
+ [Export] private Godot.Object[] property_GodotObjectOrDerivedArray { get; set; } = { null };
+ [Export] private object[] property_SystemObjectArray { get; set; } = { 0, 1f, 2d, "foo", Vector3i.Up };
+
+ // Generics
+ [Export] private Godot.Collections.Dictionary<string, string> property_GodotGenericDictionary { get; set; } =
+ new Godot.Collections.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private Godot.Collections.Array<string> property_GodotGenericArray { get; set; } =
+ new Godot.Collections.Array<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.Dictionary<string, string> property_SystemGenericDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.List<string> property_SystemGenericList { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IDictionary<string, string> property_GenericIDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.ICollection<string> property_GenericICollection { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IEnumerable<string> property_GenericIEnumerable { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ // Variant
+ [Export] private object property_SystemObject { get; set; } = "foo";
+
+ // Classes
+ [Export] private Godot.Object property_GodotObjectOrDerived { get; set; }
+ [Export] private Godot.Texture property_GodotResourceTexture { get; set; }
+ [Export] private StringName property_StringName { get; set; } = new StringName("foo");
+ [Export] private NodePath property_NodePath { get; set; } = new NodePath("foo");
+ [Export] private RID property_RID { get; set; }
+
+ [Export] private Godot.Collections.Dictionary property_GodotDictionary { get; set; } =
+ new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private Godot.Collections.Array property_GodotArray { get; set; } =
+ new() { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IDictionary property_IDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<object, object>
+ { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private System.Collections.ICollection property_ICollection { get; set; } =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IEnumerable property_IEnumerable { get; set; } =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs
index 2ddb8880c2..b21b035b4d 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs
@@ -1,16 +1,21 @@
+#pragma warning disable CS0169
+
namespace Godot.SourceGenerators.Sample
{
partial class Generic<T> : Godot.Object
{
+ private int _field;
}
// Generic again but different generic parameters
partial class Generic<T, R> : Godot.Object
{
+ private int _field;
}
// Generic again but without generic parameters
partial class Generic : Godot.Object
{
+ private int _field;
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
index c5a29a53f7..a042fb313f 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
@@ -7,6 +7,8 @@
<PropertyGroup>
<!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
<GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
+ <!-- For compiling GetGodotPropertyDefaultValues. -->
+ <DefineConstants>$(DefineConstants);TOOLS</DefineConstants>
</PropertyGroup>
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
index 0c7328284e..bfc8ef2fb5 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
@@ -1,4 +1,4 @@
-using System;
+#pragma warning disable CS0169
namespace Godot.SourceGenerators.Sample
{
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
index fa41c85322..0b8a2777e5 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -60,5 +61,110 @@ namespace Godot.SourceGenerators
outerTypeDeclSyntax.GetLocation(),
outerTypeDeclSyntax.SyntaxTree.FilePath));
}
+
+ public static void ReportExportedMemberIsStatic(
+ GeneratorExecutionContext context,
+ ISymbol exportedMemberSymbol
+ )
+ {
+ var locations = exportedMemberSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+ bool isField = exportedMemberSymbol is IFieldSymbol;
+
+ string message = $"Attempted to export static {(isField ? "field" : "property")}: " +
+ $"'{exportedMemberSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Only instance fields and properties can be exported." +
+ " Remove the 'static' modifier or the '[Export]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0101",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportExportedMemberTypeNotSupported(
+ GeneratorExecutionContext context,
+ ISymbol exportedMemberSymbol
+ )
+ {
+ var locations = exportedMemberSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+ bool isField = exportedMemberSymbol is IFieldSymbol;
+
+ string message = $"The type of the exported {(isField ? "field" : "property")} " +
+ $"is not supported: '{exportedMemberSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Use a supported type or remove the '[Export]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0102",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportExportedMemberIsReadOnly(
+ GeneratorExecutionContext context,
+ ISymbol exportedMemberSymbol
+ )
+ {
+ var locations = exportedMemberSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+ bool isField = exportedMemberSymbol is IFieldSymbol;
+
+ string message = $"The exported {(isField ? "field" : "property")} " +
+ $"is read-only: '{exportedMemberSymbol.ToDisplayString()}'";
+
+ string description = isField ?
+ $"{message}. Exported fields cannot be read-only." :
+ $"{message}. Exported properties must be writable.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0103",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportExportedMemberIsWriteOnly(
+ GeneratorExecutionContext context,
+ ISymbol exportedMemberSymbol
+ )
+ {
+ var locations = exportedMemberSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Exported properties must be readable.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0104",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index 9586e71d02..2179aeea88 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -24,30 +25,55 @@ namespace Godot.SourceGenerators
toggle != null &&
toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
- private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
+ public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
{
- if (symbol == null)
- return false;
-
- while (true)
+ while (symbol != null)
{
- if (symbol.ToString() == baseName)
+ if (symbol.ContainingAssembly.Name == assemblyName &&
+ symbol.ToString() == typeFullName)
{
return true;
}
- if (symbol.BaseType != null)
- {
- symbol = symbol.BaseType;
- continue;
- }
-
- break;
+ symbol = symbol.BaseType;
}
return false;
}
+ public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol)
+ {
+ var symbol = classTypeSymbol;
+
+ while (symbol != null)
+ {
+ if (symbol.ContainingAssembly.Name == "GodotSharp")
+ return symbol;
+
+ symbol = symbol.BaseType;
+ }
+
+ return null;
+ }
+
+ public static string? GetGodotScriptNativeClassName(this INamedTypeSymbol classTypeSymbol)
+ {
+ var nativeType = classTypeSymbol.GetGodotScriptNativeClass();
+
+ if (nativeType == null)
+ return null;
+
+ var godotClassNameAttr = nativeType.GetAttributes()
+ .FirstOrDefault(a => a.AttributeClass?.IsGodotClassNameAttribute() ?? false);
+
+ string? godotClassName = null;
+
+ if (godotClassNameAttr is { ConstructorArguments: { Length: > 0 } })
+ godotClassName = godotClassNameAttr.ConstructorArguments[0].Value?.ToString();
+
+ return godotClassName ?? nativeType.Name;
+ }
+
private static bool IsGodotScriptClass(
this ClassDeclarationSyntax cds, Compilation compilation,
out INamedTypeSymbol? symbol
@@ -58,7 +84,7 @@ namespace Godot.SourceGenerators
var classTypeSymbol = sm.GetDeclaredSymbol(cds);
if (classTypeSymbol?.BaseType == null
- || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object))
+ || !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object))
{
symbol = null;
return false;
@@ -129,7 +155,101 @@ namespace Godot.SourceGenerators
public static string FullQualifiedName(this ITypeSymbol symbol)
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
+ public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
+ {
+ return symbol.IsGenericType ?
+ string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") :
+ symbol.Name;
+ }
+
public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
+
+ public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName)
+ => qualifiedName
+ // AddSource() doesn't support angle brackets
+ .Replace("<", "(Of ")
+ .Replace(">", ")");
+
+ public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.ExportAttr;
+
+ public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.GodotClassNameAttr;
+
+ public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.SystemFlagsAttr;
+
+ public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
+ this IEnumerable<IMethodSymbol> methods,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var method in methods)
+ {
+ if (method.IsGenericMethod)
+ continue;
+
+ var retType = method.ReturnsVoid ?
+ null :
+ MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
+
+ if (retType == null && !method.ReturnsVoid)
+ continue;
+
+ var parameters = method.Parameters;
+
+ var paramTypes = parameters
+ // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
+ .Where(p => p.RefKind == RefKind.None)
+ // Attempt to determine the variant type
+ .Select(p => MarshalUtils.ConvertManagedTypeToMarshalType(p.Type, typeCache))
+ // Discard parameter types that couldn't be determined (null entries)
+ .Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
+
+ // If any parameter type was incompatible, it was discarded so the length won't match
+ if (parameters.Length > paramTypes.Length)
+ continue;
+
+ yield return new GodotMethodData(method, paramTypes, parameters
+ .Select(p => p.Type).ToImmutableArray(), retType);
+ }
+ }
+
+ public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
+ this IEnumerable<IPropertySymbol> properties,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var property in properties)
+ {
+ // Ignore properties without a getter. Godot properties must be readable.
+ if (property.IsWriteOnly)
+ continue;
+
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
+
+ if (marshalType == null)
+ continue;
+
+ yield return new GodotPropertyData(property, marshalType.Value);
+ }
+ }
+
+ public static IEnumerable<GodotFieldData> WhereIsGodotCompatibleType(
+ this IEnumerable<IFieldSymbol> fields,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var field in fields)
+ {
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
+
+ if (marshalType == null)
+ continue;
+
+ yield return new GodotFieldData(field, marshalType.Value);
+ }
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
index 791ad85572..d61d9f7f14 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
- <LangVersion>8.0</LangVersion>
+ <LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
index 7cc8fa17fc..0ea1b2f5ce 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -4,5 +4,8 @@ namespace Godot.SourceGenerators
{
public const string Object = "Godot.Object";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
+ public const string ExportAttr = "Godot.ExportAttribute";
+ public const string GodotClassNameAttr = "Godot.GodotClassName";
+ public const string SystemFlagsAttr = "System.FlagsAttribute";
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
new file mode 100644
index 0000000000..99d3a49546
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -0,0 +1,134 @@
+using System;
+
+namespace Godot.SourceGenerators
+{
+ internal enum VariantType
+ {
+ Nil = 0,
+ Bool = 1,
+ Int = 2,
+ Float = 3,
+ String = 4,
+ Vector2 = 5,
+ Vector2i = 6,
+ Rect2 = 7,
+ Rect2i = 8,
+ Vector3 = 9,
+ Vector3i = 10,
+ Transform2d = 11,
+ Vector4 = 12,
+ Vector4i = 13,
+ Plane = 14,
+ Quaternion = 15,
+ Aabb = 16,
+ Basis = 17,
+ Transform3d = 18,
+ Projection = 19,
+ Color = 20,
+ StringName = 21,
+ NodePath = 22,
+ Rid = 23,
+ Object = 24,
+ Callable = 25,
+ Signal = 26,
+ Dictionary = 27,
+ Array = 28,
+ PackedByteArray = 29,
+ PackedInt32Array = 30,
+ PackedInt64Array = 31,
+ PackedFloat32Array = 32,
+ PackedFloat64Array = 33,
+ PackedStringArray = 34,
+ PackedVector2Array = 35,
+ PackedVector3Array = 36,
+ PackedColorArray = 37,
+ Max = 38
+ }
+
+ internal enum PropertyHint
+ {
+ None = 0,
+ Range = 1,
+ Enum = 2,
+ EnumSuggestion = 3,
+ ExpEasing = 4,
+ Link = 5,
+ Flags = 6,
+ Layers2dRender = 7,
+ Layers2dPhysics = 8,
+ Layers2dNavigation = 9,
+ Layers3dRender = 10,
+ Layers3dPhysics = 11,
+ Layers3dNavigation = 12,
+ File = 13,
+ Dir = 14,
+ GlobalFile = 15,
+ GlobalDir = 16,
+ ResourceType = 17,
+ MultilineText = 18,
+ Expression = 19,
+ PlaceholderText = 20,
+ ColorNoAlpha = 21,
+ ImageCompressLossy = 22,
+ ImageCompressLossless = 23,
+ ObjectId = 24,
+ TypeString = 25,
+ NodePathToEditedNode = 26,
+ MethodOfVariantType = 27,
+ MethodOfBaseType = 28,
+ MethodOfInstance = 29,
+ MethodOfScript = 30,
+ PropertyOfVariantType = 31,
+ PropertyOfBaseType = 32,
+ PropertyOfInstance = 33,
+ PropertyOfScript = 34,
+ ObjectTooBig = 35,
+ NodePathValidTypes = 36,
+ SaveFile = 37,
+ GlobalSaveFile = 38,
+ IntIsObjectid = 39,
+ IntIsPointer = 41,
+ ArrayType = 40,
+ LocaleId = 42,
+ LocalizableString = 43,
+ NodeType = 44,
+ Max = 45
+ }
+
+ [Flags]
+ internal enum PropertyUsageFlags
+ {
+ None = 0,
+ Storage = 2,
+ Editor = 4,
+ Checkable = 8,
+ Checked = 16,
+ Internationalized = 32,
+ Group = 64,
+ Category = 128,
+ Subgroup = 256,
+ ClassIsBitfield = 512,
+ NoInstanceState = 1024,
+ RestartIfChanged = 2048,
+ ScriptVariable = 4096,
+ StoreIfNull = 8192,
+ AnimateAsTrigger = 16384,
+ UpdateAllIfModified = 32768,
+ ScriptDefaultValue = 65536,
+ ClassIsEnum = 131072,
+ NilIsVariant = 262144,
+ Internal = 524288,
+ DoNotShareOnDuplicate = 1048576,
+ HighEndGfx = 2097152,
+ NodePathFromSceneRoot = 4194304,
+ ResourceNotPersistent = 8388608,
+ KeyingIncrements = 16777216,
+ DeferredSetResource = 33554432,
+ EditorInstantiateObject = 67108864,
+ EditorBasicSetting = 134217728,
+ Array = 536870912,
+ Default = 6,
+ DefaultIntl = 38,
+ NoEditor = 2
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
new file mode 100644
index 0000000000..ff640a7a96
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
@@ -0,0 +1,46 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace Godot.SourceGenerators
+{
+ public struct GodotMethodData
+ {
+ public GodotMethodData(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes,
+ ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType)
+ {
+ Method = method;
+ ParamTypes = paramTypes;
+ ParamTypeSymbols = paramTypeSymbols;
+ RetType = retType;
+ }
+
+ public IMethodSymbol Method { get; }
+ public ImmutableArray<MarshalType> ParamTypes { get; }
+ public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
+ public MarshalType? RetType { get; }
+ }
+
+ public struct GodotPropertyData
+ {
+ public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type)
+ {
+ PropertySymbol = propertySymbol;
+ Type = type;
+ }
+
+ public IPropertySymbol PropertySymbol { get; }
+ public MarshalType Type { get; }
+ }
+
+ public struct GodotFieldData
+ {
+ public GodotFieldData(IFieldSymbol fieldSymbol, MarshalType type)
+ {
+ FieldSymbol = fieldSymbol;
+ Type = type;
+ }
+
+ public IFieldSymbol FieldSymbol { get; }
+ public MarshalType Type { get; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
index 7c8345d16a..1c4c19569e 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
@@ -30,6 +30,9 @@ namespace Godot.SourceGenerators
Basis,
Quaternion,
Transform3D,
+ Vector4,
+ Vector4i,
+ Projection,
AABB,
Color,
Plane,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
index a77e1800fb..5a4badd66e 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
@@ -1,9 +1,10 @@
using System;
+using System.Linq;
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators
{
- public static class MarshalUtils
+ internal static class MarshalUtils
{
public class TypeCache
{
@@ -35,7 +36,73 @@ namespace Godot.SourceGenerators
}
}
- public static MarshalType? ConvertManagedTypeToVariantType(ITypeSymbol type, TypeCache typeCache)
+ public static VariantType? ConvertMarshalTypeToVariantType(MarshalType marshalType)
+ => marshalType switch
+ {
+ MarshalType.Boolean => VariantType.Bool,
+ MarshalType.Char => VariantType.Int,
+ MarshalType.SByte => VariantType.Int,
+ MarshalType.Int16 => VariantType.Int,
+ MarshalType.Int32 => VariantType.Int,
+ MarshalType.Int64 => VariantType.Int,
+ MarshalType.Byte => VariantType.Int,
+ MarshalType.UInt16 => VariantType.Int,
+ MarshalType.UInt32 => VariantType.Int,
+ MarshalType.UInt64 => VariantType.Int,
+ MarshalType.Single => VariantType.Float,
+ MarshalType.Double => VariantType.Float,
+ MarshalType.String => VariantType.String,
+ MarshalType.Vector2 => VariantType.Vector2,
+ MarshalType.Vector2i => VariantType.Vector2i,
+ MarshalType.Rect2 => VariantType.Rect2,
+ MarshalType.Rect2i => VariantType.Rect2i,
+ MarshalType.Transform2D => VariantType.Transform2d,
+ MarshalType.Vector3 => VariantType.Vector3,
+ MarshalType.Vector3i => VariantType.Vector3i,
+ MarshalType.Basis => VariantType.Basis,
+ MarshalType.Quaternion => VariantType.Quaternion,
+ MarshalType.Transform3D => VariantType.Transform3d,
+ MarshalType.Vector4 => VariantType.Vector4,
+ MarshalType.Vector4i => VariantType.Vector4i,
+ MarshalType.Projection => VariantType.Projection,
+ MarshalType.AABB => VariantType.Aabb,
+ MarshalType.Color => VariantType.Color,
+ MarshalType.Plane => VariantType.Plane,
+ MarshalType.Callable => VariantType.Callable,
+ MarshalType.SignalInfo => VariantType.Signal,
+ MarshalType.Enum => VariantType.Int,
+ MarshalType.ByteArray => VariantType.PackedByteArray,
+ MarshalType.Int32Array => VariantType.PackedInt32Array,
+ MarshalType.Int64Array => VariantType.PackedInt64Array,
+ MarshalType.SingleArray => VariantType.PackedFloat32Array,
+ MarshalType.DoubleArray => VariantType.PackedFloat64Array,
+ MarshalType.StringArray => VariantType.PackedStringArray,
+ MarshalType.Vector2Array => VariantType.PackedVector2Array,
+ MarshalType.Vector3Array => VariantType.PackedVector3Array,
+ MarshalType.ColorArray => VariantType.PackedColorArray,
+ MarshalType.GodotObjectOrDerivedArray => VariantType.Array,
+ MarshalType.SystemObjectArray => VariantType.Array,
+ MarshalType.GodotGenericDictionary => VariantType.Dictionary,
+ MarshalType.GodotGenericArray => VariantType.Array,
+ MarshalType.SystemGenericDictionary => VariantType.Dictionary,
+ MarshalType.SystemGenericList => VariantType.Array,
+ MarshalType.GenericIDictionary => VariantType.Dictionary,
+ MarshalType.GenericICollection => VariantType.Array,
+ MarshalType.GenericIEnumerable => VariantType.Array,
+ MarshalType.SystemObject => VariantType.Nil,
+ MarshalType.GodotObjectOrDerived => VariantType.Object,
+ MarshalType.StringName => VariantType.StringName,
+ MarshalType.NodePath => VariantType.NodePath,
+ MarshalType.RID => VariantType.Rid,
+ MarshalType.GodotDictionary => VariantType.Dictionary,
+ MarshalType.GodotArray => VariantType.Array,
+ MarshalType.IDictionary => VariantType.Dictionary,
+ MarshalType.ICollection => VariantType.Array,
+ MarshalType.IEnumerable => VariantType.Array,
+ _ => null
+ };
+
+ public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache)
{
var specialType = type.SpecialType;
@@ -69,39 +136,44 @@ namespace Godot.SourceGenerators
return MarshalType.String;
case SpecialType.System_Object:
return MarshalType.SystemObject;
- case SpecialType.System_ValueType:
+ default:
{
- if (type.ContainingAssembly.Name == "GodotSharp" &&
- type.ContainingNamespace.Name == "Godot")
+ var typeKind = type.TypeKind;
+
+ if (typeKind == TypeKind.Enum)
+ return MarshalType.Enum;
+
+ if (typeKind == TypeKind.Struct)
{
- return type switch
+ if (type.ContainingAssembly.Name == "GodotSharp" &&
+ type.ContainingNamespace.Name == "Godot")
{
- { Name: "Vector2" } => MarshalType.Vector2,
- { Name: "Vector2i" } => MarshalType.Vector2i,
- { Name: "Rect2" } => MarshalType.Rect2,
- { Name: "Rect2i" } => MarshalType.Rect2i,
- { Name: "Transform2D" } => MarshalType.Transform2D,
- { Name: "Vector3" } => MarshalType.Vector3,
- { Name: "Vector3i" } => MarshalType.Vector3i,
- { Name: "Basis" } => MarshalType.Basis,
- { Name: "Quaternion" } => MarshalType.Quaternion,
- { Name: "Transform3D" } => MarshalType.Transform3D,
- { Name: "AABB" } => MarshalType.AABB,
- { Name: "Color" } => MarshalType.Color,
- { Name: "Plane" } => MarshalType.Plane,
- { Name: "RID" } => MarshalType.RID,
- { Name: "Callable" } => MarshalType.Callable,
- { Name: "SignalInfo" } => MarshalType.SignalInfo,
- { TypeKind: TypeKind.Enum } => MarshalType.Enum,
- _ => null
- };
+ return type switch
+ {
+ { Name: "Vector2" } => MarshalType.Vector2,
+ { Name: "Vector2i" } => MarshalType.Vector2i,
+ { Name: "Rect2" } => MarshalType.Rect2,
+ { Name: "Rect2i" } => MarshalType.Rect2i,
+ { Name: "Transform2D" } => MarshalType.Transform2D,
+ { Name: "Vector3" } => MarshalType.Vector3,
+ { Name: "Vector3i" } => MarshalType.Vector3i,
+ { Name: "Basis" } => MarshalType.Basis,
+ { Name: "Quaternion" } => MarshalType.Quaternion,
+ { Name: "Transform3D" } => MarshalType.Transform3D,
+ { Name: "Vector4" } => MarshalType.Vector4,
+ { Name: "Vector4i" } => MarshalType.Vector4i,
+ { Name: "Projection" } => MarshalType.Projection,
+ { Name: "AABB" } => MarshalType.AABB,
+ { Name: "Color" } => MarshalType.Color,
+ { Name: "Plane" } => MarshalType.Plane,
+ { Name: "RID" } => MarshalType.RID,
+ { Name: "Callable" } => MarshalType.Callable,
+ { Name: "SignalInfo" } => MarshalType.SignalInfo,
+ _ => null
+ };
+ }
}
-
- return null;
- }
- default:
- {
- if (type.TypeKind == TypeKind.Array)
+ else if (typeKind == TypeKind.Array)
{
var arrayType = (IArrayTypeSymbol)type;
var elementType = arrayType.ElementType;
@@ -127,17 +199,24 @@ namespace Godot.SourceGenerators
if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType))
return MarshalType.GodotObjectOrDerivedArray;
- if (type.ContainingAssembly.Name == "GodotSharp" &&
- type.ContainingNamespace.Name == "Godot")
+ if (elementType.ContainingAssembly.Name == "GodotSharp" &&
+ elementType.ContainingNamespace.Name == "Godot")
{
- return elementType switch
+ switch (elementType)
{
- { Name: "Vector2" } => MarshalType.Vector2Array,
- { Name: "Vector3" } => MarshalType.Vector3Array,
- { Name: "Color" } => MarshalType.ColorArray,
- _ => null
- };
+ case { Name: "Vector2" }:
+ return MarshalType.Vector2Array;
+ case { Name: "Vector3" }:
+ return MarshalType.Vector3Array;
+ case { Name: "Color" }:
+ return MarshalType.ColorArray;
+ }
}
+
+ if (ConvertManagedTypeToMarshalType(elementType, typeCache) != null)
+ return MarshalType.GodotArray;
+
+ return null;
}
else if (type is INamedTypeSymbol { IsGenericType: true } genericType)
{
@@ -190,7 +269,10 @@ namespace Godot.SourceGenerators
{ Name: "NodePath" } => MarshalType.NodePath,
_ => null
};
- case "Godot.Collections" when !(type is INamedTypeSymbol { IsGenericType: true }):
+ case "Collections"
+ when !(type is INamedTypeSymbol { IsGenericType: true }) &&
+ type.ContainingNamespace.FullQualifiedName() ==
+ "Godot.Collections":
return type switch
{
{ Name: "Dictionary" } => MarshalType.GodotDictionary,
@@ -220,5 +302,19 @@ namespace Godot.SourceGenerators
return false;
}
+
+ public static ITypeSymbol? GetArrayElementType(ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol.TypeKind == TypeKind.Array)
+ {
+ var arrayType = (IArrayTypeSymbol)typeSymbol;
+ return arrayType.ElementType;
+ }
+
+ if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
+ return genericType.TypeArguments.FirstOrDefault();
+
+ return null;
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
index 51e9406c15..6d3d03c495 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -9,7 +7,7 @@ using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
- public class ScriptBoilerplateGenerator : ISourceGenerator
+ public class ScriptMemberInvokerGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
@@ -61,8 +59,6 @@ namespace Godot.SourceGenerators
INamedTypeSymbol symbol
)
{
- string className = symbol.Name;
-
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
@@ -71,9 +67,8 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
- string uniqueName = hasNamespace ?
- classNs + "." + className + "_ScriptBoilerplate_Generated" :
- className + "_ScriptBoilerplate_Generated";
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptMemberInvoker_Generated";
var source = new StringBuilder();
@@ -97,7 +92,7 @@ namespace Godot.SourceGenerators
source.Append("partial ");
source.Append(containingType.GetDeclarationKeyword());
source.Append(" ");
- source.Append(containingType.Name);
+ source.Append(containingType.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
@@ -105,7 +100,7 @@ namespace Godot.SourceGenerators
}
source.Append("partial class ");
- source.Append(className);
+ source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var members = symbol.GetMembers();
@@ -113,27 +108,28 @@ namespace Godot.SourceGenerators
// TODO: Static static marshaling (no reflection, no runtime type checks)
var methodSymbols = members
- .Where(s => s.Kind == SymbolKind.Method)
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
.Cast<IMethodSymbol>()
- .Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsImplicitlyDeclared);
+ .Where(m => m.MethodKind == MethodKind.Ordinary);
var propertySymbols = members
- .Where(s => s.Kind == SymbolKind.Property)
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>();
var fieldSymbols = members
- .Where(s => s.Kind == SymbolKind.Field)
- .Cast<IFieldSymbol>()
- .Where(p => !p.IsImplicitlyDeclared);
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>();
- var godotClassMethods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray();
- var godotClassProperties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray();
- var godotClassFields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray();
+ var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache).ToArray();
+ var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
- source.Append(" private class GodotInternal {\n");
+ source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
+ // TODO: Move the generation of these cached StringNames to its own generator
+
foreach (var method in godotClassMethods)
{
string methodName = method.Method.Name;
@@ -144,26 +140,6 @@ namespace Godot.SourceGenerators
source.Append("\";\n");
}
- foreach (var property in godotClassProperties)
- {
- string propertyName = property.Property.Name;
- source.Append(" public static readonly StringName PropName_");
- source.Append(propertyName);
- source.Append(" = \"");
- source.Append(propertyName);
- source.Append("\";\n");
- }
-
- foreach (var field in godotClassFields)
- {
- string fieldName = field.Field.Name;
- source.Append(" public static readonly StringName PropName_");
- source.Append(fieldName);
- source.Append(" = \"");
- source.Append(fieldName);
- source.Append("\";\n");
- }
-
source.Append(" }\n"); // class GodotInternal
// Generate InvokeGodotClassMethod
@@ -191,8 +167,8 @@ namespace Godot.SourceGenerators
// Setters
- bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.Field.IsReadOnly) &&
- godotClassProperties.All(pi => pi.Property.IsReadOnly);
+ bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
+ godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
if (!allPropertiesAreReadOnly)
{
@@ -202,21 +178,21 @@ namespace Godot.SourceGenerators
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
- if (property.Property.IsReadOnly)
+ if (property.PropertySymbol.IsReadOnly)
continue;
- GeneratePropertySetter(property.Property.Name,
- property.Property.Type.FullQualifiedName(), source, isFirstEntry);
+ GeneratePropertySetter(property.PropertySymbol.Name,
+ property.PropertySymbol.Type.FullQualifiedName(), source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
- if (field.Field.IsReadOnly)
+ if (field.FieldSymbol.IsReadOnly)
continue;
- GeneratePropertySetter(field.Field.Name,
- field.Field.Type.FullQualifiedName(), source, isFirstEntry);
+ GeneratePropertySetter(field.FieldSymbol.Name,
+ field.FieldSymbol.Type.FullQualifiedName(), source, isFirstEntry);
isFirstEntry = false;
}
@@ -233,13 +209,13 @@ namespace Godot.SourceGenerators
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
- GeneratePropertyGetter(property.Property.Name, source, isFirstEntry);
+ GeneratePropertyGetter(property.PropertySymbol.Name, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
- GeneratePropertyGetter(field.Field.Name, source, isFirstEntry);
+ GeneratePropertyGetter(field.FieldSymbol.Name, source, isFirstEntry);
isFirstEntry = false;
}
@@ -285,11 +261,11 @@ namespace Godot.SourceGenerators
source.Append("\n}\n");
}
- context.AddSource(uniqueName, SourceText.From(source.ToString(), Encoding.UTF8));
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void GenerateMethodInvoker(
- GodotMethodInfo method,
+ GodotMethodData method,
StringBuilder source
)
{
@@ -399,7 +375,7 @@ namespace Godot.SourceGenerators
}
private static void GenerateHasMethodEntry(
- GodotMethodInfo method,
+ GodotMethodData method,
StringBuilder source,
bool isFirstEntry
)
@@ -417,118 +393,5 @@ namespace Godot.SourceGenerators
public void Initialize(GeneratorInitializationContext context)
{
}
-
- private struct GodotMethodInfo
- {
- public GodotMethodInfo(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes,
- ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType)
- {
- Method = method;
- ParamTypes = paramTypes;
- ParamTypeSymbols = paramTypeSymbols;
- RetType = retType;
- }
-
- public IMethodSymbol Method { get; }
- public ImmutableArray<MarshalType> ParamTypes { get; }
- public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
- public MarshalType? RetType { get; }
- }
-
- private struct GodotPropertyInfo
- {
- public GodotPropertyInfo(IPropertySymbol property, MarshalType type)
- {
- Property = property;
- Type = type;
- }
-
- public IPropertySymbol Property { get; }
- public MarshalType Type { get; }
- }
-
- private struct GodotFieldInfo
- {
- public GodotFieldInfo(IFieldSymbol field, MarshalType type)
- {
- Field = field;
- Type = type;
- }
-
- public IFieldSymbol Field { get; }
- public MarshalType Type { get; }
- }
-
- private static IEnumerable<GodotMethodInfo> WhereHasCompatibleGodotType(
- IEnumerable<IMethodSymbol> methods,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var method in methods)
- {
- if (method.IsGenericMethod)
- continue;
-
- var retType = method.ReturnsVoid ?
- null :
- MarshalUtils.ConvertManagedTypeToVariantType(method.ReturnType, typeCache);
-
- if (retType == null && !method.ReturnsVoid)
- continue;
-
- var parameters = method.Parameters;
-
- var paramTypes = parameters
- // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
- .Where(p => p.RefKind == RefKind.None)
- // Attempt to determine the variant type
- .Select(p => MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache))
- // Discard parameter types that couldn't be determined (null entries)
- .Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
-
- // If any parameter type was incompatible, it was discarded so the length won't match
- if (parameters.Length > paramTypes.Length)
- continue; // Ignore incompatible method
-
- yield return new GodotMethodInfo(method, paramTypes, parameters
- .Select(p => p.Type).ToImmutableArray(), retType);
- }
- }
-
- private static IEnumerable<GodotPropertyInfo> WhereIsCompatibleGodotType(
- IEnumerable<IPropertySymbol> properties,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var property in properties)
- {
- // Ignore properties without a getter. Godot properties must be readable.
- if (property.IsWriteOnly)
- continue;
-
- var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(property.Type, typeCache);
-
- if (marshalType == null)
- continue;
-
- yield return new GodotPropertyInfo(property, marshalType.Value);
- }
- }
-
- private static IEnumerable<GodotFieldInfo> WhereIsCompatibleGodotType(
- IEnumerable<IFieldSymbol> fields,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var field in fields)
- {
- var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(field.Type, typeCache);
-
- if (marshalType == null)
- continue;
-
- yield return new GodotFieldInfo(field, marshalType.Value);
- }
- }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
index b625287087..e8a9e28d0c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -90,21 +90,14 @@ namespace Godot.SourceGenerators
attributes.Append(@""")]");
}
- string className = symbol.Name;
-
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
- var uniqueName = new StringBuilder();
- if (hasNamespace)
- uniqueName.Append($"{classNs}.");
- uniqueName.Append(className);
- if (symbol.IsGenericType)
- uniqueName.Append($"Of{string.Join(string.Empty, symbol.TypeParameters)}");
- uniqueName.Append("_ScriptPath_Generated");
+ var uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptPath_Generated";
var source = new StringBuilder();
@@ -124,10 +117,8 @@ namespace Godot.SourceGenerators
}
source.Append(attributes);
- source.Append("\n partial class ");
- source.Append(className);
- if (symbol.IsGenericType)
- source.Append($"<{string.Join(", ", symbol.TypeParameters)}>");
+ source.Append("\npartial class ");
+ source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n}\n");
if (hasNamespace)
@@ -135,7 +126,7 @@ namespace Godot.SourceGenerators
source.Append("\n}\n");
}
- context.AddSource(uniqueName.ToString(), SourceText.From(source.ToString(), Encoding.UTF8));
+ context.AddSource(uniqueHint.ToString(), SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
new file mode 100644
index 0000000000..85fa65d1af
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -0,0 +1,527 @@
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class ScriptPropertiesGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.AreGodotSourceGeneratorsDisabled())
+ return;
+
+ INamedTypeSymbol[] godotClasses = context
+ .Compilation.SyntaxTrees
+ .SelectMany(tree =>
+ tree.GetRoot().DescendantNodes()
+ .OfType<ClassDeclarationSyntax>()
+ .SelectGodotScriptClasses(context.Compilation)
+ // Report and skip non-partial classes
+ .Where(x =>
+ {
+ if (x.cds.IsPartial())
+ {
+ if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+ {
+ Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+ return false;
+ }
+
+ return true;
+ }
+
+ Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+ return false;
+ })
+ .Select(x => x.symbol)
+ )
+ .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
+ .ToArray();
+
+ if (godotClasses.Length > 0)
+ {
+ var typeCache = new MarshalUtils.TypeCache(context);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ private static void VisitGodotScriptClass(
+ GeneratorExecutionContext context,
+ MarshalUtils.TypeCache typeCache,
+ INamedTypeSymbol symbol
+ )
+ {
+ INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+ string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+ namespaceSymbol.FullQualifiedName() :
+ string.Empty;
+ bool hasNamespace = classNs.Length != 0;
+
+ bool isInnerClass = symbol.ContainingType != null;
+
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptProperties_Generated";
+
+ var source = new StringBuilder();
+
+ source.Append("using Godot;\n");
+ source.Append("using Godot.NativeInterop;\n");
+ source.Append("\n");
+
+ if (hasNamespace)
+ {
+ source.Append("namespace ");
+ source.Append(classNs);
+ source.Append(" {\n\n");
+ }
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("partial ");
+ source.Append(containingType.GetDeclarationKeyword());
+ source.Append(" ");
+ source.Append(containingType.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ source.Append("partial class ");
+ source.Append(symbol.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ var members = symbol.GetMembers();
+
+ var propertySymbols = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
+ .Cast<IPropertySymbol>();
+
+ var fieldSymbols = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>();
+
+ var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+
+ source.Append(" private partial class GodotInternal {\n");
+
+ // Generate cached StringNames for methods and properties, for fast lookup
+
+ foreach (var property in godotClassProperties)
+ {
+ string propertyName = property.PropertySymbol.Name;
+ source.Append(" public static readonly StringName PropName_");
+ source.Append(propertyName);
+ source.Append(" = \"");
+ source.Append(propertyName);
+ source.Append("\";\n");
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ string fieldName = field.FieldSymbol.Name;
+ source.Append(" public static readonly StringName PropName_");
+ source.Append(fieldName);
+ source.Append(" = \"");
+ source.Append(fieldName);
+ source.Append("\";\n");
+ }
+
+ source.Append(" }\n"); // class GodotInternal
+
+ // Generate GetGodotPropertiesMetadata
+
+ if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ string dictionaryType = "System.Collections.Generic.List<Godot.Bridge.PropertyInfo>";
+
+ source.Append(" internal new static ")
+ .Append(dictionaryType)
+ .Append(" GetGodotPropertiesMetadata()\n {\n");
+
+ source.Append(" var properties = new ")
+ .Append(dictionaryType)
+ .Append("();\n");
+
+ foreach (var property in godotClassProperties)
+ {
+ var propertyInfo = GetPropertyMetadata(context, typeCache,
+ property.PropertySymbol, property.Type);
+
+ if (propertyInfo == null)
+ continue;
+
+ AppendPropertyInfo(source, propertyInfo.Value);
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ var propertyInfo = GetPropertyMetadata(context, typeCache,
+ field.FieldSymbol, field.Type);
+
+ if (propertyInfo == null)
+ continue;
+
+ AppendPropertyInfo(source, propertyInfo.Value);
+ }
+
+ source.Append(" return properties;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
+ source.Append("}\n"); // partial class
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("}\n"); // outer class
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ if (hasNamespace)
+ {
+ source.Append("\n}\n");
+ }
+
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+ }
+
+ private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append(" properties.Add(new Godot.Bridge.PropertyInfo(type: (Godot.Variant.Type)")
+ .Append((int)propertyInfo.Type)
+ .Append(", name: GodotInternal.PropName_")
+ .Append(propertyInfo.Name)
+ .Append(", hint: (Godot.PropertyHint)")
+ .Append((int)propertyInfo.Hint)
+ .Append(", hintString: \"")
+ .Append(propertyInfo.HintString)
+ .Append("\", usage: (Godot.PropertyUsageFlags)")
+ .Append((int)propertyInfo.Usage)
+ .Append(", exported: ")
+ .Append(propertyInfo.Exported ? "true" : "false")
+ .Append("));\n");
+ }
+
+ private struct PropertyInfo
+ {
+ public PropertyInfo(VariantType type, string name, PropertyHint hint,
+ string? hintString, PropertyUsageFlags usage, bool exported)
+ {
+ Type = type;
+ Name = name;
+ Hint = hint;
+ HintString = hintString;
+ Usage = usage;
+ Exported = exported;
+ }
+
+ public VariantType Type { get; }
+ public string Name { get; }
+ public PropertyHint Hint { get; }
+ public string? HintString { get; }
+ public PropertyUsageFlags Usage { get; }
+ public bool Exported { get; }
+ }
+
+ private static PropertyInfo? GetPropertyMetadata(
+ GeneratorExecutionContext context,
+ MarshalUtils.TypeCache typeCache,
+ ISymbol memberSymbol,
+ MarshalType marshalType
+ )
+ {
+ var exportAttr = memberSymbol.GetAttributes()
+ .FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false);
+
+ var propertySymbol = memberSymbol as IPropertySymbol;
+ var fieldSymbol = memberSymbol as IFieldSymbol;
+
+ if (exportAttr != null && propertySymbol != null)
+ {
+ if (propertySymbol.GetMethod == null)
+ {
+ // This should never happen, as we filtered WriteOnly properties, but just in case.
+ Common.ReportExportedMemberIsWriteOnly(context, propertySymbol);
+ return null;
+ }
+
+ if (propertySymbol.SetMethod == null)
+ {
+ // This should never happen, as we filtered ReadOnly properties, but just in case.
+ Common.ReportExportedMemberIsReadOnly(context, propertySymbol);
+ return null;
+ }
+ }
+
+ var memberType = propertySymbol?.Type ?? fieldSymbol!.Type;
+
+ var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+ string memberName = memberSymbol.Name;
+
+ if (exportAttr == null)
+ {
+ return new PropertyInfo(memberVariantType, memberName, PropertyHint.None,
+ hintString: null, PropertyUsageFlags.ScriptVariable, exported: false);
+ }
+
+ if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType,
+ isTypeArgument: false, out var hint, out var hintString))
+ {
+ var constructorArguments = exportAttr.ConstructorArguments;
+
+ if (constructorArguments.Length > 0)
+ {
+ var hintValue = exportAttr.ConstructorArguments[0].Value;
+
+ hint = hintValue switch
+ {
+ null => PropertyHint.None,
+ int intValue => (PropertyHint)intValue,
+ _ => (PropertyHint)(long)hintValue
+ };
+
+ hintString = constructorArguments.Length > 1 ?
+ exportAttr.ConstructorArguments[1].Value?.ToString() :
+ null;
+ }
+ else
+ {
+ hint = PropertyHint.None;
+ }
+ }
+
+ var propUsage = PropertyUsageFlags.Default | PropertyUsageFlags.ScriptVariable;
+
+ if (memberVariantType == VariantType.Nil)
+ propUsage |= PropertyUsageFlags.NilIsVariant;
+
+ return new PropertyInfo(memberVariantType, memberName,
+ hint, hintString, propUsage, exported: true);
+ }
+
+ private static bool TryGetMemberExportHint(
+ MarshalUtils.TypeCache typeCache,
+ ITypeSymbol type, AttributeData exportAttr,
+ VariantType variantType, bool isTypeArgument,
+ out PropertyHint hint, out string? hintString
+ )
+ {
+ hint = PropertyHint.None;
+ hintString = null;
+
+ if (variantType == VariantType.Nil)
+ return true; // Variant, no export hint
+
+ if (variantType == VariantType.Int &&
+ type.IsValueType && type.TypeKind == TypeKind.Enum)
+ {
+ bool hasFlagsAttr = type.GetAttributes()
+ .Any(a => a.AttributeClass?.IsSystemFlagsAttribute() ?? false);
+
+ hint = hasFlagsAttr ? PropertyHint.Flags : PropertyHint.Enum;
+
+ var members = type.GetMembers();
+
+ var enumFields = members
+ .Where(s => s.Kind == SymbolKind.Field && s.IsStatic &&
+ s.DeclaredAccessibility == Accessibility.Public &&
+ !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>().ToArray();
+
+ var hintStringBuilder = new StringBuilder();
+ var nameOnlyHintStringBuilder = new StringBuilder();
+
+ // True: enum Foo { Bar, Baz, Qux }
+ // True: enum Foo { Bar = 0, Baz = 1, Qux = 2 }
+ // False: enum Foo { Bar = 0, Baz = 7, Qux = 5 }
+ bool usesDefaultValues = true;
+
+ for (int i = 0; i < enumFields.Length; i++)
+ {
+ var enumField = enumFields[i];
+
+ if (i > 0)
+ {
+ hintStringBuilder.Append(",");
+ nameOnlyHintStringBuilder.Append(",");
+ }
+
+ string enumFieldName = enumField.Name;
+ hintStringBuilder.Append(enumFieldName);
+ nameOnlyHintStringBuilder.Append(enumFieldName);
+
+ long val = enumField.ConstantValue switch
+ {
+ sbyte v => v,
+ short v => v,
+ int v => v,
+ long v => v,
+ byte v => v,
+ ushort v => v,
+ uint v => v,
+ ulong v => (long)v,
+ _ => 0
+ };
+
+ uint expectedVal = (uint)(hint == PropertyHint.Flags ? 1 << i : i);
+ if (val != expectedVal)
+ usesDefaultValues = false;
+
+ hintStringBuilder.Append(":");
+ hintStringBuilder.Append(val);
+ }
+
+ hintString = !usesDefaultValues ?
+ hintStringBuilder.ToString() :
+ // If we use the format NAME:VAL, that's what the editor displays.
+ // That's annoying if the user is not using custom values for the enum constants.
+ // This may not be needed in the future if the editor is changed to not display values.
+ nameOnlyHintStringBuilder.ToString();
+
+ return true;
+ }
+
+ if (variantType == VariantType.Object && type is INamedTypeSymbol memberNamedType)
+ {
+ if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource"))
+ {
+ string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;
+
+ hint = PropertyHint.ResourceType;
+ hintString = nativeTypeName;
+
+ return true;
+ }
+
+ if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Node"))
+ {
+ string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!;
+
+ hint = PropertyHint.NodeType;
+ hintString = nativeTypeName;
+
+ return true;
+ }
+ }
+
+ static bool GetStringArrayEnumHint(VariantType elementVariantType,
+ AttributeData exportAttr, out string? hintString)
+ {
+ var constructorArguments = exportAttr.ConstructorArguments;
+
+ if (constructorArguments.Length > 0)
+ {
+ var presetHintValue = exportAttr.ConstructorArguments[0].Value;
+
+ PropertyHint presetHint = presetHintValue switch
+ {
+ null => PropertyHint.None,
+ int intValue => (PropertyHint)intValue,
+ _ => (PropertyHint)(long)presetHintValue
+ };
+
+ if (presetHint == PropertyHint.Enum)
+ {
+ string? presetHintString = constructorArguments.Length > 1 ?
+ exportAttr.ConstructorArguments[1].Value?.ToString() :
+ null;
+
+ hintString = (int)elementVariantType + "/" + (int)PropertyHint.Enum + ":";
+
+ if (presetHintString != null)
+ hintString += presetHintString;
+
+ return true;
+ }
+ }
+
+ hintString = null;
+ return false;
+ }
+
+ if (!isTypeArgument && variantType == VariantType.Array)
+ {
+ var elementType = MarshalUtils.GetArrayElementType(type);
+
+ if (elementType == null)
+ return false; // Non-generic Array, so there's no hint to add
+
+ var elementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementType, typeCache)!.Value;
+ var elementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(elementMarshalType)!.Value;
+
+ bool isPresetHint = false;
+
+ if (elementVariantType == VariantType.String)
+ isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString);
+
+ if (!isPresetHint)
+ {
+ bool hintRes = TryGetMemberExportHint(typeCache, elementType,
+ exportAttr, elementVariantType, isTypeArgument: true,
+ out var elementHint, out var elementHintString);
+
+ // Format: type/hint:hint_string
+ if (hintRes)
+ {
+ hintString = (int)elementVariantType + "/" + (int)elementHint + ":";
+
+ if (elementHintString != null)
+ hintString += elementHintString;
+ }
+ else
+ {
+ hintString = (int)elementVariantType + "/" + (int)PropertyHint.None + ":";
+ }
+ }
+
+ hint = PropertyHint.TypeString;
+
+ return hintString != null;
+ }
+
+ if (!isTypeArgument && variantType == VariantType.PackedStringArray)
+ {
+ if (GetStringArrayEnumHint(VariantType.String, exportAttr, out hintString))
+ {
+ hint = PropertyHint.TypeString;
+ return true;
+ }
+ }
+
+ if (!isTypeArgument && variantType == VariantType.Dictionary)
+ {
+ // TODO: Dictionaries are not supported in the inspector
+ return false;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
new file mode 100644
index 0000000000..3b8ba21107
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -0,0 +1,293 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class ScriptPropertyDefValGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.AreGodotSourceGeneratorsDisabled())
+ return;
+
+ INamedTypeSymbol[] godotClasses = context
+ .Compilation.SyntaxTrees
+ .SelectMany(tree =>
+ tree.GetRoot().DescendantNodes()
+ .OfType<ClassDeclarationSyntax>()
+ .SelectGodotScriptClasses(context.Compilation)
+ // Report and skip non-partial classes
+ .Where(x =>
+ {
+ if (x.cds.IsPartial())
+ {
+ if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+ {
+ Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+ return false;
+ }
+
+ return true;
+ }
+
+ Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+ return false;
+ })
+ .Select(x => x.symbol)
+ )
+ .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
+ .ToArray();
+
+ if (godotClasses.Length > 0)
+ {
+ var typeCache = new MarshalUtils.TypeCache(context);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ private static void VisitGodotScriptClass(
+ GeneratorExecutionContext context,
+ MarshalUtils.TypeCache typeCache,
+ INamedTypeSymbol symbol
+ )
+ {
+ INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+ string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+ namespaceSymbol.FullQualifiedName() :
+ string.Empty;
+ bool hasNamespace = classNs.Length != 0;
+
+ bool isInnerClass = symbol.ContainingType != null;
+
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptPropertyDefVal_Generated";
+
+ var source = new StringBuilder();
+
+ source.Append("using Godot;\n");
+ source.Append("using Godot.NativeInterop;\n");
+ source.Append("\n");
+
+ if (hasNamespace)
+ {
+ source.Append("namespace ");
+ source.Append(classNs);
+ source.Append(" {\n\n");
+ }
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("partial ");
+ source.Append(containingType.GetDeclarationKeyword());
+ source.Append(" ");
+ source.Append(containingType.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ source.Append("partial class ");
+ source.Append(symbol.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ var exportedMembers = new List<ExportedPropertyMetadata>();
+
+ var members = symbol.GetMembers();
+
+ var exportedProperties = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
+ .Cast<IPropertySymbol>()
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false))
+ .ToArray();
+
+ var exportedFields = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>()
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false))
+ .ToArray();
+
+ foreach (var property in exportedProperties)
+ {
+ if (property.IsStatic)
+ {
+ Common.ReportExportedMemberIsStatic(context, property);
+ continue;
+ }
+
+ // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+ // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+ if (property.IsWriteOnly)
+ {
+ Common.ReportExportedMemberIsWriteOnly(context, property);
+ continue;
+ }
+
+ if (property.IsReadOnly)
+ {
+ Common.ReportExportedMemberIsReadOnly(context, property);
+ continue;
+ }
+
+
+ var propertyType = property.Type;
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(propertyType, typeCache);
+
+ if (marshalType == null)
+ {
+ Common.ReportExportedMemberTypeNotSupported(context, property);
+ continue;
+ }
+
+ // TODO: Detect default value from simple property getters (currently we only detect from initializers)
+
+ EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences
+ .Select(r => r.GetSyntax() as PropertyDeclarationSyntax)
+ .Select(s => s?.Initializer ?? null)
+ .FirstOrDefault();
+
+ string? value = initializer?.Value.ToString();
+
+ exportedMembers.Add(new ExportedPropertyMetadata(
+ property.Name, marshalType.Value, propertyType, value));
+ }
+
+ foreach (var field in exportedFields)
+ {
+ if (field.IsStatic)
+ {
+ Common.ReportExportedMemberIsStatic(context, field);
+ continue;
+ }
+
+ // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+ // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+ if (field.IsReadOnly)
+ {
+ Common.ReportExportedMemberIsReadOnly(context, field);
+ continue;
+ }
+
+ var fieldType = field.Type;
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(fieldType, typeCache);
+
+ if (marshalType == null)
+ {
+ Common.ReportExportedMemberTypeNotSupported(context, field);
+ continue;
+ }
+
+ EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences
+ .Select(r => r.GetSyntax())
+ .OfType<VariableDeclaratorSyntax>()
+ .Select(s => s.Initializer)
+ .FirstOrDefault(i => i != null);
+
+ string? value = initializer?.Value.ToString();
+
+ exportedMembers.Add(new ExportedPropertyMetadata(
+ field.Name, marshalType.Value, fieldType, value));
+ }
+
+ // Generate GetGodotExportedProperties
+
+ if (exportedMembers.Count > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>";
+
+ source.Append("#if TOOLS\n");
+ source.Append(" internal new static ");
+ source.Append(dictionaryType);
+ source.Append(" GetGodotPropertyDefaultValues()\n {\n");
+
+ source.Append(" var values = new ");
+ source.Append(dictionaryType);
+ source.Append("(");
+ source.Append(exportedMembers.Count);
+ source.Append(");\n");
+
+ foreach (var exportedMember in exportedMembers)
+ {
+ string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value");
+
+ source.Append(" ");
+ source.Append(exportedMember.TypeSymbol.FullQualifiedName());
+ source.Append(" ");
+ source.Append(defaultValueLocalName);
+ source.Append(" = ");
+ source.Append(exportedMember.Value ?? "default");
+ source.Append(";\n");
+ source.Append(" values.Add(GodotInternal.PropName_");
+ source.Append(exportedMember.Name);
+ source.Append(", ");
+ source.Append(defaultValueLocalName);
+ source.Append(");\n");
+ }
+
+ source.Append(" return values;\n");
+ source.Append(" }\n");
+ source.Append("#endif\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
+ source.Append("}\n"); // partial class
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("}\n"); // outer class
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ if (hasNamespace)
+ {
+ source.Append("\n}\n");
+ }
+
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+ }
+
+ private struct ExportedPropertyMetadata
+ {
+ public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value)
+ {
+ Name = name;
+ Type = type;
+ TypeSymbol = typeSymbol;
+ Value = value;
+ }
+
+ public string Name { get; }
+ public MarshalType Type { get; }
+ public ITypeSymbol TypeSymbol { get; }
+ public string? Value { get; }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 4e27f4ed14..fe83e6a281 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -513,20 +513,23 @@ namespace GodotTools
protected override void Dispose(bool disposing)
{
- base.Dispose(disposing);
-
- if (_exportPluginWeak != null)
+ if (disposing)
{
- // We need to dispose our export plugin before the editor destroys EditorSettings.
- // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
- // will be freed after EditorSettings already was, and its device polling thread
- // will try to access the EditorSettings singleton, resulting in null dereferencing.
- (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose();
+ if (IsInstanceValid(_exportPluginWeak))
+ {
+ // We need to dispose our export plugin before the editor destroys EditorSettings.
+ // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
+ // will be freed after EditorSettings already was, and its device polling thread
+ // will try to access the EditorSettings singleton, resulting in null dereferencing.
+ (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose();
- _exportPluginWeak.Dispose();
+ _exportPluginWeak.Dispose();
+ }
+
+ GodotIdeManager?.Dispose();
}
- GodotIdeManager?.Dispose();
+ base.Dispose(disposing);
}
public void OnBeforeSerialize()
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 746cb8a142..6c805c605d 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1407,6 +1407,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
}
+ // We generate a `GodotClassName` attribute if the engine class name is not the same as the
+ // generated C# class name. This allows introspection code to find the name associated with
+ // the class. If the attribute is not present, the C# class name can be used instead.
+ if (itype.name != itype.proxy_name) {
+ output << INDENT1 "[GodotClassName(\"" << itype.name << "\")]\n";
+ }
+
output.append(INDENT1 "public ");
if (itype.is_singleton) {
output.append("static partial class ");