diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs index 724fb164e02..1da48d6477c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs @@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests "AbstractGenericNode(Of T)_ScriptProperties.generated.cs" ); } + + [Fact] + public async void Inheritance() + { + await CSharpSourceGeneratorVerifier.Verify( + new string[] { "Inheritance.cs" }, + new string[] { "InheritanceBase_ScriptProperties.generated.cs", "InheritanceChild_ScriptProperties.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs index 7711bce1c79..ea5036ffe2f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertyDefValGeneratorTests.cs @@ -30,4 +30,13 @@ public class ScriptPropertyDefValGeneratorTests "ExportedComplexStrings_ScriptPropertyDefVal.generated.cs" ); } + + [Fact] + public async void Inheritance() + { + await CSharpSourceGeneratorVerifier.Verify( + new string[] { "Inheritance.cs" }, + new string[] { "InheritanceBase_ScriptPropertyDefVal.generated.cs", "InheritanceChild_ScriptPropertyDefVal.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs index 24cd446087c..73957a9b516 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptSerializationGeneratorTests.cs @@ -12,4 +12,13 @@ public class ScriptSerializationGeneratorTests "ScriptBoilerplate_ScriptSerialization.generated.cs", "OuterClass.NestedClass_ScriptSerialization.generated.cs" ); } + + [Fact] + public async void Inheritance() + { + await CSharpSourceGeneratorVerifier.VerifyNoCompilerDiagnostics( + new string[] { "Inheritance.cs" }, + new string[] { "InheritanceBase_ScriptSerialization.generated.cs", "InheritanceChild_ScriptSerialization.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptProperties.generated.cs new file mode 100644 index 00000000000..d78820cf71d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptProperties.generated.cs @@ -0,0 +1,62 @@ +using Godot; +using Godot.NativeInterop; + +partial class InheritanceBase +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// + public new class PropertyName : global::Godot.Node.PropertyName { + /// + /// Cached name for the 'MyString' property. + /// + public new static readonly global::Godot.StringName @MyString = "MyString"; + /// + /// Cached name for the 'MyInteger' property. + /// + public new static readonly global::Godot.StringName @MyInteger = "MyInteger"; + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyString) { + this.@MyString = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + if (name == PropertyName.@MyInteger) { + this.@MyInteger = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyString); + return true; + } + if (name == PropertyName.@MyInteger) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyInteger); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List(); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@MyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@MyInteger, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptPropertyDefVal.generated.cs new file mode 100644 index 00000000000..31446bdf506 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptPropertyDefVal.generated.cs @@ -0,0 +1,3 @@ +partial class InheritanceBase +{ +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptSerialization.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptSerialization.generated.cs new file mode 100644 index 00000000000..2c16025e41f --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceBase_ScriptSerialization.generated.cs @@ -0,0 +1,24 @@ +using Godot; +using Godot.NativeInterop; + +partial class InheritanceBase +{ + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) + { + base.SaveGodotObjectData(info); + info.AddProperty(PropertyName.@MyString, global::Godot.Variant.From(this.@MyString)); + info.AddProperty(PropertyName.@MyInteger, global::Godot.Variant.From(this.@MyInteger)); + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) + { + base.RestoreGodotObjectData(info); + if (info.TryGetProperty(PropertyName.@MyString, out var _value_MyString)) + this.@MyString = _value_MyString.As(); + if (info.TryGetProperty(PropertyName.@MyInteger, out var _value_MyInteger)) + this.@MyInteger = _value_MyInteger.As(); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptProperties.generated.cs new file mode 100644 index 00000000000..e1ea1dda789 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptProperties.generated.cs @@ -0,0 +1,62 @@ +using Godot; +using Godot.NativeInterop; + +partial class InheritanceChild +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// + public new class PropertyName : global::InheritanceBase.PropertyName { + /// + /// Cached name for the 'MyString' property. + /// + public new static readonly global::Godot.StringName @MyString = "MyString"; + /// + /// Cached name for the 'MyInteger' property. + /// + public new static readonly global::Godot.StringName @MyInteger = "MyInteger"; + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyString) { + this.@MyString = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + if (name == PropertyName.@MyInteger) { + this.@MyInteger = global::Godot.NativeInterop.VariantUtils.ConvertTo(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyString) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyString); + return true; + } + if (name == PropertyName.@MyInteger) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom(this.@MyInteger); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List(); + properties.Add(new(type: (global::Godot.Variant.Type)4, name: PropertyName.@MyString, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4096, exported: false)); + properties.Add(new(type: (global::Godot.Variant.Type)2, name: PropertyName.@MyInteger, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptPropertyDefVal.generated.cs new file mode 100644 index 00000000000..cab9aac948c --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptPropertyDefVal.generated.cs @@ -0,0 +1,21 @@ +partial class InheritanceChild +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword +#if TOOLS + /// + /// Get the default values for all properties declared in this class. + /// This method is used by Godot to determine the value that will be + /// used by the inspector when resetting properties. + /// Do not call this method. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.Dictionary GetGodotPropertyDefaultValues() + { + var values = new global::System.Collections.Generic.Dictionary(1); + int __MyInteger_default_value = default; + values.Add(PropertyName.@MyInteger, global::Godot.Variant.From(__MyInteger_default_value)); + return values; + } +#endif // TOOLS +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptSerialization.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptSerialization.generated.cs new file mode 100644 index 00000000000..334bb3f1f67 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/InheritanceChild_ScriptSerialization.generated.cs @@ -0,0 +1,24 @@ +using Godot; +using Godot.NativeInterop; + +partial class InheritanceChild +{ + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) + { + base.SaveGodotObjectData(info); + info.AddProperty(PropertyName.@MyString, global::Godot.Variant.From(this.@MyString)); + info.AddProperty(PropertyName.@MyInteger, global::Godot.Variant.From(this.@MyInteger)); + } + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info) + { + base.RestoreGodotObjectData(info); + if (info.TryGetProperty(PropertyName.@MyString, out var _value_MyString)) + this.@MyString = _value_MyString.As(); + if (info.TryGetProperty(PropertyName.@MyInteger, out var _value_MyInteger)) + this.@MyInteger = _value_MyInteger.As(); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Inheritance.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Inheritance.cs new file mode 100644 index 00000000000..6b6dab3985b --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/Inheritance.cs @@ -0,0 +1,15 @@ +using System; +using Godot; + +public partial class InheritanceBase : Node +{ + public virtual string MyString { get; set; } + public virtual int MyInteger { get; set; } +} + +public partial class InheritanceChild : InheritanceBase +{ + public override string MyString { get; set; } + [Export] + public override int MyInteger => 0; +} 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 62fa7b0a367..93c78c6a430 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -384,5 +384,15 @@ namespace Godot.SourceGenerators public static int StartLine(this Location location) => location.SourceTree?.GetLineSpan(location.SourceSpan).StartLinePosition.Line ?? location.GetLineSpan().StartLinePosition.Line; + + public static IMethodSymbol? GetMethodOrBaseGetMethod(this IPropertySymbol symbol) + { + return symbol.GetMethod ?? symbol.OverriddenProperty?.GetMethodOrBaseGetMethod(); + } + + public static IMethodSymbol? SetMethodOrBaseSetMethod(this IPropertySymbol symbol) + { + return symbol.SetMethod ?? symbol.OverriddenProperty?.SetMethodOrBaseSetMethod(); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs index 0760ea11bb7..68c55c72c77 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs @@ -44,6 +44,10 @@ namespace Godot.SourceGenerators public IPropertySymbol PropertySymbol { get; } public MarshalType Type { get; } + + public bool IsReadOnly => PropertySymbol.IsReadOnly || + PropertySymbol.SetMethodOrBaseSetMethod() is { IsInitOnly: true }; + public bool IsWriteOnly => PropertySymbol.IsWriteOnly; } public readonly struct GodotFieldData @@ -56,6 +60,8 @@ namespace Godot.SourceGenerators public IFieldSymbol FieldSymbol { get; } public MarshalType Type { get; } + + public bool IsReadOnly => FieldSymbol.IsReadOnly; } public struct GodotPropertyOrFieldData diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index fc67e4f5929..32109205c77 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -176,8 +176,7 @@ namespace Godot.SourceGenerators // Generate SetGodotClassPropertyValue - bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) && - godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly || pi.PropertySymbol.SetMethod!.IsInitOnly); + bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.IsReadOnly) && godotClassProperties.All(pi => pi.IsReadOnly); if (!allPropertiesAreReadOnly) { @@ -188,7 +187,7 @@ namespace Godot.SourceGenerators foreach (var property in godotClassProperties) { - if (property.PropertySymbol.IsReadOnly || property.PropertySymbol.SetMethod!.IsInitOnly) + if (property.IsReadOnly) continue; GeneratePropertySetter(property.PropertySymbol.Name, @@ -197,7 +196,7 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { - if (field.FieldSymbol.IsReadOnly) + if (field.IsReadOnly) continue; GeneratePropertySetter(field.FieldSymbol.Name, @@ -210,7 +209,7 @@ namespace Godot.SourceGenerators } // Generate GetGodotClassPropertyValue - bool allPropertiesAreWriteOnly = godotClassFields.Length == 0 && godotClassProperties.All(pi => pi.PropertySymbol.IsWriteOnly); + bool allPropertiesAreWriteOnly = godotClassFields.Length == 0 && godotClassProperties.All(pi => pi.IsWriteOnly); if (!allPropertiesAreWriteOnly) { @@ -423,7 +422,7 @@ namespace Godot.SourceGenerators if (exportAttr != null && propertySymbol != null) { - if (propertySymbol.GetMethod == null) + if (propertySymbol.GetMethodOrBaseGetMethod() is null) { // This should never happen, as we filtered WriteOnly properties, but just in case. context.ReportDiagnostic(Diagnostic.Create( @@ -434,7 +433,7 @@ namespace Godot.SourceGenerators return null; } - if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly) + if (propertySymbol.SetMethodOrBaseSetMethod() is not { IsInitOnly: false }) { // This should never happen, as we filtered ReadOnly properties, but just in case. context.ReportDiagnostic(Diagnostic.Create( diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 626f51ecae1..24af21a131c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -163,7 +163,7 @@ namespace Godot.SourceGenerators continue; } - if (property.IsReadOnly || property.SetMethod!.IsInitOnly) + if (property.IsReadOnly || property.SetMethodOrBaseSetMethod() is not { IsInitOnly: false }) { context.ReportDiagnostic(Diagnostic.Create( Common.ExportedMemberIsReadOnlyRule, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 937da763351..ccb3e3562ab 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -123,10 +123,12 @@ namespace Godot.SourceGenerators // 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, without a setter or with an init-only setter. Godot properties must be both readable and writable. - var godotClassProperties = propertySymbols.Where(property => !(property.IsReadOnly || property.IsWriteOnly || property.SetMethod!.IsInitOnly)) + var godotClassProperties = propertySymbols + .Where(property => !(property.IsReadOnly || property.IsWriteOnly) && property.SetMethodOrBaseSetMethod() is { IsInitOnly: false }) .WhereIsGodotCompatibleType(typeCache) .ToArray(); - var godotClassFields = fieldSymbols.Where(property => !property.IsReadOnly) + var godotClassFields = fieldSymbols + .Where(property => !property.IsReadOnly) .WhereIsGodotCompatibleType(typeCache) .ToArray();