-
Notifications
You must be signed in to change notification settings - Fork 463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Declarative RLP Encoding/Decoding #7975
Draft
emlautarom1
wants to merge
115
commits into
master
Choose a base branch
from
feature/declarative-rlp
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 107 commits
Commits
Show all changes
115 commits
Select commit
Hold shift + click to select a range
d3a3232
Initial Rlp + Writer
emlautarom1 9a90ccb
Rename `Sequence` -> `List`
emlautarom1 30dda91
Initial `RlpReader`
emlautarom1 d3d83f9
Initial `ReadList`
emlautarom1 65f0891
Multiple `ReadList`
emlautarom1 b4656cd
Restructure into separate files
emlautarom1 a63b64f
Use `Rlp.Read` API
emlautarom1 6735d29
Add `HeterogeneousList` test
emlautarom1 74cf2fd
Add overload for `Action`
emlautarom1 2ef4c4d
Test `UnknownLengthList`
emlautarom1 b3dddcb
Rename converter
emlautarom1 8b19e01
Use custom Exception
emlautarom1 2c67165
Add `Action` overload
emlautarom1 52e1f7b
Test for invalid readings
emlautarom1 7262407
Support long lists (+55 bytes)
emlautarom1 e4c2e63
Implement interface on `ReadOnlySpanConverter`
emlautarom1 35b9203
Make `Rlp[Reader|Writer]` symmetric
emlautarom1 e0ec1ce
Support ref struct
emlautarom1 d3a2e5b
Reorder tests
emlautarom1 67a69df
Annotate as scoped
emlautarom1 dc3798b
Initial `Choice` implementation
emlautarom1 ffa62f4
Cleanup test
emlautarom1 e626fc9
Restructure internals
emlautarom1 6f3efbc
Test for deep `Choice` (backtracking)
emlautarom1 2d2eeda
Move `IRlpConverter`
emlautarom1 67abc2b
Demo user-defined record support
emlautarom1 8f4b02a
Remove versions
emlautarom1 9db2ebb
Use `ref struct` over `Interface`
emlautarom1 2223845
Consistent error
emlautarom1 51fd038
Split tests from library
emlautarom1 5fad6ef
Initial source generator for `RlpSerializable`
emlautarom1 c22f1df
Implement `Write`
emlautarom1 70c78e2
Test for Source Generated instances
emlautarom1 f11ff9a
Move record
emlautarom1 38cbb3d
No need to copy `using` directives
emlautarom1 f284c28
Remove `Derived` namespace
emlautarom1 6e91e0d
Update docs
emlautarom1 ec2fdbc
Rename `List` to `Sequence`
emlautarom1 f158463
Add support for `List` collection
emlautarom1 6677222
Test for equivalence in collections with explicit
emlautarom1 e32a33c
Add explicit converters for IntXX
emlautarom1 f665779
Reduce duplication
emlautarom1 2e75a7d
Add `Dictionary` collection
emlautarom1 aa6b4bd
Extend generator to support Generics (WIP)
emlautarom1 fcde607
Support multi-param generics
emlautarom1 416a45b
Add Generic writer Action
emlautarom1 837c2e4
Rewrite Generics
emlautarom1 f484b8b
Add recursive record test
emlautarom1 599da39
Enable `nullable`
emlautarom1 3eff5ea
Fix `ReadBytes`
emlautarom1 b7889ed
Test for all base Integer types
emlautarom1 d41ecd3
Initial README
emlautarom1 03325a0
Make `Read` calls static
emlautarom1 914505a
Add `context` overload
emlautarom1 7dbc2dc
Remove allocations
emlautarom1 15eb6b6
Use `static` on `Write`
emlautarom1 c42eb50
Use `static` on `Read`
emlautarom1 ed0d88b
Return `byte[]` instead of `ReadOnlySpan<byte>` in overload
emlautarom1 88a8cc4
Use `static` on `Write`
emlautarom1 a596ab0
Fix typo
emlautarom1 df3ffc9
Add overloads for `TContext`
emlautarom1 42b4401
Use `context` overload when possible
emlautarom1 917a7b8
Remove duplicated overloads
emlautarom1 88e7bac
Remove "context" TODO
emlautarom1 1bc2d38
Remove allocating overload
emlautarom1 fb254e9
More `static` usage
emlautarom1 21a18b9
Use "context" overload in Generator
emlautarom1 4740466
Annotate as `scoped`
emlautarom1 5705da1
Optimize `StringRlpConverter`
emlautarom1 cc58776
Rename `FastRlp` to `FluentRlp`
emlautarom1 2456c40
Remove allocations in Dictionary
emlautarom1 868d272
Remove unused method
emlautarom1 69c8a92
Introduce "context" overload for `Reader`
emlautarom1 f0191d1
Remove allocations in `Dictionary`
emlautarom1 1011df9
Remove allocations in `List`
emlautarom1 63c3ed9
Use field Encoding
emlautarom1 0c2807b
Add `ArrayRlpConverter`
emlautarom1 410dfc7
Merge branch 'master' into feature/declarative-rlp
emlautarom1 efc25bd
Remove allocations
emlautarom1 aae0100
Add `TupleRlpConverter`
emlautarom1 c7a32d1
Support `Tuple` in source generators
emlautarom1 3dc5b65
Add support for `Representation`
emlautarom1 fd9fc30
Formatting
emlautarom1 9f5d236
Merge branch 'master' into feature/declarative-rlp
emlautarom1 8341a0e
Update docs
emlautarom1 3e1330d
Throw on trailing bytes
emlautarom1 4f68cb3
Reduce usage of `StringBuilder`
emlautarom1 ac98b21
Remove unused code
emlautarom1 79dc13f
Use format string
emlautarom1 c9ba2eb
Merge `Array` with generics
emlautarom1 e43f13c
Merge branch 'master' into feature/declarative-rlp
emlautarom1 db5408a
Formatting
emlautarom1 38eb0f0
Merge remote-tracking branch 'origin/feature/declarative-rlp' into fe…
emlautarom1 31726b6
Fix `0`
emlautarom1 149145b
Remove signed integer requirement
emlautarom1 dd57f80
Add `BytesRead`
emlautarom1 40cfd25
Include user `using` statements
emlautarom1 f419316
Fix nested Generics support
emlautarom1 d3592d8
Initial benchmark
emlautarom1 a52d923
Prefer `sizeof(T)` over `Marshal.SizeOf<T>`
emlautarom1 606a260
Formatting
emlautarom1 abf143d
Merge branch 'master' into feature/declarative-rlp
emlautarom1 2a9bee7
File encoding
emlautarom1 d0dfb12
Merge remote-tracking branch 'origin/feature/declarative-rlp' into fe…
emlautarom1 bcaa83f
Remove unused import
emlautarom1 783acc6
Avoid boxing due to interface default bodies
emlautarom1 6dc21f4
Add `MemoryDiagnoser`
emlautarom1 1c8f472
Use `IBufferWriter` instead of `byte[]`
emlautarom1 b2e5951
Use `IBufferWriter` instead of `byte[]`
emlautarom1 9622a7b
Merge remote-tracking branch 'origin/feature/declarative-rlp' into fe…
emlautarom1 11bf0c7
Merge remote-tracking branch 'origin/feature/declarative-rlp' into fe…
emlautarom1 92bc902
Merge remote-tracking branch 'origin/feature/declarative-rlp' into fe…
emlautarom1 7e03ac3
Add support for `Optional`
emlautarom1 04ac6b2
Use `sizeof` instead of `Marshal.SizeOf<T>` in `RlpReader`
emlautarom1 3250b15
More tests for `Optional`
emlautarom1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
...ind.Serialization.FluentRlp.Generator/Nethermind.Serialization.FluentRlp.Generator.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> | ||
<RootNamespace>Nethermind.Serialization.FluentRlp.Generator</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" /> | ||
</ItemGroup> | ||
|
||
</Project> |
339 changes: 339 additions & 0 deletions
339
src/Nethermind/Nethermind.Serialization.FluentRlp.Generator/RlpSourceGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Text; | ||
using System.Text; | ||
|
||
namespace Nethermind.Serialization.FluentRlp.Generator; | ||
|
||
public enum RlpRepresentation : byte | ||
{ | ||
/// <summary> | ||
/// The RLP encoding will be a sequence of RLP objects for each property. | ||
/// </summary> | ||
Record = 0, | ||
|
||
/// <summary> | ||
/// The RLP encoding will be equivalent to the only underlying property. | ||
/// </summary> | ||
Newtype = 1, | ||
} | ||
|
||
[AttributeUsage(AttributeTargets.Class)] | ||
public sealed class RlpSerializable(RlpRepresentation representation = RlpRepresentation.Record) : Attribute | ||
{ | ||
public RlpRepresentation Representation { get; } = representation; | ||
} | ||
|
||
/// <summary> | ||
/// A source generator that finds all records with [RlpSerializable] attribute and | ||
/// generates an abstract `IRlpConverter` class with `Read` and `Write` methods. | ||
/// </summary> | ||
[Generator] | ||
public sealed class RlpSourceGenerator : IIncrementalGenerator | ||
{ | ||
private const string Version = "0.1"; | ||
private const string GeneratedCodeAttribute = $"""[GeneratedCode("{nameof(RlpSourceGenerator)}", "{Version}")]"""; | ||
|
||
public void Initialize(IncrementalGeneratorInitializationContext context) | ||
{ | ||
var provider = context.SyntaxProvider.CreateSyntaxProvider( | ||
predicate: static (s, _) => s is RecordDeclarationSyntax, | ||
transform: static (ctx, _) => (RecordDeclarationSyntax)ctx.Node | ||
); | ||
|
||
var compilation = context.CompilationProvider.Combine(provider.Collect()); | ||
|
||
context.RegisterSourceOutput(compilation, Execute); | ||
} | ||
|
||
private void Execute( | ||
SourceProductionContext context, | ||
(Compilation Compilation, ImmutableArray<RecordDeclarationSyntax> RecordsDeclarationSyntaxes) p) | ||
{ | ||
// For each record with the attribute, generate the RlpConverter class | ||
foreach (var recordDecl in p.RecordsDeclarationSyntaxes) | ||
{ | ||
// Check if the record has the [RlpSerializable] attribute | ||
SemanticModel semanticModel = p.Compilation.GetSemanticModel(recordDecl.SyntaxTree); | ||
ISymbol? symbol = semanticModel.GetDeclaredSymbol(recordDecl); | ||
if (symbol is null) continue; | ||
|
||
AttributeData? rlpSerializableAttribute = symbol | ||
.GetAttributes() | ||
.FirstOrDefault(a => | ||
a.AttributeClass?.Name == nameof(RlpSerializable) || | ||
a.AttributeClass?.ToDisplayString() == nameof(RlpSerializable)); | ||
|
||
// If not, skip the record | ||
if (rlpSerializableAttribute is null) continue; | ||
|
||
// Extract the fully qualified record name with its namespace | ||
var recordName = symbol.Name; | ||
var fullTypeName = symbol.ToDisplayString(); | ||
// TODO: Deal with missing and nested namespaces | ||
var @namespace = symbol.ContainingNamespace?.ToDisplayString(); | ||
|
||
// Extract all `using` directives | ||
var usingDirectives = semanticModel.SyntaxTree.GetCompilationUnitRoot() | ||
.Usings | ||
.Select(u => u.ToString()) | ||
.ToList(); | ||
|
||
// Get the `RlpRepresentation` mode | ||
var representation = (RlpRepresentation)(rlpSerializableAttribute.ConstructorArguments[0].Value ?? 0); | ||
|
||
// Gather recursively all members that are fields or primary constructor parameters | ||
// so we can read them in the same order they are declared. | ||
var parameters = GetRecordParameters(recordDecl); | ||
|
||
// Ensure `Newtype` is only used in single-property records | ||
if (representation == RlpRepresentation.Newtype && parameters.Count != 1) | ||
{ | ||
var descriptor = new DiagnosticDescriptor( | ||
"RLP0001", | ||
$"Invalid {nameof(RlpRepresentation)}", | ||
$"'{nameof(RlpRepresentation.Newtype)}' representation is only allowed for records with a single property", | ||
"", DiagnosticSeverity.Error, true); | ||
context.ReportDiagnostic(Diagnostic.Create(descriptor: descriptor, recordDecl.GetLocation())); | ||
|
||
return; | ||
} | ||
|
||
// Build the converter class source | ||
var generatedCode = GenerateConverterClass(@namespace, usingDirectives, fullTypeName, recordName, parameters, representation); | ||
|
||
// Add to the compilation | ||
context.AddSource($"{recordName}RlpConverter.g.cs", SourceText.From(generatedCode, Encoding.UTF8)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gathers the record’s primary constructor parameters and public fields/properties | ||
/// in the order they appear in the record declaration. | ||
/// </summary> | ||
private static List<(string Name, TypeSyntax TypeName)> GetRecordParameters(RecordDeclarationSyntax recordDecl) | ||
{ | ||
List<(string, TypeSyntax)> parameters = []; | ||
|
||
// Primary constructor parameters | ||
if (recordDecl.ParameterList is not null) | ||
{ | ||
foreach (var param in recordDecl.ParameterList.Parameters) | ||
{ | ||
var paramName = param.Identifier.Text; | ||
var paramType = param.Type!; | ||
|
||
parameters.Add((paramName, paramType)); | ||
} | ||
} | ||
|
||
return parameters; | ||
} | ||
|
||
private static string GenerateConverterClass( | ||
string? @namespace, | ||
List<string> usingDirectives, | ||
string fullTypeName, | ||
string recordName, | ||
List<(string Name, TypeSyntax TypeName)> parameters, | ||
RlpRepresentation representation) | ||
{ | ||
List<string> defaultUsingDirectives = | ||
[ | ||
"using System;", | ||
"using System.CodeDom.Compiler;", | ||
"using Nethermind.Serialization.FluentRlp;", | ||
"using Nethermind.Serialization.FluentRlp.Instances;" | ||
]; | ||
IEnumerable<string> directives = defaultUsingDirectives.Concat(usingDirectives).Distinct(); | ||
var usingStatements = new StringBuilder(); | ||
foreach (var usingDirective in directives) | ||
{ | ||
usingStatements.AppendLine(usingDirective); | ||
} | ||
|
||
var writeCalls = new StringBuilder(); | ||
foreach (var (name, typeName) in parameters) | ||
{ | ||
var writeCall = MapTypeToWriteCall(name, typeName); | ||
writeCalls.AppendLine($"w.{writeCall};"); | ||
} | ||
|
||
var readCalls = new StringBuilder(); | ||
foreach (var (name, typeName) in parameters) | ||
{ | ||
var readCall = MapTypeToReadCall(typeName); | ||
readCalls.AppendLine($"var {name} = r.{readCall};"); | ||
} | ||
|
||
var constructorCall = new StringBuilder($"{fullTypeName}("); | ||
for (int i = 0; i < parameters.Count; i++) | ||
{ | ||
constructorCall.Append(parameters[i].Name); | ||
if (i < parameters.Count - 1) constructorCall.Append(", "); | ||
} | ||
constructorCall.Append(");"); | ||
|
||
return | ||
$$""" | ||
// <auto-generated /> | ||
#nullable enable | ||
{{usingStatements}} | ||
{{(@namespace is null ? "" : $"namespace {@namespace};")}} | ||
|
||
{{GeneratedCodeAttribute}} | ||
public abstract class {{recordName}}RlpConverter : IRlpConverter<{{fullTypeName}}> | ||
{ | ||
public static void Write(ref RlpWriter w, {{fullTypeName}} value) | ||
{ | ||
{{(representation == RlpRepresentation.Record | ||
? $$""" | ||
w.WriteSequence(value, static (ref RlpWriter w, {{fullTypeName}} value) => | ||
{ | ||
{{writeCalls}} | ||
}); | ||
""" | ||
: writeCalls)}} | ||
} | ||
|
||
public static {{fullTypeName}} Read(ref RlpReader r) | ||
{ | ||
{{(representation == RlpRepresentation.Record | ||
? $$""" | ||
return r.ReadSequence(static (scoped ref RlpReader r) => | ||
{ | ||
{{readCalls}} | ||
|
||
return new {{constructorCall}} | ||
}); | ||
""" | ||
: $""" | ||
{readCalls} | ||
|
||
return new {constructorCall} | ||
""")}} | ||
} | ||
} | ||
|
||
{{GeneratedCodeAttribute}} | ||
public static class {{recordName}}Ext | ||
{ | ||
public static {{fullTypeName}} Read{{recordName}}(this ref RlpReader reader) => {{recordName}}RlpConverter.Read(ref reader); | ||
public static void Write(this ref RlpWriter writer, {{fullTypeName}} value) => {{recordName}}RlpConverter.Write(ref writer, value); | ||
} | ||
"""; | ||
} | ||
|
||
/// <summary> | ||
/// Map the type name to the appropriate Read method on the `RlpReader` | ||
/// Extend this mapping for more types as needed. | ||
/// </summary> | ||
private static string MapTypeToReadCall(TypeSyntax syntax) | ||
{ | ||
// Hard-coded cases | ||
switch (syntax.ToString()) | ||
{ | ||
case "byte[]" or "Byte[]" or "System.Byte[]": | ||
return "ReadBytes().ToArray()"; | ||
case "Span<byte>" or "System.Span<byte>" or "ReadOnlySpan<byte>" or "System.ReadOnlySpan<byte>": | ||
return "ReadBytes()"; | ||
} | ||
|
||
// Generics | ||
if (syntax is GenericNameSyntax or TupleTypeSyntax or ArrayTypeSyntax) | ||
{ | ||
var typeConstructor = syntax switch | ||
{ | ||
GenericNameSyntax generic => generic.Identifier.ToString(), | ||
TupleTypeSyntax _ => "Tuple", | ||
ArrayTypeSyntax _ => "Array", | ||
_ => throw new ArgumentOutOfRangeException(nameof(syntax)) | ||
}; | ||
|
||
var typeParameters = syntax switch | ||
{ | ||
GenericNameSyntax generic => generic.TypeArgumentList.Arguments, | ||
TupleTypeSyntax tuple => tuple.Elements.Select(e => e.Type), | ||
ArrayTypeSyntax array => [array.ElementType], | ||
_ => throw new ArgumentOutOfRangeException(nameof(syntax)) | ||
}; | ||
|
||
var sb = new StringBuilder(); | ||
sb.AppendLine($"Read{typeConstructor.Capitalize()}("); | ||
foreach (var typeParameter in typeParameters) | ||
{ | ||
sb.AppendLine($$"""static (scoped ref RlpReader r) => { return r.{{MapTypeToReadCall(typeParameter)}}; },"""); | ||
} | ||
sb.Length -= 2; // Remove the trailing `,\n` | ||
sb.Append(")"); | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
// Default | ||
return $"Read{MapTypeAlias(syntax.ToString())}()"; | ||
} | ||
|
||
/// <summary> | ||
/// Map the type name to the appropriate Write method on the `RlpWriter` | ||
/// Extend this mapping for more types as needed. | ||
/// </summary> | ||
private static string MapTypeToWriteCall(string? propertyName, TypeSyntax syntax) | ||
{ | ||
// Hard-coded cases | ||
switch (syntax.ToString()) | ||
{ | ||
case "byte[]" or "Byte[]" or "System.Byte[]" or "Span<byte>" or "System.Span<byte>" or "ReadOnlySpan<byte>" or "System.ReadOnlySpan<byte>": | ||
return propertyName is null ? "Write(value)" : $"Write(value.{propertyName})"; | ||
} | ||
|
||
// Generics | ||
if (syntax is GenericNameSyntax or TupleTypeSyntax or ArrayTypeSyntax) | ||
{ | ||
var typeParameters = syntax switch | ||
{ | ||
GenericNameSyntax generic => generic.TypeArgumentList.Arguments, | ||
TupleTypeSyntax tuple => tuple.Elements.Select(e => e.Type), | ||
ArrayTypeSyntax array => [array.ElementType], | ||
_ => throw new ArgumentOutOfRangeException(nameof(syntax)) | ||
}; | ||
|
||
var sb = new StringBuilder(); | ||
sb.AppendLine(propertyName is null ? "Write(value," : $"Write(value.{propertyName},"); | ||
foreach (var typeParameter in typeParameters) | ||
{ | ||
sb.AppendLine($$"""static (ref RlpWriter w, {{typeParameter}} value) => { w.{{MapTypeToWriteCall(null, typeParameter)}}; },"""); | ||
} | ||
sb.Length -= 2; // Remove the trailing `,\n` | ||
sb.Append(")"); | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
// Default | ||
return propertyName is not null ? $"Write(value.{propertyName})" : "Write(value)"; | ||
} | ||
|
||
private static string MapTypeAlias(string alias) => | ||
alias switch | ||
{ | ||
"string" => "String", | ||
"short" => "Int16", | ||
"int" => "Int32", | ||
"long" => "Int64", | ||
_ => alias | ||
}; | ||
} | ||
|
||
public static class StringExt | ||
{ | ||
public static string Capitalize(this string str) => str[0].ToString().ToUpper() + str[1..]; | ||
} |
29 changes: 29 additions & 0 deletions
29
...nd/Nethermind.Serialization.FluentRlp.Test/Nethermind.Serialization.FluentRlp.Test.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<LangVersion>preview</LangVersion> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="coverlet.collector"/> | ||
<PackageReference Include="FluentAssertions" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk"/> | ||
<PackageReference Include="NUnit"/> | ||
<PackageReference Include="NUnit.Analyzers"/> | ||
<PackageReference Include="NUnit3TestAdapter"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Using Include="NUnit.Framework"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Nethermind.Serialization.FluentRlp\Nethermind.Serialization.FluentRlp.csproj" /> | ||
<ProjectReference Include="..\Nethermind.Serialization.FluentRlp.Generator\Nethermind.Serialization.FluentRlp.Generator.csproj" OutputItemType="Analyzer" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider attribute based creation using ForAttributeWithMetadataName. It should be much more cheaper than scanning all the records and only then select on attribute basis.