Skip to content

Commit e9659fa

Browse files
fix(generator): resolve CS8628 error for nullable reference types in collections
Fixed compilation error CS8628 "Cannot use a nullable reference type in object creation" that occurred when generating code for nullable collection types like Dictionary<K,V>?, List<T>?, Queue<T>?, etc. The issue was caused by typeSymbol.GetDisplayString() returning type names with the trailing '?' nullable annotation, which is invalid in 'new' expressions. Centralized the fix in NinoTypeHelper.GetDisplayString() by calling GetPureType() to strip the nullable annotation before converting to string. This ensures all BuiltInTypeGenerators (Dictionary, List, HashSet, Queue, Stack, etc.) automatically generate correct code without the nullable annotation in object creation, while still maintaining type safety. Added comprehensive unit tests for nullable collection types including: - Dictionary<string, byte[]?>? - List<string?>? - HashSet<byte[]?>? - Queue<int?>? 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 7cfe212 commit e9659fa

File tree

3 files changed

+129
-3
lines changed

3 files changed

+129
-3
lines changed

src/Nino.Generator/NinoTypeHelper.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ public static string GetDisplayString(this ITypeSymbol typeSymbol)
230230
return ret;
231231
}
232232

233-
ret = typeSymbol.ToDisplayString();
233+
// Remove nullable annotation and normalize tuple types before converting to string
234+
// This ensures that types used in 'new' expressions don't have the trailing '?'
235+
// which would cause CS8628: Cannot use a nullable reference type in object creation
236+
var pureType = typeSymbol.GetPureType().GetNormalizedTypeSymbol();
237+
ret = pureType.ToDisplayString();
234238

235239
// Sanitize multi-dimensional array syntax: [*,*] -> [,], [*,*,*] -> [,,], etc.
236240
// This handles all cases including user-defined types and nested arrays

src/Nino.UnitTests/GenericTypeTests.cs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Concurrent;
43
using System.Collections.Generic;
54
using System.Threading.Tasks;
@@ -243,4 +242,91 @@ public void TestUnboundGeneric()
243242
var deserialized = NinoDeserializer.Deserialize<ClassWithUnboundGenericType>(serialized);
244243
Assert.AreEqual(obj.OtherData, deserialized.OtherData);
245244
}
245+
246+
#nullable enable
247+
248+
[TestMethod]
249+
public void TestNullableCollectionTypes()
250+
{
251+
// Test nullable collection types to ensure no CS8628 error occurs during code generation
252+
var obj = new NullableCollectionTestClass
253+
{
254+
NullableDict = new Dictionary<string, byte[]?>
255+
{
256+
{ "key1", new byte[] { 1, 2, 3 } },
257+
{ "key2", null },
258+
{ "key3", new byte[] { 4, 5 } }
259+
},
260+
NullableList = new List<string?>
261+
{
262+
"item1",
263+
null,
264+
"item3"
265+
},
266+
NullableHashSet = new HashSet<byte[]?>
267+
{
268+
new byte[] { 1, 2 },
269+
null,
270+
new byte[] { 3, 4 }
271+
},
272+
NullableQueue = new Queue<int?>()
273+
};
274+
obj.NullableQueue.Enqueue(1);
275+
obj.NullableQueue.Enqueue(null);
276+
obj.NullableQueue.Enqueue(3);
277+
278+
// Serialize
279+
byte[] bytes = NinoSerializer.Serialize(obj);
280+
Assert.IsNotNull(bytes);
281+
Console.WriteLine($"Serialized nullable collections: {bytes.Length} bytes");
282+
283+
// Deserialize
284+
var result = NinoDeserializer.Deserialize<NullableCollectionTestClass>(bytes);
285+
286+
// Verify Dictionary
287+
Assert.IsNotNull(result.NullableDict);
288+
Assert.AreEqual(3, result.NullableDict.Count);
289+
Assert.IsTrue(result.NullableDict.ContainsKey("key1"));
290+
Assert.IsTrue(result.NullableDict.ContainsKey("key2"));
291+
Assert.IsTrue(result.NullableDict.ContainsKey("key3"));
292+
Assert.IsNotNull(result.NullableDict["key1"]);
293+
Assert.IsNull(result.NullableDict["key2"]);
294+
295+
// Verify List
296+
Assert.IsNotNull(result.NullableList);
297+
Assert.AreEqual(3, result.NullableList.Count);
298+
Assert.AreEqual("item1", result.NullableList[0]);
299+
Assert.IsNull(result.NullableList[1]);
300+
Assert.AreEqual("item3", result.NullableList[2]);
301+
302+
// Verify HashSet
303+
Assert.IsNotNull(result.NullableHashSet);
304+
Assert.AreEqual(3, result.NullableHashSet.Count);
305+
306+
// Verify Queue
307+
Assert.IsNotNull(result.NullableQueue);
308+
Assert.AreEqual(3, result.NullableQueue.Count);
309+
var q1 = result.NullableQueue.Dequeue();
310+
var q2 = result.NullableQueue.Dequeue();
311+
var q3 = result.NullableQueue.Dequeue();
312+
Assert.AreEqual(1, q1);
313+
Assert.IsNull(q2);
314+
Assert.AreEqual(3, q3);
315+
316+
// Test with null collections
317+
var obj2 = new NullableCollectionTestClass
318+
{
319+
NullableDict = null,
320+
NullableList = null,
321+
NullableHashSet = null,
322+
NullableQueue = null
323+
};
324+
325+
bytes = NinoSerializer.Serialize(obj2);
326+
var result2 = NinoDeserializer.Deserialize<NullableCollectionTestClass>(bytes);
327+
Assert.IsNull(result2.NullableDict);
328+
Assert.IsNull(result2.NullableList);
329+
Assert.IsNull(result2.NullableHashSet);
330+
Assert.IsNull(result2.NullableQueue);
331+
}
246332
}

src/Nino.UnitTests/TestClass.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
5+
using System.Threading.Tasks;
56
using Nino.Core;
67

78
#nullable disable
@@ -469,10 +470,25 @@ public sealed class TestClass3 : TestClass2
469470
}
470471

471472
[NinoType]
472-
public struct TestStruct
473+
public struct TestStruct : IEquatable<TestStruct>
473474
{
474475
public int A;
475476
public string B;
477+
478+
public bool Equals(TestStruct other)
479+
{
480+
return A == other.A && B == other.B;
481+
}
482+
483+
public override bool Equals(object obj)
484+
{
485+
return obj is TestStruct other && Equals(other);
486+
}
487+
488+
public override int GetHashCode()
489+
{
490+
return HashCode.Combine(A, B);
491+
}
476492
}
477493

478494
[NinoType]
@@ -646,3 +662,23 @@ public class ClassWithUnboundGenericType
646662
public int OtherData = 1;
647663
}
648664
}
665+
666+
#nullable enable
667+
namespace Nino.UnitTests
668+
{
669+
/// <summary>
670+
/// Test class with nullable collection types to verify CS8628 fix
671+
/// </summary>
672+
[NinoType]
673+
public class NullableCollectionTestClass
674+
{
675+
public Dictionary<string, byte[]?>? NullableDict;
676+
public List<string?>? NullableList;
677+
public HashSet<byte[]?>? NullableHashSet;
678+
public Queue<int?>? NullableQueue;
679+
public Task<Dictionary<string, byte[]?>?>? NullableTask;
680+
public Task<Dictionary<string, byte[]?>?> NullableTask2 = null!;
681+
public Task<Dictionary<string, byte[]?>>? NullableTask3;
682+
}
683+
}
684+
#nullable disable

0 commit comments

Comments
 (0)