This commit is contained in:
Paul Joannon 2024-10-22 22:13:40 +02:00 committed by GitHub
commit 509312a63a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 265 additions and 10 deletions

View File

@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests
"AbstractGenericNode(Of T)_ScriptProperties.generated.cs"
);
}
[Fact]
public async void Inheritance()
{
await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify(
new string[] { "Inheritance.cs" },
new string[] { "InheritanceBase_ScriptProperties.generated.cs", "InheritanceChild_ScriptProperties.generated.cs" }
);
}
}

View File

@ -30,4 +30,13 @@ public class ScriptPropertyDefValGeneratorTests
"ExportedComplexStrings_ScriptPropertyDefVal.generated.cs"
);
}
[Fact]
public async void Inheritance()
{
await CSharpSourceGeneratorVerifier<ScriptPropertyDefValGenerator>.Verify(
new string[] { "Inheritance.cs" },
new string[] { "InheritanceBase_ScriptPropertyDefVal.generated.cs", "InheritanceChild_ScriptPropertyDefVal.generated.cs" }
);
}
}

View File

@ -12,4 +12,13 @@ public class ScriptSerializationGeneratorTests
"ScriptBoilerplate_ScriptSerialization.generated.cs", "OuterClass.NestedClass_ScriptSerialization.generated.cs"
);
}
[Fact]
public async void Inheritance()
{
await CSharpSourceGeneratorVerifier<ScriptSerializationGenerator>.VerifyNoCompilerDiagnostics(
new string[] { "Inheritance.cs" },
new string[] { "InheritanceBase_ScriptSerialization.generated.cs", "InheritanceChild_ScriptSerialization.generated.cs" }
);
}
}

View File

@ -0,0 +1,62 @@
using Godot;
using Godot.NativeInterop;
partial class InheritanceBase
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::Godot.Node.PropertyName {
/// <summary>
/// Cached name for the 'MyString' property.
/// </summary>
public new static readonly global::Godot.StringName @MyString = "MyString";
/// <summary>
/// Cached name for the 'MyInteger' property.
/// </summary>
public new static readonly global::Godot.StringName @MyInteger = "MyInteger";
}
/// <inheritdoc/>
[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<string>(value);
return true;
}
if (name == PropertyName.@MyInteger) {
this.@MyInteger = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
return true;
}
return base.SetGodotClassPropertyValue(name, value);
}
/// <inheritdoc/>
[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<string>(this.@MyString);
return true;
}
if (name == PropertyName.@MyInteger) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@MyInteger);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// 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.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
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
}

View File

@ -0,0 +1,24 @@
using Godot;
using Godot.NativeInterop;
partial class InheritanceBase
{
/// <inheritdoc/>
[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<string>(this.@MyString));
info.AddProperty(PropertyName.@MyInteger, global::Godot.Variant.From<int>(this.@MyInteger));
}
/// <inheritdoc/>
[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<string>();
if (info.TryGetProperty(PropertyName.@MyInteger, out var _value_MyInteger))
this.@MyInteger = _value_MyInteger.As<int>();
}
}

View File

@ -0,0 +1,62 @@
using Godot;
using Godot.NativeInterop;
partial class InheritanceChild
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
/// <summary>
/// Cached StringNames for the properties and fields contained in this class, for fast lookup.
/// </summary>
public new class PropertyName : global::InheritanceBase.PropertyName {
/// <summary>
/// Cached name for the 'MyString' property.
/// </summary>
public new static readonly global::Godot.StringName @MyString = "MyString";
/// <summary>
/// Cached name for the 'MyInteger' property.
/// </summary>
public new static readonly global::Godot.StringName @MyInteger = "MyInteger";
}
/// <inheritdoc/>
[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<string>(value);
return true;
}
if (name == PropertyName.@MyInteger) {
this.@MyInteger = global::Godot.NativeInterop.VariantUtils.ConvertTo<int>(value);
return true;
}
return base.SetGodotClassPropertyValue(name, value);
}
/// <inheritdoc/>
[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<string>(this.@MyString);
return true;
}
if (name == PropertyName.@MyInteger) {
value = global::Godot.NativeInterop.VariantUtils.CreateFrom<int>(this.@MyInteger);
return true;
}
return base.GetGodotClassPropertyValue(name, out value);
}
/// <summary>
/// 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.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList()
{
var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>();
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
}

View File

@ -0,0 +1,21 @@
partial class InheritanceChild
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
#if TOOLS
/// <summary>
/// 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.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues()
{
var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(1);
int __MyInteger_default_value = default;
values.Add(PropertyName.@MyInteger, global::Godot.Variant.From<int>(__MyInteger_default_value));
return values;
}
#endif // TOOLS
#pragma warning restore CS0109
}

View File

@ -0,0 +1,24 @@
using Godot;
using Godot.NativeInterop;
partial class InheritanceChild
{
/// <inheritdoc/>
[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<string>(this.@MyString));
info.AddProperty(PropertyName.@MyInteger, global::Godot.Variant.From<int>(this.@MyInteger));
}
/// <inheritdoc/>
[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<string>();
if (info.TryGetProperty(PropertyName.@MyInteger, out var _value_MyInteger))
this.@MyInteger = _value_MyInteger.As<int>();
}
}

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -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();