diff --git a/src/Generation/Generator/Generator/Internal/UntypedRecord.cs b/src/Generation/Generator/Generator/Internal/UntypedRecord.cs new file mode 100644 index 000000000..97960e5a0 --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/UntypedRecord.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class UntypedRecord : Generator +{ + private readonly Publisher _publisher; + + public UntypedRecord(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsUntyped(obj)) + return; + + var source = Renderer.Internal.UntypedRecord.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/UntypedRecordData.cs b/src/Generation/Generator/Generator/Internal/UntypedRecordData.cs new file mode 100644 index 000000000..ca336396a --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/UntypedRecordData.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class UntypedRecordData : Generator +{ + private readonly Publisher _publisher; + + public UntypedRecordData(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsUntyped(obj)) + return; + + var source = Renderer.Internal.UntypedRecordData.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: Model.UntypedRecord.GetDataName(obj), + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Internal/UntypedRecordHandle.cs b/src/Generation/Generator/Generator/Internal/UntypedRecordHandle.cs new file mode 100644 index 000000000..20ed8c198 --- /dev/null +++ b/src/Generation/Generator/Generator/Internal/UntypedRecordHandle.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Internal; + +internal class UntypedRecordHandle : Generator +{ + private readonly Publisher _publisher; + + public UntypedRecordHandle(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record obj) + { + if (!Record.IsUntyped(obj)) + return; + + var source = Renderer.Internal.UntypedRecordHandle.Render(obj); + var codeUnit = new CodeUnit( + Project: Namespace.GetCanonicalName(obj.Namespace), + Name: Model.UntypedRecord.GetInternalHandle(obj), + Source: source, + IsInternal: true + ); + + _publisher.Publish(codeUnit); + } +} diff --git a/src/Generation/Generator/Generator/Public/UntypedRecord.cs b/src/Generation/Generator/Generator/Public/UntypedRecord.cs new file mode 100644 index 000000000..878b0d7c9 --- /dev/null +++ b/src/Generation/Generator/Generator/Public/UntypedRecord.cs @@ -0,0 +1,29 @@ +using Generator.Model; + +namespace Generator.Generator.Public; + +internal class UntypedRecord : Generator +{ + private readonly Publisher _publisher; + + public UntypedRecord(Publisher publisher) + { + _publisher = publisher; + } + + public void Generate(GirModel.Record record) + { + if (!Record.IsUntyped(record)) + return; + + var source = Renderer.Public.UntypedRecord.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 b2ad48e35..2cfb55935 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 !IsForeignTyped(record) && !IsOpaqueTyped(record) && !IsOpaqueUntyped(record) && !IsTyped(record); + return !IsForeignTyped(record) && !IsOpaqueTyped(record) && !IsOpaqueUntyped(record) && !IsTyped(record) && !IsUntyped(record); } public static bool IsForeignTyped(GirModel.Record record) @@ -34,6 +34,11 @@ public static bool IsTyped(GirModel.Record record) //record is actually fundamental and does not have a type function. return record is { Foreign: false, Opaque: false, TypeFunction.CIdentifier: not "intern" }; } + + public static bool IsUntyped(GirModel.Record record) + { + return record is { Foreign: false, Opaque: false, TypeFunction: null or { CIdentifier: "intern" } }; + } public static string GetFullyQualifiedInternalStructName(GirModel.Record record) => Namespace.GetInternalName(record.Namespace) + "." + GetInternalStructName(record); diff --git a/src/Generation/Generator/Model/UntypedRecord.cs b/src/Generation/Generator/Model/UntypedRecord.cs new file mode 100644 index 000000000..2f15a36c8 --- /dev/null +++ b/src/Generation/Generator/Model/UntypedRecord.cs @@ -0,0 +1,86 @@ +using System.Linq; + +namespace Generator.Model; + +internal static class UntypedRecord +{ + 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 GetInternalManagedHandle(GirModel.Record record) + => $"{Type.GetName(record)}ManagedHandle"; + + 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 GetFullyQuallifiedHandle(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"; + + public static string GetFullyQuallifiedDataName(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetDataName(record)}"; + + public static string GetFullyQuallifiedManagedHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalManagedHandle(record)}"; + + public static string GetInternalArrayHandle(GirModel.Record record) + { + var prefix = $"{Type.GetName(record)}Array"; + if (record.Namespace.Records.Select(x => x.Name).Contains(prefix)) + prefix += "2"; + + return $"{prefix}Handle"; + } + + public static string GetFullyQuallifiedArrayHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalArrayHandle(record)}"; + + public static string GetInternalArrayOwnedHandle(GirModel.Record record) + { + var prefix = $"{Type.GetName(record)}Array"; + if (record.Namespace.Records.Select(x => x.Name).Contains(prefix)) + prefix += "2"; + + return $"{prefix}OwnedHandle"; + } + + public static string GetFullyQuallifiedArrayOwnedHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalArrayOwnedHandle(record)}"; + + public static string GetInternalArrayUnownedHandle(GirModel.Record record) + { + var prefix = $"{Type.GetName(record)}Array"; + if (record.Namespace.Records.Select(x => x.Name).Contains(prefix)) + prefix += "2"; + return $"{prefix}UnownedHandle"; + } + + public static string GetFullyQuallifiedArrayUnownedHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalArrayUnownedHandle(record)}"; + + public static string GetFullyQuallifiedArrayNullHandle(GirModel.Record record) + => $"{Namespace.GetInternalName(record.Namespace)}.{GetInternalArrayUnownedHandle(record)}.NullHandle"; +} diff --git a/src/Generation/Generator/Records.cs b/src/Generation/Generator/Records.cs index 6cf1c0a61..7da937e1d 100644 --- a/src/Generation/Generator/Records.cs +++ b/src/Generation/Generator/Records.cs @@ -12,6 +12,12 @@ public static void Generate(IEnumerable records, string path) var publisher = new Publisher(path); var generators = new List>() { + //Untyped records + new Generator.Internal.UntypedRecord(publisher), + new Generator.Internal.UntypedRecordData(publisher), + new Generator.Internal.UntypedRecordHandle(publisher), + new Generator.Public.UntypedRecord(publisher), + //Foreign typed records new Generator.Internal.ForeignTypedRecord(publisher), new Generator.Internal.ForeignTypedRecordHandle(publisher), diff --git a/src/Generation/Generator/Renderer/Internal/Field/Converter/TypedRecordArray.cs b/src/Generation/Generator/Renderer/Internal/Field/Converter/TypedRecordArray.cs index 70e606d12..4354eff1b 100644 --- a/src/Generation/Generator/Renderer/Internal/Field/Converter/TypedRecordArray.cs +++ b/src/Generation/Generator/Renderer/Internal/Field/Converter/TypedRecordArray.cs @@ -28,6 +28,6 @@ private static string GetNullableTypeName(GirModel.Field field) { var arrayType = field.AnyTypeOrCallback.AsT0.AsT1; var type = (GirModel.Record) arrayType.AnyType.AsT0; - return Model.Record.GetFullyQualifiedInternalStructName(type) + "[]"; + return Model.TypedRecord.GetFullyQuallifiedDataName(type) + "[]"; } } diff --git a/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecord.cs new file mode 100644 index 000000000..c54092caa --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecord.cs @@ -0,0 +1,28 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal.Field; + +internal class UntypedRecord : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.Is(out var record) && Model.Record.IsUntyped(record); + } + + public RenderableField Convert(GirModel.Field field) + { + return new RenderableField( + Name: Model.Field.GetName(field), + Attribute: null, + NullableTypeName: GetNullableTypeName(field) + ); + } + + private static string GetNullableTypeName(GirModel.Field field) + { + var type = (GirModel.Record) field.AnyTypeOrCallback.AsT0.AsT0; + return field.IsPointer + ? Type.Pointer + : Model.Record.GetFullyQualifiedInternalStructName(type); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecordArray.cs b/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecordArray.cs new file mode 100644 index 000000000..938b8bc48 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Field/Converter/UntypedRecordArray.cs @@ -0,0 +1,33 @@ +namespace Generator.Renderer.Internal.Field; + +internal class UntypedRecordArray : FieldConverter +{ + public bool Supports(GirModel.Field field) + { + return field.AnyTypeOrCallback.TryPickT0(out var anyType, out _) && anyType.IsArray(out var record) && Model.Record.IsUntyped(record); ; + } + + public RenderableField Convert(GirModel.Field field) + { + return new RenderableField( + Name: Model.Field.GetName(field), + Attribute: GetAttribute(field), + NullableTypeName: GetNullableTypeName(field) + ); + } + + private static string? GetAttribute(GirModel.Field field) + { + var arrayType = field.AnyTypeOrCallback.AsT0.AsT1; + return arrayType.FixedSize is not null + ? MarshalAs.UnmanagedByValArray(sizeConst: arrayType.FixedSize.Value) + : null; + } + + private static string GetNullableTypeName(GirModel.Field field) + { + var arrayType = field.AnyTypeOrCallback.AsT0.AsT1; + var type = (GirModel.Record) arrayType.AnyType.AsT0; + return Model.UntypedRecord.GetFullyQuallifiedDataName(type) + "[]"; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/Field/Fields.cs b/src/Generation/Generator/Renderer/Internal/Field/Fields.cs index 91bcbaf8e..6f27f9cc6 100644 --- a/src/Generation/Generator/Renderer/Internal/Field/Fields.cs +++ b/src/Generation/Generator/Renderer/Internal/Field/Fields.cs @@ -31,6 +31,8 @@ internal static class Fields new Field.TypedRecordArray(), new Field.Union(), new Field.UnionArray(), + new Field.UntypedRecord(), + new Field.UntypedRecordArray() }; public static string Render(IEnumerable fields) diff --git a/src/Generation/Generator/Renderer/Internal/InstanceParameter/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Internal/InstanceParameter/Converter/UntypedRecord.cs new file mode 100644 index 000000000..2e72b4f85 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/InstanceParameter/Converter/UntypedRecord.cs @@ -0,0 +1,30 @@ +using System; + +namespace Generator.Renderer.Internal.InstanceParameter; + +internal class UntypedRecord : InstanceParameterConverter +{ + public bool Supports(GirModel.Type type) + { + return type is GirModel.Record r && Model.Record.IsUntyped(r); + } + + public RenderableInstanceParameter Convert(GirModel.InstanceParameter instanceParameter) + { + return new RenderableInstanceParameter( + Name: Model.InstanceParameter.GetName(instanceParameter), + NullableTypeName: GetNullableTypeName(instanceParameter) + ); + } + + private static string GetNullableTypeName(GirModel.InstanceParameter instanceParameter) + { + var type = (GirModel.Record) instanceParameter.Type; + return instanceParameter switch + { + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.None } => Model.UntypedRecord.GetFullyQuallifiedHandle(type), + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.Full } => throw new Exception("Can't transfer ownership of untyped record"), + _ => throw new System.Exception($"Can't detect typed record instance parameter type {instanceParameter.Name}: CallerAllocates={instanceParameter.CallerAllocates} Direction={instanceParameter.Direction} Transfer={instanceParameter.Transfer}") + }; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/InstanceParameter/InstanceParameters.cs b/src/Generation/Generator/Renderer/Internal/InstanceParameter/InstanceParameters.cs index c84f7211e..40ca0040f 100644 --- a/src/Generation/Generator/Renderer/Internal/InstanceParameter/InstanceParameters.cs +++ b/src/Generation/Generator/Renderer/Internal/InstanceParameter/InstanceParameters.cs @@ -15,7 +15,8 @@ internal static class InstanceParameters new InstanceParameter.Pointer(), new InstanceParameter.Record(), new InstanceParameter.TypedRecord(), - new InstanceParameter.Union() + new InstanceParameter.Union(), + new InstanceParameter.UntypedRecord() }; public static string Render(GirModel.InstanceParameter instanceParameter) diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/UntypedRecord.cs new file mode 100644 index 000000000..1c79d2bfc --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Converter/UntypedRecord.cs @@ -0,0 +1,39 @@ +using System; + +namespace Generator.Renderer.Internal.Parameter; + +internal class UntypedRecord : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.Is(out var record) && Model.Record.IsUntyped(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) + { + var type = (GirModel.Record) parameter.AnyTypeOrVarArgs.AsT0.AsT0; + return parameter switch + { + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.None } => Model.UntypedRecord.GetFullyQuallifiedHandle(type), + { Direction: GirModel.Direction.In, Transfer: GirModel.Transfer.Full } => throw new Exception("Ownership transfer for untyped records not supported"), + _ => throw new Exception($"Can't detect untyped 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 untyped record parameter {parameter.Name}") + }; +} diff --git a/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs b/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs index c67b88377..67b1d0183 100644 --- a/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs +++ b/src/Generation/Generator/Renderer/Internal/Parameter/Parameters.cs @@ -48,6 +48,7 @@ internal static class Parameters new Parameter.Union(), new Parameter.UnionArray(), new Parameter.UnsignedPointer(), + new Parameter.UntypedRecord(), new Parameter.Utf8StringArray(), new Parameter.Void(), }; diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecord.cs new file mode 100644 index 000000000..290d807f1 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecord.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using GirModel; + +namespace Generator.Renderer.Internal.ParameterToManagedExpressions; + +internal class UntypedRecord : ToManagedParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.Is(out var record) && Model.Record.IsUntyped(record); + + public void Initialize(ParameterToManagedData parameterData, IEnumerable parameters) + { + if (!parameterData.Parameter.IsPointer) + throw new NotImplementedException($"Unpointed untyped record parameter {parameterData.Parameter.Name} ({parameterData.Parameter.AnyTypeOrVarArgs}) can not yet be converted to managed"); + + switch (parameterData.Parameter.Direction) + { + case Direction.In: + InRecord(parameterData); + break; + case Direction.Out: + OutRecord(parameterData); + break; + default: + throw new NotImplementedException($"{parameterData.Parameter.AnyTypeOrVarArgs}: untyped record with direction {parameterData.Parameter.Direction} not yet supported"); + } + } + + private static void OutRecord(ParameterToManagedData parameterData) + { + var parameterName = Model.Parameter.GetName(parameterData.Parameter); + parameterData.SetSignatureName(parameterName); + parameterData.SetCallName("out " + parameterName); + } + + private static void InRecord(ParameterToManagedData parameterData) + { + var record = (GirModel.Record) parameterData.Parameter.AnyTypeOrVarArgs.AsT0.AsT0; + var ownedHandle = parameterData.Parameter.Transfer == GirModel.Transfer.Full; + var variableName = Model.Parameter.GetConvertedName(parameterData.Parameter); + + var handleClass = ownedHandle + ? Model.UntypedRecord.GetFullyQuallifiedOwnedHandle(record) + : Model.UntypedRecord.GetFullyQuallifiedUnownedHandle(record); + + var signatureName = Model.Parameter.GetName(parameterData.Parameter); + + parameterData.SetSignatureName(signatureName); + parameterData.SetExpression($"var {variableName} = new {Model.Record.GetFullyQualifiedPublicClassName(record)}(new {handleClass}({signatureName}));"); + parameterData.SetCallName(variableName); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecordArray.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecordArray.cs new file mode 100644 index 000000000..348bed0b1 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/Converter/UntypedRecordArray.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Generator.Renderer.Internal.ParameterToManagedExpressions; + +internal class UntypedRecordArray : ToManagedParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.IsArray(out var record) && Model.Record.IsUntyped(record); + + public void Initialize(ParameterToManagedData parameterData, IEnumerable parameters) + { + switch (parameterData.Parameter) + { + case { Direction: GirModel.Direction.In } + when parameterData.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.Length is not null: + WithLength(parameterData, parameters); + break; + case { Direction: GirModel.Direction.In }: + WithoutLength(parameterData); + break; + default: + throw new Exception($"{parameterData.Parameter}: This kind of typed record array is not yet supported"); + } + } + + private static void WithoutLength(ParameterToManagedData parameter) + { + var parameterName = Model.Parameter.GetName(parameter.Parameter); + parameter.SetSignatureName(parameterName); + parameter.SetCallName($"ref {parameterName}"); + + //TODO + throw new Exception("Test missing for untyped record array passed in via a ref to managed"); + } + + private static void WithLength(ParameterToManagedData parameter, IEnumerable allParameters) + { + if (parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.IsPointer) + PointerArrayWithLength(parameter, allParameters); + else + StructArrayWithLength(parameter, allParameters); + } + + private static void PointerArrayWithLength(ParameterToManagedData parameter, IEnumerable allParameters) + { + throw new Exception("Pointer array not yet supported for typed record arrays"); + } + + private static void StructArrayWithLength(ParameterToManagedData parameter, IEnumerable allParameters) + { + if (parameter.Parameter.Transfer == GirModel.Transfer.Container || parameter.Parameter.Transfer == GirModel.Transfer.Full) + throw new Exception("Can't transfer ownership to native code for untyped record"); + + var record = (GirModel.Record) parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.AnyType.AsT0; + var parameterName = Model.Parameter.GetName(parameter.Parameter); + var nativeVariableName = parameterName + "Native"; + + var lengthIndex = parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.Length ?? throw new Exception("Length missing"); + var lengthParameter = allParameters.ElementAt(lengthIndex); + + var method = parameter.Parameter.Nullable + ? "ToNullableArray" + : "ToArray"; + + parameter.SetSignatureName(parameterName); + parameter.SetCallName($"{nativeVariableName}.{method}((int){lengthParameter.GetCallName()})"); + + var nullableExpression = parameter.Parameter.Nullable + ? $"{parameterName} == System.IntPtr.Zero ? {Model.UntypedRecord.GetFullyQuallifiedArrayNullHandle(record)} : " + : string.Empty; + + parameter.SetExpression($"var {nativeVariableName} = {nullableExpression} new {Model.UntypedRecord.GetFullyQuallifiedArrayUnownedHandle(record)}({parameterName}, (int) {lengthParameter.GetCallName()});"); + } +} diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedData.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedData.cs index c0b51ca2d..a5b043478 100644 --- a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedData.cs +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedData.cs @@ -44,7 +44,7 @@ public void SetCallName(string name) public string GetCallName() { - return _callName ?? throw new Exception($"Callname of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set"); + return _callName ?? throw new Exception($"Callname of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set while converting to managed"); } public void SetSignatureName(string signatureName) @@ -54,6 +54,6 @@ public void SetSignatureName(string signatureName) public string GetSignatureName() { - return _signatureName ?? throw new Exception($"Signaturename of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set"); + return _signatureName ?? throw new Exception($"Signaturename of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set while converting to managed"); } } diff --git a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs index 12ee8c880..75553bb99 100644 --- a/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs +++ b/src/Generation/Generator/Renderer/Internal/ParameterToManagedExpression/ParameterToManagedExpression.cs @@ -28,6 +28,8 @@ internal static class ParameterToManagedExpression new ParameterToManagedExpressions.String(), new ParameterToManagedExpressions.TypedRecord(), new ParameterToManagedExpressions.TypedRecordArray(), + new ParameterToManagedExpressions.UntypedRecord(), + new ParameterToManagedExpressions.UntypedRecordArray(), new ParameterToManagedExpressions.Utf8StringArray(), }; diff --git a/src/Generation/Generator/Renderer/Internal/UntypedRecord.cs b/src/Generation/Generator/Renderer/Internal/UntypedRecord.cs new file mode 100644 index 000000000..4e71b59db --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/UntypedRecord.cs @@ -0,0 +1,30 @@ +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class UntypedRecord +{ + 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/UntypedRecordData.cs b/src/Generation/Generator/Renderer/Internal/UntypedRecordData.cs new file mode 100644 index 000000000..d5cd379b9 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/UntypedRecordData.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using Generator.Model; + +namespace Generator.Renderer.Internal; + +internal static class UntypedRecordData +{ + 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)} +{{ + {record.Fields + .Select(x => x.AnyTypeOrCallback) + .Where(x => x.IsT1) + .Select(x => x.AsT1) + .Select(Callback.Render) + .Join(Environment.NewLine)} + + {Fields.Render(record.Fields)} +}}"; + } +} diff --git a/src/Generation/Generator/Renderer/Internal/UntypedRecordHandle.cs b/src/Generation/Generator/Renderer/Internal/UntypedRecordHandle.cs new file mode 100644 index 000000000..dbd8bd9a3 --- /dev/null +++ b/src/Generation/Generator/Renderer/Internal/UntypedRecordHandle.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using System.Text; +using Generator.Model; +using Generator.Renderer.Internal.Field; + +namespace Generator.Renderer.Internal; + +internal static class UntypedRecordHandle +{ + public static string Render(GirModel.Record record) + { + var typeName = Model.UntypedRecord.GetInternalHandle(record); + var dataName = Model.UntypedRecord.GetDataName(record); + var unownedHandleTypeName = Model.UntypedRecord.GetInternalUnownedHandle(record); + var ownedHandleTypeName = Model.UntypedRecord.GetInternalOwnedHandle(record); + + return $@"using System; +using GObject; +using System.Runtime.CompilerServices; +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) {{ }} + + {record.Fields.Select(x => RenderField(record, x)).Join(Environment.NewLine)} +}} + +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); + }} + + /// + /// Creates a new owned Handle + /// + public static {ownedHandleTypeName} Create() + {{ + var size = Marshal.SizeOf<{dataName}>(); + var ptr = GLib.Functions.Malloc((nuint)size); + + var str = new {dataName}(); + Marshal.StructureToPtr(str, ptr, false); + + return new {ownedHandleTypeName}(ptr); + }} + + protected override bool ReleaseHandle() + {{ + GLib.Functions.Free(handle); + return true; + }} +}}"; + } + + private static string RenderField(GirModel.Record record, GirModel.Field field) + { + var renderableField = Fields.GetRenderableField(field); + + if (field is { IsReadable: false, IsWritable: false } || field.IsPrivate) + return string.Empty; + + var result = new StringBuilder(); + + if (field.IsReadable) + result.AppendLine(RenderFieldGetter(record, field, renderableField)); + + if (field.IsWritable) + result.AppendLine(RenderFieldSetter(record, field, renderableField)); + + return result.ToString(); + + } + + private static string RenderFieldGetter(GirModel.Record record, GirModel.Field field, RenderableField renderableField) + { + var typePrefix = field.AnyTypeOrCallback.IsT1 ? $"{Model.UntypedRecord.GetDataName(record)}." : string.Empty; + var dataName = Model.UntypedRecord.GetDataName(record); + + return @$"public unsafe {typePrefix}{renderableField.NullableTypeName} Get{renderableField.Name}() +{{ + if (IsClosed || IsInvalid) + throw new InvalidOperationException(""Handle is closed or invalid""); + + return Unsafe.AsRef<{dataName}>((void*)handle).{renderableField.Name}; +}}"; + } + + private static string RenderFieldSetter(GirModel.Record record, GirModel.Field field, RenderableField renderableField) + { + var dataName = Model.UntypedRecord.GetDataName(record); + + return @$"public unsafe void Set{renderableField.Name}({renderableField.NullableTypeName} value) +{{ + if (IsClosed || IsInvalid) + throw new InvalidOperationException(""Handle is closed or invalid""); + + Unsafe.AsRef<{dataName}>((void*)handle).{renderableField.Name} = value; +}}"; + } + + +} diff --git a/src/Generation/Generator/Renderer/Public/Field/Fields.cs b/src/Generation/Generator/Renderer/Public/Field/Fields.cs index c21d74198..36f32e3ee 100644 --- a/src/Generation/Generator/Renderer/Public/Field/Fields.cs +++ b/src/Generation/Generator/Renderer/Public/Field/Fields.cs @@ -18,6 +18,6 @@ public static Field.RenderableField GetRenderableField(GirModel.Field field) if (converter.Supports(field)) return converter.Convert(field); - throw new System.Exception($"Internal field \"{field.Name}\" of type {field.AnyTypeOrCallback} can not be rendered"); + throw new System.Exception($"Public field \"{field.Name}\" of type {field.AnyTypeOrCallback} can not be rendered"); } } diff --git a/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecord.cs new file mode 100644 index 000000000..5685d3c3a --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecord.cs @@ -0,0 +1,32 @@ +using System; + +namespace Generator.Renderer.Public.Parameter; + +internal class UntypedRecord : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.Is(out var record) && Model.Record.IsUntyped(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.UntypedRecord.GetFullyQualifiedPublicClassName(type) + Nullable.Render(parameter); + } + + private static string GetDirection(GirModel.Parameter parameter) => parameter switch + { + { Direction: GirModel.Direction.In } => ParameterDirection.In(), + { Direction: GirModel.Direction.Out } => ParameterDirection.Out(), + _ => throw new Exception($"untyped records with direction '{parameter.Direction}' not yet supported") + }; +} diff --git a/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecordArray.cs b/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecordArray.cs new file mode 100644 index 000000000..25ce6afe6 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/Parameter/Converter/UntypedRecordArray.cs @@ -0,0 +1,31 @@ +using System; + +namespace Generator.Renderer.Public.Parameter; + +internal class UntypedRecordArray : ParameterConverter +{ + public bool Supports(GirModel.AnyType anyType) + { + return anyType.IsArray(out var record) && Model.Record.IsUntyped(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.UntypedRecord.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 untyped 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 28715d801..03d7b645c 100644 --- a/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs +++ b/src/Generation/Generator/Renderer/Public/Parameter/ParameterRenderer.cs @@ -33,6 +33,8 @@ internal static class ParameterRenderer new Parameter.TypedRecord(), new Parameter.TypedRecordArray(), new Parameter.Union(), + new Parameter.UntypedRecord(), + new Parameter.UntypedRecordArray(), new Parameter.Void(), }; diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecord.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecord.cs new file mode 100644 index 000000000..9fa387563 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecord.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Generator.Model; + +namespace Generator.Renderer.Public.ParameterToNativeExpressions; + +internal class UntypedRecord : ToNativeParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.Is(out var record) && Model.Record.IsUntyped(record); + + public void Initialize(ParameterToNativeData parameter, IEnumerable _) + { + if (parameter.Parameter.Direction != GirModel.Direction.In) + throw new NotImplementedException($"{parameter.Parameter.AnyTypeOrVarArgs}: untyped record parameter '{parameter.Parameter.Name}' with direction != in not yet supported"); + + var record = (GirModel.Record) parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT0; + var typeHandle = Model.UntypedRecord.GetFullyQuallifiedHandle(record); + var nullHandle = Model.UntypedRecord.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", + { Transfer: GirModel.Transfer.Full } => throw new Exception("Ownership transfer not supported for untyped records"), + _ => throw new Exception($"Can't detect call name for untyped parameter record parameter {parameter.Parameter.Name}") + }; + + parameter.SetSignatureName(signatureName); + parameter.SetCallName(callName); + } +} diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecordArray.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecordArray.cs new file mode 100644 index 000000000..86db9b108 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/Converter/UntypedRecordArray.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Generator.Renderer.Public.ParameterToNativeExpressions; + +internal class UntypedRecordArray : ToNativeParameterConverter +{ + public bool Supports(GirModel.AnyType type) + => type.IsArray(out var record) && Model.Record.IsUntyped(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 untyped 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 untyped record array passed in via a ref"); + } + + private static void Span(ParameterToNativeData parameter, IEnumerable allParameters) + { + if (parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.IsPointer) + PointerArray(parameter, allParameters); + else + StructArray(parameter, allParameters); + } + + private static void PointerArray(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 untyped record array"); + } + } + + private static void StructArray(ParameterToNativeData parameter, IEnumerable allParameters) + { + if (parameter.Parameter.Transfer == GirModel.Transfer.Container || parameter.Parameter.Transfer == GirModel.Transfer.Full) + throw new Exception("Can't transfer ownership to native code for typed record"); + + var record = (GirModel.Record) parameter.Parameter.AnyTypeOrVarArgs.AsT0.AsT1.AnyType.AsT0; + var parameterName = Model.Parameter.GetName(parameter.Parameter); + var nativeVariableName = parameterName + "Native"; + + parameter.SetSignatureName(parameterName); + parameter.SetCallName(nativeVariableName); + + var nullable = parameter.Parameter.Nullable + ? $"{parameterName} is null ? ({Model.TypedRecord.GetFullyQuallifiedArrayHandle(record)}){Model.TypedRecord.GetFullyQuallifiedArrayNullHandle(record)} : " + : string.Empty; + + parameter.SetExpression($"var {nativeVariableName} = {nullable} {Model.TypedRecord.GetFullyQuallifiedArrayOwnedHandle(record)}.Create({parameterName});"); + + 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/ParameterToNativeData.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeData.cs index 1a49114b5..1bea66cde 100644 --- a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeData.cs +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeData.cs @@ -45,7 +45,7 @@ public void SetCallName(string name) public string GetCallName() { - return _callName ?? throw new Exception($"Callname of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set"); + return _callName ?? throw new Exception($"Callname of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set while converting to native"); } public void SetSignatureName(string signatureName) @@ -55,6 +55,6 @@ public void SetSignatureName(string signatureName) public string GetSignatureName() { - return _signatureName ?? throw new Exception($"Signaturename of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set"); + return _signatureName ?? throw new Exception($"Signaturename of parameter {Parameter.Name} ({Parameter.AnyTypeOrVarArgs} is not set while converting to native"); } } diff --git a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs index 4670d00fd..48adc65e9 100644 --- a/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs +++ b/src/Generation/Generator/Renderer/Public/ParameterToNativeExpression/ParameterToNativeExpression.cs @@ -32,6 +32,8 @@ internal static class ParameterToNativeExpression new ParameterToNativeExpressions.RecordArray(), new ParameterToNativeExpressions.TypedRecord(), new ParameterToNativeExpressions.TypedRecordArray(), + new ParameterToNativeExpressions.UntypedRecord(), + new ParameterToNativeExpressions.UntypedRecordArray(), new ParameterToNativeExpressions.Utf8String(), new ParameterToNativeExpressions.Utf8StringArray(), }; diff --git a/src/Generation/Generator/Renderer/Public/UntypedRecord.cs b/src/Generation/Generator/Renderer/Public/UntypedRecord.cs new file mode 100644 index 000000000..53485ef81 --- /dev/null +++ b/src/Generation/Generator/Renderer/Public/UntypedRecord.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Text; +using Generator.Model; + +namespace Generator.Renderer.Public; + +internal static class UntypedRecord +{ + public static string Render(GirModel.Record record) + { + var name = Model.UntypedRecord.GetPublicClassName(record); + var internalHandleName = Model.UntypedRecord.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; + }} + + public {name}() : this({Model.UntypedRecord.GetFullyQuallifiedOwnedHandle(record)}.Create()) + {{ + }} + + {record.Constructors + .Select(ConstructorRenderer.Render) + .Join(Environment.NewLine)} + + {record.Fields + .Select(f => RenderField(record, f)) + .Join(Environment.NewLine)} + + {record.Functions + .Select(FunctionRenderer.Render) + .Join(Environment.NewLine)} + + {record.Methods + .Where(Method.IsEnabled) + .Select(MethodRenderer.Render) + .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/Native/GirTestLib/girtest-untyped-record-tester.c b/src/Native/GirTestLib/girtest-untyped-record-tester.c new file mode 100644 index 000000000..6bfb44d98 --- /dev/null +++ b/src/Native/GirTestLib/girtest-untyped-record-tester.c @@ -0,0 +1,12 @@ +#include "girtest-untyped-record-tester.h" + +/** + * GirTestUntypedRecordTester: + * + * Test untyped records + */ + +int girtest_untyped_record_tester_get_a(GirTestUntypedRecordTester* record) +{ + return record->a; +} \ No newline at end of file diff --git a/src/Native/GirTestLib/girtest-untyped-record-tester.h b/src/Native/GirTestLib/girtest-untyped-record-tester.h new file mode 100644 index 000000000..536c107ac --- /dev/null +++ b/src/Native/GirTestLib/girtest-untyped-record-tester.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +typedef struct _GirTestUntypedRecordTester GirTestUntypedRecordTester; + +struct _GirTestUntypedRecordTester +{ + int a; +}; + +int girtest_untyped_record_tester_get_a(GirTestUntypedRecordTester* record); +G_END_DECLS \ No newline at end of file diff --git a/src/Native/GirTestLib/girtest.h b/src/Native/GirTestLib/girtest.h index b00bd5c5f..15a39c936 100644 --- a/src/Native/GirTestLib/girtest.h +++ b/src/Native/GirTestLib/girtest.h @@ -20,6 +20,7 @@ #include "girtest-signal-tester.h" #include "girtest-string-tester.h" #include "girtest-typed-record-tester.h" +#include "girtest-untyped-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 6f79923eb..9890cfb82 100644 --- a/src/Native/GirTestLib/meson.build +++ b/src/Native/GirTestLib/meson.build @@ -23,6 +23,7 @@ header_files = [ 'girtest-signal-tester.h', 'girtest-string-tester.h', 'girtest-typed-record-tester.h', + 'girtest-untyped-record-tester.h', 'girtest-utf8-string-array-null-terminated-tester.h', 'data/girtest-executor.h', 'data/girtest-executor-impl.h', @@ -47,6 +48,7 @@ source_files = [ 'girtest-signal-tester.c', 'girtest-string-tester.c', 'girtest-typed-record-tester.c', + 'girtest-untyped-record-tester.c', 'girtest-utf8-string-array-null-terminated-tester.c', 'data/girtest-executor.c', 'data/girtest-executor-impl.c', diff --git a/src/Tests/Libs/GirTest-0.1.Tests/UntypedRecordTest.cs b/src/Tests/Libs/GirTest-0.1.Tests/UntypedRecordTest.cs new file mode 100644 index 000000000..ae44df58a --- /dev/null +++ b/src/Tests/Libs/GirTest-0.1.Tests/UntypedRecordTest.cs @@ -0,0 +1,16 @@ +using System; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GirTest.Tests; + +[TestClass, TestCategory("BindingTest")] +public class UntypedRecordTest : Test +{ + [TestMethod] + public void Bla() + { + var recordTester = UntypedRecordTester + recordTester.Handle.DangerousGetHandle().Should().NotBe(IntPtr.Zero); + } +}