|
1 | 1 | using System.Collections.Generic; |
| 2 | +using System.Linq; |
2 | 3 |
|
3 | 4 | namespace FastCloner.SourceGenerator; |
4 | 5 |
|
@@ -32,6 +33,10 @@ public static void GenerateHelpers(CloneGeneratorContext context) |
32 | 33 | WriteArrayCloneMethod(context, member); |
33 | 34 | break; |
34 | 35 |
|
| 36 | + case MemberTypeKind.MultiDimArray: |
| 37 | + WriteMultiDimArrayCloneMethod(context, member); |
| 38 | + break; |
| 39 | + |
35 | 40 | case MemberTypeKind.Collection: |
36 | 41 | WriteCollectionCloneMethod(context, member); |
37 | 42 | break; |
@@ -698,8 +703,11 @@ private static void WriteArrayCloneMethod(CloneGeneratorContext context, MemberM |
698 | 703 | sb.AppendLine(); |
699 | 704 | } |
700 | 705 |
|
701 | | - // Arrays use Length, not Count |
702 | | - sb.AppendLine($" var result = new {member.ElementTypeName}[source.Length];"); |
| 706 | + // Create array - handle jagged arrays properly |
| 707 | + // For jagged arrays like int[][], the element type is int[] |
| 708 | + // We need to create: new int[source.Length][] (not new int[][source.Length]) |
| 709 | + var arrayCreationExpr = GetArrayCreationExpression(member.ElementTypeName, "source.Length"); |
| 710 | + sb.AppendLine($" var result = {arrayCreationExpr};"); |
703 | 711 |
|
704 | 712 | if (needsState) |
705 | 713 | { |
@@ -754,6 +762,151 @@ private static void WriteArrayCloneMethod(CloneGeneratorContext context, MemberM |
754 | 762 | sb.AppendLine(); |
755 | 763 | } |
756 | 764 |
|
| 765 | + private static void WriteMultiDimArrayCloneMethod(CloneGeneratorContext context, MemberModel member) |
| 766 | + { |
| 767 | + if (member.ElementTypeName == null) return; |
| 768 | + |
| 769 | + var typeName = member.TypeFullName; |
| 770 | + var methodName = context.GetMethodName(typeName); |
| 771 | + var isSafe = member.ElementIsSafe; |
| 772 | + var hasClonableAttr = member.ElementHasClonableAttr; |
| 773 | + var needsState = MemberCloneGenerator.MemberNeedsCircularRefTracking(context, member); |
| 774 | + var rank = member.ArrayRank; |
| 775 | + var sb = context.Source; |
| 776 | + |
| 777 | + if (needsState) |
| 778 | + { |
| 779 | + context.NeedsStateClass = true; |
| 780 | + } |
| 781 | + |
| 782 | + // Arrays are always reference types |
| 783 | + WriteHelperMethodSignature(context, typeName, methodName, needsState, false); |
| 784 | + sb.AppendLine(" {"); |
| 785 | + sb.AppendLine(" if (source == null) return null;"); |
| 786 | + |
| 787 | + if (needsState) |
| 788 | + { |
| 789 | + sb.AppendLine(" if (state != null)"); |
| 790 | + sb.AppendLine(" {"); |
| 791 | + sb.AppendLine(" var known = state.GetKnownRef(source);"); |
| 792 | + sb.AppendLine($" if (known != null) return ({typeName})known;"); |
| 793 | + sb.AppendLine(" }"); |
| 794 | + sb.AppendLine(); |
| 795 | + } |
| 796 | + |
| 797 | + // Generate dimension length variables: len0, len1, len2, ... |
| 798 | + for (int d = 0; d < rank; d++) |
| 799 | + { |
| 800 | + sb.AppendLine($" int len{d} = source.GetLength({d});"); |
| 801 | + } |
| 802 | + sb.AppendLine(); |
| 803 | + |
| 804 | + // Create the result array with the same dimensions |
| 805 | + // e.g., new T[len0, len1, len2] |
| 806 | + var dimList = string.Join(", ", Enumerable.Range(0, rank).Select(d => $"len{d}")); |
| 807 | + sb.AppendLine($" var result = new {member.ElementTypeName}[{dimList}];"); |
| 808 | + |
| 809 | + if (needsState) |
| 810 | + { |
| 811 | + sb.AppendLine(" state?.AddKnownRef(source, result);"); |
| 812 | + } |
| 813 | + sb.AppendLine(); |
| 814 | + |
| 815 | + // Optimization: if element type is safe, use Array.Copy |
| 816 | + if (isSafe) |
| 817 | + { |
| 818 | + sb.AppendLine(" // Element type is safe, use fast Array.Copy"); |
| 819 | + sb.AppendLine(" global::System.Array.Copy(source, result, source.Length);"); |
| 820 | + } |
| 821 | + else |
| 822 | + { |
| 823 | + // Generate nested loops for each dimension |
| 824 | + // for (int i0 = 0; i0 < len0; i0++) |
| 825 | + // for (int i1 = 0; i1 < len1; i1++) |
| 826 | + // ... |
| 827 | + // result[i0, i1, ...] = Clone(source[i0, i1, ...]); |
| 828 | + |
| 829 | + for (int d = 0; d < rank; d++) |
| 830 | + { |
| 831 | + var indent = new string(' ', 12 + d * 4); |
| 832 | + sb.AppendLine($"{indent}for (int i{d} = 0; i{d} < len{d}; i{d}++)"); |
| 833 | + } |
| 834 | + |
| 835 | + var innerIndent = new string(' ', 12 + rank * 4); |
| 836 | + var indexList = string.Join(", ", Enumerable.Range(0, rank).Select(d => $"i{d}")); |
| 837 | + |
| 838 | + // Determine how to clone each element |
| 839 | + string itemExpr; |
| 840 | + if (hasClonableAttr) |
| 841 | + { |
| 842 | + itemExpr = $"source[{indexList}]?.FastDeepClone()"; |
| 843 | + } |
| 844 | + else if (context.TryGetMemberModel(member.ElementTypeName!, out var nestedModel)) |
| 845 | + { |
| 846 | + var helperName = context.GetOrCreateHelperMethodName(nestedModel); |
| 847 | + var elementNeedsState = MemberCloneGenerator.MemberNeedsCircularRefTracking(context, nestedModel); |
| 848 | + var actualStateVar = needsState ? "state" : "null"; |
| 849 | + itemExpr = GetHelperMethodCall(context, helperName, $"source[{indexList}]", elementNeedsState, actualStateVar); |
| 850 | + } |
| 851 | + else if (context.TryGetImplicitTypeModel(member.ElementTypeName!, out var implicitModel)) |
| 852 | + { |
| 853 | + var helperName = context.GetOrCreateHelperMethodName(implicitModel.FullyQualifiedName); |
| 854 | + var elementNeedsState = implicitModel.CanHaveCircularReferences && context.CanHaveCircularReferences; |
| 855 | + var actualStateVar = needsState ? "state" : "null"; |
| 856 | + itemExpr = GetHelperMethodCall(context, helperName, $"source[{indexList}]", elementNeedsState, actualStateVar); |
| 857 | + } |
| 858 | + else if (context.IsFastClonerAvailable) |
| 859 | + { |
| 860 | + // Use runtime FastCloner for elements that require it |
| 861 | + itemExpr = $"({member.ElementTypeName})FastCloner.DeepClone(source[{indexList}])"; |
| 862 | + } |
| 863 | + else |
| 864 | + { |
| 865 | + // Fallback to shallow copy |
| 866 | + itemExpr = $"source[{indexList}]"; |
| 867 | + } |
| 868 | + |
| 869 | + sb.AppendLine($"{innerIndent}result[{indexList}] = {itemExpr};"); |
| 870 | + } |
| 871 | + |
| 872 | + sb.AppendLine(); |
| 873 | + sb.AppendLine(" return result;"); |
| 874 | + sb.AppendLine(" }"); |
| 875 | + sb.AppendLine(); |
| 876 | + } |
| 877 | + |
| 878 | + /// <summary> |
| 879 | + /// Creates the correct array instantiation expression for both regular and jagged arrays. |
| 880 | + /// For regular arrays like int[], creates: new int[size] |
| 881 | + /// For jagged arrays like int[][], creates: new int[size][] (not new int[][size] which is invalid) |
| 882 | + /// </summary> |
| 883 | + private static string GetArrayCreationExpression(string elementTypeName, string sizeExpression) |
| 884 | + { |
| 885 | + // Check if the element type is itself an array (jagged array scenario) |
| 886 | + // e.g., for int[][], elementTypeName is "int[]" |
| 887 | + var bracketIndex = elementTypeName.IndexOf('['); |
| 888 | + |
| 889 | + if (bracketIndex >= 0) |
| 890 | + { |
| 891 | + // Jagged array: element type contains brackets |
| 892 | + // Split into base type and trailing brackets |
| 893 | + // int[] → baseType="int", trailingBrackets="[]" |
| 894 | + // int[][] → baseType="int", trailingBrackets="[][]" |
| 895 | + var baseType = elementTypeName.Substring(0, bracketIndex); |
| 896 | + var trailingBrackets = elementTypeName.Substring(bracketIndex); |
| 897 | + |
| 898 | + // Create: new baseType[size]trailingBrackets |
| 899 | + // e.g., new int[source.Length][] for int[][] array |
| 900 | + return $"new {baseType}[{sizeExpression}]{trailingBrackets}"; |
| 901 | + } |
| 902 | + else |
| 903 | + { |
| 904 | + // Regular array: element type has no brackets |
| 905 | + // Create: new elementTypeName[size] |
| 906 | + return $"new {elementTypeName}[{sizeExpression}]"; |
| 907 | + } |
| 908 | + } |
| 909 | + |
757 | 910 | private static void WriteHelperMethodSignature(CloneGeneratorContext context, string typeName, string methodName, bool needsState, bool isValueType) |
758 | 911 | { |
759 | 912 | var typeParams = GetTypeParametersString(context.Model); |
|
0 commit comments