Skip to content

Commit 7ae0abd

Browse files
fix: convert TypeInfoDto from struct to class to resolve self-referential generic field error
- Changed TypeInfoDto from record struct to sealed record class - Changed TupleElementDto from record struct to sealed record class - Added default values (string.Empty, null!) to all non-nullable properties - Fixed all .HasValue and .Value usages for TypeInfoDto? (now nullable reference instead of Nullable<T>) - Added null-conditional operators in NinoType.Equals, GetHashCode, IsPolyMorphic, and ToString The self-referential generic field error occurred because TypeInfoDto (as a struct) contained EquatableArray<TypeInfoDto>, creating circular type definition. Converting to class resolves this. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 6067e27 commit 7ae0abd

File tree

8 files changed

+36
-37
lines changed

8 files changed

+36
-37
lines changed

src/Nino.Generator/BuiltInType/ArrayGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ public class ArrayGenerator(
4444
public override bool Filter(TypeInfoDto typeInfo)
4545
{
4646
if (typeInfo.ArrayRank == 0) return false;
47-
if (!typeInfo.ArrayElementType.HasValue) return false;
48-
var elementType = typeInfo.ArrayElementType.Value;
47+
if (typeInfo.ArrayElementType == null) return false;
48+
var elementType = typeInfo.ArrayElementType;
4949
return TypeInfoDtoExtensions.GetKind(elementType, NinoGraph, GeneratedTypeIds) != NinoTypeKind.Invalid;
5050
}
5151

5252
protected override void GenerateSerializer(TypeInfoDto typeInfo, Writer writer)
5353
{
54-
var elementType = typeInfo.ArrayElementType!.Value;
54+
var elementType = typeInfo.ArrayElementType!;
5555
var rank = typeInfo.ArrayRank;
5656
var typeName = typeInfo.DisplayName;
5757

@@ -204,7 +204,7 @@ protected override void GenerateSerializer(TypeInfoDto typeInfo, Writer writer)
204204

205205
protected override void GenerateDeserializer(TypeInfoDto typeInfo, Writer writer)
206206
{
207-
var elementType = typeInfo.ArrayElementType!.Value;
207+
var elementType = typeInfo.ArrayElementType!;
208208
var elemType = elementType.DisplayName;
209209
var rank = typeInfo.ArrayRank;
210210
var typeName = typeInfo.DisplayName;

src/Nino.Generator/Common/DeserializerGenerator.Trivial.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ private void GeneratePrivateFieldDeserializeFallback(StringBuilder sb, NinoMembe
812812
var formatterType = member.CustomFormatterType;
813813
if (formatterType != null)
814814
{
815-
var varName = formatterType.Value.GetCachedVariableName("formatter");
815+
var varName = formatterType.GetCachedVariableName("formatter");
816816
customFormatterVarsByMember[member] = varName;
817817
// Note: Static field should be generated at class level, not as local variable
818818
}
@@ -1348,8 +1348,8 @@ private bool TryGetInlineDeserializeCall(TypeInfoDto type, bool byRef, string va
13481348
var formatterType = member.CustomFormatterType;
13491349
if (formatterType != null)
13501350
{
1351-
var key = $"{formatterType.Value.DisplayName}_{member.Type.DisplayName}";
1352-
globalCustomFormatters[key] = (formatterType.Value, member.Type);
1351+
var key = $"{formatterType.DisplayName}_{member.Type.DisplayName}";
1352+
globalCustomFormatters[key] = (formatterType, member.Type);
13531353
}
13541354
}
13551355
}

src/Nino.Generator/Common/SerializerGenerator.Trivial.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ private static string GenerateWriterAccessMethodBody(string typeName, string ind
159159
var formatterType = member.CustomFormatterType;
160160
if (formatterType != null)
161161
{
162-
var key = $"{formatterType.Value.DisplayName}_{member.Type.DisplayName}";
163-
globalCustomFormatters[key] = (formatterType.Value, member.Type);
162+
var key = $"{formatterType.DisplayName}_{member.Type.DisplayName}";
163+
globalCustomFormatters[key] = (formatterType, member.Type);
164164
}
165165
}
166166
}
@@ -263,7 +263,7 @@ private void WriteMembers(NinoType type, string valName, StringBuilder sb, Sourc
263263
var formatterType = member.CustomFormatterType;
264264
if (formatterType != null)
265265
{
266-
var varName = formatterType.Value.GetCachedVariableName("formatter");
266+
var varName = formatterType.GetCachedVariableName("formatter");
267267
customFormatterVarsByMember[member] = varName;
268268
// Note: Static field should be generated at class level, not as local variable
269269
}

src/Nino.Generator/GlobalGenerator.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using Nino.Generator.Common;
88
using Nino.Generator.Metadata;
99
using Nino.Generator.Pipeline;
10-
using Nino.Generator.Template;
1110

1211
namespace Nino.Generator;
1312

@@ -57,11 +56,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5756
transform: static (ctx, ct) =>
5857
{
5958
if (ctx.TargetSymbol is not ITypeSymbol typeSymbol)
60-
return (NinoType?)null;
59+
return null;
6160

6261
// Only process public types
6362
if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
64-
return (NinoType?)null;
63+
return null;
6564

6665
// Skip invalid generic types
6766
if (!typeSymbol.CheckGenericValidity())
@@ -125,15 +124,15 @@ node is TypeDeclarationSyntax tds &&
125124
transform: static (ctx, ct) =>
126125
{
127126
if (ctx.SemanticModel.GetDeclaredSymbol(ctx.Node, ct) is not ITypeSymbol typeSymbol)
128-
return (NinoType?)null;
127+
return null;
129128

130129
// Only process public types
131130
if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
132-
return (NinoType?)null;
131+
return null;
133132

134133
// Skip invalid generic types
135134
if (!typeSymbol.CheckGenericValidity())
136-
return (NinoType?)null;
135+
return null;
137136

138137
// Check if this type inherits NinoTypeAttribute
139138
if (!SymbolDataExtractor.IsNinoType(typeSymbol))

src/Nino.Generator/Metadata/CircularTypeDetector.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,16 @@ private static bool IsTypeRelated(
129129
}
130130

131131
// Check array element type (e.g., Node[] in Node)
132-
if (memberType.ArrayRank > 0 && memberType.ArrayElementType.HasValue)
132+
if (memberType.ArrayRank > 0 && memberType.ArrayElementType != null)
133133
{
134-
if (IsTypeRelated(memberType.ArrayElementType.Value, originalTypeId, allTypes, baseTypes))
134+
if (IsTypeRelated(memberType.ArrayElementType, originalTypeId, allTypes, baseTypes))
135135
return true;
136136
}
137137

138138
// Check nullable underlying type (e.g., Node? in Node for value types)
139-
if (memberType.IsNullableValueType && memberType.NullableUnderlyingType.HasValue)
139+
if (memberType.IsNullableValueType && memberType.NullableUnderlyingType != null)
140140
{
141-
if (IsTypeRelated(memberType.NullableUnderlyingType.Value, originalTypeId, allTypes, baseTypes))
141+
if (IsTypeRelated(memberType.NullableUnderlyingType, originalTypeId, allTypes, baseTypes))
142142
return true;
143143
}
144144

src/Nino.Generator/Metadata/NinoMember.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public readonly record struct NinoMember
5757
/// <summary>
5858
/// Determines if this member has a custom formatter.
5959
/// </summary>
60-
public bool HasCustomFormatter() => CustomFormatterType.HasValue;
60+
public bool HasCustomFormatter() => CustomFormatterType != null;
6161

6262
/// <summary>
6363
/// String representation for debugging.

src/Nino.Generator/Metadata/NinoType.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,20 @@ public bool IsPolyMorphic()
124124
if (ParentTypeIds.Length > 0)
125125
return true;
126126

127-
return TypeInfo.IsPolymorphicType();
127+
return TypeInfo?.IsPolymorphicType() ?? false;
128128
}
129129

130130
/// <summary>
131131
/// Custom equality based on TypeId only.
132132
/// This allows Dictionary&lt;NinoType, ...&gt; to work with value-based equality.
133133
/// Two NinoTypes are equal if they represent the same type (same TypeId).
134134
/// </summary>
135-
public bool Equals(NinoType other) => TypeInfo.TypeId == other.TypeInfo.TypeId;
135+
public bool Equals(NinoType other) => TypeInfo?.TypeId == other.TypeInfo?.TypeId;
136136

137137
/// <summary>
138138
/// Hash code based on TypeId for efficient dictionary lookups.
139139
/// </summary>
140-
public override int GetHashCode() => TypeInfo.TypeId;
140+
public override int GetHashCode() => TypeInfo?.TypeId ?? 0;
141141

142142
/// <summary>
143143
/// String representation for debugging.
@@ -146,7 +146,7 @@ public override string ToString()
146146
{
147147
var lines = new List<string>
148148
{
149-
$"Type: {TypeInfo.DisplayName}"
149+
$"Type: {TypeInfo?.DisplayName ?? "null"}"
150150
};
151151

152152
if (!string.IsNullOrEmpty(CustomSerializer))

src/Nino.Generator/Metadata/TypeInfoDto.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ namespace Nino.Generator.Metadata;
33
/// <summary>
44
/// Immutable DTO representing all required type information for code generation.
55
/// This replaces direct ITypeSymbol usage in the metadata layer to enable proper incremental caching.
6-
/// All data is extracted once from ITypeSymbol via SymbolDataExtractor and stored as value types.
6+
/// All data is extracted once from ITypeSymbol via SymbolDataExtractor and stored as immutable records.
77
/// </summary>
8-
public readonly record struct TypeInfoDto
8+
public sealed record TypeInfoDto
99
{
1010
// ===== Type Identity =====
1111

1212
/// <summary>
1313
/// Fully qualified name (e.g., "global::System.Collections.Generic.List&lt;int&gt;").
1414
/// </summary>
15-
public string FullyQualifiedName { get; init; }
15+
public string FullyQualifiedName { get; init; } = string.Empty;
1616

1717
/// <summary>
1818
/// Assembly qualified name for cross-assembly references.
1919
/// </summary>
20-
public string AssemblyQualifiedName { get; init; }
20+
public string AssemblyQualifiedName { get; init; } = string.Empty;
2121

2222
/// <summary>
2323
/// Deterministic hash of FullyQualifiedName for fast lookups and equality comparisons.
@@ -149,44 +149,44 @@ public readonly record struct TypeInfoDto
149149
/// <summary>
150150
/// Containing namespace (e.g., "System.Collections.Generic").
151151
/// </summary>
152-
public string ContainingNamespace { get; init; }
152+
public string ContainingNamespace { get; init; } = string.Empty;
153153

154154
/// <summary>
155155
/// Containing assembly name.
156156
/// </summary>
157-
public string ContainingAssemblyName { get; init; }
157+
public string ContainingAssemblyName { get; init; } = string.Empty;
158158

159159
// ===== Display Names (Pre-computed for Code Generation) =====
160160

161161
/// <summary>
162162
/// Display name for code generation (without 'global::' prefix).
163163
/// </summary>
164-
public string DisplayName { get; init; }
164+
public string DisplayName { get; init; } = string.Empty;
165165

166166
/// <summary>
167167
/// Suggested variable name for instances of this type (e.g., "list" for List&lt;T&gt;).
168168
/// </summary>
169-
public string InstanceVariableName { get; init; }
169+
public string InstanceVariableName { get; init; } = string.Empty;
170170

171171
/// <summary>
172172
/// Simple type name without namespace (e.g., "List", "Dictionary", "ValueTuple").
173173
/// For generic types, includes type parameters (e.g., "List&lt;T&gt;").
174174
/// </summary>
175-
public string Name { get; init; }
175+
public string Name { get; init; } = string.Empty;
176176
}
177177

178178
/// <summary>
179179
/// DTO for tuple element metadata.
180180
/// </summary>
181-
public readonly record struct TupleElementDto
181+
public sealed record TupleElementDto
182182
{
183183
/// <summary>
184184
/// Element type.
185185
/// </summary>
186-
public TypeInfoDto Type { get; init; }
186+
public TypeInfoDto Type { get; init; } = null!;
187187

188188
/// <summary>
189189
/// Element name (e.g., "Item1" or custom name).
190190
/// </summary>
191-
public string Name { get; init; }
191+
public string Name { get; init; } = string.Empty;
192192
}

0 commit comments

Comments
 (0)