Skip to content

Commit 4186f7e

Browse files
authored
Fix reading fixed-size collections via proxies (#277)
1 parent 29b2211 commit 4186f7e

7 files changed

Lines changed: 219 additions & 14 deletions

File tree

src/serde/Proxies.List.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Collections.Immutable;
6+
using System.Linq;
67
using System.Runtime.CompilerServices;
78
using System.Runtime.InteropServices;
89

@@ -52,8 +53,8 @@ public abstract class DeListBase<
5253
TProvider>
5354
: IDeserialize<TList>
5455
where TSelf : IDeserialize<TList>, new()
55-
where TFixBuilder : IList<T>
56-
where TVarBuilder : ICollection<T>
56+
where TFixBuilder : class
57+
where TVarBuilder : class
5758
where TProvider : IDeserializeProvider<T>
5859
{
5960
public static IDeserialize<TList> Instance { get; } = new TSelf();
@@ -76,7 +77,7 @@ public TList Deserialize(IDeserializer deserializer)
7677
var builder = GetFixBuilder(size);
7778
for (int i = 0; i < size; i++)
7879
{
79-
builder.Add(_de.Deserialize(deCollection, info, i));
80+
FixAdd(builder, _de.Deserialize(deCollection, info, i));
8081
}
8182
return FromFix(builder);
8283
}
@@ -92,13 +93,15 @@ public TList Deserialize(IDeserializer deserializer)
9293
break;
9394
}
9495

95-
builder.Add(_de.Deserialize(deCollection, info, index));
96+
VarAdd(builder, _de.Deserialize(deCollection, info, index));
9697
}
9798
return FromVar(builder);
9899
}
99100
}
100101

101102
protected abstract TFixBuilder GetFixBuilder(int size);
103+
protected abstract void FixAdd(TFixBuilder builder, T item);
104+
protected abstract void VarAdd(TVarBuilder builder, T item);
102105
protected abstract TVarBuilder GetVarBuilder();
103106
protected abstract TList FromFix(TFixBuilder builder);
104107
protected abstract TList FromVar(TVarBuilder builder);
@@ -113,7 +116,7 @@ public abstract class DeListBase<
113116
: DeListBase<TSelf, T, TList, TBuilder, TBuilder, TProvider>,
114117
IDeserializeProvider<TList>
115118
where TSelf : IDeserialize<TList>, new()
116-
where TBuilder : IList<T>
119+
where TBuilder : class
117120
where TProvider : IDeserializeProvider<T>
118121
{
119122
protected DeListBase(ISerdeInfo serdeInfo)
@@ -124,6 +127,10 @@ protected DeListBase(ISerdeInfo serdeInfo)
124127
protected sealed override TBuilder GetFixBuilder(int size) => GetBuilder(size);
125128
protected sealed override TBuilder GetVarBuilder() => GetBuilder(null);
126129

130+
protected abstract void Add(TBuilder builder, T item);
131+
protected sealed override void FixAdd(TBuilder builder, T item) => Add(builder, item);
132+
protected sealed override void VarAdd(TBuilder builder, T item) => Add(builder, item);
133+
127134
protected abstract TList ToList(TBuilder builder);
128135
protected sealed override TList FromFix(TBuilder builder) => ToList(builder);
129136
protected sealed override TList FromVar(TBuilder builder) => ToList(builder);
@@ -145,13 +152,29 @@ public class Ser<T, TProvider>()
145152
}
146153

147154
public sealed class De<T, TProvider>()
148-
: DeListBase<De<T, TProvider>, T, T[], T[], List<T>, TProvider>(ArraySerdeTypeInfo<T>.SerdeInfo),
155+
: DeListBase<
156+
De<T, TProvider>,
157+
T,
158+
T[],
159+
De<T, TProvider>.ArrayBuilder,
160+
List<T>,
161+
TProvider
162+
>(ArraySerdeTypeInfo<T>.SerdeInfo),
149163
IDeserializeProvider<T[]>
150164
where TProvider : IDeserializeProvider<T>
151165
{
152-
protected override T[] GetFixBuilder(int size) => new T[size];
166+
public sealed class ArrayBuilder(int size)
167+
{
168+
private int _index = 0;
169+
private readonly T[] _array = new T[size];
170+
public void Add(T item) => _array[_index++] = item;
171+
public T[] ToArray() => _array;
172+
}
173+
protected override ArrayBuilder GetFixBuilder(int size) => new(size);
174+
protected override void FixAdd(ArrayBuilder builder, T item) => builder.Add(item);
175+
protected override void VarAdd(List<T> builder, T item) => builder.Add(item);
153176
protected override List<T> GetVarBuilder() => [];
154-
protected override T[] FromFix(T[] builder) => builder;
177+
protected override T[] FromFix(ArrayBuilder builder) => builder.ToArray();
155178
protected override T[] FromVar(List<T> builder) => builder.ToArray();
156179
}
157180
}
@@ -185,6 +208,11 @@ protected override List<T> GetBuilder(int? sizeOpt)
185208
return new List<T>();
186209
}
187210

211+
protected override void Add(List<T> builder, T item)
212+
{
213+
builder.Add(item);
214+
}
215+
188216
protected override List<T> ToList(List<T> builder) => builder;
189217
}
190218
}
@@ -212,6 +240,7 @@ public sealed class De<T, TProvider>()
212240
protected override ImmutableArray<T>.Builder GetBuilder(int? sizeOpt) => sizeOpt is int size
213241
? ImmutableArray.CreateBuilder<T>(size)
214242
: ImmutableArray.CreateBuilder<T>();
243+
protected override void Add(ImmutableArray<T>.Builder builder, T item) => builder.Add(item);
215244
protected override ImmutableArray<T> ToList(ImmutableArray<T>.Builder builder) => builder.ToImmutable();
216245
}
217246
}

src/serde/json/JsonSerializerExtensions.cs renamed to src/serde/ProxyExtensions.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,43 @@
22
#if NET10_0_OR_GREATER
33
using System.Collections.Generic;
44

5-
namespace Serde.Json;
5+
namespace Serde;
6+
7+
public static class ArrayTExtensions
8+
{
9+
extension<T>(T[]) where T : IDeserializeProvider<T>
10+
{
11+
public static IDeserialize<T[]> Deserialize => ArrayProxy.De<T, T>.Instance;
12+
}
13+
extension<T>(T[]) where T : ISerializeProvider<T>
14+
{
15+
public static ISerialize<T[]> Serialize => ArrayProxy.Ser<T, T>.Instance;
16+
}
17+
}
18+
19+
public static class ArrayIntExtensions
20+
{
21+
extension(int[])
22+
{
23+
public static IDeserialize<int[]> Deserialize => ArrayProxy.De<int, I32Proxy>.Instance;
24+
}
25+
extension(int[])
26+
{
27+
public static ISerialize<int[]> Serialize => ArrayProxy.Ser<int, I32Proxy>.Instance;
28+
}
29+
}
30+
31+
public static class ArrayStringExtensions
32+
{
33+
extension(string[])
34+
{
35+
public static IDeserialize<string[]> Deserialize => ArrayProxy.De<string, StringProxy>.Instance;
36+
}
37+
extension(string[])
38+
{
39+
public static ISerialize<string[]> Serialize => ArrayProxy.Ser<string, StringProxy>.Instance;
40+
}
41+
}
642

743
public static class ListTExtensions
844
{

test/Serde.Test/GenericWrapperTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
using System;
33
using System.Collections.Immutable;
44
using System.Linq;
5-
using System.Text;
6-
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client;
75
using Xunit;
86

97
namespace Serde.Test;
@@ -118,6 +116,8 @@ protected override ImmutableArray<T>.Builder GetBuilder(int? sizeOpt)
118116
: ImmutableArray.CreateBuilder<T>();
119117
}
120118

119+
protected override void Add(ImmutableArray<T>.Builder builder, T item) => builder.Add(item);
120+
121121
protected override CustomImArray<T> ToList(ImmutableArray<T>.Builder builder)
122122
{
123123
return new CustomImArray<T>(builder.ToImmutable());
@@ -154,6 +154,8 @@ protected override ImmutableArray<T>.Builder GetBuilder(int? sizeOpt)
154154
: ImmutableArray.CreateBuilder<T>();
155155
}
156156

157+
protected override void Add(ImmutableArray<T>.Builder builder, T item) => builder.Add(item);
158+
157159
protected override CustomImArray2<T> ToList(ImmutableArray<T>.Builder builder)
158160
{
159161
return new CustomImArray2<T>(builder.ToImmutable());

test/Serde.Test/RoundtripTests.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
2-
using System;
31
using Serde.Json;
42
using Xunit;
3+
using IntArr = int[];
4+
using StringArr = string[];
5+
using PointArr = Serde.Test.RoundtripTests.Point[];
56

67
namespace Serde.Test;
78

8-
public sealed class RoundtripTests
9+
public sealed partial class RoundtripTests
910
{
1011
[Fact]
1112
public void MaxSizeTypeTest()
@@ -14,9 +15,42 @@ public void MaxSizeTypeTest()
1415
AssertRoundTrip(t);
1516
}
1617

18+
[Fact]
19+
public void TestArray()
20+
{
21+
AssertRoundTrip(
22+
new[] { 1, 2, 3 },
23+
IntArr.Serialize,
24+
IntArr.Deserialize);
25+
AssertRoundTrip(
26+
new[] { "a", "b", "c" },
27+
StringArr.Serialize,
28+
StringArr.Deserialize);
29+
AssertRoundTrip(
30+
new[] { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } },
31+
PointArr.Serialize,
32+
PointArr.Deserialize);
33+
}
34+
35+
36+
[GenerateSerde]
37+
[SerdeTypeOptions(MemberFormat = MemberFormat.None)]
38+
public partial record Point
39+
{
40+
public int X { get; init; }
41+
public int Y { get; init; }
42+
}
43+
1744
private static void AssertRoundTrip<T>(T t) where T : ISerializeProvider<T>, IDeserializeProvider<T>
1845
{
1946
var result = JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(t));
2047
Assert.Equal(t, result);
2148
}
49+
50+
private static void AssertRoundTrip<T>(T expected, ISerialize<T> serializeImpl, IDeserialize<T> deserializeImpl)
51+
{
52+
var serialized = JsonSerializer.Serialize(expected, serializeImpl);
53+
var actual = JsonSerializer.Deserialize(serialized, deserializeImpl);
54+
Assert.Equal(expected, actual);
55+
}
2256
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
#nullable enable
3+
4+
using System;
5+
using Serde;
6+
7+
namespace Serde.Test;
8+
9+
partial class RoundtripTests
10+
{
11+
partial record Point
12+
{
13+
sealed partial class _SerdeObj : global::Serde.ISerde<Serde.Test.RoundtripTests.Point>
14+
{
15+
global::Serde.ISerdeInfo global::Serde.ISerdeInfoProvider.SerdeInfo => Serde.Test.RoundtripTests.Point.s_serdeInfo;
16+
17+
void global::Serde.ISerialize<Serde.Test.RoundtripTests.Point>.Serialize(Serde.Test.RoundtripTests.Point value, global::Serde.ISerializer serializer)
18+
{
19+
var _l_info = global::Serde.SerdeInfoProvider.GetInfo(this);
20+
var _l_type = serializer.WriteType(_l_info);
21+
_l_type.WriteI32(_l_info, 0, value.X);
22+
_l_type.WriteI32(_l_info, 1, value.Y);
23+
_l_type.End(_l_info);
24+
}
25+
Serde.Test.RoundtripTests.Point Serde.IDeserialize<Serde.Test.RoundtripTests.Point>.Deserialize(IDeserializer deserializer)
26+
{
27+
int _l_x = default!;
28+
int _l_y = default!;
29+
30+
byte _r_assignedValid = 0;
31+
32+
var _l_serdeInfo = global::Serde.SerdeInfoProvider.GetInfo(this);
33+
var typeDeserialize = deserializer.ReadType(_l_serdeInfo);
34+
while (true)
35+
{
36+
var (_l_index_, _) = typeDeserialize.TryReadIndexWithName(_l_serdeInfo);
37+
if (_l_index_ == Serde.ITypeDeserializer.EndOfType)
38+
{
39+
break;
40+
}
41+
42+
switch (_l_index_)
43+
{
44+
case 0:
45+
Serde.DeserializeException.ThrowIfDuplicate(_r_assignedValid, 0, _l_serdeInfo);
46+
_l_x = typeDeserialize.ReadI32(_l_serdeInfo, _l_index_);
47+
_r_assignedValid |= ((byte)1) << 0;
48+
break;
49+
case 1:
50+
Serde.DeserializeException.ThrowIfDuplicate(_r_assignedValid, 1, _l_serdeInfo);
51+
_l_y = typeDeserialize.ReadI32(_l_serdeInfo, _l_index_);
52+
_r_assignedValid |= ((byte)1) << 1;
53+
break;
54+
case Serde.ITypeDeserializer.IndexNotFound:
55+
typeDeserialize.SkipValue(_l_serdeInfo, _l_index_);
56+
break;
57+
default:
58+
throw new InvalidOperationException("Unexpected index: " + _l_index_);
59+
}
60+
}
61+
if ((_r_assignedValid & 0b11) != 0b11)
62+
{
63+
throw Serde.DeserializeException.UnassignedMember();
64+
}
65+
var newType = new Serde.Test.RoundtripTests.Point() {
66+
X = _l_x,
67+
Y = _l_y,
68+
};
69+
70+
return newType;
71+
}
72+
}
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
#nullable enable
3+
4+
namespace Serde.Test;
5+
6+
partial class RoundtripTests
7+
{
8+
partial record Point
9+
{
10+
private static global::Serde.ISerdeInfo s_serdeInfo = Serde.SerdeInfo.MakeCustom(
11+
"Point",
12+
typeof(Serde.Test.RoundtripTests.Point).GetCustomAttributesData(),
13+
new (string, global::Serde.ISerdeInfo, System.Reflection.MemberInfo?)[] {
14+
("X", global::Serde.SerdeInfoProvider.GetSerializeInfo<int, global::Serde.I32Proxy>(), typeof(Serde.Test.RoundtripTests.Point).GetProperty("X")),
15+
("Y", global::Serde.SerdeInfoProvider.GetSerializeInfo<int, global::Serde.I32Proxy>(), typeof(Serde.Test.RoundtripTests.Point).GetProperty("Y"))
16+
}
17+
);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
namespace Serde.Test;
3+
4+
partial class RoundtripTests
5+
{
6+
partial record Point : Serde.ISerdeProvider<Serde.Test.RoundtripTests.Point, Serde.Test.RoundtripTests.Point._SerdeObj, Serde.Test.RoundtripTests.Point>
7+
{
8+
static Serde.Test.RoundtripTests.Point._SerdeObj global::Serde.ISerdeProvider<Serde.Test.RoundtripTests.Point, Serde.Test.RoundtripTests.Point._SerdeObj, Serde.Test.RoundtripTests.Point>.Instance { get; }
9+
= new Serde.Test.RoundtripTests.Point._SerdeObj();
10+
}
11+
}

0 commit comments

Comments
 (0)