Copilot/add readonlymemory support - #188
Conversation
Agent-Logs-Url: https://github.com/AlgorithmsAreCool/LightProto/sessions/baab16b3-347e-4972-ba9b-bb4ef9467a61 Co-authored-by: AlgorithmsAreCool <10837869+AlgorithmsAreCool@users.noreply.github.com>
Agent-Logs-Url: https://github.com/AlgorithmsAreCool/LightProto/sessions/baab16b3-347e-4972-ba9b-bb4ef9467a61 Co-authored-by: AlgorithmsAreCool <10837869+AlgorithmsAreCool@users.noreply.github.com>
Agent-Logs-Url: https://github.com/AlgorithmsAreCool/LightProto/sessions/81c76820-749c-4e93-a4eb-b0d9bb99a31d Co-authored-by: AlgorithmsAreCool <10837869+AlgorithmsAreCool@users.noreply.github.com>
…lySequence<byte> as repeated bytes Agent-Logs-Url: https://github.com/AlgorithmsAreCool/LightProto/sessions/b0e20744-35f3-4e1d-89f5-e161346fdd8c Co-authored-by: AlgorithmsAreCool <10837869+AlgorithmsAreCool@users.noreply.github.com>
Agent-Logs-Url: https://github.com/AlgorithmsAreCool/LightProto/sessions/367968f0-079c-44f8-b8ae-23d7cdad6437 Co-authored-by: AlgorithmsAreCool <10837869+AlgorithmsAreCool@users.noreply.github.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 101 |
| Duplication | 18 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR adds first-class support for Memory<T>, ReadOnlyMemory<T>, and ReadOnlySequence<T> collection members in LightProto parsing/writing and generator logic, along with new tests and public API updates.
Changes:
- Introduces new
*ProtoReader/*ProtoWriterimplementations forMemory<T>,ReadOnlyMemory<T>, andReadOnlySequence<T>. - Updates the source generator to treat these types as collections and ensure correct tag behavior for
byte-backed memory types. - Adds unit tests validating serialization/deserialization behavior and “single tag” encoding for byte memory members.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/LightProto.Tests/RuntimeParserTests.cs | Adds runtime (reflection-free) tests ensuring byte memory members encode with a single length-delimited tag. |
| tests/LightProto.Tests/Parsers/parser.proto | Formatting-only change (closing brace line numbering/whitespace). |
| tests/LightProto.Tests/Parsers/ReadOnlySequenceTests.cs | Adds coverage for ReadOnlySequence<int> parsing/writing behaviors (protobuf-net disabled). |
| tests/LightProto.Tests/Parsers/ReadOnlySequenceBytesTests.cs | Adds coverage for ReadOnlySequence<byte>, including multi-segment sequences. |
| tests/LightProto.Tests/Parsers/ReadOnlyMemoryTests.cs | Adds coverage for ReadOnlyMemory<int> parsing/writing behaviors. |
| tests/LightProto.Tests/Parsers/ReadOnlyMemoryBytesTests.cs | Adds coverage for ReadOnlyMemory<byte> parsing/writing behaviors. |
| tests/LightProto.Tests/Parsers/MemoryTests.cs | Adds coverage for Memory<int> parsing/writing behaviors. |
| tests/LightProto.Tests/Parsers/MemoryBytesTests.cs | Adds coverage for Memory<byte> parsing/writing behaviors. |
| src/LightProto/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt | Ships public API surface for new memory/sequence readers and writers (netstandard2.0). |
| src/LightProto/PublicAPI/net/PublicAPI.Shipped.txt | Ships public API surface for new memory/sequence readers and writers (.NET). |
| src/LightProto/Parser/ReadOnlySequence.cs | Adds ReadOnlySequenceProtoReader/Writer implementations with byte special-casing. |
| src/LightProto/Parser/ReadOnlyMemory.cs | Adds ReadOnlyMemoryProtoReader/Writer implementations with byte special-casing. |
| src/LightProto/Parser/Memory.cs | Adds MemoryProtoReader/Writer implementations with byte special-casing. |
| src/LightProto.Generator/ProtoMember.cs | Updates “not empty” check generation to handle value-type collections (e.g., Memory<T>). |
| src/LightProto.Generator/Helper.cs | Treats memory/sequence types as collections; supports Length: long; adjusts tag choice for byte memory types. |
| if (IsByte) | ||
| { | ||
| var bytes = (Memory<byte>)(object)value; | ||
| output.WriteTag(Tag); | ||
| output.WriteLongLength(bytes.Length); | ||
| WritingPrimitives.WriteRawBytes(ref output.buffer, ref output.state, bytes.Span); | ||
| return; | ||
| } |
| if (IsByte) | ||
| { | ||
| var bytes = (ReadOnlyMemory<byte>)(object)value; | ||
| output.WriteTag(Tag); | ||
| output.WriteLongLength(bytes.Length); | ||
| WritingPrimitives.WriteRawBytes(ref output.buffer, ref output.state, bytes.Span); | ||
| return; | ||
| } |
| foreach (var segment in value) | ||
| { | ||
| if (segment.IsEmpty) | ||
| continue; | ||
| WritingPrimitives.WriteRawBytes(ref output.buffer, ref output.state, ((ReadOnlyMemory<byte>)(object)segment).Span); | ||
| } |
| foreach (var item in value) | ||
| { | ||
| foreach (var current in item.Span) | ||
| { | ||
| if (current is null) | ||
| throw new Exception("Sequence contained null element"); | ||
| output.WriteTag(Tag); | ||
| ItemWriter.WriteMessageTo(ref output, current); | ||
| } | ||
| } |
| public ReadOnlySequenceProtoReader(IProtoReader<TItem> itemReader, uint tag, int itemFixedSize) | ||
| : this(itemReader, itemFixedSize) { } |
dameng324
left a comment
There was a problem hiding this comment.
Thanks for your pull request. Here are some review suggestions.
| IProtoWriter<T> ItemWriter { get; } | ||
| uint Tag { get; set; } | ||
| int ItemFixedSize { get; } | ||
| static bool IsByte => typeof(T) == typeof(byte); |
There was a problem hiding this comment.
It is better that check T is byte or not at compile time rather than runtime, like byte array did before.
There was a problem hiding this comment.
Still check it at runtime. you can see how ArrayProtoWriter and ByteArrayProtoWriter is implemented.
Will you try to understand the code and fix it without using AI? I just want to make sure you can understand what I am talking about.
| @@ -0,0 +1,151 @@ | |||
| namespace LightProto.Parser | |||
| { | |||
| public sealed class MemoryProtoWriter<T> : IProtoWriter, IProtoWriter<Memory<T>>, ICollectionWriter | |||
There was a problem hiding this comment.
The whole MemoryProtoWriter has too much duplicate codes that ArrayProtoWriter already has. We should figure out how to avoid this.
There was a problem hiding this comment.
I'll consolidate the implementations
| @@ -0,0 +1,151 @@ | |||
| namespace LightProto.Parser | |||
| { | |||
| public sealed class ReadOnlyMemoryProtoWriter<T> : IProtoWriter, IProtoWriter<ReadOnlyMemory<T>>, ICollectionWriter | |||
There was a problem hiding this comment.
Same issue as MemoryProtoWriter.
|
|
||
| namespace LightProto.Parser | ||
| { | ||
| public sealed class ReadOnlySequenceProtoWriter<T> : IProtoWriter, IProtoWriter<ReadOnlySequence<T>>, ICollectionWriter |
There was a problem hiding this comment.
Same issue as MemoryProtoWriter.
| public partial record Message | ||
| { | ||
| [ProtoMember(1)] | ||
| [ProtoBuf.ProtoMember(1)] |
There was a problem hiding this comment.
It is better that adding a packed=true repeated field test case.
|
@dameng324 As a question however, are the writers and readers implementing the correct interfaces? |
Yes, The implementation seems right. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/LightProto/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt:263
- These are new public APIs. With PublicApiAnalyzers, new surface area is typically added to
PublicAPI.Unshipped.txtuntil an official release, then moved toPublicAPI.Shipped.txt. Putting them directly inPublicAPI.Shipped.txtmakes the API appear already shipped and can complicate versioning/reviews.
LightProto.Parser.MemoryProtoWriter<T>.IsMessage.get -> bool
LightProto.Parser.MemoryProtoWriter<T>.MemoryProtoWriter(LightProto.IProtoWriter<T>! itemWriter, uint tag, int itemFixedSize) -> void
LightProto.Parser.MemoryProtoWriter<T>.WireType.get -> LightProto.WireFormat.WireType
LightProto.Parser.MemoryProtoWriter<T>.WriteTo(ref LightProto.WriterContext output, System.Memory<T> value) -> void
LightProto.Parser.NullableProtoReader<T>
LightProto.Parser.NullableProtoReader<T>.IsMessage.get -> bool
LightProto.Parser.NullableProtoReader<T>.NullableProtoReader(LightProto.IProtoReader<T>! valueReader) -> void
LightProto.Parser.NullableProtoReader<T>.ParseFrom(ref LightProto.ReaderContext input) -> T?
LightProto.Parser.NullableProtoReader<T>.ValueReader.get -> LightProto.IProtoReader<T>!
LightProto.Parser.NullableProtoReader<T>.WireType.get -> LightProto.WireFormat.WireType
LightProto.Parser.NullableProtoWriter<T>
LightProto.Parser.NullableProtoWriter<T>.CalculateSize(T? value) -> int
LightProto.Parser.NullableProtoWriter<T>.IsMessage.get -> bool
LightProto.Parser.NullableProtoWriter<T>.NullableProtoWriter(LightProto.IProtoWriter<T>! valueWriter) -> void
LightProto.Parser.NullableProtoWriter<T>.ValueWriter.get -> LightProto.IProtoWriter<T>!
LightProto.Parser.NullableProtoWriter<T>.WireType.get -> LightProto.WireFormat.WireType
| LightProto.Parser.LinkedListProtoReader<T>.LinkedListProtoReader(LightProto.IProtoReader<T>! itemReader, uint tag, int itemFixedSize) -> void | ||
| LightProto.Parser.LinkedListProtoWriter<T> | ||
| LightProto.Parser.LinkedListProtoWriter<T>.LinkedListProtoWriter(LightProto.IProtoWriter<T>! itemWriter, uint tag, int itemFixedSize) -> void | ||
| LightProto.Parser.ListProtoReader<T> | ||
| LightProto.Parser.ListProtoReader<T>.ListProtoReader(LightProto.IProtoReader<T>! itemReader, int itemFixedSize) -> void | ||
| LightProto.Parser.ListProtoReader<T>.ListProtoReader(LightProto.IProtoReader<T>! itemReader, uint tag, int itemFixedSize) -> void | ||
| LightProto.Parser.ListProtoWriter<T> | ||
| LightProto.Parser.ListProtoWriter<T>.ListProtoWriter(LightProto.IProtoWriter<T>! itemWriter, uint tag, int itemFixedSize) -> void | ||
| LightProto.Parser.Matrix3x2ProtoParser | ||
| LightProto.Parser.Matrix3x2ProtoParser.Matrix3x2ProtoParser() -> void | ||
| LightProto.Parser.Matrix4x4ProtoParser | ||
| LightProto.Parser.Matrix4x4ProtoParser.Matrix4x4ProtoParser() -> void | ||
| LightProto.Parser.MemoryProtoReader<TItem> | ||
| LightProto.Parser.MemoryProtoReader<TItem>.Empty.get -> System.Memory<TItem> | ||
| LightProto.Parser.MemoryProtoReader<TItem>.IsMessage.get -> bool | ||
| LightProto.Parser.MemoryProtoReader<TItem>.ItemReader.get -> LightProto.IProtoReader<TItem>! |
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (68.93%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #188 +/- ##
==========================================
- Coverage 95.36% 93.53% -1.83%
==========================================
Files 121 127 +6
Lines 3061 3328 +267
Branches 423 470 +47
==========================================
+ Hits 2919 3113 +194
- Misses 76 131 +55
- Partials 66 84 +18 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dameng324
left a comment
There was a problem hiding this comment.
I found this issue is too much complex for a new contributor.
If you just want this feature, I can take over this PR.
If you want to become a contributor of this project, you may need more understand of the codebase and more coding by hand instead of AI. I'm glad to help with you.
| { | ||
| if (elementType.SpecialType == SpecialType.System_Byte && IsArrayType(type)) | ||
| return false; | ||
| if (IsReadOnlyMemoryType(type) || IsMemoryType(type) || IsReadOnlySequenceType(type)) |
There was a problem hiding this comment.
If T is byte, these types are not collection types any more.
| IProtoWriter<T> ItemWriter { get; } | ||
| uint Tag { get; set; } | ||
| int ItemFixedSize { get; } | ||
| static bool IsByte => typeof(T) == typeof(byte); |
There was a problem hiding this comment.
Still check it at runtime. you can see how ArrayProtoWriter and ByteArrayProtoWriter is implemented.
Will you try to understand the code and fix it without using AI? I just want to make sure you can understand what I am talking about.
Yeah, I need to study this problem more to understand the mechanics. I'll try to find time at somepoint to understand it better. That being said, this feature isn't critical, i can just use arrays or my own surrogates |
This pr adds built in support for Memory/ReadOnlyMemory/ReadOnlySequence
Copilot using Codex 5.3 was the primary LLM used with human review and critique.
I don't fully understand all of the reader/writer interfaces in this project, but the code seems to be functioning as expected