Skip to content

Commit 55019e3

Browse files
JasonXuDeveloperGemini
andcommitted
fix(generator): gracefully handle unbound generic types
The generator would previously crash when encountering an unbound generic type due to an `InvalidOperationException` in `GetTypeHierarchyLevel` when calling `.Max()` on an empty collection of type arguments. Additionally, the generator would produce invalid code if an unbound generic type was not filtered out correctly. This commit addresses the issue by: 1. Making `GetTypeHierarchyLevel` robust against empty type argument lists. 2. Adding an explicit check for unbound generic types in `CheckGenericValidity` to ensure they are filtered out early in the type collection process. Adds a new test case to verify that the generator no longer crashes and produces valid code when an unbound generic type is referenced. Co-authored-by: Gemini <gemini@google.com>
1 parent 41606e6 commit 55019e3

3 files changed

Lines changed: 34 additions & 2 deletions

File tree

src/Nino.Generator/NinoTypeHelper.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public static (bool isValid, Compilation newCompilation) IsValidCompilation(this
192192
.Any(u =>
193193
{
194194
var name = u.Name.ToString();
195-
return name == "Nino.Core" || name == "global::Nino.Core" || name?.StartsWith("Nino.Core.") == true;
195+
return name.Contains("Nino.Core");
196196
}));
197197

198198
if (!hasNinoCoreUsage)
@@ -344,6 +344,9 @@ public static bool CheckGenericValidity(this ITypeSymbol containingType)
344344
case IArrayTypeSymbol arrayTypeSymbol:
345345
toValidate.Push(arrayTypeSymbol.ElementType);
346346
break;
347+
348+
case INamedTypeSymbol { IsUnboundGenericType: true }:
349+
return false;
347350

348351
case INamedTypeSymbol { IsGenericType: true } namedTypeSymbol:
349352
// Validate generic type
@@ -842,7 +845,9 @@ public static int GetTypeHierarchyLevel(this ITypeSymbol type)
842845

843846
// Generics: 1 + max level of type arguments
844847
INamedTypeSymbol { IsGenericType: true } namedType =>
845-
1 + namedType.TypeArguments.Max(GetTypeHierarchyLevel),
848+
namedType.TypeArguments.Length > 0
849+
? 1 + namedType.TypeArguments.Max(GetTypeHierarchyLevel)
850+
: 1,
846851

847852
// Base types (including non-generic named types)
848853
_ => 1

src/Nino.UnitTests/GenericTypeTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,16 @@ public void TestListObject()
231231

232232
Assert.IsNull(result[3]); // null
233233
}
234+
235+
[TestMethod]
236+
public void TestUnboundGeneric()
237+
{
238+
var obj = new ClassWithUnboundGenericType()
239+
{
240+
OtherData = 42
241+
};
242+
var serialized = NinoSerializer.Serialize(obj);
243+
var deserialized = NinoDeserializer.Deserialize<ClassWithUnboundGenericType>(serialized);
244+
Assert.AreEqual(obj.OtherData, deserialized.OtherData);
245+
}
234246
}

src/Nino.UnitTests/TestClass.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,11 +623,26 @@ public sealed class SimpleClassWithConstructor
623623

624624
// [NinoConstructor(nameof(Id), nameof(Name), nameof(CreateTime))] - we try not to use this and test if it still works
625625
// should automatically use this constructor since this is the only public constructor
626+
626627
public SimpleClassWithConstructor(int id, string name, DateTime createTime)
627628
{
628629
Id = id;
629630
Name = name;
630631
CreateTime = createTime;
631632
}
632633
}
634+
635+
[NinoType]
636+
public class UnboundGenericTest<T>
637+
{
638+
public T Data;
639+
}
640+
641+
[NinoType]
642+
public class ClassWithUnboundGenericType
643+
{
644+
[NinoIgnore]
645+
public Type UnboundType = typeof(UnboundGenericTest<>);
646+
public int OtherData = 1;
647+
}
633648
}

0 commit comments

Comments
 (0)