Skip to content

Commit fc54eec

Browse files
author
Felix Deimel
committed
[WIP] Support for nullable structs
1 parent 6250525 commit fc54eec

File tree

7 files changed

+139
-18
lines changed

7 files changed

+139
-18
lines changed

Generator/Beyond.NET.CodeGenerator/Collectors/TypeCollector.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,25 @@ out string? unsupportedReason
364364
return false;
365365
}
366366

367-
if (type.IsNullableValueType(out Type? nullableValueType)) {
368-
unsupportedReason = $"Is Nullable Value Type ({nullableValueType.FullName}?)";
367+
bool isNullableValueType = type.IsNullableValueType(out Type? nullableValueType);
368+
369+
// Only nullable structs, not primitives or enums are currently supported
370+
bool isNullableStruct = isNullableValueType &&
371+
(nullableValueType?.IsStruct() ?? false);
372+
373+
// if (isNullableValueType) {
374+
// unsupportedReason = $"Is Nullable Value Type ({nullableValueType.FullName}?)";
375+
// return false;
376+
// }
377+
378+
if (isNullableValueType &&
379+
!isNullableStruct) {
380+
unsupportedReason = $"Is Nullable Value Type, but not a struct ({nullableValueType?.FullName}?)";
369381
return false;
370382
}
371383

372-
if (!m_enableGenericsSupport &&
384+
if (!isNullableStruct &&
385+
!m_enableGenericsSupport &&
373386
type.IsGenericInAnyWay(true)) {
374387
unsupportedReason = "Is Generic (disabled by configuration)";
375388
return false;
@@ -382,7 +395,8 @@ out string? unsupportedReason
382395
}
383396

384397
// TODO: Generic Types as arguments, properties, etc.
385-
if (type.IsConstructedGenericType) {
398+
if (!isNullableValueType &&
399+
type.IsConstructedGenericType) {
386400
Type genericTypeDefinition = type.GetGenericTypeDefinition();
387401

388402
if (UNSUPPORTED_TYPES.Contains(genericTypeDefinition)) {

Generator/Beyond.NET.CodeGenerator/Extensions/TypeExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ internal static string GetFullNameOrName(this Type type)
8484

8585
internal static string CTypeName(this Type type)
8686
{
87+
if (type.IsNullableValueType(out Type? valueType)) {
88+
type = valueType;
89+
}
90+
8791
string fullTypeName = type.GetFullNameOrName();
8892

8993
string cTypeName = fullTypeName

Generator/Beyond.NET.CodeGenerator/Syntax/C/CMethodSyntaxWriter.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,21 @@ out string generatedName
155155
CodeLanguage.C
156156
);
157157
}
158+
159+
bool isNullableValueTypeReturnType = returnOrSetterOrEventHandlerType.IsNullableValueType(out Type? nullableValueReturnType);
158160

159-
bool isGenericReturnType = returnOrSetterOrEventHandlerType.IsGenericParameter ||
160-
returnOrSetterOrEventHandlerType.IsGenericMethodParameter ||
161-
returnOrSetterOrEventHandlerType.IsGenericType ||
162-
returnOrSetterOrEventHandlerType.IsGenericTypeDefinition ||
163-
returnOrSetterOrEventHandlerType.IsGenericTypeParameter ||
164-
returnOrSetterOrEventHandlerType.IsConstructedGenericType;
165-
166-
bool isConstructedGenericReturnType = returnOrSetterOrEventHandlerType.IsConstructedGenericType;
161+
bool isGenericReturnType = !isNullableValueTypeReturnType &&
162+
(
163+
returnOrSetterOrEventHandlerType.IsGenericParameter ||
164+
returnOrSetterOrEventHandlerType.IsGenericMethodParameter ||
165+
returnOrSetterOrEventHandlerType.IsGenericType ||
166+
returnOrSetterOrEventHandlerType.IsGenericTypeDefinition ||
167+
returnOrSetterOrEventHandlerType.IsGenericTypeParameter ||
168+
returnOrSetterOrEventHandlerType.IsConstructedGenericType
169+
);
170+
171+
bool isConstructedGenericReturnType = !isNullableValueTypeReturnType &&
172+
returnOrSetterOrEventHandlerType.IsConstructedGenericType;
167173

168174
bool isGenericArrayReturnType = false;
169175

@@ -192,10 +198,17 @@ out string generatedName
192198
}
193199
}
194200

195-
bool returnOrSetterOrEventHandlerTypeIsByRef = returnOrSetterOrEventHandlerType.IsByRef;
201+
bool returnOrSetterOrEventHandlerTypeIsByRef;
202+
203+
if (isNullableValueTypeReturnType) {
204+
returnOrSetterOrEventHandlerTypeIsByRef = true;
205+
returnOrSetterOrEventHandlerType = nullableValueReturnType ?? returnOrSetterOrEventHandlerType;
206+
} else {
207+
returnOrSetterOrEventHandlerTypeIsByRef = returnOrSetterOrEventHandlerType.IsByRef;
196208

197-
if (returnOrSetterOrEventHandlerTypeIsByRef) {
198-
returnOrSetterOrEventHandlerType = returnOrSetterOrEventHandlerType.GetNonByRefType();
209+
if (returnOrSetterOrEventHandlerTypeIsByRef) {
210+
returnOrSetterOrEventHandlerType = returnOrSetterOrEventHandlerType.GetNonByRefType();
211+
}
199212
}
200213

201214
TypeDescriptor returnOrSetterTypeDescriptor = returnOrSetterOrEventHandlerType.GetTypeDescriptor(typeDescriptorRegistry);

Generator/Beyond.NET.CodeGenerator/Syntax/C/CTypeSyntaxWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public string Write(Type type, State state, ISyntaxWriterConfiguration? configur
4242
// Result cSharpUnmanagedResult = state.CSharpUnmanagedResult ?? throw new Exception("No CSharpUnmanagedResult provided");
4343

4444
if (type.IsPointer ||
45-
type.IsByRef) {
45+
type.IsByRef ||
46+
type.IsNullableValueType(out _)) {
4647
// No need to generate C code for those kinds of types
4748

4849
return string.Empty;

Generator/Beyond.NET.CodeGenerator/Types/TypeDescriptor.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using Beyond.NET.CodeGenerator.Extensions;
23

34
namespace Beyond.NET.CodeGenerator.Types;
@@ -15,7 +16,12 @@ public class TypeDescriptor
1516
public bool IsVoid => ManagedType.IsVoid();
1617
public bool IsDelegate => ManagedType.IsDelegate();
1718
public bool IsManagedPointer => ManagedType.IsPointer;
18-
public bool RequiresNativePointer => !IsVoid && !IsEnum && !IsPrimitive && !IsBool;
19+
public bool RequiresNativePointer => !IsVoid && !IsEnum && !IsPrimitive && !IsBool;
20+
21+
public bool IsNullableValueType([NotNullWhen(true)] out Type? valueType)
22+
{
23+
return ManagedType.IsNullableValueType(out valueType);
24+
}
1925

2026
private readonly Dictionary<CodeLanguage, string> m_typeNames;
2127
private readonly Dictionary<LanguagePair, string> m_typeConversions;
@@ -284,7 +290,11 @@ CodeLanguage targetLanguage
284290

285291
conversion = $"new {cTypeName}({{0}}).AllocateGCHandleAndGetAddress()";
286292
} else {
287-
conversion = "{0}.AllocateGCHandleAndGetAddress()";
293+
if (IsNullableValueType(out Type? valueType)) {
294+
conversion = "{0}.HasValue ? {0}.Value.AllocateGCHandleAndGetAddress() : default";
295+
} else {
296+
conversion = "{0}.AllocateGCHandleAndGetAddress()";
297+
}
288298
}
289299

290300
return conversion;

Samples/Beyond.NET.Sample.Managed/Source/StructTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,21 @@ public StructTest(string? name)
1717
return new StructTest("Test");
1818
}
1919
}
20+
21+
public static void GetNullableStructReturnValueInOutParameter(
22+
bool returnNull,
23+
out StructTest? returnValue
24+
)
25+
{
26+
if (returnNull) {
27+
returnValue = null;
28+
} else {
29+
returnValue = new StructTest("Test");
30+
}
31+
}
32+
33+
public static StructTest? GetNullableStructReturnValueOfRefParameter(ref StructTest? returnValue)
34+
{
35+
return returnValue;
36+
}
2037
}

Samples/Beyond.NET.Sample.Swift/Tests/TestClasses/StructTestTests.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,66 @@ final class StructTestTests: XCTestCase {
4040

4141
XCTAssertEqual(nameNew, nameNewRet)
4242
}
43+
44+
func testNullableValueTypes() {
45+
do {
46+
let nullRetVal = try Beyond_NET_Sample_StructTest.getNullableStructReturnValue(true)
47+
48+
XCTAssertNil(nullRetVal)
49+
} catch {
50+
XCTFail("StructTest.GetNullableStructReturnValue should not throw")
51+
}
52+
53+
do {
54+
let structRetVal = try Beyond_NET_Sample_StructTest.getNullableStructReturnValue(false)
55+
56+
XCTAssertNotNil(structRetVal)
57+
} catch {
58+
XCTFail("StructTest.GetNullableStructReturnValue should not throw")
59+
}
60+
61+
do {
62+
var nullOutRetVal: Beyond_NET_Sample_StructTest?
63+
64+
try Beyond_NET_Sample_StructTest.getNullableStructReturnValueInOutParameter(true,
65+
&nullOutRetVal)
66+
67+
XCTAssertNil(nullOutRetVal)
68+
} catch {
69+
XCTFail("StructTest.GetNullableStructReturnValueInOutParameter should not throw")
70+
}
71+
72+
do {
73+
var structRetVal: Beyond_NET_Sample_StructTest?
74+
75+
try Beyond_NET_Sample_StructTest.getNullableStructReturnValueInOutParameter(false,
76+
&structRetVal)
77+
78+
XCTAssertNotNil(structRetVal)
79+
} catch {
80+
XCTFail("StructTest.GetNullableStructReturnValueInOutParameter should not throw")
81+
}
82+
83+
do {
84+
var nullRef: Beyond_NET_Sample_StructTest?
85+
86+
let ret = try Beyond_NET_Sample_StructTest.getNullableStructReturnValueOfRefParameter(&nullRef)
87+
88+
XCTAssertNil(ret)
89+
XCTAssertTrue(nullRef == ret)
90+
} catch {
91+
XCTFail("StructTest.GetNullableStructReturnValueOfRefParameter should not throw")
92+
}
93+
94+
do {
95+
var structRef = try Beyond_NET_Sample_StructTest("test".dotNETString())
96+
97+
let ret = try Beyond_NET_Sample_StructTest.getNullableStructReturnValueOfRefParameter(&structRef)
98+
99+
XCTAssertNotNil(ret)
100+
XCTAssertTrue(structRef == ret)
101+
} catch {
102+
XCTFail("StructTest.GetNullableStructReturnValueOfRefParameter should not throw")
103+
}
104+
}
43105
}

0 commit comments

Comments
 (0)