From bd0856d2dfa2b8e3018bf8133ed5fd657b14b122 Mon Sep 17 00:00:00 2001 From: badcel <1218031+badcel@users.noreply.github.com> Date: Sat, 6 Jan 2024 22:25:28 +0100 Subject: [PATCH] TypedRecord: Add field access --- ...ublicMethodsColldingWithPropertiesFixer.cs | 31 ++++++++++++ src/Generation/Generator/Fixer/Records.cs | 3 +- .../Internal/Field/Converter/String.cs | 4 +- .../Renderer/Internal/TypedRecordHandle.cs | 6 +-- .../Public/Field/Converter/Bitfield.cs | 39 +++++++++++++++ .../Public/Field/Converter/Enumeration.cs | 37 ++++++++++++++ .../Field/Converter/PrimitiveValueType.cs | 36 +++++++++++++ .../Renderer/Public/Field/Converter/String.cs | 33 ++++++++++++ .../Renderer/Public/Field/FieldConverter.cs | 7 +++ .../Generator/Renderer/Public/Field/Fields.cs | 23 +++++++++ .../Renderer/Public/Field/RenderableField.cs | 7 +++ .../Generator/Renderer/Public/TypedRecord.cs | 35 +++++++++++++ src/Libs/GLib-2.0/Public/GException.cs | 3 +- .../GirTestLib/girtest-typed-record-tester.c | 2 + .../GirTestLib/girtest-typed-record-tester.h | 29 +++++++++++ .../Libs/GirTest-0.1.Tests/TypedRecordTest.cs | 50 +++++++++++++++++++ 16 files changed, 336 insertions(+), 9 deletions(-) create mode 100644 src/Generation/Generator/Fixer/Record/PublicMethodsColldingWithPropertiesFixer.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/Converter/Bitfield.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/Converter/Enumeration.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/Converter/PrimitiveValueType.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/Converter/String.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/FieldConverter.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/Fields.cs create mode 100644 src/Generation/Generator/Renderer/Public/Field/RenderableField.cs diff --git a/src/Generation/Generator/Fixer/Record/PublicMethodsColldingWithPropertiesFixer.cs b/src/Generation/Generator/Fixer/Record/PublicMethodsColldingWithPropertiesFixer.cs new file mode 100644 index 000000000..47ca5b4c0 --- /dev/null +++ b/src/Generation/Generator/Fixer/Record/PublicMethodsColldingWithPropertiesFixer.cs @@ -0,0 +1,31 @@ +using Generator.Model; + +namespace Generator.Fixer.Record; + +internal class PublicMethodsColldingWithFieldFixer : Fixer +{ + public void Fixup(GirModel.Record record) + { + foreach (var field in record.Fields) + { + foreach (var method in record.Methods) + { + if (Field.GetName(field) == Method.GetPublicName(method)) + { + if (method.ReturnType.AnyType.Is()) + { + Log.Warning($"{record.Namespace.Name}.{record.Name}: Method {Method.GetPublicName(method)} is named like a field but returns no value. It makes no sense to prefix the method with 'Get'. Thus it will be ignored."); + Method.Disable(method); + } + else + { + var newName = $"Get{Method.GetPublicName(method)}"; + Log.Warning($"{record.Namespace.Name}.{record.Name}: Method {Method.GetPublicName(method)} collides with field {Field.GetName(field)} and is renamed to {newName}."); + + Method.SetPublicName(method, newName); + } + } + } + } + } +} diff --git a/src/Generation/Generator/Fixer/Records.cs b/src/Generation/Generator/Fixer/Records.cs index a13a5a297..8c06de00b 100644 --- a/src/Generation/Generator/Fixer/Records.cs +++ b/src/Generation/Generator/Fixer/Records.cs @@ -7,7 +7,8 @@ public static class Records { private static readonly List> Fixers = new() { - new InternalMethodsNamedLikeRecordFixer() + new InternalMethodsNamedLikeRecordFixer(), + new PublicMethodsColldingWithFieldFixer() }; public static void Fixup(IEnumerable records) diff --git a/src/Generation/Generator/Renderer/Internal/Field/Converter/String.cs b/src/Generation/Generator/Renderer/Internal/Field/Converter/String.cs index c197538a8..7971a023f 100644 --- a/src/Generation/Generator/Renderer/Internal/Field/Converter/String.cs +++ b/src/Generation/Generator/Renderer/Internal/Field/Converter/String.cs @@ -13,8 +13,8 @@ public RenderableField Convert(GirModel.Field field) { return new RenderableField( Name: Model.Field.GetName(field), - Attribute: MarshalAs.UnmanagedLpString(), - NullableTypeName: Type.GetName(field.AnyTypeOrCallback.AsT0.AsT0) + Attribute: null, + NullableTypeName: Type.Pointer ); } } diff --git a/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs b/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs index aa870df0b..256974900 100644 --- a/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs +++ b/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs @@ -252,8 +252,7 @@ private static string RenderFieldGetter(GirModel.Record record, GirModel.Field f if (IsClosed || IsInvalid) throw new InvalidOperationException(""Handle is closed or invalid""); - var data = Unsafe.AsRef<{dataName}>((void*)handle); - return data.{renderableField.Name}; + return Unsafe.AsRef<{dataName}>((void*)handle).{renderableField.Name}; }}"; } @@ -266,8 +265,7 @@ private static string RenderFieldSetter(GirModel.Record record, GirModel.Field f if (IsClosed || IsInvalid) throw new InvalidOperationException(""Handle is closed or invalid""); - var data = Unsafe.AsRef<{dataName}>((void*)handle); - data.{renderableField.Name} = value; + Unsafe.AsRef<{dataName}>((void*)handle).{renderableField.Name} = value; }}"; } diff --git a/src/Generation/Generator/Renderer/Public/Field/Converter/Bitfield.cs b/src/Generation/Generator/Renderer/Public/Field/Converter/Bitfield.cs new file mode 100644 index 000000000..b9e8fb0bd --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/Converter/Bitfield.cs @@ -0,0 +1,39 @@ +namespace Generator.Renderer.Public.Field; + +internal class Bitfield : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.Is(); + } + + public RenderableField Convert(GirModel.Field field) + { + var name = Model.Field.GetName(field); + return new RenderableField( + Name: name, + NullableTypeName: GetNullableTypeName(field), + SetExpression: SetExpression, + GetExpression: GetExpression + ); + } + + private static string GetNullableTypeName(GirModel.Field field) + { + var type = (GirModel.Bitfield) field.AnyTypeOrCallback.AsT0.AsT0; + return field.IsPointer + ? Model.Type.Pointer + : Model.ComplexType.GetFullyQualified(type); + } + + private static string SetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Set{Model.Field.GetName(field)}(value)"; + } + + private static string GetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Get{Model.Field.GetName(field)}()"; + } +} + diff --git a/src/Generation/Generator/Renderer/Public/Field/Converter/Enumeration.cs b/src/Generation/Generator/Renderer/Public/Field/Converter/Enumeration.cs new file mode 100644 index 000000000..c644ddf98 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/Converter/Enumeration.cs @@ -0,0 +1,37 @@ +namespace Generator.Renderer.Public.Field; + +internal class Enumeration : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.Is(); + } + + public RenderableField Convert(GirModel.Field field) + { + return new RenderableField( + Name: Model.Field.GetName(field), + NullableTypeName: GetNullableTypeName(field), + SetExpression: SetExpression, + GetExpression: GetExpression + ); + } + + private static string GetNullableTypeName(GirModel.Field field) + { + var type = (GirModel.Enumeration) field.AnyTypeOrCallback.AsT0.AsT0; + return field.IsPointer + ? Model.Type.Pointer + : Model.ComplexType.GetFullyQualified(type); + } + + private static string SetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Set{Model.Field.GetName(field)}(value)"; + } + + private static string GetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Get{Model.Field.GetName(field)}()"; + } +} diff --git a/src/Generation/Generator/Renderer/Public/Field/Converter/PrimitiveValueType.cs b/src/Generation/Generator/Renderer/Public/Field/Converter/PrimitiveValueType.cs new file mode 100644 index 000000000..44a8a0e79 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/Converter/PrimitiveValueType.cs @@ -0,0 +1,36 @@ +namespace Generator.Renderer.Public.Field; + +internal class PrimitiveValueType : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.Is(); + } + + public RenderableField Convert(GirModel.Field field) + { + return new RenderableField( + Name: Model.Field.GetName(field), + NullableTypeName: GetNullableTypeName(field), + SetExpression: SetExpression, + GetExpression: GetExpression + ); + } + + private static string GetNullableTypeName(GirModel.Field field) + { + return field.IsPointer + ? Model.Type.Pointer + : Model.Type.GetName(field.AnyTypeOrCallback.AsT0.AsT0); + } + + private static string SetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Set{Model.Field.GetName(field)}(value)"; + } + + private static string GetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Get{Model.Field.GetName(field)}()"; + } +} diff --git a/src/Generation/Generator/Renderer/Public/Field/Converter/String.cs b/src/Generation/Generator/Renderer/Public/Field/Converter/String.cs new file mode 100644 index 000000000..a96d246ac --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/Converter/String.cs @@ -0,0 +1,33 @@ +using Generator.Model; + +namespace Generator.Renderer.Public.Field; + +internal class String : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.Is(); + } + + public RenderableField Convert(GirModel.Field field) + { + //Struct fields are always nullable as there are no information on nullability + + return new RenderableField( + Name: Model.Field.GetName(field), + NullableTypeName: $"{Type.GetName(field.AnyTypeOrCallback.AsT0.AsT0)}?", + SetExpression: SetExpression, + GetExpression: GetExpression + ); + } + + private static string SetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Handle.Set{Model.Field.GetName(field)}(GLib.Internal.StringHelper.StringToPtrUtf8(value))"; + } + + private static string GetExpression(GirModel.Record record, GirModel.Field field) + { + return $"Marshal.PtrToStringUTF8(Handle.Get{Model.Field.GetName(field)}())"; + } +} diff --git a/src/Generation/Generator/Renderer/Public/Field/FieldConverter.cs b/src/Generation/Generator/Renderer/Public/Field/FieldConverter.cs new file mode 100644 index 000000000..0874cdc3b --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/FieldConverter.cs @@ -0,0 +1,7 @@ +namespace Generator.Renderer.Public.Field; + +public interface FieldConverter +{ + bool Supports(GirModel.Field field); + RenderableField Convert(GirModel.Field field); +} diff --git a/src/Generation/Generator/Renderer/Public/Field/Fields.cs b/src/Generation/Generator/Renderer/Public/Field/Fields.cs new file mode 100644 index 000000000..c21d74198 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/Fields.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Generator.Renderer.Public; + +internal static class Fields +{ + private static readonly List Converters = new() + { + new Field.Bitfield(), + new Field.Enumeration(), + new Field.PrimitiveValueType(), + new Field.String(), + }; + + public static Field.RenderableField GetRenderableField(GirModel.Field field) + { + foreach (var converter in Converters) + if (converter.Supports(field)) + return converter.Convert(field); + + throw new System.Exception($"Internal field \"{field.Name}\" of type {field.AnyTypeOrCallback} can not be rendered"); + } +} diff --git a/src/Generation/Generator/Renderer/Public/Field/RenderableField.cs b/src/Generation/Generator/Renderer/Public/Field/RenderableField.cs new file mode 100644 index 000000000..07b3f4d91 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Field/RenderableField.cs @@ -0,0 +1,7 @@ +using System; + +namespace Generator.Renderer.Public.Field; + +public delegate string Expression(GirModel.Record record, GirModel.Field field); + +public record RenderableField(string Name, string NullableTypeName, Expression SetExpression, Expression GetExpression); diff --git a/src/Generation/Generator/Renderer/Public/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/TypedRecord.cs index f8c3b81e2..53368bfbd 100644 --- a/src/Generation/Generator/Renderer/Public/TypedRecord.cs +++ b/src/Generation/Generator/Renderer/Public/TypedRecord.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Text; using Generator.Model; namespace Generator.Renderer.Public; @@ -49,6 +50,10 @@ public partial class {name} {FunctionRenderer.Render(record.TypeFunction)} + {record.Fields + .Select(f => RenderField(record, f)) + .Join(Environment.NewLine)} + {record.Functions .Select(FunctionRenderer.Render) .Join(Environment.NewLine)} @@ -59,4 +64,34 @@ public partial class {name} .Join(Environment.NewLine)} }}"; } + + private static string RenderField(GirModel.Record record, GirModel.Field field) + { + try + { + var renderableField = Fields.GetRenderableField(field); + + if (field is { IsReadable: false, IsWritable: false } || field.IsPrivate) + return string.Empty; + + var result = new StringBuilder(); + + result.AppendLine($"public {renderableField.NullableTypeName} {renderableField.Name} {{"); + + if (field.IsReadable) + result.AppendLine($"get => {renderableField.GetExpression(record, field)};"); + + if (field.IsWritable) + result.AppendLine($"set => {renderableField.SetExpression(record, field)};"); + + result.AppendLine("}"); + + return result.ToString(); + } + catch (Exception ex) + { + Log.Warning($"Did not render typed record {record.Name} field {field.Name}: {ex.Message}"); + return string.Empty; + } + } } diff --git a/src/Libs/GLib-2.0/Public/GException.cs b/src/Libs/GLib-2.0/Public/GException.cs index cc5655101..8fcb3fa45 100644 --- a/src/Libs/GLib-2.0/Public/GException.cs +++ b/src/Libs/GLib-2.0/Public/GException.cs @@ -8,9 +8,8 @@ public sealed class GException : Exception, IDisposable private readonly Internal.ErrorHandle _errorHandle; public GException(Internal.ErrorHandle errorHandle) - : base(Marshal.PtrToStructure(errorHandle.DangerousGetHandle()).Message) + : base(Marshal.PtrToStringUTF8(errorHandle.GetMessage())) { - _errorHandle = errorHandle; } diff --git a/src/Native/GirTestLib/girtest-typed-record-tester.c b/src/Native/GirTestLib/girtest-typed-record-tester.c index a3261bf9b..f709b2bf4 100644 --- a/src/Native/GirTestLib/girtest-typed-record-tester.c +++ b/src/Native/GirTestLib/girtest-typed-record-tester.c @@ -13,6 +13,7 @@ girtest_typed_record_tester_new () GirTestTypedRecordTester *result; result = g_new0 (GirTestTypedRecordTester, 1); result->ref_count = 1; + result->custom_string = g_strdup("Hello"); return result; } @@ -113,6 +114,7 @@ girtest_typed_record_tester_unref (GirTestTypedRecordTester *self) if (self->ref_count > 0) return; + g_free(self->custom_string); g_free (self); } diff --git a/src/Native/GirTestLib/girtest-typed-record-tester.h b/src/Native/GirTestLib/girtest-typed-record-tester.h index b8a8bb5c0..c9bbd3cc0 100644 --- a/src/Native/GirTestLib/girtest-typed-record-tester.h +++ b/src/Native/GirTestLib/girtest-typed-record-tester.h @@ -4,6 +4,30 @@ G_BEGIN_DECLS +/** + * GirTestTypedRecordTesterEnum: + * @A: 1 + * @B: 2 + * + * Enum to test bindings. + */ +typedef enum { + TYPED_RECORD_TESTER_ENUM_A = 0, + TYPED_RECORD_TESTER_ENUM_B = 1 +} GirTestTypedRecordTesterEnum; + +/** + * GirTestTypedRecordTesterBitfield: + * @ZERO: No flags set. + * @ONE: Set first flag. + * + * Flags to test bindings. + */ +typedef enum { + TYPED_RECORD_TESTER_ZERO = 0, + TYPED_RECORD_TESTER_ONE = (1 << 0) +} GirTestTypedRecordTesterBitfield; + /** * GirTestTypedRecordTester: * @@ -12,6 +36,11 @@ G_BEGIN_DECLS struct _GirTestTypedRecordTester { int ref_count; + GirTestTypedRecordTesterEnum custom_enum; + GirTestTypedRecordTesterBitfield custom_bitfield; + gchar* custom_string; + /* < private > */ + int custom_int_private; }; typedef struct _GirTestTypedRecordTester GirTestTypedRecordTester; diff --git a/src/Tests/Libs/GirTest-0.1.Tests/TypedRecordTest.cs b/src/Tests/Libs/GirTest-0.1.Tests/TypedRecordTest.cs index 22891d5e9..7743cb9f8 100644 --- a/src/Tests/Libs/GirTest-0.1.Tests/TypedRecordTest.cs +++ b/src/Tests/Libs/GirTest-0.1.Tests/TypedRecordTest.cs @@ -317,4 +317,54 @@ void Callback(out TypedRecordTester? recordTester) var result = TypedRecordTester.RunCallbackCreateNullableFullOwnershipTransferOut(Callback); result!.Handle.DangerousGetHandle().Should().Be(instance.Handle.DangerousGetHandle()); } + + [TestMethod] + public void SupportsPrimitiveValueTypeField() + { + var instance = TypedRecordTester.New(); + instance.RefCount.Should().Be(1); + instance.RefCount = 2; + instance.RefCount.Should().Be(2); + instance.RefCount = 1; + instance.RefCount.Should().Be(1); + } + + [TestMethod] + public void SupportsEnumerationField() + { + var instance = TypedRecordTester.New(); + instance.CustomEnum.Should().Be(TypedRecordTesterEnum.A); + instance.CustomEnum = TypedRecordTesterEnum.B; + instance.CustomEnum.Should().Be(TypedRecordTesterEnum.B); + } + + [TestMethod] + public void SupportsBitfieldField() + { + var instance = TypedRecordTester.New(); + instance.CustomBitfield.Should().Be(TypedRecordTesterBitfield.Zero); + instance.CustomBitfield = TypedRecordTesterBitfield.One; + instance.CustomBitfield.Should().Be(TypedRecordTesterBitfield.One); + } + + [TestMethod] + public void SupportsStringField() + { + var instance = TypedRecordTester.New(); + instance.CustomString.Should().Be("Hello"); + instance.CustomString = "Test"; + instance.CustomString.Should().Be("Test"); + instance.CustomString = null; + instance.CustomString.Should().BeNull(); + } + + [TestMethod] + public void SupportsPrivateFields() + { + var data = new Internal.TypedRecordTesterData(); + data.CustomIntPrivate.Should().Be(0); + + //Private fields are not rendered in the public API + typeof(TypedRecordTester).GetProperty(nameof(data.CustomIntPrivate)).Should().BeNull(); + } }