summaryrefslogtreecommitdiff
path: root/modules/mono/editor/Godot.NET.Sdk
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono/editor/Godot.NET.Sdk')
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props20
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs113
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs113
-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.csproj4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs31
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs36
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs337
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs236
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs148
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs84
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs63
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs73
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs678
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs100
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs23
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs408
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs36
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs658
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs298
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs19
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs284
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs428
31 files changed, 4182 insertions, 88 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
index d1868f52ef..03a7dc453c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
EndProject
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
index 4e9e7184da..013b210ff4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -26,16 +26,8 @@
<None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" />
<!-- SdkPackageVersions.props -->
- <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk">
+ <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
</ItemGroup>
-
- <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
- <PropertyGroup>
- <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
- <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
- </PropertyGroup>
- <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
- </Target>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index 5a499742e9..59ce1da17b 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -77,12 +77,11 @@
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'macos' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
- <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'ios' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
- <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'web' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
<GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
</PropertyGroup>
@@ -95,21 +94,4 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
-
- <!-- Godot API references -->
- <ItemGroup>
- <!--
- TODO:
- We should consider a nuget package for reference assemblies. This is difficult because the
- Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
- -->
- <Reference Include="GodotSharp">
- <Private>false</Private>
- <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
- </Reference>
- <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
- <Private>false</Private>
- <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
- </Reference>
- </ItemGroup>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
index 397ede9644..aad4ea4553 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -19,4 +19,10 @@
<ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
<PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" />
</ItemGroup>
+
+ <!-- Godot API references -->
+ <ItemGroup Condition=" '$(DisableImplicitGodotSharpReferences)' != 'true' ">
+ <PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" />
+ <PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " />
+ </ItemGroup>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs
new file mode 100644
index 0000000000..764ba8f121
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs
@@ -0,0 +1,7 @@
+namespace Godot.SourceGenerators.Sample;
+
+public partial class EventSignals : Godot.Object
+{
+ [Signal]
+ public delegate void MySignalEventHandler(string str, int num);
+}
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..ac8d6473a6
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
@@ -0,0 +1,113 @@
+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 StringName[] field_StringNameArray = { "foo", "bar" };
+ [Export] private NodePath[] field_NodePathArray = { "foo", "bar" };
+ [Export] private RID[] field_RIDArray = { default, default, default };
+
+ // Variant
+ [Export] private Variant field_Variant = "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 Godot.Collections.Dictionary<string, bool> field_GodotGenericDictionary =
+ new() { { "foo", true }, { "bar", false } };
+
+ [Export]
+ private Godot.Collections.Array<int> field_GodotGenericArray =
+ new() { 0, 1, 2, 3, 4, 5, 6 };
+ }
+}
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..3020cfbc50
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
@@ -0,0 +1,113 @@
+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 StringName[] field_StringNameArray { get; set; } = { "foo", "bar" };
+ [Export] private NodePath[] field_NodePathArray { get; set; } = { "foo", "bar" };
+ [Export] private RID[] field_RIDArray { get; set; } = { default, default, default };
+
+ // Variant
+ [Export] private Variant property_Variant { 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 Godot.Collections.Dictionary<string, bool> property_GodotGenericDictionary { get; set; } =
+ new() { { "foo", true }, { "bar", false } };
+
+ [Export]
+ private Godot.Collections.Array<int> property_GodotGenericArray { get; set; } =
+ new() { 0, 1, 2, 3, 4, 5, 6 };
+ }
+}
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 24f7909861..8e78e0385d 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
@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.1</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<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/Methods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs
new file mode 100644
index 0000000000..618ba24abc
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.SourceGenerators.Sample;
+
+[SuppressMessage("ReSharper", "RedundantNameQualifier")]
+public partial class Methods : Godot.Object
+{
+ private void MethodWithOverload()
+ {
+ }
+
+ private void MethodWithOverload(int a)
+ {
+ }
+
+ private void MethodWithOverload(int a, int b)
+ {
+ }
+
+ // Should be ignored. The previous one is picked.
+ // ReSharper disable once UnusedMember.Local
+ private void MethodWithOverload(float a, float b)
+ {
+ }
+
+ // Generic methods should be ignored.
+ // ReSharper disable once UnusedMember.Local
+ private void GenericMethod<T>(T t)
+ {
+ }
+}
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
new file mode 100644
index 0000000000..a1667dbb8f
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
@@ -0,0 +1,36 @@
+#pragma warning disable CS0169
+
+namespace Godot.SourceGenerators.Sample
+{
+ public partial class ScriptBoilerplate : Node
+ {
+ private NodePath _nodePath;
+ private int _velocity;
+
+ public override void _Process(float delta)
+ {
+ _ = delta;
+
+ base._Process(delta);
+ }
+
+ public int Bazz(StringName name)
+ {
+ _ = name;
+ return 1;
+ }
+
+ public void IgnoreThisMethodWithByRefParams(ref int a)
+ {
+ _ = a;
+ }
+ }
+
+ partial struct OuterClass
+ {
+ public partial class NesterClass : RefCounted
+ {
+ public override Variant _Get(StringName property) => default;
+ }
+ }
+}
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 4867c986e6..e28788ec0b 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -1,5 +1,7 @@
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
namespace Godot.SourceGenerators
{
@@ -14,12 +16,11 @@ namespace Godot.SourceGenerators
"Missing partial modifier on declaration of type '" +
$"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
- string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
- "declared with the partial modifier or annotated with the " +
- $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
+ string description = $"{message}. Subclasses of '{GodotClasses.Object}' " +
+ "must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
- new DiagnosticDescriptor(id: "GODOT-G0001",
+ new DiagnosticDescriptor(id: "GD0001",
title: message,
messageFormat: message,
category: "Usage",
@@ -29,5 +30,333 @@ namespace Godot.SourceGenerators
cds.GetLocation(),
cds.SyntaxTree.FilePath));
}
+
+ public static void ReportNonPartialGodotScriptOuterClass(
+ GeneratorExecutionContext context,
+ TypeDeclarationSyntax outerTypeDeclSyntax
+ )
+ {
+ var outerSymbol = context.Compilation
+ .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
+ .GetDeclaredSymbol(outerTypeDeclSyntax);
+
+ string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ?
+ namedTypeSymbol.FullQualifiedName() :
+ "type not found";
+
+ string message =
+ $"Missing partial modifier on declaration of type '{fullQualifiedName}', " +
+ $"which contains one or more subclasses of '{GodotClasses.Object}'";
+
+ string description = $"{message}. Subclasses of '{GodotClasses.Object}' and their " +
+ "containing types must be declared with the partial modifier.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0002",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ 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: "GD0101",
+ 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: "GD0102",
+ 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: "GD0103",
+ 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: "GD0104",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportExportedMemberIsIndexer(
+ GeneratorExecutionContext context,
+ ISymbol exportedMemberSymbol
+ )
+ {
+ var locations = exportedMemberSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = $"Attempted to export indexer property: " +
+ $"'{exportedMemberSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Indexer properties can't be exported." +
+ " Remove the '[Export]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0105",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportSignalDelegateMissingSuffix(
+ GeneratorExecutionContext context,
+ INamedTypeSymbol delegateSymbol)
+ {
+ var locations = delegateSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = "The name of the delegate must end with 'EventHandler': " +
+ delegateSymbol.ToDisplayString() +
+ $". Did you mean '{delegateSymbol.Name}EventHandler'?";
+
+ string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0201",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportSignalParameterTypeNotSupported(
+ GeneratorExecutionContext context,
+ IParameterSymbol parameterSymbol)
+ {
+ var locations = parameterSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = "The parameter of the delegate signature of the signal " +
+ $"is not supported: '{parameterSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0202",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportSignalDelegateSignatureMustReturnVoid(
+ GeneratorExecutionContext context,
+ INamedTypeSymbol delegateSymbol)
+ {
+ var locations = delegateSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = "The delegate signature of the signal " +
+ $"must return void: '{delegateSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Return void or remove the '[Signal]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0203",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static readonly DiagnosticDescriptor GenericTypeArgumentMustBeVariantRule =
+ new DiagnosticDescriptor(id: "GD0301",
+ title: "The generic type argument must be a Variant compatible type",
+ messageFormat: "The generic type argument must be a Variant compatible type: {0}",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.");
+
+ public static void ReportGenericTypeArgumentMustBeVariant(
+ SyntaxNodeAnalysisContext context,
+ SyntaxNode typeArgumentSyntax,
+ ISymbol typeArgumentSymbol)
+ {
+ string message = "The generic type argument " +
+ $"must be a Variant compatible type: '{typeArgumentSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Use a Variant compatible type as the generic type argument.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0301",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ typeArgumentSyntax.GetLocation(),
+ typeArgumentSyntax.SyntaxTree.FilePath));
+ }
+
+ public static readonly DiagnosticDescriptor GenericTypeParameterMustBeVariantAnnotatedRule =
+ new DiagnosticDescriptor(id: "GD0302",
+ title: "The generic type parameter must be annotated with the MustBeVariant attribute",
+ messageFormat: "The generic type argument must be a Variant type: {0}",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.");
+
+ public static void ReportGenericTypeParameterMustBeVariantAnnotated(
+ SyntaxNodeAnalysisContext context,
+ SyntaxNode typeArgumentSyntax,
+ ISymbol typeArgumentSymbol)
+ {
+ string message = "The generic type parameter must be annotated with the MustBeVariant attribute";
+
+ string description = $"{message}. Add the MustBeVariant attribute to the generic type parameter.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0302",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ typeArgumentSyntax.GetLocation(),
+ typeArgumentSyntax.SyntaxTree.FilePath));
+ }
+
+ public static readonly DiagnosticDescriptor TypeArgumentParentSymbolUnhandledRule =
+ new DiagnosticDescriptor(id: "GD0303",
+ title: "The generic type parameter must be annotated with the MustBeVariant attribute",
+ messageFormat: "The generic type argument must be a Variant type: {0}",
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.");
+
+ public static void ReportTypeArgumentParentSymbolUnhandled(
+ SyntaxNodeAnalysisContext context,
+ SyntaxNode typeArgumentSyntax,
+ ISymbol parentSymbol)
+ {
+ string message = $"Symbol '{parentSymbol.ToDisplayString()}' parent of a type argument " +
+ "that must be Variant compatible was not handled.";
+
+ string description = $"{message}. Handle type arguments that are children of the unhandled symbol type.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GD0303",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ typeArgumentSyntax.GetLocation(),
+ typeArgumentSyntax.SyntaxTree.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 e16f72f43a..de3b6c862a 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -1,4 +1,6 @@
+using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -13,30 +15,65 @@ namespace Godot.SourceGenerators
) => context.AnalyzerConfigOptions.GlobalOptions
.TryGetValue("build_property." + property, out value);
- private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
- {
- if (symbol == null)
- return false;
+ public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
+ => context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
+ toggle != null &&
+ toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
+
+ public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
+ => context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
+ toggle != null &&
+ toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
- while (true)
+ public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
+ {
+ 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
@@ -47,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;
@@ -69,21 +106,182 @@ namespace Godot.SourceGenerators
}
}
- public static bool IsPartial(this ClassDeclarationSyntax cds)
+ public static bool IsNested(this TypeDeclarationSyntax cds)
+ => cds.Parent is TypeDeclarationSyntax;
+
+ public static bool IsPartial(this TypeDeclarationSyntax cds)
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
- public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
- => symbol.GetAttributes().Any(attr =>
- attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
+ public static bool AreAllOuterTypesPartial(
+ this TypeDeclarationSyntax cds,
+ out TypeDeclarationSyntax? typeMissingPartial
+ )
+ {
+ SyntaxNode? outerSyntaxNode = cds.Parent;
+
+ while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax)
+ {
+ if (!outerTypeDeclSyntax.IsPartial())
+ {
+ typeMissingPartial = outerTypeDeclSyntax;
+ return false;
+ }
+
+ outerSyntaxNode = outerSyntaxNode.Parent;
+ }
+
+ typeMissingPartial = null;
+ return true;
+ }
+
+ public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol)
+ {
+ string? keyword = namedTypeSymbol.DeclaringSyntaxReferences
+ .OfType<TypeDeclarationSyntax>().FirstOrDefault()?
+ .Keyword.Text;
+
+ return keyword ?? namedTypeSymbol.TypeKind switch
+ {
+ TypeKind.Interface => "interface",
+ TypeKind.Struct => "struct",
+ _ => "class"
+ };
+ }
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
- public static string FullQualifiedName(this INamedTypeSymbol symbol)
+ 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 IsGodotSignalAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.SignalAttr;
+
+ public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.MustBeVariantAttr;
+
+ public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.GodotClassNameAttr;
+
+ public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.SystemFlagsAttr;
+
+ public static GodotMethodData? HasGodotCompatibleSignature(
+ this IMethodSymbol method,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ if (method.IsGenericMethod)
+ return null;
+
+ var retSymbol = method.ReturnType;
+ var retType = method.ReturnsVoid ?
+ null :
+ MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
+
+ if (retType == null && !method.ReturnsVoid)
+ return null;
+
+ 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)
+ return null; // Ignore incompatible method
+
+ return new GodotMethodData(method, paramTypes, parameters
+ .Select(p => p.Type).ToImmutableArray(), retType, retSymbol);
+ }
+
+ public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
+ this IEnumerable<IMethodSymbol> methods,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var method in methods)
+ {
+ var methodData = HasGodotCompatibleSignature(method, typeCache);
+
+ if (methodData != null)
+ yield return methodData.Value;
+ }
+ }
+
+ public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
+ this IEnumerable<IPropertySymbol> properties,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var property in properties)
+ {
+ // 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 || property.IsReadOnly)
+ 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)
+ {
+ // 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)
+ continue;
+
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
+
+ if (marshalType == null)
+ continue;
+
+ yield return new GodotFieldData(field, marshalType.Value);
+ }
+ }
+
+ public static string Path(this Location location)
+ => location.SourceTree?.GetLineSpan(location.SourceSpan).Path
+ ?? location.GetLineSpan().Path;
+
+ public static int StartLine(this Location location)
+ => location.SourceTree?.GetLineSpan(location.SourceSpan).StartLinePosition.Line
+ ?? location.GetLineSpan().StartLinePosition.Line;
}
}
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 11d8e0f72b..f51b5970c3 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>
@@ -21,21 +21,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
- <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<!-- Package the props file -->
- <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
+ <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="true" />
</ItemGroup>
-
- <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
- <PropertyGroup>
- <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
- <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
- </PropertyGroup>
- <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
- </Target>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
index f9b47ad5b1..7881ed0a8c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
@@ -2,6 +2,7 @@
<ItemGroup>
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
<CompilerVisibleProperty Include="GodotProjectDir" />
- <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
+ <CompilerVisibleProperty Include="GodotSourceGenerators" />
+ <CompilerVisibleProperty Include="IsGodotToolsProject" />
</ItemGroup>
</Project>
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 29e41d155a..1d8ddbabf2 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -3,7 +3,14 @@ namespace Godot.SourceGenerators
public static class GodotClasses
{
public const string Object = "Godot.Object";
- public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
+ public const string ExportAttr = "Godot.ExportAttribute";
+ public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute";
+ public const string ExportGroupAttr = "Godot.ExportGroupAttribute";
+ public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute";
+ public const string SignalAttr = "Godot.SignalAttribute";
+ public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute";
+ 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..1a25d684a0
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -0,0 +1,148 @@
+using System;
+
+namespace Godot.SourceGenerators
+{
+ // TODO: May need to think about compatibility here. Could Godot change these values between minor versions?
+
+ 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
+ }
+
+ public enum MethodFlags
+ {
+ Normal = 1,
+ Editor = 2,
+ Const = 4,
+ Virtual = 8,
+ Vararg = 16,
+ Static = 32,
+ ObjectCore = 64,
+ Default = 1
+ }
+}
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..db395e21cb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
@@ -0,0 +1,84 @@
+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, ITypeSymbol? retSymbol)
+ {
+ Method = method;
+ ParamTypes = paramTypes;
+ ParamTypeSymbols = paramTypeSymbols;
+ RetType = retType;
+ RetSymbol = retSymbol;
+ }
+
+ public IMethodSymbol Method { get; }
+ public ImmutableArray<MarshalType> ParamTypes { get; }
+ public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
+ public MarshalType? RetType { get; }
+ public ITypeSymbol? RetSymbol { get; }
+ }
+
+ public struct GodotSignalDelegateData
+ {
+ public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData)
+ {
+ Name = name;
+ DelegateSymbol = delegateSymbol;
+ InvokeMethodData = invokeMethodData;
+ }
+
+ public string Name { get; }
+ public INamedTypeSymbol DelegateSymbol { get; }
+ public GodotMethodData InvokeMethodData { 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; }
+ }
+
+ public struct GodotPropertyOrFieldData
+ {
+ public GodotPropertyOrFieldData(ISymbol symbol, MarshalType type)
+ {
+ Symbol = symbol;
+ Type = type;
+ }
+
+ public GodotPropertyOrFieldData(GodotPropertyData propertyData)
+ : this(propertyData.PropertySymbol, propertyData.Type)
+ {
+ }
+
+ public GodotPropertyOrFieldData(GodotFieldData fieldData)
+ : this(fieldData.FieldSymbol, fieldData.Type)
+ {
+ }
+
+ public ISymbol Symbol { get; }
+ public MarshalType Type { get; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
new file mode 100644
index 0000000000..7ec3f88e5d
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs
@@ -0,0 +1,63 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class GodotPluginsInitializerGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.IsGodotToolsProject())
+ return;
+
+ string source =
+ @"using System;
+using System.Runtime.InteropServices;
+using Godot.Bridge;
+using Godot.NativeInterop;
+
+namespace GodotPlugins.Game
+{
+ internal static partial class Main
+ {
+ [UnmanagedCallersOnly(EntryPoint = ""godotsharp_game_main_init"")]
+ private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPtr outManagedCallbacks,
+ IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
+ {
+ try
+ {
+ DllImportResolver dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
+
+ var coreApiAssembly = typeof(Godot.Object).Assembly;
+
+ NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver);
+
+ NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
+
+ ManagedCallbacks.Create(outManagedCallbacks);
+
+ ScriptManagerBridge.LookupScriptsInAssembly(typeof(GodotPlugins.Game.Main).Assembly);
+
+ return godot_bool.True;
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ return false.ToGodotBool();
+ }
+ }
+ }
+}
+";
+
+ context.AddSource("GodotPlugins.Game_Generated",
+ SourceText.From(source, Encoding.UTF8));
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
new file mode 100644
index 0000000000..15f5803bf0
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
@@ -0,0 +1,73 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.SourceGenerators
+{
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public enum MarshalType
+ {
+ Boolean,
+ Char,
+ SByte,
+ Int16,
+ Int32,
+ Int64,
+ Byte,
+ UInt16,
+ UInt32,
+ UInt64,
+ Single,
+ Double,
+ String,
+
+ // Godot structs
+ Vector2,
+ Vector2i,
+ Rect2,
+ Rect2i,
+ Transform2D,
+ Vector3,
+ Vector3i,
+ Basis,
+ Quaternion,
+ Transform3D,
+ Vector4,
+ Vector4i,
+ Projection,
+ AABB,
+ Color,
+ Plane,
+ Callable,
+ SignalInfo,
+
+ // Enums
+ Enum,
+
+ // Arrays
+ ByteArray,
+ Int32Array,
+ Int64Array,
+ Float32Array,
+ Float64Array,
+ StringArray,
+ Vector2Array,
+ Vector3Array,
+ ColorArray,
+ GodotObjectOrDerivedArray,
+ SystemArrayOfStringName,
+ SystemArrayOfNodePath,
+ SystemArrayOfRID,
+
+ // Variant
+ Variant,
+
+ // Classes
+ GodotObjectOrDerived,
+ StringName,
+ NodePath,
+ RID,
+ GodotDictionary,
+ GodotArray,
+ GodotGenericDictionary,
+ GodotGenericArray,
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
new file mode 100644
index 0000000000..efdd50098e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
@@ -0,0 +1,678 @@
+using System;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Godot.SourceGenerators
+{
+ internal static class MarshalUtils
+ {
+ public class TypeCache
+ {
+ public INamedTypeSymbol GodotObjectType { get; }
+
+ public TypeCache(Compilation compilation)
+ {
+ INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName)
+ {
+ return compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ??
+ throw new InvalidOperationException($"Type not found: '{fullyQualifiedMetadataName}'.");
+ }
+
+ GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object");
+ }
+ }
+
+ 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.Float32Array => VariantType.PackedFloat32Array,
+ MarshalType.Float64Array => VariantType.PackedFloat64Array,
+ MarshalType.StringArray => VariantType.PackedStringArray,
+ MarshalType.Vector2Array => VariantType.PackedVector2Array,
+ MarshalType.Vector3Array => VariantType.PackedVector3Array,
+ MarshalType.ColorArray => VariantType.PackedColorArray,
+ MarshalType.GodotObjectOrDerivedArray => VariantType.Array,
+ MarshalType.SystemArrayOfStringName => VariantType.Array,
+ MarshalType.SystemArrayOfNodePath => VariantType.Array,
+ MarshalType.SystemArrayOfRID => VariantType.Array,
+ MarshalType.Variant => 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.GodotGenericDictionary => VariantType.Dictionary,
+ MarshalType.GodotGenericArray => VariantType.Array,
+ _ => null
+ };
+
+ public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache)
+ {
+ var specialType = type.SpecialType;
+
+ switch (specialType)
+ {
+ case SpecialType.System_Boolean:
+ return MarshalType.Boolean;
+ case SpecialType.System_Char:
+ return MarshalType.Char;
+ case SpecialType.System_SByte:
+ return MarshalType.SByte;
+ case SpecialType.System_Int16:
+ return MarshalType.Int16;
+ case SpecialType.System_Int32:
+ return MarshalType.Int32;
+ case SpecialType.System_Int64:
+ return MarshalType.Int64;
+ case SpecialType.System_Byte:
+ return MarshalType.Byte;
+ case SpecialType.System_UInt16:
+ return MarshalType.UInt16;
+ case SpecialType.System_UInt32:
+ return MarshalType.UInt32;
+ case SpecialType.System_UInt64:
+ return MarshalType.UInt64;
+ case SpecialType.System_Single:
+ return MarshalType.Single;
+ case SpecialType.System_Double:
+ return MarshalType.Double;
+ case SpecialType.System_String:
+ return MarshalType.String;
+ default:
+ {
+ var typeKind = type.TypeKind;
+
+ if (typeKind == TypeKind.Enum)
+ return MarshalType.Enum;
+
+ if (typeKind == TypeKind.Struct)
+ {
+ if (type.ContainingAssembly.Name == "GodotSharp" &&
+ type.ContainingNamespace.Name == "Godot")
+ {
+ 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,
+ { Name: "Variant" } => MarshalType.Variant,
+ _ => null
+ };
+ }
+ }
+ else if (typeKind == TypeKind.Array)
+ {
+ var arrayType = (IArrayTypeSymbol)type;
+ var elementType = arrayType.ElementType;
+
+ switch (elementType.SpecialType)
+ {
+ case SpecialType.System_Byte:
+ return MarshalType.ByteArray;
+ case SpecialType.System_Int32:
+ return MarshalType.Int32Array;
+ case SpecialType.System_Int64:
+ return MarshalType.Int64Array;
+ case SpecialType.System_Single:
+ return MarshalType.Float32Array;
+ case SpecialType.System_Double:
+ return MarshalType.Float64Array;
+ case SpecialType.System_String:
+ return MarshalType.StringArray;
+ }
+
+ if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType))
+ return MarshalType.GodotObjectOrDerivedArray;
+
+ if (elementType.ContainingAssembly.Name == "GodotSharp" &&
+ elementType.ContainingNamespace.Name == "Godot")
+ {
+ switch (elementType)
+ {
+ case { Name: "Vector2" }:
+ return MarshalType.Vector2Array;
+ case { Name: "Vector3" }:
+ return MarshalType.Vector3Array;
+ case { Name: "Color" }:
+ return MarshalType.ColorArray;
+ case { Name: "StringName" }:
+ return MarshalType.SystemArrayOfStringName;
+ case { Name: "NodePath" }:
+ return MarshalType.SystemArrayOfNodePath;
+ case { Name: "RID" }:
+ return MarshalType.SystemArrayOfRID;
+ }
+ }
+
+ return null;
+ }
+ else
+ {
+ if (type.SimpleDerivesFrom(typeCache.GodotObjectType))
+ return MarshalType.GodotObjectOrDerived;
+
+ if (type.ContainingAssembly.Name == "GodotSharp")
+ {
+ switch (type.ContainingNamespace.Name)
+ {
+ case "Godot":
+ return type switch
+ {
+ { Name: "StringName" } => MarshalType.StringName,
+ { Name: "NodePath" } => MarshalType.NodePath,
+ _ => null
+ };
+ case "Collections"
+ when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections":
+ return type switch
+ {
+ { Name: "Dictionary" } =>
+ type is INamedTypeSymbol { IsGenericType: false } ?
+ MarshalType.GodotDictionary :
+ MarshalType.GodotGenericDictionary,
+ { Name: "Array" } =>
+ type is INamedTypeSymbol { IsGenericType: false } ?
+ MarshalType.GodotArray :
+ MarshalType.GodotGenericArray,
+ _ => null
+ };
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType)
+ {
+ while (type != null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType))
+ return true;
+
+ type = type.BaseType;
+ }
+
+ 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;
+ }
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b)
+ => source.Append(a).Append(b);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b, string c)
+ => source.Append(a).Append(b).Append(c);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b,
+ string c, string d)
+ => source.Append(a).Append(b).Append(c).Append(d);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b,
+ string c, string d, string e)
+ => source.Append(a).Append(b).Append(c).Append(d).Append(e);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b,
+ string c, string d, string e, string f)
+ => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b,
+ string c, string d, string e, string f, string g)
+ => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g);
+
+ private static StringBuilder Append(this StringBuilder source, string a, string b,
+ string c, string d, string e, string f, string g, string h)
+ => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g).Append(h);
+
+ private const string VariantUtils = "global::Godot.NativeInterop.VariantUtils";
+
+ public static StringBuilder AppendNativeVariantToManagedExpr(this StringBuilder source,
+ string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType)
+ {
+ return marshalType switch
+ {
+ MarshalType.Boolean =>
+ source.Append(VariantUtils, ".ConvertToBool(", inputExpr, ")"),
+ MarshalType.Char =>
+ source.Append("(char)", VariantUtils, ".ConvertToUInt16(", inputExpr, ")"),
+ MarshalType.SByte =>
+ source.Append(VariantUtils, ".ConvertToInt8(", inputExpr, ")"),
+ MarshalType.Int16 =>
+ source.Append(VariantUtils, ".ConvertToInt16(", inputExpr, ")"),
+ MarshalType.Int32 =>
+ source.Append(VariantUtils, ".ConvertToInt32(", inputExpr, ")"),
+ MarshalType.Int64 =>
+ source.Append(VariantUtils, ".ConvertToInt64(", inputExpr, ")"),
+ MarshalType.Byte =>
+ source.Append(VariantUtils, ".ConvertToUInt8(", inputExpr, ")"),
+ MarshalType.UInt16 =>
+ source.Append(VariantUtils, ".ConvertToUInt16(", inputExpr, ")"),
+ MarshalType.UInt32 =>
+ source.Append(VariantUtils, ".ConvertToUInt32(", inputExpr, ")"),
+ MarshalType.UInt64 =>
+ source.Append(VariantUtils, ".ConvertToUInt64(", inputExpr, ")"),
+ MarshalType.Single =>
+ source.Append(VariantUtils, ".ConvertToFloat32(", inputExpr, ")"),
+ MarshalType.Double =>
+ source.Append(VariantUtils, ".ConvertToFloat64(", inputExpr, ")"),
+ MarshalType.String =>
+ source.Append(VariantUtils, ".ConvertToStringObject(", inputExpr, ")"),
+ MarshalType.Vector2 =>
+ source.Append(VariantUtils, ".ConvertToVector2(", inputExpr, ")"),
+ MarshalType.Vector2i =>
+ source.Append(VariantUtils, ".ConvertToVector2i(", inputExpr, ")"),
+ MarshalType.Rect2 =>
+ source.Append(VariantUtils, ".ConvertToRect2(", inputExpr, ")"),
+ MarshalType.Rect2i =>
+ source.Append(VariantUtils, ".ConvertToRect2i(", inputExpr, ")"),
+ MarshalType.Transform2D =>
+ source.Append(VariantUtils, ".ConvertToTransform2D(", inputExpr, ")"),
+ MarshalType.Vector3 =>
+ source.Append(VariantUtils, ".ConvertToVector3(", inputExpr, ")"),
+ MarshalType.Vector3i =>
+ source.Append(VariantUtils, ".ConvertToVector3i(", inputExpr, ")"),
+ MarshalType.Basis =>
+ source.Append(VariantUtils, ".ConvertToBasis(", inputExpr, ")"),
+ MarshalType.Quaternion =>
+ source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"),
+ MarshalType.Transform3D =>
+ source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"),
+ MarshalType.Vector4 =>
+ source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"),
+ MarshalType.Vector4i =>
+ source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"),
+ MarshalType.Projection =>
+ source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"),
+ MarshalType.AABB =>
+ source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"),
+ MarshalType.Color =>
+ source.Append(VariantUtils, ".ConvertToColor(", inputExpr, ")"),
+ MarshalType.Plane =>
+ source.Append(VariantUtils, ".ConvertToPlane(", inputExpr, ")"),
+ MarshalType.Callable =>
+ source.Append(VariantUtils, ".ConvertToCallableManaged(", inputExpr, ")"),
+ MarshalType.SignalInfo =>
+ source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"),
+ MarshalType.Enum =>
+ source.Append("(", typeSymbol.FullQualifiedName(),
+ ")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"),
+ MarshalType.ByteArray =>
+ source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Int32Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedInt32ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Int64Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedInt64ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Float32Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedFloat32ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Float64Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedFloat64ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.StringArray =>
+ source.Append(VariantUtils, ".ConvertAsPackedStringArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Vector2Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedVector2ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.Vector3Array =>
+ source.Append(VariantUtils, ".ConvertAsPackedVector3ArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.ColorArray =>
+ source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"),
+ MarshalType.GodotObjectOrDerivedArray =>
+ source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<",
+ ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"),
+ MarshalType.SystemArrayOfStringName =>
+ source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"),
+ MarshalType.SystemArrayOfNodePath =>
+ source.Append(VariantUtils, ".ConvertToSystemArrayOfNodePath(", inputExpr, ")"),
+ MarshalType.SystemArrayOfRID =>
+ source.Append(VariantUtils, ".ConvertToSystemArrayOfRID(", inputExpr, ")"),
+ MarshalType.Variant =>
+ source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"),
+ MarshalType.GodotObjectOrDerived =>
+ source.Append("(", typeSymbol.FullQualifiedName(),
+ ")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"),
+ MarshalType.StringName =>
+ source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"),
+ MarshalType.NodePath =>
+ source.Append(VariantUtils, ".ConvertToNodePathObject(", inputExpr, ")"),
+ MarshalType.RID =>
+ source.Append(VariantUtils, ".ConvertToRID(", inputExpr, ")"),
+ MarshalType.GodotDictionary =>
+ source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"),
+ MarshalType.GodotArray =>
+ source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"),
+ MarshalType.GodotGenericDictionary =>
+ source.Append(VariantUtils, ".ConvertToDictionaryObject<",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"),
+ MarshalType.GodotGenericArray =>
+ source.Append(VariantUtils, ".ConvertToArrayObject<",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"),
+ _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
+ "Received unexpected marshal type")
+ };
+ }
+
+ public static StringBuilder AppendManagedToNativeVariantExpr(
+ this StringBuilder source, string inputExpr, MarshalType marshalType)
+ {
+ return marshalType switch
+ {
+ MarshalType.Boolean =>
+ source.Append(VariantUtils, ".CreateFromBool(", inputExpr, ")"),
+ MarshalType.Char =>
+ source.Append(VariantUtils, ".CreateFromInt((ushort)", inputExpr, ")"),
+ MarshalType.SByte =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.Int16 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.Int32 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.Int64 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.Byte =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.UInt16 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.UInt32 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.UInt64 =>
+ source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"),
+ MarshalType.Single =>
+ source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"),
+ MarshalType.Double =>
+ source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"),
+ MarshalType.String =>
+ source.Append(VariantUtils, ".CreateFromString(", inputExpr, ")"),
+ MarshalType.Vector2 =>
+ source.Append(VariantUtils, ".CreateFromVector2(", inputExpr, ")"),
+ MarshalType.Vector2i =>
+ source.Append(VariantUtils, ".CreateFromVector2i(", inputExpr, ")"),
+ MarshalType.Rect2 =>
+ source.Append(VariantUtils, ".CreateFromRect2(", inputExpr, ")"),
+ MarshalType.Rect2i =>
+ source.Append(VariantUtils, ".CreateFromRect2i(", inputExpr, ")"),
+ MarshalType.Transform2D =>
+ source.Append(VariantUtils, ".CreateFromTransform2D(", inputExpr, ")"),
+ MarshalType.Vector3 =>
+ source.Append(VariantUtils, ".CreateFromVector3(", inputExpr, ")"),
+ MarshalType.Vector3i =>
+ source.Append(VariantUtils, ".CreateFromVector3i(", inputExpr, ")"),
+ MarshalType.Basis =>
+ source.Append(VariantUtils, ".CreateFromBasis(", inputExpr, ")"),
+ MarshalType.Quaternion =>
+ source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"),
+ MarshalType.Transform3D =>
+ source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"),
+ MarshalType.Vector4 =>
+ source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"),
+ MarshalType.Vector4i =>
+ source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"),
+ MarshalType.Projection =>
+ source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"),
+ MarshalType.AABB =>
+ source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"),
+ MarshalType.Color =>
+ source.Append(VariantUtils, ".CreateFromColor(", inputExpr, ")"),
+ MarshalType.Plane =>
+ source.Append(VariantUtils, ".CreateFromPlane(", inputExpr, ")"),
+ MarshalType.Callable =>
+ source.Append(VariantUtils, ".CreateFromCallable(", inputExpr, ")"),
+ MarshalType.SignalInfo =>
+ source.Append(VariantUtils, ".CreateFromSignalInfo(", inputExpr, ")"),
+ MarshalType.Enum =>
+ source.Append(VariantUtils, ".CreateFromInt((int)", inputExpr, ")"),
+ MarshalType.ByteArray =>
+ source.Append(VariantUtils, ".CreateFromPackedByteArray(", inputExpr, ")"),
+ MarshalType.Int32Array =>
+ source.Append(VariantUtils, ".CreateFromPackedInt32Array(", inputExpr, ")"),
+ MarshalType.Int64Array =>
+ source.Append(VariantUtils, ".CreateFromPackedInt64Array(", inputExpr, ")"),
+ MarshalType.Float32Array =>
+ source.Append(VariantUtils, ".CreateFromPackedFloat32Array(", inputExpr, ")"),
+ MarshalType.Float64Array =>
+ source.Append(VariantUtils, ".CreateFromPackedFloat64Array(", inputExpr, ")"),
+ MarshalType.StringArray =>
+ source.Append(VariantUtils, ".CreateFromPackedStringArray(", inputExpr, ")"),
+ MarshalType.Vector2Array =>
+ source.Append(VariantUtils, ".CreateFromPackedVector2Array(", inputExpr, ")"),
+ MarshalType.Vector3Array =>
+ source.Append(VariantUtils, ".CreateFromPackedVector3Array(", inputExpr, ")"),
+ MarshalType.ColorArray =>
+ source.Append(VariantUtils, ".CreateFromPackedColorArray(", inputExpr, ")"),
+ MarshalType.GodotObjectOrDerivedArray =>
+ source.Append(VariantUtils, ".CreateFromSystemArrayOfGodotObject(", inputExpr, ")"),
+ MarshalType.SystemArrayOfStringName =>
+ source.Append(VariantUtils, ".CreateFromSystemArrayOfStringName(", inputExpr, ")"),
+ MarshalType.SystemArrayOfNodePath =>
+ source.Append(VariantUtils, ".CreateFromSystemArrayOfNodePath(", inputExpr, ")"),
+ MarshalType.SystemArrayOfRID =>
+ source.Append(VariantUtils, ".CreateFromSystemArrayOfRID(", inputExpr, ")"),
+ MarshalType.Variant =>
+ source.Append(inputExpr, ".CopyNativeVariant()"),
+ MarshalType.GodotObjectOrDerived =>
+ source.Append(VariantUtils, ".CreateFromGodotObject(", inputExpr, ")"),
+ MarshalType.StringName =>
+ source.Append(VariantUtils, ".CreateFromStringName(", inputExpr, ")"),
+ MarshalType.NodePath =>
+ source.Append(VariantUtils, ".CreateFromNodePath(", inputExpr, ")"),
+ MarshalType.RID =>
+ source.Append(VariantUtils, ".CreateFromRID(", inputExpr, ")"),
+ MarshalType.GodotDictionary =>
+ source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"),
+ MarshalType.GodotArray =>
+ source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"),
+ MarshalType.GodotGenericDictionary =>
+ source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"),
+ MarshalType.GodotGenericArray =>
+ source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"),
+ _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
+ "Received unexpected marshal type")
+ };
+ }
+
+ public static StringBuilder AppendVariantToManagedExpr(this StringBuilder source,
+ string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType)
+ {
+ return marshalType switch
+ {
+ MarshalType.Boolean => source.Append(inputExpr, ".AsBool()"),
+ MarshalType.Char => source.Append(inputExpr, ".AsChar()"),
+ MarshalType.SByte => source.Append(inputExpr, ".AsSByte()"),
+ MarshalType.Int16 => source.Append(inputExpr, ".AsInt16()"),
+ MarshalType.Int32 => source.Append(inputExpr, ".AsInt32()"),
+ MarshalType.Int64 => source.Append(inputExpr, ".AsInt64()"),
+ MarshalType.Byte => source.Append(inputExpr, ".AsByte()"),
+ MarshalType.UInt16 => source.Append(inputExpr, ".AsUInt16()"),
+ MarshalType.UInt32 => source.Append(inputExpr, ".AsUInt32()"),
+ MarshalType.UInt64 => source.Append(inputExpr, ".AsUInt64()"),
+ MarshalType.Single => source.Append(inputExpr, ".AsSingle()"),
+ MarshalType.Double => source.Append(inputExpr, ".AsDouble()"),
+ MarshalType.String => source.Append(inputExpr, ".AsString()"),
+ MarshalType.Vector2 => source.Append(inputExpr, ".AsVector2()"),
+ MarshalType.Vector2i => source.Append(inputExpr, ".AsVector2i()"),
+ MarshalType.Rect2 => source.Append(inputExpr, ".AsRect2()"),
+ MarshalType.Rect2i => source.Append(inputExpr, ".AsRect2i()"),
+ MarshalType.Transform2D => source.Append(inputExpr, ".AsTransform2D()"),
+ MarshalType.Vector3 => source.Append(inputExpr, ".AsVector3()"),
+ MarshalType.Vector3i => source.Append(inputExpr, ".AsVector3i()"),
+ MarshalType.Basis => source.Append(inputExpr, ".AsBasis()"),
+ MarshalType.Quaternion => source.Append(inputExpr, ".AsQuaternion()"),
+ MarshalType.Transform3D => source.Append(inputExpr, ".AsTransform3D()"),
+ MarshalType.Vector4 => source.Append(inputExpr, ".AsVector4()"),
+ MarshalType.Vector4i => source.Append(inputExpr, ".AsVector4i()"),
+ MarshalType.Projection => source.Append(inputExpr, ".AsProjection()"),
+ MarshalType.AABB => source.Append(inputExpr, ".AsAABB()"),
+ MarshalType.Color => source.Append(inputExpr, ".AsColor()"),
+ MarshalType.Plane => source.Append(inputExpr, ".AsPlane()"),
+ MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"),
+ MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"),
+ MarshalType.Enum =>
+ source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"),
+ MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"),
+ MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"),
+ MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"),
+ MarshalType.Float32Array => source.Append(inputExpr, ".AsFloat32Array()"),
+ MarshalType.Float64Array => source.Append(inputExpr, ".AsFloat64Array()"),
+ MarshalType.StringArray => source.Append(inputExpr, ".AsStringArray()"),
+ MarshalType.Vector2Array => source.Append(inputExpr, ".AsVector2Array()"),
+ MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"),
+ MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"),
+ MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<",
+ ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"),
+ MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"),
+ MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"),
+ MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"),
+ MarshalType.Variant => source.Append(inputExpr),
+ MarshalType.GodotObjectOrDerived => source.Append("(",
+ typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"),
+ MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"),
+ MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"),
+ MarshalType.RID => source.Append(inputExpr, ".AsRID()"),
+ MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"),
+ MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"),
+ MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"),
+ MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<",
+ ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"),
+ _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
+ "Received unexpected marshal type")
+ };
+ }
+
+ public static StringBuilder AppendManagedToVariantExpr(this StringBuilder source,
+ string inputExpr, MarshalType marshalType)
+ {
+ switch (marshalType)
+ {
+ case MarshalType.Boolean:
+ case MarshalType.Char:
+ case MarshalType.SByte:
+ case MarshalType.Int16:
+ case MarshalType.Int32:
+ case MarshalType.Int64:
+ case MarshalType.Byte:
+ case MarshalType.UInt16:
+ case MarshalType.UInt32:
+ case MarshalType.UInt64:
+ case MarshalType.Single:
+ case MarshalType.Double:
+ case MarshalType.String:
+ case MarshalType.Vector2:
+ case MarshalType.Vector2i:
+ case MarshalType.Rect2:
+ case MarshalType.Rect2i:
+ case MarshalType.Transform2D:
+ case MarshalType.Vector3:
+ case MarshalType.Vector3i:
+ case MarshalType.Basis:
+ case MarshalType.Quaternion:
+ case MarshalType.Transform3D:
+ case MarshalType.Vector4:
+ case MarshalType.Vector4i:
+ case MarshalType.Projection:
+ case MarshalType.AABB:
+ case MarshalType.Color:
+ case MarshalType.Plane:
+ case MarshalType.Callable:
+ case MarshalType.SignalInfo:
+ case MarshalType.ByteArray:
+ case MarshalType.Int32Array:
+ case MarshalType.Int64Array:
+ case MarshalType.Float32Array:
+ case MarshalType.Float64Array:
+ case MarshalType.StringArray:
+ case MarshalType.Vector2Array:
+ case MarshalType.Vector3Array:
+ case MarshalType.ColorArray:
+ case MarshalType.GodotObjectOrDerivedArray:
+ case MarshalType.SystemArrayOfStringName:
+ case MarshalType.SystemArrayOfNodePath:
+ case MarshalType.SystemArrayOfRID:
+ case MarshalType.GodotObjectOrDerived:
+ case MarshalType.StringName:
+ case MarshalType.NodePath:
+ case MarshalType.RID:
+ case MarshalType.GodotDictionary:
+ case MarshalType.GodotArray:
+ case MarshalType.GodotGenericDictionary:
+ case MarshalType.GodotGenericArray:
+ return source.Append("Variant.CreateFrom(", inputExpr, ")");
+ case MarshalType.Enum:
+ return source.Append("Variant.CreateFrom((long)", inputExpr, ")");
+ case MarshalType.Variant:
+ return source.Append(inputExpr);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType,
+ "Received unexpected marshal type");
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs
new file mode 100644
index 0000000000..81c6f2b7d5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Godot.SourceGenerators
+{
+ internal struct MethodInfo
+ {
+ public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags,
+ List<PropertyInfo>? arguments,
+ List<string?>? defaultArguments)
+ {
+ Name = name;
+ ReturnVal = returnVal;
+ Flags = flags;
+ Arguments = arguments;
+ DefaultArguments = defaultArguments;
+ }
+
+ public string Name { get; }
+ public PropertyInfo ReturnVal { get; }
+ public MethodFlags Flags { get; }
+ public List<PropertyInfo>? Arguments { get; }
+ public List<string?>? DefaultArguments { get; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
new file mode 100644
index 0000000000..7aaadb27be
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
@@ -0,0 +1,100 @@
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Godot.SourceGenerators
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class MustBeVariantAnalyzer : DiagnosticAnalyzer
+ {
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
+ => ImmutableArray.Create(
+ Common.GenericTypeArgumentMustBeVariantRule,
+ Common.GenericTypeParameterMustBeVariantAnnotatedRule,
+ Common.TypeArgumentParentSymbolUnhandledRule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.TypeArgumentList);
+ }
+
+ private void AnalyzeNode(SyntaxNodeAnalysisContext context)
+ {
+ var typeArgListSyntax = (TypeArgumentListSyntax)context.Node;
+
+ // Method invocation or variable declaration that contained the type arguments
+ var parentSyntax = context.Node.Parent;
+ Debug.Assert(parentSyntax != null);
+
+ var sm = context.SemanticModel;
+
+ var typeCache = new MarshalUtils.TypeCache(context.Compilation);
+
+ for (int i = 0; i < typeArgListSyntax.Arguments.Count; i++)
+ {
+ var typeSyntax = typeArgListSyntax.Arguments[i];
+ var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol;
+ Debug.Assert(typeSymbol != null);
+
+ var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol;
+
+ if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i))
+ {
+ return;
+ }
+
+ if (typeSymbol is ITypeParameterSymbol typeParamSymbol)
+ {
+ if (!typeParamSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false))
+ {
+ Common.ReportGenericTypeParameterMustBeVariantAnnotated(context, typeSyntax, typeSymbol);
+ }
+ continue;
+ }
+
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(typeSymbol, typeCache);
+
+ if (marshalType == null)
+ {
+ Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol);
+ continue;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Check if the given type argument is being used in a type parameter that contains
+ /// the <c>MustBeVariantAttribute</c>; otherwise, we ignore the attribute.
+ /// </summary>
+ /// <param name="context">Context for a syntax node action.</param>
+ /// <param name="parentSyntax">The parent node syntax that contains the type node syntax.</param>
+ /// <param name="parentSymbol">The symbol retrieved for the parent node syntax.</param>
+ /// <param name="typeArgumentSyntax">The type node syntax of the argument type to check.</param>
+ /// <param name="typeArgumentSymbol">The symbol retrieved for the type node syntax.</param>
+ /// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns>
+ private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex)
+ {
+ var typeParamSymbol = parentSymbol switch
+ {
+ IMethodSymbol methodSymbol => methodSymbol.TypeParameters[typeArgumentIndex],
+ INamedTypeSymbol typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex],
+ _ => null,
+ };
+
+ if (typeParamSymbol == null)
+ {
+ Common.ReportTypeArgumentParentSymbolUnhandled(context, typeArgumentSyntax, parentSymbol);
+ return false;
+ }
+
+ return typeParamSymbol.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false);
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs
new file mode 100644
index 0000000000..b345f5f84d
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs
@@ -0,0 +1,23 @@
+namespace Godot.SourceGenerators
+{
+ internal 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; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
new file mode 100644
index 0000000000..5ac4f4a47e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
@@ -0,0 +1,408 @@
+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 ScriptMethodsGenerator : 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.Compilation);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData>
+ {
+ public bool Equals(GodotMethodData x, GodotMethodData y)
+ => x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name;
+
+ public int GetHashCode(GodotMethodData obj)
+ {
+ unchecked
+ {
+ return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode();
+ }
+ }
+ }
+
+ 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()
+ + "_ScriptMethods_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 methodSymbols = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
+ .Cast<IMethodSymbol>()
+ .Where(m => m.MethodKind == MethodKind.Ordinary);
+
+ var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache)
+ .Distinct(new MethodOverloadEqualityComparer())
+ .ToArray();
+
+ source.Append(" private partial class GodotInternal {\n");
+
+ // Generate cached StringNames for methods and properties, for fast lookup
+
+ var distinctMethodNames = godotClassMethods
+ .Select(m => m.Method.Name)
+ .Distinct()
+ .ToArray();
+
+ foreach (string methodName in distinctMethodNames)
+ {
+ source.Append(" public static readonly StringName MethodName_");
+ source.Append(methodName);
+ source.Append(" = \"");
+ source.Append(methodName);
+ source.Append("\";\n");
+ }
+
+ source.Append(" }\n"); // class GodotInternal
+
+ // Generate GetGodotMethodList
+
+ if (godotClassMethods.Length > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
+
+ source.Append(" internal new static ")
+ .Append(listType)
+ .Append(" GetGodotMethodList()\n {\n");
+
+ source.Append(" var methods = new ")
+ .Append(listType)
+ .Append("(")
+ .Append(godotClassMethods.Length)
+ .Append(");\n");
+
+ foreach (var method in godotClassMethods)
+ {
+ var methodInfo = DetermineMethodInfo(method);
+ AppendMethodInfo(source, methodInfo);
+ }
+
+ source.Append(" return methods;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
+ // Generate InvokeGodotClassMethod
+
+ if (godotClassMethods.Length > 0)
+ {
+ source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
+ source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
+
+ foreach (var method in godotClassMethods)
+ {
+ GenerateMethodInvoker(method, source);
+ }
+
+ source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
+
+ source.Append(" }\n");
+ }
+
+ // Generate HasGodotClassMethod
+
+ if (distinctMethodNames.Length > 0)
+ {
+ source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
+
+ bool isFirstEntry = true;
+ foreach (string methodName in distinctMethodNames)
+ {
+ GenerateHasMethodEntry(methodName, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.HasGodotClassMethod(method);\n");
+
+ source.Append(" }\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 AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
+ {
+ source.Append(" methods.Add(new(name: GodotInternal.MethodName_")
+ .Append(methodInfo.Name)
+ .Append(", returnVal: ");
+
+ AppendPropertyInfo(source, methodInfo.ReturnVal);
+
+ source.Append(", flags: (Godot.MethodFlags)")
+ .Append((int)methodInfo.Flags)
+ .Append(", arguments: ");
+
+ if (methodInfo.Arguments is { Count: > 0 })
+ {
+ source.Append("new() { ");
+
+ foreach (var param in methodInfo.Arguments)
+ {
+ AppendPropertyInfo(source, param);
+
+ // C# allows colon after the last element
+ source.Append(", ");
+ }
+
+ source.Append(" }");
+ }
+ else
+ {
+ source.Append("null");
+ }
+
+ source.Append(", defaultArguments: null));\n");
+ }
+
+ private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append("new(type: (Godot.Variant.Type)")
+ .Append((int)propertyInfo.Type)
+ .Append(", name: \"")
+ .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(")");
+ }
+
+ private static MethodInfo DetermineMethodInfo(GodotMethodData method)
+ {
+ PropertyInfo returnVal;
+
+ if (method.RetType != null)
+ {
+ returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty);
+ }
+ else
+ {
+ returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
+ hintString: null, PropertyUsageFlags.Default, exported: false);
+ }
+
+ int paramCount = method.ParamTypes.Length;
+
+ List<PropertyInfo>? arguments;
+
+ if (paramCount > 0)
+ {
+ arguments = new(capacity: paramCount);
+
+ for (int i = 0; i < paramCount; i++)
+ {
+ arguments.Add(DeterminePropertyInfo(method.ParamTypes[i],
+ name: method.Method.Parameters[i].Name));
+ }
+ }
+ else
+ {
+ arguments = null;
+ }
+
+ return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
+ defaultArguments: null);
+ }
+
+ private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
+ {
+ var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+
+ var propUsage = PropertyUsageFlags.Default;
+
+ if (memberVariantType == VariantType.Nil)
+ propUsage |= PropertyUsageFlags.NilIsVariant;
+
+ return new PropertyInfo(memberVariantType, name,
+ PropertyHint.None, string.Empty, propUsage, exported: false);
+ }
+
+ private static void GenerateHasMethodEntry(
+ string methodName,
+ StringBuilder source,
+ bool isFirstEntry
+ )
+ {
+ source.Append(" ");
+ if (!isFirstEntry)
+ source.Append("else ");
+ source.Append("if (method == GodotInternal.MethodName_");
+ source.Append(methodName);
+ source.Append(") {\n return true;\n }\n");
+ }
+
+ private static void GenerateMethodInvoker(
+ GodotMethodData method,
+ StringBuilder source
+ )
+ {
+ string methodName = method.Method.Name;
+
+ source.Append(" if (method == GodotInternal.MethodName_");
+ source.Append(methodName);
+ source.Append(" && argCount == ");
+ source.Append(method.ParamTypes.Length);
+ source.Append(") {\n");
+
+ if (method.RetType != null)
+ source.Append(" var callRet = ");
+ else
+ source.Append(" ");
+
+ source.Append(methodName);
+ source.Append("(");
+
+ for (int i = 0; i < method.ParamTypes.Length; i++)
+ {
+ if (i != 0)
+ source.Append(", ");
+
+ source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
+ method.ParamTypeSymbols[i], method.ParamTypes[i]);
+ }
+
+ source.Append(");\n");
+
+ if (method.RetType != null)
+ {
+ source.Append(" ret = ");
+
+ source.AppendManagedToNativeVariantExpr("callRet", method.RetType.Value);
+ source.Append(";\n");
+
+ source.Append(" return true;\n");
+ }
+ else
+ {
+ source.Append(" ret = default;\n");
+ source.Append(" return true;\n");
+ }
+
+ source.Append(" }\n");
+ }
+ }
+}
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 fa65595290..e8a9e28d0c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -14,13 +14,13 @@ namespace Godot.SourceGenerators
{
public void Execute(GeneratorExecutionContext context)
{
- if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
- && toggle == "disabled")
- {
+ if (context.AreGodotSourceGeneratorsDisabled())
+ return;
+
+ if (context.IsGodotToolsProject())
return;
- }
- // NOTE: IsNullOrEmpty doesn't work well with nullable checks
+ // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0
// ReSharper disable once ReplaceWithStringIsNullOrEmpty
if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
|| godotProjectDir!.Length == 0)
@@ -28,17 +28,18 @@ namespace Godot.SourceGenerators
throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
}
- var godotClasses = context.Compilation.SyntaxTrees
+ Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context
+ .Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
// Ignore inner classes
- .Where(cds => !(cds.Parent is ClassDeclarationSyntax))
+ .Where(cds => !cds.IsNested())
.SelectGodotScriptClasses(context.Compilation)
// Report and skip non-partial classes
.Where(x =>
{
- if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
+ if (x.cds.IsPartial())
return true;
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
return false;
@@ -89,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();
@@ -123,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)
@@ -134,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..fc46d82dff
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -0,0 +1,658 @@
+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 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.Compilation);
+
+ 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>()
+ .Where(s => !s.IsIndexer);
+
+ 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
+
+ if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
+ {
+ bool isFirstEntry;
+
+ // Generate SetGodotClassPropertyValue
+
+ bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
+ godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
+
+ if (!allPropertiesAreReadOnly)
+ {
+ source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
+ source.Append("in godot_variant value)\n {\n");
+
+ isFirstEntry = true;
+ foreach (var property in godotClassProperties)
+ {
+ if (property.PropertySymbol.IsReadOnly)
+ continue;
+
+ GeneratePropertySetter(property.PropertySymbol.Name,
+ property.PropertySymbol.Type, property.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ if (field.FieldSymbol.IsReadOnly)
+ continue;
+
+ GeneratePropertySetter(field.FieldSymbol.Name,
+ field.FieldSymbol.Type, field.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
+
+ source.Append(" }\n");
+ }
+
+ // Generate GetGodotClassPropertyValue
+
+ source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
+ source.Append("out godot_variant value)\n {\n");
+
+ isFirstEntry = true;
+ foreach (var property in godotClassProperties)
+ {
+ GeneratePropertyGetter(property.PropertySymbol.Name,
+ property.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ GeneratePropertyGetter(field.FieldSymbol.Name,
+ field.Type, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
+
+ source.Append(" }\n");
+
+ // Generate GetGodotPropertyList
+
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>";
+
+ source.Append(" internal new static ")
+ .Append(dictionaryType)
+ .Append(" GetGodotPropertyList()\n {\n");
+
+ source.Append(" var properties = new ")
+ .Append(dictionaryType)
+ .Append("();\n");
+
+ // To retain the definition order (and display categories correctly), we want to
+ // iterate over fields and properties at the same time, sorted by line number.
+ var godotClassPropertiesAndFields = Enumerable.Empty<GodotPropertyOrFieldData>()
+ .Concat(godotClassProperties.Select(propertyData => new GodotPropertyOrFieldData(propertyData)))
+ .Concat(godotClassFields.Select(fieldData => new GodotPropertyOrFieldData(fieldData)))
+ .OrderBy(data => data.Symbol.Locations[0].Path())
+ .ThenBy(data => data.Symbol.Locations[0].StartLine());
+
+ foreach (var member in godotClassPropertiesAndFields)
+ {
+ foreach (var groupingInfo in DetermineGroupingPropertyInfo(member.Symbol))
+ AppendGroupingPropertyInfo(source, groupingInfo);
+
+ var propertyInfo = DeterminePropertyInfo(context, typeCache,
+ member.Symbol, member.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 GeneratePropertySetter(
+ string propertyMemberName,
+ ITypeSymbol propertyTypeSymbol,
+ MarshalType propertyMarshalType,
+ StringBuilder source,
+ bool isFirstEntry
+ )
+ {
+ source.Append(" ");
+
+ if (!isFirstEntry)
+ source.Append("else ");
+
+ source.Append("if (name == GodotInternal.PropName_")
+ .Append(propertyMemberName)
+ .Append(") {\n")
+ .Append(" ")
+ .Append(propertyMemberName)
+ .Append(" = ")
+ .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
+ .Append(";\n")
+ .Append(" return true;\n")
+ .Append(" }\n");
+ }
+
+ private static void GeneratePropertyGetter(
+ string propertyMemberName,
+ MarshalType propertyMarshalType,
+ StringBuilder source,
+ bool isFirstEntry
+ )
+ {
+ source.Append(" ");
+
+ if (!isFirstEntry)
+ source.Append("else ");
+
+ source.Append("if (name == GodotInternal.PropName_")
+ .Append(propertyMemberName)
+ .Append(") {\n")
+ .Append(" value = ")
+ .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType)
+ .Append(";\n")
+ .Append(" return true;\n")
+ .Append(" }\n");
+ }
+
+ private static void AppendGroupingPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append(" properties.Add(new(type: (Godot.Variant.Type)")
+ .Append((int)VariantType.Nil)
+ .Append(", name: \"")
+ .Append(propertyInfo.Name)
+ .Append("\", hint: (Godot.PropertyHint)")
+ .Append((int)PropertyHint.None)
+ .Append(", hintString: \"")
+ .Append(propertyInfo.HintString)
+ .Append("\", usage: (Godot.PropertyUsageFlags)")
+ .Append((int)propertyInfo.Usage)
+ .Append(", exported: true));\n");
+ }
+
+ private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append(" properties.Add(new(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 static IEnumerable<PropertyInfo> DetermineGroupingPropertyInfo(ISymbol memberSymbol)
+ {
+ foreach (var attr in memberSymbol.GetAttributes())
+ {
+ PropertyUsageFlags? propertyUsage = attr.AttributeClass?.ToString() switch
+ {
+ GodotClasses.ExportCategoryAttr => PropertyUsageFlags.Category,
+ GodotClasses.ExportGroupAttr => PropertyUsageFlags.Group,
+ GodotClasses.ExportSubgroupAttr => PropertyUsageFlags.Subgroup,
+ _ => null
+ };
+
+ if (propertyUsage is null)
+ continue;
+
+ if (attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string name)
+ {
+ string? hintString = null;
+ if (propertyUsage != PropertyUsageFlags.Category && attr.ConstructorArguments.Length > 1)
+ hintString = attr.ConstructorArguments[1].Value?.ToString();
+
+ yield return new PropertyInfo(VariantType.Nil, name, PropertyHint.None, hintString, propertyUsage.Value, true);
+ }
+ }
+ }
+
+ private static PropertyInfo? DeterminePropertyInfo(
+ 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..c7745391d0
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -0,0 +1,298 @@
+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.Compilation);
+
+ 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;
+ }
+
+ if (property.IsIndexer)
+ {
+ Common.ReportExportedMemberIsIndexer(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/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs
new file mode 100644
index 0000000000..ec04a319e2
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs
@@ -0,0 +1,19 @@
+using Microsoft.CodeAnalysis;
+
+namespace Godot.SourceGenerators
+{
+ // Placeholder. Once we switch to native extensions this will act as the registrar for all
+ // user Godot classes in the assembly. Think of it as something similar to `register_types`.
+ public class ScriptRegistrarGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
new file mode 100644
index 0000000000..39a99ff8ba
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
@@ -0,0 +1,284 @@
+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 ScriptSerializationGenerator : 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.Compilation);
+
+ 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()
+ + "_ScriptSerialization_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>()
+ .Where(s => !s.IsIndexer);
+
+ 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();
+
+ var signalDelegateSymbols = members
+ .Where(s => s.Kind == SymbolKind.NamedType)
+ .Cast<INamedTypeSymbol>()
+ .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
+
+ List<GodotSignalDelegateData> godotSignalDelegates = new();
+
+ foreach (var signalDelegateSymbol in signalDelegateSymbols)
+ {
+ if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
+ continue;
+
+ string signalName = signalDelegateSymbol.Name;
+ signalName = signalName.Substring(0,
+ signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
+
+ var invokeMethodData = signalDelegateSymbol
+ .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
+
+ if (invokeMethodData == null)
+ continue;
+
+ godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
+ }
+
+ source.Append(
+ " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
+ source.Append(" base.SaveGodotObjectData(info);\n");
+
+ // Save properties
+
+ foreach (var property in godotClassProperties)
+ {
+ string propertyName = property.PropertySymbol.Name;
+
+ source.Append(" info.AddProperty(GodotInternal.PropName_")
+ .Append(propertyName)
+ .Append(", ")
+ .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type)
+ .Append(");\n");
+ }
+
+ // Save fields
+
+ foreach (var field in godotClassFields)
+ {
+ string fieldName = field.FieldSymbol.Name;
+
+ source.Append(" info.AddProperty(GodotInternal.PropName_")
+ .Append(fieldName)
+ .Append(", ")
+ .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type)
+ .Append(");\n");
+ }
+
+ // Save signal events
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+
+ source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_")
+ .Append(signalName)
+ .Append(", this.backing_")
+ .Append(signalName)
+ .Append(");\n");
+ }
+
+ source.Append(" }\n");
+
+ source.Append(
+ " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
+ source.Append(" base.RestoreGodotObjectData(info);\n");
+
+ // Restore properties
+
+ foreach (var property in godotClassProperties)
+ {
+ string propertyName = property.PropertySymbol.Name;
+
+ source.Append(" if (info.TryGetProperty(GodotInternal.PropName_")
+ .Append(propertyName)
+ .Append(", out var _value_")
+ .Append(propertyName)
+ .Append("))\n")
+ .Append(" this.")
+ .Append(propertyName)
+ .Append(" = ")
+ .AppendVariantToManagedExpr(string.Concat("_value_", propertyName),
+ property.PropertySymbol.Type, property.Type)
+ .Append(";\n");
+ }
+
+ // Restore fields
+
+ foreach (var field in godotClassFields)
+ {
+ string fieldName = field.FieldSymbol.Name;
+
+ source.Append(" if (info.TryGetProperty(GodotInternal.PropName_")
+ .Append(fieldName)
+ .Append(", out var _value_")
+ .Append(fieldName)
+ .Append("))\n")
+ .Append(" this.")
+ .Append(fieldName)
+ .Append(" = ")
+ .AppendVariantToManagedExpr(string.Concat("_value_", fieldName),
+ field.FieldSymbol.Type, field.Type)
+ .Append(";\n");
+ }
+
+ // Restore signal events
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+ string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName();
+
+ source.Append(" if (info.TryGetSignalEventDelegate<")
+ .Append(signalDelegateQualifiedName)
+ .Append(">(GodotInternal.SignalName_")
+ .Append(signalName)
+ .Append(", out var _value_")
+ .Append(signalName)
+ .Append("))\n")
+ .Append(" this.backing_")
+ .Append(signalName)
+ .Append(" = _value_")
+ .Append(signalName)
+ .Append(";\n");
+ }
+
+ source.Append(" }\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));
+ }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
new file mode 100644
index 0000000000..6b06f10db1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -0,0 +1,428 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+// TODO:
+// Determine a proper way to emit the signal.
+// 'Emit(nameof(TheEvent))' creates a StringName everytime and has the overhead of string marshaling.
+// I haven't decided on the best option yet. Some possibilities:
+// - Expose the generated StringName fields to the user, for use with 'Emit(...)'.
+// - Generate a 'EmitSignalName' method for each event signal.
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class ScriptSignalsGenerator : 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.Compilation);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ internal static string SignalDelegateSuffix = "EventHandler";
+
+ 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()
+ + "_ScriptSignals_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 signalDelegateSymbols = members
+ .Where(s => s.Kind == SymbolKind.NamedType)
+ .Cast<INamedTypeSymbol>()
+ .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
+
+ List<GodotSignalDelegateData> godotSignalDelegates = new();
+
+ foreach (var signalDelegateSymbol in signalDelegateSymbols)
+ {
+ if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix))
+ {
+ Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol);
+ continue;
+ }
+
+ string signalName = signalDelegateSymbol.Name;
+ signalName = signalName.Substring(0, signalName.Length - SignalDelegateSuffix.Length);
+
+ var invokeMethodData = signalDelegateSymbol
+ .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
+
+ if (invokeMethodData == null)
+ {
+ if (signalDelegateSymbol.DelegateInvokeMethod is IMethodSymbol methodSymbol)
+ {
+ foreach (var parameter in methodSymbol.Parameters)
+ {
+ if (parameter.RefKind != RefKind.None)
+ {
+ Common.ReportSignalParameterTypeNotSupported(context, parameter);
+ continue;
+ }
+
+ var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(parameter.Type, typeCache);
+
+ if (marshalType == null)
+ {
+ Common.ReportSignalParameterTypeNotSupported(context, parameter);
+ }
+ }
+
+ if (!methodSymbol.ReturnsVoid)
+ {
+ Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol);
+ }
+ }
+ continue;
+ }
+
+ godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
+ }
+
+ source.Append(" private partial class GodotInternal {\n");
+
+ // Generate cached StringNames for methods and properties, for fast lookup
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+ source.Append(" public static readonly StringName SignalName_");
+ source.Append(signalName);
+ source.Append(" = \"");
+ source.Append(signalName);
+ source.Append("\";\n");
+ }
+
+ source.Append(" }\n"); // class GodotInternal
+
+ // Generate GetGodotSignalList
+
+ if (godotSignalDelegates.Count > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
+
+ source.Append(" internal new static ")
+ .Append(listType)
+ .Append(" GetGodotSignalList()\n {\n");
+
+ source.Append(" var signals = new ")
+ .Append(listType)
+ .Append("(")
+ .Append(godotSignalDelegates.Count)
+ .Append(");\n");
+
+ foreach (var signalDelegateData in godotSignalDelegates)
+ {
+ var methodInfo = DetermineMethodInfo(signalDelegateData);
+ AppendMethodInfo(source, methodInfo);
+ }
+
+ source.Append(" return signals;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
+ // Generate signal event
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+
+ // TODO: Hide backing event from code-completion and debugger
+ // The reason we have a backing field is to hide the invoke method from the event,
+ // as it doesn't emit the signal, only the event delegates. This can confuse users.
+ // Maybe we should directly connect the delegates, as we do with native signals?
+ source.Append(" private ")
+ .Append(signalDelegate.DelegateSymbol.FullQualifiedName())
+ .Append(" backing_")
+ .Append(signalName)
+ .Append(";\n");
+
+ source.Append(" public event ")
+ .Append(signalDelegate.DelegateSymbol.FullQualifiedName())
+ .Append(" ")
+ .Append(signalName)
+ .Append(" {\n")
+ .Append(" add => backing_")
+ .Append(signalName)
+ .Append(" += value;\n")
+ .Append(" remove => backing_")
+ .Append(signalName)
+ .Append(" -= value;\n")
+ .Append("}\n");
+ }
+
+ // Generate RaiseGodotClassSignalCallbacks
+
+ if (godotSignalDelegates.Count > 0)
+ {
+ source.Append(
+ " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
+ source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
+
+ foreach (var signal in godotSignalDelegates)
+ {
+ GenerateSignalEventInvoker(signal, source);
+ }
+
+ source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+
+ source.Append(" }\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 AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
+ {
+ source.Append(" signals.Add(new(name: GodotInternal.SignalName_")
+ .Append(methodInfo.Name)
+ .Append(", returnVal: ");
+
+ AppendPropertyInfo(source, methodInfo.ReturnVal);
+
+ source.Append(", flags: (Godot.MethodFlags)")
+ .Append((int)methodInfo.Flags)
+ .Append(", arguments: ");
+
+ if (methodInfo.Arguments is { Count: > 0 })
+ {
+ source.Append("new() { ");
+
+ foreach (var param in methodInfo.Arguments)
+ {
+ AppendPropertyInfo(source, param);
+
+ // C# allows colon after the last element
+ source.Append(", ");
+ }
+
+ source.Append(" }");
+ }
+ else
+ {
+ source.Append("null");
+ }
+
+ source.Append(", defaultArguments: null));\n");
+ }
+
+ private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append("new(type: (Godot.Variant.Type)")
+ .Append((int)propertyInfo.Type)
+ .Append(", name: \"")
+ .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(")");
+ }
+
+ private static MethodInfo DetermineMethodInfo(GodotSignalDelegateData signalDelegateData)
+ {
+ var invokeMethodData = signalDelegateData.InvokeMethodData;
+
+ PropertyInfo returnVal;
+
+ if (invokeMethodData.RetType != null)
+ {
+ returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty);
+ }
+ else
+ {
+ returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
+ hintString: null, PropertyUsageFlags.Default, exported: false);
+ }
+
+ int paramCount = invokeMethodData.ParamTypes.Length;
+
+ List<PropertyInfo>? arguments;
+
+ if (paramCount > 0)
+ {
+ arguments = new(capacity: paramCount);
+
+ for (int i = 0; i < paramCount; i++)
+ {
+ arguments.Add(DeterminePropertyInfo(invokeMethodData.ParamTypes[i],
+ name: invokeMethodData.Method.Parameters[i].Name));
+ }
+ }
+ else
+ {
+ arguments = null;
+ }
+
+ return new MethodInfo(signalDelegateData.Name, returnVal, MethodFlags.Default, arguments,
+ defaultArguments: null);
+ }
+
+ private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
+ {
+ var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+
+ var propUsage = PropertyUsageFlags.Default;
+
+ if (memberVariantType == VariantType.Nil)
+ propUsage |= PropertyUsageFlags.NilIsVariant;
+
+ return new PropertyInfo(memberVariantType, name,
+ PropertyHint.None, string.Empty, propUsage, exported: false);
+ }
+
+ private static void GenerateSignalEventInvoker(
+ GodotSignalDelegateData signal,
+ StringBuilder source
+ )
+ {
+ string signalName = signal.Name;
+ var invokeMethodData = signal.InvokeMethodData;
+
+ source.Append(" if (signal == GodotInternal.SignalName_");
+ source.Append(signalName);
+ source.Append(" && argCount == ");
+ source.Append(invokeMethodData.ParamTypes.Length);
+ source.Append(") {\n");
+ source.Append(" backing_");
+ source.Append(signalName);
+ source.Append("?.Invoke(");
+
+ for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
+ {
+ if (i != 0)
+ source.Append(", ");
+
+ source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
+ invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
+ }
+
+ source.Append(");\n");
+
+ source.Append(" return;\n");
+
+ source.Append(" }\n");
+ }
+ }
+}