From 9a8ea7b8c4caedf0382e5786b56be872906e4305 Mon Sep 17 00:00:00 2001 From: badcel <1218031+badcel@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:16:15 +0100 Subject: [PATCH] Support boxed records --- .../Generator/Internal/TypedRecord.cs | 29 ++ .../Generator/Internal/TypedRecordData.cs | 29 ++ .../Generator/Internal/TypedRecordHandle.cs | 29 ++ .../Generator/Generator/Public/TypedRecord.cs | 29 ++ src/Generation/Generator/Model/Record.cs | 12 +- src/Generation/Generator/Model/TypedRecord.cs | 37 ++ src/Generation/Generator/Records.cs | 5 + .../Internal/Parameter/CallbackParameters.cs | 1 + .../Parameter/Converter/TypedRecord.cs | 41 +++ .../Parameter/Converter/TypedRecordArray.cs | 27 ++ .../Converter/TypedRecordCallback.cs | 30 ++ .../Renderer/Internal/Parameter/Parameters.cs | 2 + .../Converter/TypedRecord.cs | 36 ++ .../ParameterToManagedExpression.cs | 1 + .../ReturnType/Converter/TypedRecord.cs | 25 ++ .../Converter/TypedRecordCallback.cs | 14 + .../Internal/ReturnType/ReturnTypeRenderer.cs | 1 + .../ReturnType/ReturnTypeRendererCallback.cs | 1 + .../Converter/TypedRecord.cs | 21 ++ .../ReturnTypeToNativeExpression.cs | 1 + .../Renderer/Internal/TypedRecord.cs | 30 ++ .../Renderer/Internal/TypedRecordData.cs | 27 ++ .../Renderer/Internal/TypedRecordHandle.cs | 103 ++++++ .../Public/Parameter/Converter/TypedRecord.cs | 31 ++ .../Parameter/Converter/TypedRecordArray.cs | 31 ++ .../Public/Parameter/ParameterRenderer.cs | 2 + .../Converter/TypedRecord.cs | 33 ++ .../Converter/TypedRecordArray.cs | 71 ++++ .../ParameterToNativeExpression.cs | 2 + .../ReturnType/Converter/TypedRecord.cs | 16 + .../Public/ReturnType/ReturnTypeRenderer.cs | 2 + .../ReturnType/ReturnTypeRendererCallback.cs | 3 +- .../Converter/TypedRecord.cs | 28 ++ .../ReturnTypeToManagedExpression.cs | 1 + .../Generator/Renderer/Public/TypedRecord.cs | 62 ++++ src/Libs/GLib-2.0/Public/Source.cs | 2 - .../GirTestLib/girtest-typed-record-tester.c | 329 ++++++++++++++++++ .../GirTestLib/girtest-typed-record-tester.h | 96 +++++ src/Native/GirTestLib/girtest.h | 1 + src/Native/GirTestLib/meson.build | 2 + 40 files changed, 1238 insertions(+), 5 deletions(-) create mode 100644 src/Generation/Generator/Generator/Internal/TypedRecord.cs create mode 100644 src/Generation/Generator/Generator/Internal/TypedRecordData.cs create mode 100644 src/Generation/Generator/Generator/Internal/TypedRecordHandle.cs create mode 100644 src/Generation/Generator/Generator/Public/TypedRecord.cs create mode 100644 src/Generation/Generator/Model/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordArray.cs create mode 100644 src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordCallback.cs create mode 100644 src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecordCallback.cs create mode 100644 src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Internal/TypedRecordData.cs create mode 100644 src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs create mode 100644 src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecordArray.cs create mode 100644 src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecordArray.cs create mode 100644 src/Generation/Generator/Renderer/Public/ReturnType/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/Converter/TypedRecord.cs create mode 100644 src/Generation/Generator/Renderer/Public/TypedRecord.cs create mode 100644 src/Native/GirTestLib/girtest-typed-record-tester.c create mode 100644 src/Native/GirTestLib/girtest-typed-record-tester.h diff --git a/src/Generation/Generator/Generator/Internal/TypedRecord.cs b/src/Generation/Generator/Generator/Internal/TypedRecord.cs new file mode 100644 index 000000000..2176dc1f0 --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/TypedRecord.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class TypedRecord : Generator +{ + private readonly Publisher _publisher; + + public TypedRecord(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsTyped(obj)) + return; + + var source = Renderer.Internal.TypedRecord.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: obj.Name, + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Internal/TypedRecordData.cs b/src/Generation/Generator/Generator/Internal/TypedRecordData.cs new file mode 100644 index 000000000..77f95a5ef --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/TypedRecordData.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class TypedRecordData : Generator +{ + private readonly Publisher _publisher; + + public TypedRecordData(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsTyped(obj)) + return; + + var source = Renderer.Internal.TypedRecordData.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: Model.TypedRecord.GetDataName(obj), + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Internal/TypedRecordHandle.cs b/src/Generation/Generator/Generator/Internal/TypedRecordHandle.cs new file mode 100644 index 000000000..313e53964 --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/TypedRecordHandle.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class TypedRecordHandle : Generator +{ + private readonly Publisher _publisher; + + public TypedRecordHandle(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsTyped(obj)) + return; + + var source = Renderer.Internal.TypedRecordHandle.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: Model.TypedRecord.GetInternalHandle(obj), + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Public/TypedRecord.cs b/src/Generation/Generator/Generator/Public/TypedRecord.cs new file mode 100644 index 000000000..d8880ac1c --- /dev/null +++ b/src/Generation/Generator/Generator/Public/TypedRecord.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Public; + +internal class TypedRecord : Generator +{ + private readonly Publisher _publisher; + + public TypedRecord(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record record) + { + if (!Record.IsTyped(record)) + return; + + var source = Renderer.Public.TypedRecord.Render(record); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(record.Namespace), + Name: Record.GetPublicClassName(record), + Source: source, + IsInternal: false + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Model/Record.cs b/src/Generation/Generator/Model/Record.cs index 86bf13f6a..f5be36b7b 100644 --- a/src/Generation/Generator/Model/Record.cs +++ b/src/Generation/Generator/Model/Record.cs @@ -4,7 +4,7 @@ internal static partial class Record { public static bool IsStandard(GirModel.Record record) { - return !IsOpaqueTyped(record) && !IsOpaqueUntyped(record); + return !IsOpaqueTyped(record) && !IsOpaqueUntyped(record) && !IsTyped(record); } public static bool IsOpaqueTyped(GirModel.Record record) @@ -21,7 +21,15 @@ public static bool IsOpaqueUntyped(GirModel.Record record) //untyped. return record is { Opaque: true, TypeFunction: null or { CIdentifier: "intern" } }; } - + + public static bool IsTyped(GirModel.Record record) + { + //Even if there is a TypeFunction it does not mean that it actually is + //a typed / boxed record. There is a magic keyword "intern" which means this + //record is actually fundamental and does not have a type function. + return record is { Opaque: false, TypeFunction.CIdentifier: not "intern" }; + } + public static string GetFullyQualifiedInternalStructName(GirModel.Record record) => Namespace.GetInternalName(record.Namespace) + "." + GetInternalStructName(record); diff --git a/src/Generation/Generator/Model/TypedRecord.cs b/src/Generation/Generator/Model/TypedRecord.cs new file mode 100644 index 000000000..5379128c2 --- /dev/null +++ b/src/Generation/Generator/Model/TypedRecord.cs @@ -0,0 +1,37 @@ +namespace Generator.Model; + +internal static class TypedRecord +{ + public static string GetPublicClassName(GirModel.Record record) + => record.Name; + + public static string GetFullyQualifiedPublicClassName(GirModel.Record record) + => Namespace.GetPublicName(record.Namespace) + "." + GetPublicClassName(record); + + public static string GetFullyQualifiedInternalClassName(GirModel.Record record) + => Namespace.GetInternalName(record.Namespace) + "." + record.Name; + + public static string GetInternalHandle(GirModel.Record record) + => $"{Type.GetName(record)}Handle"; + + public static string GetInternalOwnedHandle(GirModel.Record record) + => $"{Type.GetName(record)}OwnedHandle"; + + public static string GetInternalUnownedHandle(GirModel.Record record) + => $"{Type.GetName(record)}UnownedHandle"; + + public static string GetFullyQuallifiedInternalHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalHandle(record)}"; + + public static string GetFullyQuallifiedOwnedHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalOwnedHandle(record)}"; + + public static string GetFullyQuallifiedUnownedHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalUnownedHandle(record)}"; + + public static string GetFullyQuallifiedNullHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalUnownedHandle(record)}.NullHandle"; + + public static string GetDataName(GirModel.Record record) + => $"{Type.GetName(record)}Data"; +} diff --git a/src/Generation/Generator/Records.cs b/src/Generation/Generator/Records.cs index b59f7f2c0..c10fbc96d 100644 --- a/src/Generation/Generator/Records.cs +++ b/src/Generation/Generator/Records.cs @@ -22,6 +22,11 @@ public static void Generate(IEnumerable records, string path) new Generator.Internal.OpaqueUntypedRecordHandle(publisher), new Generator.Public.OpaqueUntypedRecord(publisher), + //Typed records + new Generator.Internal.TypedRecord(publisher), + new Generator.Internal.TypedRecordHandle(publisher), + new Generator.Public.TypedRecord(publisher), + //Regular records new Generator.Internal.RecordDelegates(publisher), new Generator.Internal.RecordHandle(publisher), diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/CallbackParameters.cs b/src/Generation/Generator/Renderer/Internal/Parameter/CallbackParameters.cs index 3642783fe..806079862 100644 --- a/src/Generation/Generator/Renderer/Internal/Parameter/CallbackParameters.cs +++ b/src/Generation/Generator/Renderer/Internal/Parameter/CallbackParameters.cs @@ -36,6 +36,7 @@ internal static class CallbackParameters new Parameter.RecordCallback(), //Callbacks do not support record safe handles in parameters new Parameter.RecordGLibPtrArray(), new Parameter.String(), + new Parameter.TypedRecordCallback(), new Parameter.Union(), new Parameter.UnsignedPointer(), new Parameter.Utf8StringArrayCallback(), diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecord.cs new file mode 100644 index 000000000..40bec6417 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecord.cs @@ -0,0 +1,41 @@ +using System; + +namespace Generator.Renderer.Internal.Parameter; + +internal class TypedRecord : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.Is(out var record) && Model.Record.IsTyped(record); + } + + public RenderableParameter Convert(GirModel.Parameter parameter) + { + return new RenderableParameter( + Attribute: string.Empty, + Direction: GetDirection(parameter), + NullableTypeName: GetNullableTypeName(parameter), + Name: Model.Parameter.GetName(parameter) + ); + } + + private static string GetNullableTypeName(GirModel.Parameter parameter) + { + //Native records are represented as SafeHandles and are not nullable + + var type = (GirModel.Record) parameter.AnyTypeOrVarArgs.AsT0.AsT0; + return parameter switch + { + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.None } => Model.TypedRecord.GetFullyQuallifiedInternalHandle(type), + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.Full } => Model.TypedRecord.GetFullyQuallifiedUnownedHandle(type), + _ => throw new Exception($"Can't detect record parameter type {parameter.Name}: CallerAllocates={parameter.CallerAllocates} Direction={parameter.Direction} Transfer={parameter.Transfer}") + }; + } + + private static string GetDirection(GirModel.Parameter parameter) => parameter switch + { + { Direction: GirModel.Direction.In } => ParameterDirection.In(), + { Direction: GirModel.Direction.InOut } => ParameterDirection.In(), + _ => throw new Exception($"Unknown parameter direction for opaque typed record parameter {parameter.Name}") + }; +} diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordArray.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordArray.cs new file mode 100644 index 000000000..2e18dcc45 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordArray.cs @@ -0,0 +1,27 @@ +using System; + +namespace Generator.Renderer.Internal.Parameter; + +internal class TypedRecordArray : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.IsArray(out var record) && Model.Record.IsTyped(record); + } + + public RenderableParameter Convert(GirModel.Parameter parameter) + { + if (!parameter.AnyTypeOrVarArgs.AsT0.AsT1.IsPointer) + { + var record = (GirModel.Record) parameter.AnyTypeOrVarArgs.AsT0.AsT1.AnyType.AsT0; + throw new Exception($"Unpointed record array of type {record.Name} not yet supported"); + } + + return new RenderableParameter( + Attribute: string.Empty, + Direction: string.Empty, + NullableTypeName: $"ref {Model.Type.Pointer}", + Name: Model.Parameter.GetName(parameter) + ); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordCallback.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordCallback.cs new file mode 100644 index 000000000..ffd774957 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/TypedRecordCallback.cs @@ -0,0 +1,30 @@ +using System; + +namespace Generator.Renderer.Internal.Parameter; + +internal class TypedRecordCallback : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.Is(out var record) && Model.Record.IsTyped(record); + } + + public RenderableParameter Convert(GirModel.Parameter parameter) + { + return new RenderableParameter( + Attribute: string.Empty, + Direction: GetDirection(parameter), + NullableTypeName: Model.Type.Pointer, + Name: Model.Parameter.GetName(parameter) + ); + } + + private static string GetDirection(GirModel.Parameter parameter) => parameter switch + { + { Direction: GirModel.Direction.In } => ParameterDirection.In(), + { Direction: GirModel.Direction.InOut } => ParameterDirection.In(), + { Direction: GirModel.Direction.Out, CallerAllocates: true } => ParameterDirection.In(), + { Direction: GirModel.Direction.Out } => ParameterDirection.Out(), + _ => throw new Exception("Unknown direction for record parameter in callback") + }; +} diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs index e82a16601..30945b3ce 100644 --- a/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs @@ -41,6 +41,8 @@ internal static class Parameters new Parameter.RecordGLibPtrArray(), new Parameter.String(), new Parameter.StringGLibPtrArray(), + new Parameter.TypedRecord(), + new Parameter.TypedRecordArray(), new Parameter.Union(), new Parameter.UnsignedPointer(), new Parameter.Utf8StringArray(), diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/TypedRecord.cs new file mode 100644 index 000000000..7704b4d86 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/TypedRecord.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Generator.Renderer.Internal.ParameterToManagedExpressions; + +internal class TypedRecord : ToManagedParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.Is(out var record) && Model.Record.IsTyped(record); + + public void Initialize(ParameterToManagedData parameterData, IEnumerable parameters) + { + if (parameterData.Parameter.Direction != GirModel.Direction.In) + throw new NotImplementedException($"{parameterData.Parameter.AnyTypeOrVarArgs}: typed record with direction != in not yet supported"); + + var record = (GirModel.Record) parameterData.Parameter.AnyTypeOrVarArgs.AsT0.AsT0; + var variableName = Model.Parameter.GetConvertedName(parameterData.Parameter); + + var signatureName = Model.Parameter.GetName(parameterData.Parameter); + + var ownedHandle = parameterData.Parameter switch + { + { Transfer: GirModel.Transfer.Full } => $"new {Model.TypedRecord.GetFullyQuallifiedOwnedHandle(record)}({signatureName})", + { Transfer: GirModel.Transfer.None } => $"{Model.TypedRecord.GetFullyQuallifiedOwnedHandle(record)}.FromUnowned({signatureName})", + _ => throw new Exception($"Unknown transfer type for typed record parameter {parameterData.Parameter.Name}") + }; + + var nullable = parameterData.Parameter.Nullable + ? $" {signatureName} == IntPtr.Zero ? null :" + : string.Empty; + + parameterData.SetSignatureName(signatureName); + parameterData.SetExpression($"var {variableName} ={nullable} new {Model.TypedRecord.GetFullyQualifiedPublicClassName(record)}({ownedHandle});"); + parameterData.SetCallName(variableName); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs index 6831f53a0..065ccae32 100644 --- a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs @@ -25,6 +25,7 @@ internal static class ParameterToManagedExpression new ParameterToManagedExpressions.Record(), new ParameterToManagedExpressions.RecordArray(), new ParameterToManagedExpressions.String(), + new ParameterToManagedExpressions.TypedRecord(), new ParameterToManagedExpressions.Utf8StringArray(), }; diff --git a/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecord.cs new file mode 100644 index 000000000..3c92ed109 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecord.cs @@ -0,0 +1,25 @@ +using GirModel; + +namespace Generator.Renderer.Internal.ReturnType; + +internal class TypedRecord : ReturnTypeConverter +{ + public bool Supports(GirModel.ReturnType returnType) + { + return returnType.AnyType.Is(out var record) && Model.Record.IsTyped(record); + } + + public RenderableReturnType Convert(GirModel.ReturnType returnType) + { + var type = (GirModel.Record) returnType.AnyType.AsT0; + + var typeName = returnType switch + { + { Transfer: Transfer.Full } => Model.TypedRecord.GetFullyQuallifiedOwnedHandle(type), + _ => Model.TypedRecord.GetFullyQuallifiedUnownedHandle(type) + }; + + //Returned SafeHandles are never "null" but "invalid" in case of C NULL. + return new RenderableReturnType(typeName); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecordCallback.cs b/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecordCallback.cs new file mode 100644 index 000000000..7fa30b97e --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ReturnType/Converter/TypedRecordCallback.cs @@ -0,0 +1,14 @@ +namespace Generator.Renderer.Internal.ReturnType; + +internal class TypedRecordCallback : ReturnTypeConverter +{ + public bool Supports(GirModel.ReturnType returnType) + { + return returnType.AnyType.Is(out var record) && Model.Record.IsTyped(record); + } + + public RenderableReturnType Convert(GirModel.ReturnType returnType) + { + return new RenderableReturnType(Model.Type.Pointer); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRenderer.cs b/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRenderer.cs index 6e290ee9d..5794c1523 100644 --- a/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRenderer.cs +++ b/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRenderer.cs @@ -23,6 +23,7 @@ internal static class ReturnTypeRenderer new ReturnType.PrimitiveValueTypeArray(), new ReturnType.Record(), new ReturnType.RecordArray(), + new ReturnType.TypedRecord(), new ReturnType.Union(), new ReturnType.Utf8String(), new ReturnType.Utf8StringArray(), diff --git a/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRendererCallback.cs b/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRendererCallback.cs index 0dd765b1d..d79c490ae 100644 --- a/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRendererCallback.cs +++ b/src/Generation/Generator/Renderer/Internal/ReturnType/ReturnTypeRendererCallback.cs @@ -22,6 +22,7 @@ internal static class ReturnTypeRendererCallback new ReturnType.PrimitiveValueTypeArray(), new ReturnType.RecordArray(), new ReturnType.RecordInCallback(), + new ReturnType.TypedRecordCallback(), new ReturnType.Union(), new ReturnType.Utf8StringInCallback(), new ReturnType.Utf8StringArrayInCallback(), diff --git a/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/Converter/TypedRecord.cs new file mode 100644 index 000000000..9a9ec1d7a --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/Converter/TypedRecord.cs @@ -0,0 +1,21 @@ +using System; + +namespace Generator.Renderer.Internal.ReturnTypeToNativeExpressions; + +internal class TypedRecord : ReturnTypeConverter +{ + public bool Supports(GirModel.AnyType type) + => type.Is(out var record) && Model.Record.IsTyped(record); + + public string GetString(GirModel.ReturnType returnType, string fromVariableName) + { + return returnType switch + { + { Transfer: GirModel.Transfer.None, Nullable: true } => $"{fromVariableName}?.Handle.DangerousGetHandle() ?? IntPtr.Zero", + { Transfer: GirModel.Transfer.None, Nullable: false } => $"{fromVariableName}.Handle.DangerousGetHandle()", + { Transfer: GirModel.Transfer.Full, Nullable: true } => $"{fromVariableName}?.Handle.UnownedCopy().DangerousGetHandle() ?? IntPtr.Zero", + { Transfer: GirModel.Transfer.Full, Nullable: false } => $"{fromVariableName}.Handle.UnownedCopy().DangerousGetHandle()", + _ => throw new Exception($"Unknown transfer type for record return type which should be converted to native.") + }; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/ReturnTypeToNativeExpression.cs b/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/ReturnTypeToNativeExpression.cs index dff6039c5..8b58d46c5 100644 --- a/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/ReturnTypeToNativeExpression.cs +++ b/src/Generation/Generator/Renderer/Internal/ReturnTypeToNativeExpression/ReturnTypeToNativeExpression.cs @@ -16,6 +16,7 @@ internal static class ReturnTypeToNativeExpression new ReturnTypeToNativeExpressions.PrimitiveValueType(), new ReturnTypeToNativeExpressions.PrimitiveValueTypeAlias(), new ReturnTypeToNativeExpressions.Record(), + new ReturnTypeToNativeExpressions.TypedRecord(), new ReturnTypeToNativeExpressions.Utf8String(), }; diff --git a/src/Generation/Generator/Renderer/Internal/TypedRecord.cs b/src/Generation/Generator/Renderer/Internal/TypedRecord.cs new file mode 100644 index 000000000..d3fa8c473 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/TypedRecord.cs @@ -0,0 +1,30 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class TypedRecord +{ + public static string Render(GirModel.Record record) + { + return $@" +using System; +using GObject; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +#nullable enable + +namespace {Namespace.GetInternalName(record.Namespace)}; + +// AUTOGENERATED FILE - DO NOT MODIFY + +{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} +public partial class {record.Name} +{{ + {Constructors.Render(record.Constructors)} + {Functions.Render(record.TypeFunction)} + {Functions.Render(record.Functions)} + {Methods.Render(record.Methods)} +}}"; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/TypedRecordData.cs b/src/Generation/Generator/Renderer/Internal/TypedRecordData.cs new file mode 100644 index 000000000..573ccd3a6 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/TypedRecordData.cs @@ -0,0 +1,27 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class TypedRecordData +{ + public static string Render(GirModel.Record record) + { + return $@" +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +#nullable enable + +namespace {Namespace.GetInternalName(record.Namespace)}; + +// AUTOGENERATED FILE - DO NOT MODIFY + +{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} +[StructLayout(LayoutKind.Sequential)] +public partial struct {Model.TypedRecord.GetDataName(record)} +{{ + {Fields.Render(record.Fields)} +}}"; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs b/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs new file mode 100644 index 000000000..1208b95c8 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/TypedRecordHandle.cs @@ -0,0 +1,103 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class TypedRecordHandle +{ + public static string Render(GirModel.Record record) + { + var typeName = Model.TypedRecord.GetInternalHandle(record); + var unownedHandleTypeName = Model.TypedRecord.GetInternalUnownedHandle(record); + var ownedHandleTypeName = Model.TypedRecord.GetInternalOwnedHandle(record); + var getGType = $"{Model.TypedRecord.GetFullyQualifiedInternalClassName(record)}.{Function.GetGType}()"; + + return $@"using System; +using GObject; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +#nullable enable + +namespace {Namespace.GetInternalName(record.Namespace)}; + +// AUTOGENERATED FILE - DO NOT MODIFY + +{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} +public abstract class {typeName} : SafeHandle +{{ + public sealed override bool IsInvalid => handle == IntPtr.Zero; + + protected {typeName}(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) {{ }} + + public {ownedHandleTypeName} OwnedCopy() + {{ + var ptr = GObject.Internal.Functions.BoxedCopy({getGType}, handle); + return new {ownedHandleTypeName}(ptr); + }} + + public {unownedHandleTypeName} UnownedCopy() + {{ + var ptr = GObject.Internal.Functions.BoxedCopy({getGType}, handle); + return new {unownedHandleTypeName}(ptr); + }} +}} + +public class {unownedHandleTypeName} : {typeName} +{{ + private static {unownedHandleTypeName}? nullHandle; + public static {unownedHandleTypeName} NullHandle => nullHandle ??= new {unownedHandleTypeName}(); + + /// + /// Creates a new instance of {unownedHandleTypeName}. Used automatically by PInvoke. + /// + internal {unownedHandleTypeName}() : base(false) {{ }} + + /// + /// Creates a new instance of {ownedHandleTypeName}. Assumes that the given pointer is unowned by the runtime. + /// + public {unownedHandleTypeName}(IntPtr ptr) : base(false) + {{ + SetHandle(ptr); + }} + + protected override bool ReleaseHandle() + {{ + throw new Exception(""UnownedHandle must not be freed""); + }} +}} + +public class {ownedHandleTypeName} : {typeName} +{{ + /// + /// Creates a new instance of {ownedHandleTypeName}. Used automatically by PInvoke. + /// + internal {ownedHandleTypeName}() : base(true) {{ }} + + /// + /// Creates a new instance of {ownedHandleTypeName}. Assumes that the given pointer is owned by the runtime. + /// + public {ownedHandleTypeName}(IntPtr ptr) : base(true) + {{ + SetHandle(ptr); + }} + + /// + /// Create a {ownedHandleTypeName} from a pointer that is assumed unowned. To do so a + /// boxed copy is created of the given pointer to be used as the handle. + /// + /// A pointer to a {record.Name} which is not owned by the runtime. + /// A {ownedHandleTypeName} + public static {ownedHandleTypeName} FromUnowned(IntPtr ptr) + {{ + var ownedPtr = GObject.Internal.Functions.BoxedCopy({getGType}, ptr); + return new {ownedHandleTypeName}(ownedPtr); + }} + + protected override bool ReleaseHandle() + {{ + GObject.Internal.Functions.BoxedFree({getGType}, handle); + return true; + }} +}}"; + } +} diff --git a/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecord.cs new file mode 100644 index 000000000..8993d6abf --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecord.cs @@ -0,0 +1,31 @@ +using System; + +namespace Generator.Renderer.Public.Parameter; + +internal class TypedRecord : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.Is(out var record) && Model.Record.IsTyped(record); + } + + public ParameterTypeData Create(GirModel.Parameter parameter) + { + return new ParameterTypeData( + Direction: GetDirection(parameter), + NullableTypeName: GetNullableTypeName(parameter) + ); + } + + private static string GetNullableTypeName(GirModel.Parameter parameter) + { + var type = (GirModel.Record) parameter.AnyTypeOrVarArgs.AsT0.AsT0; + return Model.TypedRecord.GetFullyQualifiedPublicClassName(type) + Nullable.Render(parameter); + } + + private static string GetDirection(GirModel.Parameter parameter) => parameter switch + { + { Direction: GirModel.Direction.In } => ParameterDirection.In(), + _ => throw new Exception("records with direction != in not yet supported") + }; +} diff --git a/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecordArray.cs b/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecordArray.cs new file mode 100644 index 000000000..fe5eb1130 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Parameter/Converter/TypedRecordArray.cs @@ -0,0 +1,31 @@ +using System; + +namespace Generator.Renderer.Public.Parameter; + +internal class TypedRecordArray : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.IsArray(out var record) && Model.Record.IsTyped(record); + } + + public ParameterTypeData Create(GirModel.Parameter parameter) + { + return new ParameterTypeData( + Direction: GetDirection(parameter), + NullableTypeName: GetNullableTypeName(parameter) + ); + } + + private static string GetNullableTypeName(GirModel.Parameter parameter) + { + var arrayType = parameter.AnyTypeOrVarArgs.AsT0.AsT1; + return $"{Model.TypedRecord.GetFullyQualifiedPublicClassName((GirModel.Record) arrayType.AnyType.AsT0)}[]{Nullable.Render(parameter)}"; + } + + private static string GetDirection(GirModel.Parameter parameter) => parameter switch + { + { Direction: GirModel.Direction.In } => ParameterDirection.In(), + _ => throw new Exception($"Unknown direction for typed record in parameter {parameter.Name}.") + }; +} diff --git a/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs b/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs index e1f863ff6..742e47424 100644 --- a/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs +++ b/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs @@ -28,6 +28,8 @@ internal static class ParameterRenderer new Parameter.RecordArray(), new Parameter.String(), new Parameter.StringArray(), + new Parameter.TypedRecord(), + new Parameter.TypedRecordArray(), new Parameter.Union(), new Parameter.Void(), }; diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecord.cs new file mode 100644 index 000000000..ee5b8e194 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecord.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace Generator.Renderer.Public.ParameterToNativeExpressions; + +internal class TypedRecord : ToNativeParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.Is(out var record) && Model.Record.IsTyped(record); + + public void Initialize(ParameterToNativeData parameter, IEnumerable _) + { + if (parameter.Parameter.Direction != GirModel.Direction.In) + throw new NotImplementedException($"{parameter.Parameter.AnyTypeOrVarArgs}: record parameter '{parameter.Parameter.Name}' with direction != in not yet supported"); + + var record = (GirModel.Record) parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT0; + var typeHandle = Model.TypedRecord.GetFullyQuallifiedInternalHandle(record); + var nullHandle = Model.TypedRecord.GetFullyQuallifiedNullHandle(record); + var signatureName = Model.Parameter.GetName(parameter.Parameter); + + var callName = parameter.Parameter switch + { + { Nullable: true, Transfer: GirModel.Transfer.None } => $"({typeHandle}?) {signatureName}?.Handle ?? {nullHandle}", + { Nullable: false, Transfer: GirModel.Transfer.None } => $"{signatureName}.Handle", + { Nullable: true, Transfer: GirModel.Transfer.Full } => $"{signatureName}?.Handle.UnownedCopy() ?? {nullHandle}", + { Nullable: false, Transfer: GirModel.Transfer.Full } => $"{signatureName}.Handle.UnownedCopy()", + _ => throw new Exception($"Can't detect call name for parameter record parameter {parameter.Parameter.Name}") + }; + + parameter.SetSignatureName(signatureName); + parameter.SetCallName(callName); + } +} diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecordArray.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecordArray.cs new file mode 100644 index 000000000..744fcbb9f --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/TypedRecordArray.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Generator.Renderer.Public.ParameterToNativeExpressions; + +internal class TypedRecordArray : ToNativeParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.IsArray(out var record) && Model.Record.IsTyped(record); + + public void Initialize(ParameterToNativeData parameterData, IEnumerable parameters) + { + switch (parameterData.Parameter) + { + case { Direction: GirModel.Direction.In } + when parameterData.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.Length is not null: + Span(parameterData, parameters); + break; + case { Direction: GirModel.Direction.In }: + Ref(parameterData); + break; + default: + throw new Exception($"{parameterData.Parameter}: This kind of typed record array is not yet supported"); + } + } + + private static void Ref(ParameterToNativeData parameter) + { + var parameterName = Model.Parameter.GetName(parameter.Parameter); + parameter.SetSignatureName(parameterName); + parameter.SetCallName($"ref {parameterName}"); + + //TODO + throw new Exception("Test missing for typed record array passed in via a ref"); + } + + private static void Span(ParameterToNativeData parameter, IEnumerable allParameters) + { + var parameterName = Model.Parameter.GetName(parameter.Parameter); + var nativeVariableName = parameterName + "Native"; + + parameter.SetSignatureName(parameterName); + parameter.SetCallName($"ref MemoryMarshal.GetReference({nativeVariableName})"); + + var nullable = parameter.Parameter.Nullable + ? $"{parameterName} is null ? null : " + : string.Empty; + + parameter.SetExpression($"var {nativeVariableName} = new Span({nullable}{parameterName}" + + $".Select(record => record.Handle.DangerousGetHandle()).ToArray());"); + + var lengthIndex = parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.Length ?? throw new Exception("Length missing"); + var lengthParameter = allParameters.ElementAt(lengthIndex); + var lengthParameterType = Model.Type.GetName(lengthParameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT0); + + switch (lengthParameter.Parameter.Direction) + { + case GirModel.Direction.In: + lengthParameter.IsArrayLengthParameter = true; + lengthParameter.SetCallName(parameter.Parameter.Nullable + ? $"({lengthParameterType}) ({parameterName}?.Length ?? 0)" + : $"({lengthParameterType}) {parameterName}.Length" + ); + break; + default: + throw new Exception("Unknown direction for length parameter in typed record array"); + } + } +} diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs index 1c2da09e0..19143d1d6 100644 --- a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs @@ -28,6 +28,8 @@ internal static class ParameterToNativeExpression new ParameterToNativeExpressions.PrimitiveValueTypeArray(), new ParameterToNativeExpressions.Record(), new ParameterToNativeExpressions.RecordArray(), + new ParameterToNativeExpressions.TypedRecord(), + new ParameterToNativeExpressions.TypedRecordArray(), new ParameterToNativeExpressions.Utf8String(), new ParameterToNativeExpressions.Utf8StringArray(), }; diff --git a/src/Generation/Generator/Renderer/Public/ReturnType/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/ReturnType/Converter/TypedRecord.cs new file mode 100644 index 000000000..be399d460 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ReturnType/Converter/TypedRecord.cs @@ -0,0 +1,16 @@ +using Generator.Model; + +namespace Generator.Renderer.Public.ReturnType; + +internal class TypedRecord : ReturnTypeConverter +{ + public RenderableReturnType Create(GirModel.ReturnType returnType) + { + var typeName = ComplexType.GetFullyQualified((GirModel.Record) returnType.AnyType.AsT0); + + return new RenderableReturnType(typeName + Nullable.Render(returnType)); + } + + public bool Supports(GirModel.ReturnType returnType) + => returnType.AnyType.Is(out var record) && Model.Record.IsTyped(record); +} diff --git a/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRenderer.cs b/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRenderer.cs index 7ddfa13da..0df6586f9 100644 --- a/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRenderer.cs +++ b/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRenderer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Reflection.Metadata.Ecma335; namespace Generator.Renderer.Public; @@ -20,6 +21,7 @@ internal static class ReturnTypeRenderer new ReturnType.RecordArray(), new ReturnType.String(), new ReturnType.StringArray(), + new ReturnType.TypedRecord(), new ReturnType.Void(), }; diff --git a/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRendererCallback.cs b/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRendererCallback.cs index 5742eba14..6eb7b65cd 100644 --- a/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRendererCallback.cs +++ b/src/Generation/Generator/Renderer/Public/ReturnType/ReturnTypeRendererCallback.cs @@ -18,8 +18,9 @@ internal static class ReturnTypeRendererCallback new ReturnType.PrimitiveValueTypeAlias(), new ReturnType.Record(), new ReturnType.RecordArray(), - new ReturnType.StringInCallback(), new ReturnType.StringArray(), + new ReturnType.StringInCallback(), + new ReturnType.TypedRecord(), new ReturnType.Void(), }; diff --git a/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/Converter/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/Converter/TypedRecord.cs new file mode 100644 index 000000000..7b6c7f9a9 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/Converter/TypedRecord.cs @@ -0,0 +1,28 @@ +using System; +using GirModel; + +namespace Generator.Renderer.Public.ReturnTypeToManagedExpressions; + +internal class TypedRecord : ReturnTypeConverter +{ + public bool Supports(AnyType type) + => type.Is(out var record) && Model.Record.IsTyped(record); + + public string GetString(GirModel.ReturnType returnType, string fromVariableName) + { + var record = (GirModel.Record) returnType.AnyType.AsT0; + + var handleExpression = returnType switch + { + { Transfer: Transfer.Full } => fromVariableName, + { Transfer: Transfer.None } => $"{fromVariableName}.OwnedCopy()", + _ => throw new NotImplementedException("Unknown transfer type") + }; + + var createNewInstance = $"new {Model.ComplexType.GetFullyQualified(record)}({handleExpression})"; + + return returnType.Nullable + ? $"{fromVariableName}.IsInvalid ? null : {createNewInstance};" + : createNewInstance; + } +} diff --git a/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/ReturnTypeToManagedExpression.cs b/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/ReturnTypeToManagedExpression.cs index 0546d7f24..a147ffbc4 100644 --- a/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/ReturnTypeToManagedExpression.cs +++ b/src/Generation/Generator/Renderer/Public/ReturnTypeToManagedExpression/ReturnTypeToManagedExpression.cs @@ -20,6 +20,7 @@ internal static class ReturnTypeToManagedExpression new ReturnTypeToManagedExpressions.PrimitiveValueTypeAlias(), new ReturnTypeToManagedExpressions.PrimitiveValueTypeArray(), new ReturnTypeToManagedExpressions.Record(), + new ReturnTypeToManagedExpressions.TypedRecord(), new ReturnTypeToManagedExpressions.Utf8String(), new ReturnTypeToManagedExpressions.Utf8StringArray(), }; diff --git a/src/Generation/Generator/Renderer/Public/TypedRecord.cs b/src/Generation/Generator/Renderer/Public/TypedRecord.cs new file mode 100644 index 000000000..f8c3b81e2 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/TypedRecord.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using Generator.Model; + +namespace Generator.Renderer.Public; + +internal static class TypedRecord +{ + public static string Render(GirModel.Record record) + { + var name = Model.TypedRecord.GetPublicClassName(record); + var internalHandleName = Model.TypedRecord.GetFullyQuallifiedOwnedHandle(record); + + return $@" +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +#nullable enable + +namespace {Namespace.GetPublicName(record.Namespace)}; + +// AUTOGENERATED FILE - DO NOT MODIFY + +{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)} +public partial class {name} +{{ + public {internalHandleName} Handle {{ get; }} + + public {name}({internalHandleName} handle) + {{ + Handle = handle; + Initialize(); + }} + + //TODO: This is a workaround constructor as long as we are + //not having https://github.com/gircore/gir.core/issues/397 + private {name}(IntPtr ptr, bool ownsHandle) : this(ownsHandle + ? new {Model.OpaqueTypedRecord.GetFullyQuallifiedOwnedHandle(record)}(ptr) + : new {Model.OpaqueTypedRecord.GetFullyQuallifiedUnownedHandle(record)}(ptr).OwnedCopy()){{ }} + + // Implement this to perform additional steps in the constructor + partial void Initialize(); + + {record.Constructors + .Select(ConstructorRenderer.Render) + .Join(Environment.NewLine)} + + {FunctionRenderer.Render(record.TypeFunction)} + + {record.Functions + .Select(FunctionRenderer.Render) + .Join(Environment.NewLine)} + + {record.Methods + .Where(Method.IsEnabled) + .Select(MethodRenderer.Render) + .Join(Environment.NewLine)} +}}"; + } +} diff --git a/src/Libs/GLib-2.0/Public/Source.cs b/src/Libs/GLib-2.0/Public/Source.cs index d401bc8d6..1d1f40005 100644 --- a/src/Libs/GLib-2.0/Public/Source.cs +++ b/src/Libs/GLib-2.0/Public/Source.cs @@ -14,6 +14,4 @@ public void Attach(MainContext mainContext) { Internal.Source.Attach(Handle, mainContext.Handle); } - - public static void Remove(uint tag) => Internal.Functions.SourceRemove(tag); } diff --git a/src/Native/GirTestLib/girtest-typed-record-tester.c b/src/Native/GirTestLib/girtest-typed-record-tester.c new file mode 100644 index 000000000..2a1063cd6 --- /dev/null +++ b/src/Native/GirTestLib/girtest-typed-record-tester.c @@ -0,0 +1,329 @@ +#include "girtest-typed-record-tester.h" + +G_DEFINE_BOXED_TYPE (GirTestTypedRecordTester, girtest_typed_record_tester, girtest_typed_record_tester_ref, girtest_typed_record_tester_unref) + +/** + * girtest_typed_record_tester_new: (constructor) + * + * Returns: (transfer full): a new `GirTestTypedRecordTester` + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_new () +{ + GirTestTypedRecordTester *result; + result = g_new0 (GirTestTypedRecordTester, 1); + result->ref_count = 1; + return result; +} + +/** + * girtest_typed_record_tester_try_new: + * @returnNull: TRUE to return null, FALSE to create a new instance. + * + * Returns: (transfer full) (nullable): a new `GirTestTypedRecordTester` or NULL + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_try_new (gboolean returnNull) +{ + if(returnNull) + return NULL; + + return girtest_typed_record_tester_new(); +} + +/** + * girtest_typed_record_tester_ref: + * @self: a `GirTestRecordTester` + * + * Increments the reference count on `data`. + * + * Returns: (transfer full): the data. + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_ref (GirTestTypedRecordTester *self) +{ + g_return_val_if_fail (self != NULL, NULL); + self->ref_count += 1; + return self; +} + +/** + * girtest_typed_record_tester_try_ref: + * @self: a `GirTestRecordTester` + * @returnNull: TRUE to return NULL, otherwise FALSE + * + * Increments the reference count on `data`. + * + * Returns: (transfer full) (nullable): the data or NULL + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_try_ref (GirTestTypedRecordTester *self, gboolean returnNull) +{ + if(returnNull) + return NULL; + + return girtest_typed_record_tester_ref(self); +} + +/** + * girtest_typed_record_tester_mirror: + * @data: a `GirTestRecordTester` + * + * Mirrors the given data as the return value. Ownership is not transferred. + * + * Returns: (transfer none): the mirrored data. + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_mirror(GirTestTypedRecordTester *data) +{ + return data; +} + +/** + * girtest_typed_record_tester_nullable_mirror: + * @data: a `GirTestRecordTester` + * @mirror: true to mirror data, false to return NULL + * + * Mirrors the given data as the return value if @mirror is true. Ownership is not transferred. + * + * Returns: (transfer none) (nullable): the mirrored data or NULL. + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_nullable_mirror(GirTestTypedRecordTester *data, gboolean mirror) +{ + if(!mirror) + return NULL; + + return data; +} + +/** + * girtrest_typed_record_tester_unref: + * @self: (transfer full): a `GirTestTypedRecordTester` + * + * Decrements the reference count on `data` and frees the + * data if the reference count is 0. + **/ +void +girtest_typed_record_tester_unref (GirTestTypedRecordTester *self) +{ + g_return_if_fail (self != NULL); + + self->ref_count -= 1; + if (self->ref_count > 0) + return; + + g_free (self); +} + +/** + * girtest_typed_record_tester_get_ref_count: + * @self: a `GirTestTypedRecordTester` + * + * Returns: The current ref count of the record. + **/ +int +girtest_typed_record_tester_get_ref_count(GirTestTypedRecordTester *self) +{ + g_return_val_if_fail (self != NULL, -1); + return self->ref_count; +} + +/** + * girtest_typed_record_tester_try_get_ref_count: + * @dummy: not used + * @self: (nullable): a `GirTestTypedRecordTester` + * + * Returns: The current ref count of the record or -1 if @self is NULL + **/ +int girtest_typed_record_tester_try_get_ref_count(int dummy, GirTestTypedRecordTester *self) +{ + if(self == NULL) + return -1; + + return self->ref_count; +} + +/** + * girtest_typed_record_tester_take_and_unref: + * @self: (transfer full): a `GirTestTypedRecordTester` + * + * Takes ownership and decrements the reference count on `data` and frees the + * data if the reference count is 0. + **/ +void +girtest_typed_record_tester_take_and_unref(GirTestTypedRecordTester *self) +{ + girtest_typed_record_tester_unref(self); +} + +/** + * girtest_typed_record_tester_take_and_unref_func: + * @dummy: Just an unused dummy value + * @data: (transfer full): a `GirTestTypedRecordTester` + * + * Takes ownership and decrements the reference count on `data` and frees the + * data if the reference count is 0. + **/ +void +girtest_typed_record_tester_take_and_unref_func(int dummy, GirTestTypedRecordTester *data) +{ + girtest_typed_record_tester_take_and_unref(data); +} + +/** + * girtest_typed_record_tester_take_and_unref_func_nullable: + * @dummy: Just an unused dummy value + * @data: (transfer full) (nullable): a `GirTestTypedRecordTester` + * + * Takes ownership and decrements the reference count on `data` and frees the + * data if the reference count is 0. + **/ +void +girtest_typed_record_tester_take_and_unref_func_nullable(int dummy, GirTestTypedRecordTester *data) +{ + if(data == NULL) + return; + + girtest_typed_record_tester_take_and_unref(data); +} + +/** + * girtest_typed_record_tester_get_ref_count_sum: + * @data: (array length=size): an array of `GirTestTypedRecordTester` pointers + * @size: The length of @data + * + * Returns: The count of all refs of the @data. + **/ +int girtest_typed_record_tester_get_ref_count_sum(GirTestTypedRecordTester * const *data, gsize size) +{ + int sum = 0; + + for (int i = 0; i < size; i++) + { + sum = sum + girtest_typed_record_tester_get_ref_count(data[i]); + } + + return sum; +} + +/** + * girtest_typed_record_tester_get_ref_count_sum_nullable: + * @data: (nullable) (array length=size): an array of `GirTestTypedRecordTester` pointers + * @size: The length of @data + * + * Returns: The count of all refs of the @data. -1 if NULL is supplied as @data. + **/ +int girtest_typed_record_tester_get_ref_count_sum_nullable(GirTestTypedRecordTester * const *data, gsize size) +{ + if(data == NULL) + return -1; + + return girtest_typed_record_tester_get_ref_count_sum(data, size); +} + +/** + * girtest_typed_record_tester_run_callback_return_no_ownership_transfer: + * @callback: (scope call): a callback + * + * Calls the callback and returns the newly created instance. + * + * Returns: (transfer none): a GirTestTypedRecordTester + **/ +GirTestTypedRecordTester * +girtest_typed_record_tester_run_callback_return_no_ownership_transfer(GirTestCreateTypedRecordTesterNoOwnershipTransfer callback) +{ + return callback(); +} + +/** + * girtest_typed_record_tester_run_callback_return_no_ownership_transfer_nullable: + * @callback: (scope call): a callback + * + * Calls the callback and returns the newly created instance or NULL + * + * Returns: (transfer none) (nullable): a GirTestTypedRecordTester + **/ +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_no_ownership_transfer_nullable(GirTestCreateTypedRecordTesterNoOwnershipTransferNullable callback) +{ + return callback(); +} + +/** + * girtest_typed_record_tester_run_callback_return_full_ownership_transfer: + * @callback: (scope call): a callback + * + * Calls the callback and returns the newly created instance. + * + * Returns: (transfer full): a GirTestTypedRecordTester + **/ +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_full_ownership_transfer(GirTestCreateTypedRecordTesterFullOwnershipTransfer callback) +{ + return callback(); +} + +/** + * girtest_typed_record_tester_run_callback_return_full_ownership_transfer_nullable: + * @callback: (scope call): a callback + * + * Calls the callback and returns the newly created instance. + * + * Returns: (transfer full) (nullable): a GirTestTypedRecordTester or NULL + **/ +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_full_ownership_transfer_nullable(GirTestCreateTypedRecordTesterFullOwnershipTransferNullable callback) +{ + return callback(); +} + +/** + * girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer: + * @callback: (scope call): a callback + * + * Calls the callback and supplies a new TypedRecordTester. + **/ +void +girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer(GirTestGetTypedRecordTesterFullOwnershipTransfer callback) +{ + callback(girtest_typed_record_tester_new()); +} + +/** + * girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer_nullable: + * @useNull: TRUE to pass null to the callback, otherwise FALSE. + * @callback: (scope call): a callback + * + * Calls the callback and supplies a new TypedRecordTester if @useNull is FALSE. + **/ +void girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer_nullable(gboolean useNull, GirTestGetTypedRecordTesterFullOwnershipTransferNullable callback) +{ + if(useNull) + callback(NULL); + else + callback(girtest_typed_record_tester_new()); +} + +/** + * girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer: + * @callback: (scope call): a callback + * @data: (transfer none): A GirTestTypedRecordTester + * + * Calls the callback and supplies the given TypedRecordTester. + **/ +void +girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer(GirTestGetTypedRecordTesterNoOwnershipTransfer callback, GirTestTypedRecordTester *data) +{ + callback(data); +} + +/** + * girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer_nullable: + * @callback: (scope call): a callback + * @data: (transfer none) (nullable): A GirTestTypedRecordTester + * + * Calls the callback and supplies the given TypedRecordTester. + **/ +void +girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer_nullable(GirTestGetTypedRecordTesterNoOwnershipTransferNullable callback, GirTestTypedRecordTester *data) +{ + callback(data); +} \ No newline at end of file diff --git a/src/Native/GirTestLib/girtest-typed-record-tester.h b/src/Native/GirTestLib/girtest-typed-record-tester.h new file mode 100644 index 000000000..4ee18e624 --- /dev/null +++ b/src/Native/GirTestLib/girtest-typed-record-tester.h @@ -0,0 +1,96 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +/** + * GirTestTypedRecordTester: + * + * Just a record. + */ +struct _GirTestTypedRecordTester +{ + int ref_count; +}; + +typedef struct _GirTestTypedRecordTester GirTestTypedRecordTester; +#define GIRTEST_TYPE_TYPED_RECORD_TESTER (girtest_typed_record_tester_get_type()) + +GType girtest_typed_record_tester_get_type (void) G_GNUC_CONST; + +/** + * GirTestCreateTypedRecordTesterNoOwnershipTransfer: + * + * Returns: (transfer none): a new OpaqueRecordTester. + */ +typedef GirTestTypedRecordTester* (*GirTestCreateTypedRecordTesterNoOwnershipTransfer) (); + +/** + * GirTestCreateTypedRecordTesterNoOwnershipTransferNullable: + * + * Returns: (transfer none) (nullable): a new OpaqueRecordTester or NULL. + */ +typedef GirTestTypedRecordTester* (*GirTestCreateTypedRecordTesterNoOwnershipTransferNullable) (); + +/** + * GirTestCreateTypedRecordTesterFullOwnershipTransfer: + * + * Returns: (transfer full): a new TypedRecordTester. + */ +typedef GirTestTypedRecordTester* (*GirTestCreateTypedRecordTesterFullOwnershipTransfer) (); + +/** + * GirTestCreateTypedRecordTesterFullOwnershipTransferNullable: + * + * Returns: (transfer full) (nullable): a new TypedRecordTester or NULL. + */ +typedef GirTestTypedRecordTester* (*GirTestCreateTypedRecordTesterFullOwnershipTransferNullable) (); + +/** + * GirTestGetTypedRecordTesterFullOwnershipTransfer: + * @data: (transfer full): An TypedRecordTester + */ +typedef void (*GirTestGetTypedRecordTesterFullOwnershipTransfer) (GirTestTypedRecordTester *data); + +/** + * GirTestGetTypedRecordTesterFullOwnershipTransferNullable: + * @data: (transfer full) (nullable): An TypedRecordTester + */ +typedef void (*GirTestGetTypedRecordTesterFullOwnershipTransferNullable) (GirTestTypedRecordTester *data); + +/** + * GirTestGetTypedRecordTesterNoOwnershipTransfer: + * @data: (transfer none): An TypedRecordTester + */ +typedef void (*GirTestGetTypedRecordTesterNoOwnershipTransfer) (GirTestTypedRecordTester *data); + +/** + * GirTestGetTypedRecordTesterNoOwnershipTransferNullable: + * @data: (transfer none) (nullable): An TypedRecordTester + */ +typedef void (*GirTestGetTypedRecordTesterNoOwnershipTransferNullable) (GirTestTypedRecordTester *data); + +GirTestTypedRecordTester * girtest_typed_record_tester_new (); +GirTestTypedRecordTester * girtest_typed_record_tester_try_new (gboolean returnNull); +GirTestTypedRecordTester * girtest_typed_record_tester_ref (GirTestTypedRecordTester *self); +GirTestTypedRecordTester * girtest_typed_record_tester_try_ref (GirTestTypedRecordTester *self, gboolean returnNull); +GirTestTypedRecordTester * girtest_typed_record_tester_mirror(GirTestTypedRecordTester *data); +GirTestTypedRecordTester * girtest_typed_record_tester_nullable_mirror(GirTestTypedRecordTester *data, gboolean mirror); +void girtest_typed_record_tester_unref(GirTestTypedRecordTester *self); +int girtest_typed_record_tester_get_ref_count(GirTestTypedRecordTester *self); +int girtest_typed_record_tester_try_get_ref_count(int dummy, GirTestTypedRecordTester *self); +void girtest_typed_record_tester_take_and_unref(GirTestTypedRecordTester *self); +void girtest_typed_record_tester_take_and_unref_func(int dummy, GirTestTypedRecordTester *data); +void girtest_typed_record_tester_take_and_unref_func_nullable(int dummy, GirTestTypedRecordTester *data); +int girtest_typed_record_tester_get_ref_count_sum(GirTestTypedRecordTester * const *data, gsize size); +int girtest_typed_record_tester_get_ref_count_sum_nullable(GirTestTypedRecordTester * const *data, gsize size); +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_no_ownership_transfer(GirTestCreateTypedRecordTesterNoOwnershipTransfer callback); +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_no_ownership_transfer_nullable(GirTestCreateTypedRecordTesterNoOwnershipTransferNullable callback); +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_full_ownership_transfer(GirTestCreateTypedRecordTesterFullOwnershipTransfer callback); +GirTestTypedRecordTester * girtest_typed_record_tester_run_callback_return_full_ownership_transfer_nullable(GirTestCreateTypedRecordTesterFullOwnershipTransferNullable callback); +void girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer(GirTestGetTypedRecordTesterFullOwnershipTransfer callback); +void girtest_typed_record_tester_run_callback_parameter_full_ownership_transfer_nullable(gboolean useNull, GirTestGetTypedRecordTesterFullOwnershipTransferNullable callback); +void girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer(GirTestGetTypedRecordTesterNoOwnershipTransfer callback, GirTestTypedRecordTester *data); +void girtest_typed_record_tester_run_callback_parameter_no_ownership_transfer_nullable(GirTestGetTypedRecordTesterNoOwnershipTransferNullable callback, GirTestTypedRecordTester *data); +G_END_DECLS diff --git a/src/Native/GirTestLib/girtest.h b/src/Native/GirTestLib/girtest.h index 72941ec63..544f93682 100644 --- a/src/Native/GirTestLib/girtest.h +++ b/src/Native/GirTestLib/girtest.h @@ -19,6 +19,7 @@ #include "girtest-returning-signal-tester.h" #include "girtest-signal-tester.h" #include "girtest-string-tester.h" +#include "girtest-typed-record-tester.h" #include "girtest-utf8-string-array-null-terminated-tester.h" #include "data/girtest-executor.h" #include "data/girtest-executor-impl.h" diff --git a/src/Native/GirTestLib/meson.build b/src/Native/GirTestLib/meson.build index 4df5d1624..fd073f315 100644 --- a/src/Native/GirTestLib/meson.build +++ b/src/Native/GirTestLib/meson.build @@ -22,6 +22,7 @@ header_files = [ 'girtest-returning-signal-tester.h', 'girtest-signal-tester.h', 'girtest-string-tester.h', + 'girtest-typed-record-tester.h', 'girtest-utf8-string-array-null-terminated-tester.h', 'data/girtest-executor.h', 'data/girtest-executor-impl.h', @@ -45,6 +46,7 @@ source_files = [ 'girtest-returning-signal-tester.c', 'girtest-signal-tester.c', 'girtest-string-tester.c', + 'girtest-typed-record-tester.c', 'girtest-utf8-string-array-null-terminated-tester.c', 'data/girtest-executor.c', 'data/girtest-executor-impl.c',