Skip to content

Commit dfd5150

Browse files
fix(generator): resolve jagged array syntax errors and centralize array creation logic
Fixed critical bugs in code generation for complex array types (jagged, multi-dimensional, and mixed arrays) across multiple built-in type generators. The generators were manually constructing array creation syntax which caused compilation errors for complex array types. Root cause: - ArrayGenerator.cs, QueueGenerator.cs, and ArraySegmentGenerator.cs had duplicate logic for array creation that incorrectly handled complex types like int[][][,] or byte[][,] - For example: `new byte[][length]` instead of `new byte[length][]` Solution: 1. Created shared `GetArrayCreationString()` helper method in NinoBuiltInTypeGenerator.cs that correctly handles all array types by inserting dimension specifier before the first bracket, accounting for generics 2. Updated ArrayGenerator.cs to use helper for both 1D and multi-dimensional arrays 3. Updated QueueGenerator.cs to use `var` for type inference instead of explicit declaration 4. Updated ArraySegmentGenerator.cs to use the helper method 5. Audited all other generators (Immutable*, LinkedList, PriorityQueue, Stack) - confirmed safe Testing: - Added comprehensive tests for Queue/Stack with complex arrays (int[][][,], string[,][][][]) - Added ArraySegment tests with jagged arrays (byte[], int[][], int[][][]) - Added PriorityQueue tests for .NET 6.0+ with: * Jagged arrays (byte[][], int[][][]) * 2D arrays (int[,]) * 3D arrays (int[,,]) * Mixed jagged/multi-dim (int[][,]) * Mixed multi-dim/jagged (int[,][]) - All 7 test methods (5 original + 2 new) pass across .NET 6.0, 8.0, and netstandard2.1 This eliminates manual array syntax construction across the codebase, preventing future regressions and ensuring consistent behavior for all complex array types. Closes #161 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3b2a575 commit dfd5150

File tree

5 files changed

+460
-65
lines changed

5 files changed

+460
-65
lines changed

src/Nino.Generator/BuiltInType/ArrayGenerator.cs

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -200,34 +200,6 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
200200
// Fast path only works for 1D arrays
201201
bool canUseFastPath = rank == 1 && elementType.GetKind(NinoGraph, GeneratedTypes) == NinoTypeHelper.NinoTypeKind.Unmanaged;
202202

203-
// Generate creation declaration for 1D arrays
204-
// For element type like "int[]", we need "int[length][]"
205-
// For element type like "int[,]", we need "int[length][,]"
206-
// For element type like "Dictionary<string, int[]>", we need "Dictionary<string, int[]>[length]"
207-
// For element type like "int", we need "int[length]"
208-
// Strategy: find the first '[' that's NOT inside angle brackets <>, then insert "[length]" before it
209-
// If no such '[' exists, append "[length]"
210-
string creationDecl = null!;
211-
if (rank == 1)
212-
{
213-
int angleDepth = 0;
214-
int firstBracket = -1;
215-
for (int i = 0; i < elemType.Length; i++)
216-
{
217-
if (elemType[i] == '<') angleDepth++;
218-
else if (elemType[i] == '>') angleDepth--;
219-
else if (elemType[i] == '[' && angleDepth == 0)
220-
{
221-
firstBracket = i;
222-
break;
223-
}
224-
}
225-
226-
creationDecl = firstBracket >= 0
227-
? elemType.Insert(firstBracket, "[length]")
228-
: $"{elemType}[length]";
229-
}
230-
231203
// Out overload
232204
WriteAggressiveInlining(writer);
233205
writer.Append("public static void Deserialize(out ");
@@ -254,7 +226,7 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
254226
w => { w.AppendLine(" Reader eleReader;"); });
255227
writer.AppendLine();
256228
writer.Append(" value = new ");
257-
writer.Append(creationDecl);
229+
writer.Append(GetArrayCreationString(elemType, "length"));
258230
writer.AppendLine(";");
259231
writer.AppendLine(" var span = value.AsSpan();");
260232
writer.AppendLine(" for (int i = 0; i < length; i++)");
@@ -315,7 +287,9 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
315287

316288
// Create array with proper dimensions using direct syntax
317289
var lengths = string.Join(", ", Enumerable.Range(0, rank).Select(i => $"len{i}"));
318-
writer.AppendLine($" value = new {elemType}[{lengths}];");
290+
writer.Append(" value = new ");
291+
writer.Append(GetArrayCreationString(elemType, lengths));
292+
writer.AppendLine(";");
319293
writer.AppendLine();
320294

321295
// Generate nested loops for space-locality-aware deserialization
@@ -393,7 +367,7 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
393367
writer.AppendLine(" if (value == null)");
394368
writer.AppendLine(" {");
395369
writer.Append(" value = new ");
396-
writer.Append(creationDecl);
370+
writer.Append(GetArrayCreationString(elemType, "length"));
397371
writer.AppendLine(";");
398372
writer.AppendLine(" }");
399373
writer.AppendLine(" else if (value.Length != length)");
@@ -469,7 +443,9 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
469443
writer.AppendLine(" if (!canReuse)");
470444
writer.AppendLine(" {");
471445
var lengths = string.Join(", ", Enumerable.Range(0, rank).Select(i => $"len{i}"));
472-
writer.AppendLine($" value = new {elemType}[{lengths}];");
446+
writer.Append(" value = new ");
447+
writer.Append(GetArrayCreationString(elemType, lengths));
448+
writer.AppendLine(";");
473449
writer.AppendLine(" }");
474450
writer.AppendLine();
475451

src/Nino.Generator/BuiltInType/ArraySegmentGenerator.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,13 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
147147
writer.AppendLine(" }");
148148
writer.AppendLine();
149149

150-
// Handle array creation - check if element type is already an array
151-
var arrayCreation = elemType.EndsWith("[]")
152-
? elemType.Insert(elemType.IndexOf("[]", System.StringComparison.Ordinal), "[length]")
153-
: $"{elemType}[length]";
154-
155-
156150
// For managed element types, deserialize element by element
157151
IfDirective(NinoTypeHelper.WeakVersionToleranceSymbol, writer,
158152
w => { w.AppendLine(" Reader eleReader;"); });
159153
writer.AppendLine();
160154

161155
writer.Append(" var array = new ");
162-
writer.Append(arrayCreation);
156+
writer.Append(GetArrayCreationString(elemType, "length"));
163157
writer.AppendLine(";");
164158
writer.AppendLine(" for (int i = 0; i < length; i++)");
165159
writer.AppendLine(" {");

src/Nino.Generator/BuiltInType/QueueGenerator.cs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -285,31 +285,11 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
285285
var elemType = elementType.GetDisplayString();
286286
var queueViewTypeName = $"Nino.Core.Internal.QueueView<{elemType}>";
287287

288-
// Build correct array creation syntax for jagged arrays
289-
// For element type like "int[]", we need "int[length][]" not "int[][length]"
290-
string creationDecl;
291-
int angleDepth = 0;
292-
int firstBracket = -1;
293-
for (int i = 0; i < elemType.Length; i++)
294-
{
295-
if (elemType[i] == '<') angleDepth++;
296-
else if (elemType[i] == '>') angleDepth--;
297-
else if (elemType[i] == '[' && angleDepth == 0)
298-
{
299-
firstBracket = i;
300-
break;
301-
}
302-
}
303-
304-
creationDecl = firstBracket >= 0
305-
? elemType.Insert(firstBracket, "[length]")
306-
: $"{elemType}[length]";
307-
308288
if (isUnmanaged)
309289
{
310290
// For unmanaged types, use efficient memcpy via GetBytes
311291
writer.Append(" var array = new ");
312-
writer.Append(creationDecl);
292+
writer.Append(GetArrayCreationString(elemType, "length"));
313293
writer.AppendLine(";");
314294
writer.Append(" reader.GetBytes(length * System.Runtime.CompilerServices.Unsafe.SizeOf<");
315295
writer.Append(elemType);
@@ -321,7 +301,7 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
321301
{
322302
// For managed types, use ref iteration for efficient element assignment
323303
writer.Append(" var array = new ");
324-
writer.Append(creationDecl);
304+
writer.Append(GetArrayCreationString(elemType, "length"));
325305
writer.AppendLine(";");
326306
writer.AppendLine(" var span = array.AsSpan();");
327307
writer.AppendLine(" for (int i = 0; i < length; i++)");
@@ -462,9 +442,6 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
462442
var queueViewTypeName = $"Nino.Core.Internal.QueueView<{elemType}>";
463443

464444
writer.AppendLine(" // Extract existing array or create new, then resize if needed");
465-
writer.Append(" ");
466-
writer.Append(elemType);
467-
writer.AppendLine("[] array;");
468445
writer.AppendLine(" if (value == null)");
469446
writer.AppendLine(" {");
470447
writer.Append(" value = new ");
@@ -477,7 +454,7 @@ protected override void GenerateDeserializer(ITypeSymbol typeSymbol, Writer writ
477454
writer.Append(", ");
478455
writer.Append(queueViewTypeName);
479456
writer.AppendLine(">(ref value);");
480-
writer.AppendLine(" array = queue._array;");
457+
writer.AppendLine(" var array = queue._array;");
481458
writer.AppendLine();
482459
writer.AppendLine(" // Resize array if needed");
483460
writer.AppendLine(" if (array == null || array.Length < length)");

src/Nino.Generator/Template/NinoBuiltInTypeGenerator.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,46 @@ protected string GetDeserializeRefString(ITypeSymbol type, string varName, strin
211211
}
212212
}
213213

214+
/// <summary>
215+
/// Generates correct array creation syntax for both jagged and multi-dimensional arrays.
216+
/// Handles complex cases like int[][], int[,][], int[][,], etc.
217+
/// </summary>
218+
/// <param name="elementTypeString">The element type as a string (e.g., "int[]", "byte[,]")</param>
219+
/// <param name="dimensionString">The dimension specifier (e.g., "length", "len0, len1")</param>
220+
/// <returns>Correct array creation string (e.g., "int[length][]", "int[len0, len1][]")</returns>
221+
protected static string GetArrayCreationString(string elementTypeString, string dimensionString)
222+
{
223+
// Find the first '[' that's not inside angle brackets (generics)
224+
// Insert [dimensionString] before it
225+
// Examples:
226+
// "int[]" + "length" -> "int[length][]"
227+
// "byte[,]" + "length" -> "byte[length][,]"
228+
// "int[][,]" + "length" -> "int[length][][,]"
229+
// "int" + "len0, len1" -> "int[len0, len1]"
230+
// "Dictionary<string, int[]>" + "length" -> "Dictionary<string, int[]>[length]"
231+
232+
int angleDepth = 0;
233+
int firstBracket = -1;
234+
235+
for (int i = 0; i < elementTypeString.Length; i++)
236+
{
237+
if (elementTypeString[i] == '<') angleDepth++;
238+
else if (elementTypeString[i] == '>') angleDepth--;
239+
else if (elementTypeString[i] == '[' && angleDepth == 0)
240+
{
241+
firstBracket = i;
242+
break;
243+
}
244+
}
245+
246+
// If no brackets found, append dimension at the end
247+
if (firstBracket < 0)
248+
return $"{elementTypeString}[{dimensionString}]";
249+
250+
// Insert dimension before the first bracket
251+
return elementTypeString.Insert(firstBracket, $"[{dimensionString}]");
252+
}
253+
214254
private bool TryGetInlineSerializeCall(ITypeSymbol type, string valueExpression, out string invocation)
215255
{
216256
invocation = null!;

0 commit comments

Comments
 (0)