From 8cc677d08cd6aa022eea9a3aa00ae31a9b89842a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 7 Dec 2024 16:46:51 -0700 Subject: [PATCH 1/2] Add netstandard2.0 target --- .github/workflows/build.yml | 2 +- Directory.Packages.props | 8 + docfx/docs/custom-converters.md | 29 +- docfx/docs/getting-started.md | 10 +- docfx/docs/migrating.md | 2 +- docfx/docs/security.md | 14 +- docfx/docs/type-shapes.md | 10 +- docfx/docs/unions.md | 30 +- samples/.editorconfig | 2 + samples/ApplyingSerializationContext.cs | 28 +- samples/CustomConverters.cs | 552 ++++++++++-------- samples/CustomizingSerialization.cs | 142 ++--- samples/GettingStarted.cs | 64 +- samples/Samples.csproj | 2 +- samples/Security.cs | 99 ++-- samples/TypeShapePatterns.cs | 92 +-- samples/Unions.cs | 173 ++++-- .../ConverterAnalyzers.cs | 2 + .../KnownSubTypeAnalyzers.cs | 15 +- .../ReferenceSymbols.cs | 8 +- src/Nerdbank.MessagePack/BufferWriter.cs | 28 +- .../ByValueEqualityComparer.cs | 150 ++++- .../ByValueEqualityComparer`1.cs | 15 - .../ByValueEqualityComparer`2.cs | 83 --- .../Converters/ArrayConverter`1.cs | 2 +- ...ArrayWithFlattenedDimensionsConverter`2.cs | 8 +- .../ArrayWithNestedDimensionsConverter`2.cs | 8 +- .../ArraysOfPrimitivesConverters.cs | 4 +- .../Converters/EnumAsOrdinalConverter`2.cs | 6 +- .../Converters/EnumAsStringConverter`2.cs | 16 + .../Converters/EnumerableConverter`2.cs | 2 +- .../Converters/HardwareAccelerated.cs | 4 + .../Converters/ObjectArrayConverter`1.cs | 4 +- .../Converters/ObjectMapConverter`1.cs | 6 +- .../Converters/PrimitiveConverters.cs | 36 ++ .../IDeepSecureEqualityComparer`1.cs | 11 +- .../IMessagePackSerializationCallbacks.cs | 8 + .../KnownSubTypeAttribute.cs | 73 ++- .../MessagePackAsyncReader.cs | 4 +- .../MessagePackAsyncWriter.cs | 32 +- .../MessagePackConverter`1.cs | 4 +- .../MessagePackNamingPolicy.cs | 8 + .../MessagePackPrimitives.Readers.cs | 4 +- .../MessagePackPrimitives.Writers.cs | 14 +- src/Nerdbank.MessagePack/MessagePackReader.cs | 20 +- ...ckSerializer.AutomatedFriendlyOverloads.cs | 5 + ...ckSerializer.AutomatedFriendlyOverloads.tt | 5 + ...MessagePackSerializer.FriendlyOverloads.cs | 143 ++++- .../MessagePackSerializer.GetSchema.cs | 14 + .../MessagePackSerializer.cs | 106 +++- src/Nerdbank.MessagePack/MessagePackWriter.cs | 7 + .../Nerdbank.MessagePack.csproj | 15 +- .../PolyfillExtensions.cs | 305 ++++++++++ src/Nerdbank.MessagePack/RawMessagePack.cs | 7 + .../ByValueAggregatingEqualityComparer`1.cs | 5 + .../ByValueByteArrayEqualityComparer.cs | 8 +- .../ByValueCustomEqualityComparer`1.cs | 5 + .../ByValueDictionaryEqualityComparer`3.cs | 5 + .../ByValueEnumerableEqualityComparer`2.cs | 16 +- .../ByValueIReadOnlyListEqualityComparer`2.cs | 5 + .../ByValueNullableEqualityComparer`1.cs | 5 + .../ByValuePropertyEqualityComparer`2.cs | 5 + .../SecureHash/ByValueVisitor.cs | 5 + .../CollisionResistantHasherUnmanaged`1.cs | 25 +- .../SecureHash/HashResistantPrimitives.cs | 31 +- .../SecureEnumerableEqualityComparer`2.cs | 13 +- .../SecureHash/SecureEqualityComparer`1.cs | 9 +- .../SecureHash/SecureVisitor.cs | 14 +- src/Nerdbank.MessagePack/SequenceReader`1.cs | 507 ++++++++++++++++ .../SerializationContext.cs | 38 +- src/Nerdbank.MessagePack/StandardVisitor.cs | 35 +- .../StreamBufferWriter.cs | 2 +- src/Nerdbank.MessagePack/StringEncoding.cs | 4 +- .../Utilities/SpanEqualityComparer.cs | 23 +- .../{ => net8.0}/PublicAPI.Shipped.txt | 0 .../{ => net8.0}/PublicAPI.Unshipped.txt | 40 +- .../netstandard2.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 383 ++++++++++++ .../ConverterAnalyzersTests.cs | 34 +- .../KnownSubTypeAnalyzersTests.cs | 124 ++++ .../MigrationAnalyzerTests.cs | 2 +- ...erdbank.MessagePack.Analyzers.Tests.csproj | 3 +- .../Verifiers/CodeFixVerifier`2.cs | 9 +- .../Verifiers/ReferencesHelper.cs | 10 +- .../ArraysOfPrimitivesTests.cs | 24 +- .../BuiltInConverterTests.cs | 8 + .../ByValueEqualityComparerTests.cs | 60 +- .../ConvertToJsonTests.cs | 11 + .../CustomConverterTests.cs | 4 +- test/Nerdbank.MessagePack.Tests/EnumTests.cs | 2 + .../KnownSubTypeTests.cs | 8 + .../MessagePackSerializerPolyfill.cs | 59 ++ .../MessagePackSerializerTestBase.cs | 75 ++- .../MessagePackSerializerTests.cs | 8 +- .../NamingPolicyApplicationTests.cs | 4 + .../Nerdbank.MessagePack.Tests.csproj | 3 +- .../ObjectsAsArraysTests.cs | 7 + .../Nerdbank.MessagePack.Tests/SchemaTests.cs | 17 +- .../SerializationContextTests.cs | 6 +- .../SharedTestCases.cs | 8 +- 100 files changed, 3335 insertions(+), 776 deletions(-) delete mode 100644 src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs delete mode 100644 src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs create mode 100644 src/Nerdbank.MessagePack/PolyfillExtensions.cs create mode 100644 src/Nerdbank.MessagePack/SequenceReader`1.cs rename src/Nerdbank.MessagePack/{ => net8.0}/PublicAPI.Shipped.txt (100%) rename src/Nerdbank.MessagePack/{ => net8.0}/PublicAPI.Unshipped.txt (88%) create mode 100644 src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Unshipped.txt create mode 100644 test/Nerdbank.MessagePack.Tests/MessagePackSerializerPolyfill.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dab6296b..22d4a0cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: os: - ubuntu-22.04 #- macos-14 - #- windows-2022 + - windows-2022 steps: - uses: actions/checkout@v4 diff --git a/Directory.Packages.props b/Directory.Packages.props index 130de510..b00ba10b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -30,6 +30,14 @@ + + + + + + + + diff --git a/docfx/docs/custom-converters.md b/docfx/docs/custom-converters.md index e2e0f4cb..a4c5d83b 100644 --- a/docfx/docs/custom-converters.md +++ b/docfx/docs/custom-converters.md @@ -41,20 +41,43 @@ Applications that have a legitimate need to exceed the default stack depth limit The @Nerdbank.MessagePack.SerializationContext.GetConverter*?displayProperty=nameWithType method may be used to obtain a converter to use for members of the type your converter is serializing or deserializing. -[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValues)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNETFX)] + +--- The above assumes that `SomeOtherType` is a type that you declare and can have @PolyType.GenerateShapeAttribute`1 applied to it. If this is not the case, you may provide your own type shape and reference that. For convenience, you may want to apply it directly to your custom converter: -[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatter)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNETFX)] + +--- The @PolyType.GenerateShapeAttribute`1 is what enables `FooConverter` to be a "provider" for the shape of `SomeOtherType`. Arrays of a type require a shape of their own. So even if you define your type `MyType` with @PolyType.GenerateShapeAttribute`1, serializing `MyType[]` would require a witness type and attribute. For example: -[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatter)] + +# [.NET](#tab/net) + +[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNETFX)] ### Version compatibility diff --git a/docfx/docs/getting-started.md b/docfx/docs/getting-started.md index ac9af6a2..a9bd0723 100644 --- a/docfx/docs/getting-started.md +++ b/docfx/docs/getting-started.md @@ -15,7 +15,15 @@ Given a type annotated with [`GenerateShapeAttribute`](xref:PolyType.GenerateSha You can serialize and deserialize it like this: -[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtrip)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNETFX)] + +--- Only the top-level types that you serialize need the attribute. All types that they reference will automatically have their 'shape' source generated as well so the whole object graph can be serialized. diff --git a/docfx/docs/migrating.md b/docfx/docs/migrating.md index fc07c558..a2130a14 100644 --- a/docfx/docs/migrating.md +++ b/docfx/docs/migrating.md @@ -32,7 +32,7 @@ Reference preservation | [✅](xref:Nerdbank.MessagePack.MessagePackSerialize JSON schema export | [✅](xref:Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema*) | ❌ | Secure defaults | ✅ | ❌ | Automatic hash collection deserialization in secure mode | ❌ | ✅ | -Automatic collision-resistant hash function for arbitrary types | [✅](xref:Nerdbank.MessagePack.ByValueEqualityComparer`1) | ❌ | +Automatic collision-resistant hash function for arbitrary types | [✅](xref:Nerdbank.MessagePack.ByValueEqualityComparer) | ❌ | Free of mutable statics | ✅ | ❌ | Security is a complex subject, and an area where Nerdbank.MessagePack is actively evolving. diff --git a/docfx/docs/security.md b/docfx/docs/security.md index 9c92fce1..98b53922 100644 --- a/docfx/docs/security.md +++ b/docfx/docs/security.md @@ -43,12 +43,20 @@ Instead, you can provide your own defense by initializing your collections with Here is an example of a defense against hash collisions: -[!code-csharp[](../../samples/Security.cs#SecureEqualityComparers)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNETFX)] + +--- Note how the collection properties do *not* define a property setter. This is crucial to the threat mitigation, since it activates the deserializer behavior of not recreating the collection using the default (insecure) equality comparer. -In this example, we use @Nerdbank.MessagePack.ByValueEqualityComparer`1.HashResistant?displayProperty=nameWithType, which provides a collision resistant implementation of @System.Collections.Generic.IEqualityComparer`1. +In this example, we use @Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant*?displayProperty=nameWithType, which provides a collision resistant implementation of @System.Collections.Generic.IEqualityComparer`1. This implementation uses the SIP hash algorithm, which is known for its high performance and collision resistance. While it will function for virtually any data type, its behavior is not correct in all cases and you may need to implement your own secure hash function. -Please review the documentation for @Nerdbank.MessagePack.ByValueEqualityComparer`1.HashResistant for more information. +Please review the documentation for @Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant* for more information. diff --git a/docfx/docs/type-shapes.md b/docfx/docs/type-shapes.md index 7db0472e..dbd0285e 100644 --- a/docfx/docs/type-shapes.md +++ b/docfx/docs/type-shapes.md @@ -16,7 +16,15 @@ Doing so leads to default serialization rules being applied to the type (e.g. on For this example, suppose you consume a `FamilyTree` type from a library that you don't control and did not annotate their type for serialization. In your own project, you can define this witness type and use it to serialize an external type. -[!code-csharp[](../../samples/TypeShapePatterns.cs#Witness)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNETFX)] + +--- Note the only special bit is providing the `Witness` class as a type argument to the `Serialize` method. The _name_ of the witness class is completely inconsequential. diff --git a/docfx/docs/unions.md b/docfx/docs/unions.md index c54b2fe0..56149e39 100644 --- a/docfx/docs/unions.md +++ b/docfx/docs/unions.md @@ -11,7 +11,15 @@ For instance, suppose you have this type to serialize: But there are many kinds of animals. You can get them to serialize and deserialize correctly like this: -[!code-csharp[](../../samples/Unions.cs#FarmAnimals)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNETFX)] + +--- This changes the schema of the serialized data to include a tag that indicates the type of the object. @@ -50,7 +58,15 @@ This is because the `Horse` type is statically known as the generic type argumen Now suppose you have different breeds of horses that each had their own subtype: -[!code-csharp[](../../samples/Unions.cs#HorseBreeds)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/Unions.cs#HorseBreedsNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/Unions.cs#HorseBreedsNETFX)] + +--- At this point your `HorsePen` *would* serialize with the union schema around each horse: ```json @@ -73,4 +89,12 @@ This witness type must be specified as a second type argument to @Nerdbank.Messa For example: -[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypes)] +# [.NET](#tab/net) + +[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNET)] + +# [.NET Standard](#tab/netfx) + +[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNETFX)] + +--- diff --git a/samples/.editorconfig b/samples/.editorconfig index e5c3fac0..4afec869 100644 --- a/samples/.editorconfig +++ b/samples/.editorconfig @@ -1,5 +1,7 @@ [*.cs] +indent_style = space + # SA1123: Do not place regions within elements dotnet_diagnostic.SA1123.severity = none diff --git a/samples/ApplyingSerializationContext.cs b/samples/ApplyingSerializationContext.cs index 0fa385a9..67b206d8 100644 --- a/samples/ApplyingSerializationContext.cs +++ b/samples/ApplyingSerializationContext.cs @@ -3,20 +3,16 @@ partial class ApplyingSerializationContext { - void ApplyingContext() - { - #region ApplyingStartingContext - MessagePackSerializer serializer = new() - { - StartingContext = new SerializationContext - { - MaxDepth = 128, - }, - }; - serializer.Serialize(new SomeType()); - #endregion - } - - [GenerateShape] - internal partial record SomeType; + void ApplyingContext() + { + #region ApplyingStartingContext + MessagePackSerializer serializer = new() + { + StartingContext = new SerializationContext + { + MaxDepth = 128, + }, + }; + #endregion + } } diff --git a/samples/CustomConverters.cs b/samples/CustomConverters.cs index c13b2682..252437d0 100644 --- a/samples/CustomConverters.cs +++ b/samples/CustomConverters.cs @@ -3,263 +3,329 @@ namespace CustomConverter { - #region YourOwnConverter - using Nerdbank.MessagePack; - - public record Foo(int MyProperty1, string? MyProperty2); - - class FooConverter : MessagePackConverter - { - public override Foo? Read(ref MessagePackReader reader, SerializationContext context) - { - if (reader.TryReadNil()) - { - return null; - } - - context.DepthStep(); - int property1 = 0; - string? property2 = null; - - int count = reader.ReadMapHeader(); - for (int i = 0; i < count; i++) - { - string? key = reader.ReadString(); - switch (key) - { - case "MyProperty": - property1 = reader.ReadInt32(); - break; - case "MyProperty2": - property2 = reader.ReadString(); - break; - default: - // Skip the value, as we don't know where to put it. - reader.Skip(context); - break; - } - } - - return new Foo(property1, property2); - } - - public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - context.DepthStep(); - writer.WriteMapHeader(2); - - writer.Write("MyProperty"); - writer.Write(value.MyProperty1); - - writer.Write("MyProperty2"); - writer.Write(value.MyProperty2); - } - } - #endregion - - class VersionSafeConverter : MessagePackConverter - { - public override Foo? Read(ref MessagePackReader reader, SerializationContext context) - { - if (reader.TryReadNil()) - { - return null; - } - - #region ReadWholeArray - context.DepthStep(); - int property1 = 0; - string? property2 = null; - int count = reader.ReadArrayHeader(); - for (int i = 0; i < count; i++) - { - switch (i) - { - case 0: - property1 = reader.ReadInt32(); - break; - case 1: - property2 = reader.ReadString(); - break; - default: - // Skip the value, as we don't know where to put it. - reader.Skip(context); - break; - } - } - - return new Foo(property1, property2); - #endregion - } - - public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) - { - throw new NotImplementedException(); - } - } + #region YourOwnConverter + using Nerdbank.MessagePack; + + public record Foo(int MyProperty1, string? MyProperty2); + + class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + if (reader.TryReadNil()) + { + return null; + } + + context.DepthStep(); + int property1 = 0; + string? property2 = null; + + int count = reader.ReadMapHeader(); + for (int i = 0; i < count; i++) + { + string? key = reader.ReadString(); + switch (key) + { + case "MyProperty": + property1 = reader.ReadInt32(); + break; + case "MyProperty2": + property2 = reader.ReadString(); + break; + default: + // Skip the value, as we don't know where to put it. + reader.Skip(context); + break; + } + } + + return new Foo(property1, property2); + } + + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + if (value is null) + { + writer.WriteNil(); + return; + } + + context.DepthStep(); + writer.WriteMapHeader(2); + + writer.Write("MyProperty"); + writer.Write(value.MyProperty1); + + writer.Write("MyProperty2"); + writer.Write(value.MyProperty2); + } + } + #endregion + + class VersionSafeConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + if (reader.TryReadNil()) + { + return null; + } + + #region ReadWholeArray + context.DepthStep(); + int property1 = 0; + string? property2 = null; + int count = reader.ReadArrayHeader(); + for (int i = 0; i < count; i++) + { + switch (i) + { + case 0: + property1 = reader.ReadInt32(); + break; + case 1: + property2 = reader.ReadString(); + break; + default: + // Skip the value, as we don't know where to put it. + reader.Skip(context); + break; + } + } + + return new Foo(property1, property2); + #endregion + } + + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + throw new NotImplementedException(); + } + } } namespace SubValues { - using Nerdbank.MessagePack; - - public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); - - [GenerateShape] - public partial record SomeOtherType; - - class FooConverter : MessagePackConverter - { - public override Foo? Read(ref MessagePackReader reader, SerializationContext context) - { - if (reader.TryReadNil()) - { - return null; - } - - context.DepthStep(); - SomeOtherType? property1 = null; - string? property2 = null; - - int count = reader.ReadMapHeader(); - for (int i = 0; i < count; i++) - { - string? key = reader.ReadString(); - switch (key) - { - case "MyProperty": - property1 = context.GetConverter().Read(ref reader, context); - break; - case "MyProperty2": - property2 = reader.ReadString(); - break; - default: - // Skip the value, as we don't know where to put it. - reader.Skip(context); - break; - } - } - - return new Foo(property1, property2); - } - - #region DelegateSubValues - public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - context.DepthStep(); - writer.WriteMapHeader(2); - - writer.Write("MyProperty"); - SomeOtherType? propertyValue = value.MyProperty1; - context.GetConverter().Write(ref writer, propertyValue, context); - - writer.Write("MyProperty2"); - writer.Write(value.MyProperty2); - } - #endregion - } + using Nerdbank.MessagePack; + + public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); + + [GenerateShape] + public partial record SomeOtherType; + + class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + if (reader.TryReadNil()) + { + return null; + } + + context.DepthStep(); + SomeOtherType? property1 = null; + string? property2 = null; + + int count = reader.ReadMapHeader(); + for (int i = 0; i < count; i++) + { + string? key = reader.ReadString(); + switch (key) + { + case "MyProperty": +#if NET + property1 = context.GetConverter().Read(ref reader, context); +#else + property1 = context.GetConverter(context.TypeShapeProvider).Read(ref reader, context); +#endif + break; + case "MyProperty2": + property2 = reader.ReadString(); + break; + default: + // Skip the value, as we don't know where to put it. + reader.Skip(context); + break; + } + } + + return new Foo(property1, property2); + } + +#if NET + #region DelegateSubValuesNET + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + if (value is null) + { + writer.WriteNil(); + return; + } + + context.DepthStep(); + writer.WriteMapHeader(2); + + writer.Write("MyProperty"); + SomeOtherType? propertyValue = value.MyProperty1; + context.GetConverter().Write(ref writer, propertyValue, context); + writer.Write("MyProperty2"); + writer.Write(value.MyProperty2); + } + + #endregion +#else +#region DelegateSubValuesNETFX + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + if (value is null) + { + writer.WriteNil(); + return; + } + + context.DepthStep(); + writer.WriteMapHeader(2); + + writer.Write("MyProperty"); + SomeOtherType? propertyValue = value.MyProperty1; + context.GetConverter(context.TypeShapeProvider).Write(ref writer, propertyValue, context); + writer.Write("MyProperty2"); + writer.Write(value.MyProperty2); + } +#endregion +#endif + } } namespace SubValuesWithWitness { - using Nerdbank.MessagePack; - - public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); - - #region WitnessOnFormatter - // SomeOtherType is outside your assembly and not attributed. - public partial record SomeOtherType; - - [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType - partial class FooConverter : MessagePackConverter - { - public override Foo? Read(ref MessagePackReader reader, SerializationContext context) - { - // ... - context.GetConverter().Read(ref reader, context); - // ... - #endregion - - throw new NotImplementedException(); - } - - public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) - { - throw new NotImplementedException(); - } - } + using Nerdbank.MessagePack; + + public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); + +#if NET + #region WitnessOnFormatterNET + // SomeOtherType is outside your assembly and not attributed. + public partial record SomeOtherType; + + [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType + partial class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + // ... + context.GetConverter().Read(ref reader, context); + // ... + #endregion + + throw new NotImplementedException(); + } + + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + throw new NotImplementedException(); + } + } +#else +#region WitnessOnFormatterNETFX + // SomeOtherType is outside your assembly and not attributed. + public partial record SomeOtherType; + + [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType + partial class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + // ... + context.GetConverter(ShapeProvider).Read(ref reader, context); + // ... +#endregion + + throw new NotImplementedException(); + } + + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + throw new NotImplementedException(); + } + } +#endif } namespace WitnessForArray { - using Nerdbank.MessagePack; - - public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); - - #region ArrayWitnessOnFormatter - // SomeOtherType is outside your assembly and not attributed. - public partial record SomeOtherType; - - [GenerateShape] - partial class FooConverter : MessagePackConverter - { - public override Foo? Read(ref MessagePackReader reader, SerializationContext context) - { - // ... - context.GetConverter().Read(ref reader, context); - // ... - #endregion - - throw new NotImplementedException(); - } - - public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) - { - throw new NotImplementedException(); - } - } + using Nerdbank.MessagePack; + + public record Foo(SomeOtherType? MyProperty1, string? MyProperty2); + +#if NET + #region ArrayWitnessOnFormatterNET + // SomeOtherType is outside your assembly and not attributed. + public partial record SomeOtherType; + + [GenerateShape] + partial class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + // ... + context.GetConverter().Read(ref reader, context); + // ... + #endregion +#else +#region ArrayWitnessOnFormatterNETFX + // SomeOtherType is outside your assembly and not attributed. + public partial record SomeOtherType; + + [GenerateShape] + partial class FooConverter : MessagePackConverter + { + public override Foo? Read(ref MessagePackReader reader, SerializationContext context) + { + // ... + context.GetConverter(ShapeProvider).Read(ref reader, context); + // ... +#endregion +#endif + throw new NotImplementedException(); + } + + public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context) + { + throw new NotImplementedException(); + } + } } namespace CustomConverterRegistration { - #region CustomConverterByAttribute - [MessagePackConverter(typeof(MyCustomTypeConverter))] - public class MyCustomType { } - #endregion - - public class MyCustomTypeConverter : MessagePackConverter - { - public override MyCustomType? Read(ref MessagePackReader reader, SerializationContext context) - { - throw new NotImplementedException(); - } - - public override void Write(ref MessagePackWriter writer, in MyCustomType? value, SerializationContext context) - { - throw new NotImplementedException(); - } - } - - class CustomConverterByRegister - { - void Main() - { - #region CustomConverterByRegister - MessagePackSerializer serializer = new(); - serializer.RegisterConverter(new MyCustomTypeConverter()); - #endregion - } - } + #region CustomConverterByAttribute + [MessagePackConverter(typeof(MyCustomTypeConverter))] + public class MyCustomType { } + #endregion + + public class MyCustomTypeConverter : MessagePackConverter + { + public override MyCustomType? Read(ref MessagePackReader reader, SerializationContext context) + { + throw new NotImplementedException(); + } + + public override void Write(ref MessagePackWriter writer, in MyCustomType? value, SerializationContext context) + { + throw new NotImplementedException(); + } + } + + class CustomConverterByRegister + { + void Main() + { + #region CustomConverterByRegister + MessagePackSerializer serializer = new(); + serializer.RegisterConverter(new MyCustomTypeConverter()); + #endregion + } + } } diff --git a/samples/CustomizingSerialization.cs b/samples/CustomizingSerialization.cs index 02965f28..fdbe7a62 100644 --- a/samples/CustomizingSerialization.cs +++ b/samples/CustomizingSerialization.cs @@ -3,101 +3,101 @@ partial class IncludingExcludingMembers { - #region IncludingExcludingMembers - class MyType - { - [PropertyShape(Ignore = true)] // exclude this property from serialization - public string? MyName { get; set; } + #region IncludingExcludingMembers + class MyType + { + [PropertyShape(Ignore = true)] // exclude this property from serialization + public string? MyName { get; set; } - [PropertyShape] // include this non-public property in serialization - internal string? InternalMember { get; set; } - } - #endregion + [PropertyShape] // include this non-public property in serialization + internal string? InternalMember { get; set; } + } + #endregion } partial class ChangingPropertyNames { - #region ChangingPropertyNames - class MyType - { - [PropertyShape(Name = "name")] // serialize this property as "name" - public string? MyName { get; set; } - } - #endregion + #region ChangingPropertyNames + class MyType + { + [PropertyShape(Name = "name")] // serialize this property as "name" + public string? MyName { get; set; } + } + #endregion } partial class ApplyNamePolicy { - class MyType - { - void Example() - { - #region ApplyNamePolicy - var serializer = new MessagePackSerializer - { - PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase, - }; - #endregion - } - } + class MyType + { + void Example() + { + #region ApplyNamePolicy + var serializer = new MessagePackSerializer + { + PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase, + }; + #endregion + } + } } namespace DeserializingConstructors { - #region DeserializingConstructors - [GenerateShape] - partial class ImmutablePerson - { - public ImmutablePerson(string? name) - { - this.Name = name; - } + #region DeserializingConstructors + [GenerateShape] + partial class ImmutablePerson + { + public ImmutablePerson(string? name) + { + this.Name = name; + } - public string? Name { get; } - } - #endregion + public string? Name { get; } + } + #endregion } namespace DeserializingConstructorsPropertyRenamed { - #region DeserializingConstructorsPropertyRenamed - [GenerateShape] - partial class ImmutablePerson - { - public ImmutablePerson(string? name) - { - this.Name = name; - } + #region DeserializingConstructorsPropertyRenamed + [GenerateShape] + partial class ImmutablePerson + { + public ImmutablePerson(string? name) + { + this.Name = name; + } - [PropertyShape(Name = "person_name")] - public string? Name { get; } - } - #endregion + [PropertyShape(Name = "person_name")] + public string? Name { get; } + } + #endregion } namespace SerializeWithKey { - #region SerializeWithKey - [GenerateShape] - partial class MyType - { - [Key(0)] - public string? OneProperty { get; set; } + #region SerializeWithKey + [GenerateShape] + partial class MyType + { + [Key(0)] + public string? OneProperty { get; set; } - [Key(1)] - public string? AnotherProperty { get; set; } - } - #endregion + [Key(1)] + public string? AnotherProperty { get; set; } + } + #endregion - #region SerializeWithKeyAndGaps - [GenerateShape] - partial class MyTypeWithGaps - { - [Key(0)] - public string? OneProperty { get; set; } + #region SerializeWithKeyAndGaps + [GenerateShape] + partial class MyTypeWithGaps + { + [Key(0)] + public string? OneProperty { get; set; } - [Key(5)] - public string? AnotherProperty { get; set; } - } - #endregion + [Key(5)] + public string? AnotherProperty { get; set; } + } + #endregion } diff --git a/samples/GettingStarted.cs b/samples/GettingStarted.cs index 425b197a..750e0294 100644 --- a/samples/GettingStarted.cs +++ b/samples/GettingStarted.cs @@ -3,25 +3,47 @@ partial class SimpleUsage { - #region SimpleRecord - [GenerateShape] - public partial record ARecord(string AString, bool ABoolean); - #endregion - - void Roundtrip() - { - #region SimpleRecordRoundtrip - // Construct a value. - var value = new ARecord("hello", true); - - // Create a serializer instance. - MessagePackSerializer serializer = new(); - - // Serialize the value to the buffer. - byte[] msgpack = serializer.Serialize(value); - - // Deserialize it back. - ARecord? deserialized = serializer.Deserialize(msgpack); - #endregion - } + #region SimpleRecord + [GenerateShape] + public partial record ARecord(string AString, bool ABoolean); + #endregion + +#if NET + #region SimpleRecordRoundtripNET + void Roundtrip() + { + // Construct a value. + var value = new ARecord("hello", true); + + // Create a serializer instance. + MessagePackSerializer serializer = new(); + + // Serialize the value to the buffer. + byte[] msgpack = serializer.Serialize(value); + + // Deserialize it back. + ARecord? deserialized = serializer.Deserialize(msgpack); + } + #endregion +#else + #region SimpleRecordRoundtripNETFX + void Roundtrip() + { + // Construct a value. + var value = new ARecord("hello", true); + + // Create a serializer instance. + MessagePackSerializer serializer = new(); + + // Serialize the value to the buffer. + byte[] msgpack = serializer.Serialize(value, Witness.ShapeProvider); + + // Deserialize it back. + ARecord? deserialized = serializer.Deserialize(msgpack, Witness.ShapeProvider); + } + + [GenerateShape] + internal partial class Witness; + #endregion +#endif } diff --git a/samples/Samples.csproj b/samples/Samples.csproj index 9b256b84..e75ef955 100644 --- a/samples/Samples.csproj +++ b/samples/Samples.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net472 false diff --git a/samples/Security.cs b/samples/Security.cs index d2eded61..ce817405 100644 --- a/samples/Security.cs +++ b/samples/Security.cs @@ -5,39 +5,68 @@ partial class Security { - void SetMaxDepth() - { - #region SetMaxDepth - var serializer = new MessagePackSerializer - { - StartingContext = new SerializationContext - { - MaxDepth = 100, - }, - }; - #endregion - } - - #region SecureEqualityComparers - [GenerateShape] - public partial class HashCollisionResistance - { - public HashCollisionResistance() - { - this.Dictionary = new(ByValueEqualityComparer.HashResistant); - this.HashSet = new(ByValueEqualityComparer.HashResistant); - } - - public Dictionary Dictionary { get; } - - public HashSet HashSet { get; } - } - - [GenerateShape] - public partial class CustomType - { - // Whatever members you want. Make them public or attribute with [PropertyShape] - // to include them in the hash and equality checks as part of the dictionary keys. - } - #endregion + void SetMaxDepth() + { + #region SetMaxDepth + var serializer = new MessagePackSerializer + { + StartingContext = new SerializationContext + { + MaxDepth = 100, + }, + }; + #endregion + } + +#if NET + #region SecureEqualityComparersNET + [GenerateShape] + public partial class HashCollisionResistance + { + public HashCollisionResistance() + { + this.Dictionary = new(ByValueEqualityComparer.GetHashResistant()); + this.HashSet = new(ByValueEqualityComparer.GetHashResistant()); + } + + public Dictionary Dictionary { get; } + + public HashSet HashSet { get; } + } + + [GenerateShape] + public partial class CustomType + { + // Whatever members you want. Make them public or attribute with [PropertyShape] + // to include them in the hash and equality checks as part of the dictionary keys. + } + + #endregion +#else + #region SecureEqualityComparersNETFX + [GenerateShape] + public partial class HashCollisionResistance + { + public HashCollisionResistance() + { + this.Dictionary = new(ByValueEqualityComparer.GetHashResistant(Witness.ShapeProvider)); + this.HashSet = new(ByValueEqualityComparer.GetHashResistant(Witness.ShapeProvider)); + } + + public Dictionary Dictionary { get; } + + public HashSet HashSet { get; } + } + + [GenerateShape] + public partial class CustomType + { + // Whatever members you want. Make them public or attribute with [PropertyShape] + // to include them in the hash and equality checks as part of the dictionary keys. + } + + [GenerateShape] + internal partial class Witness; + #endregion +#endif } diff --git a/samples/TypeShapePatterns.cs b/samples/TypeShapePatterns.cs index 7b2d2704..6634226d 100644 --- a/samples/TypeShapePatterns.cs +++ b/samples/TypeShapePatterns.cs @@ -6,51 +6,73 @@ partial class SourceGenerated { - #region NaturallyAttributed - [GenerateShape] - public partial record Tree(string Name, Fruit[] Fruits); + #region NaturallyAttributed + [GenerateShape] + public partial record Tree(string Name, Fruit[] Fruits); - public record Fruit(double Weight, string Color); - #endregion + public record Fruit(double Weight, string Color); + #endregion } partial class WitnessGenerated { - #region Witness - // This class declared in another assembly, unattributed and outside of your control. - public class FamilyTree - { - } - - // Within your own assembly, define a 'witness' class with one or more shapes generated for external types. - [GenerateShape] - partial class Witness; - - void Serialize() - { - var familyTree = new FamilyTree(); - var serializer = new MessagePackSerializer(); - - // Serialize the FamilyTree instance using the shape generated from your witness class. - byte[] msgpack = serializer.Serialize(familyTree); - } - #endregion +#if NET + #region WitnessNET + // This class declared in another assembly, unattributed and outside of your control. + public class FamilyTree + { + } + + // Within your own assembly, define a 'witness' class with one or more shapes generated for external types. + [GenerateShape] + partial class Witness; + + void Serialize() + { + var familyTree = new FamilyTree(); + var serializer = new MessagePackSerializer(); + + // Serialize the FamilyTree instance using the shape generated from your witness class. + byte[] msgpack = serializer.Serialize(familyTree); + } + #endregion +#else + #region WitnessNETFX + // This class declared in another assembly, unattributed and outside of your control. + public class FamilyTree + { + } + + // Within your own assembly, define a 'witness' class with one or more shapes generated for external types. + [GenerateShape] + partial class Witness; + + void Serialize() + { + var familyTree = new FamilyTree(); + var serializer = new MessagePackSerializer(); + + // Serialize the FamilyTree instance using the shape generated from your witness class. + byte[] msgpack = serializer.Serialize(familyTree, Witness.ShapeProvider); + } + #endregion +#endif } class ReflectionShapeProvider { - #region SerializeUnshapedType - void SerializeUnshapedType() - { - Person person = new("Andrew", "Arnott"); + #region SerializeUnshapedType + void SerializeUnshapedType() + { + Person person = new("Andrew", "Arnott"); - ITypeShape shape = ReflectionTypeShapeProvider.Default.GetShape(); - MessagePackSerializer serializer = new(); + ITypeShape shape = ReflectionTypeShapeProvider.Default.GetShape(); + MessagePackSerializer serializer = new(); - byte[] msgpack = serializer.Serialize(person, shape); - Person? deserialized = serializer.Deserialize(msgpack, shape); - } + byte[] msgpack = serializer.Serialize(person, shape); + Person? deserialized = serializer.Deserialize(msgpack, shape); + } - record Person(string FirstName, string LastName); - #endregion + record Person(string FirstName, string LastName); + #endregion } diff --git a/samples/Unions.cs b/samples/Unions.cs index 14e8c903..7796df63 100644 --- a/samples/Unions.cs +++ b/samples/Unions.cs @@ -3,71 +3,128 @@ namespace Sample1 { - #region Farm - public class Farm - { - public List? Animals { get; set; } - } - #endregion - - #region FarmAnimals - [KnownSubType(1)] - [KnownSubType(2)] - [KnownSubType(3)] - public class Animal - { - public string? Name { get; set; } - } - - [GenerateShape] - public partial class Cow : Animal { } - [GenerateShape] - public partial class Horse : Animal { } - [GenerateShape] - public partial class Dog : Animal { } - #endregion - - #region HorsePen - public class HorsePen - { - public List? Horses { get; set; } - } - #endregion - - #region HorseBreeds - [KnownSubType(1)] - [KnownSubType(2)] - public partial class Horse : Animal { } - - [GenerateShape] - public partial class QuarterHorse : Horse { } - [GenerateShape] - public partial class Thoroughbred : Horse { } - #endregion + #region Farm + public class Farm + { + public List? Animals { get; set; } + } + #endregion + +#if NET + #region FarmAnimalsNET + [KnownSubType(1)] + [KnownSubType(2)] + [KnownSubType(3)] + public class Animal + { + public string? Name { get; set; } + } + + [GenerateShape] + public partial class Cow : Animal { } + [GenerateShape] + public partial class Horse : Animal { } + [GenerateShape] + public partial class Dog : Animal { } + #endregion +#else + #region FarmAnimalsNETFX + [KnownSubType(1, typeof(Cow))] + [KnownSubType(2, typeof(Horse))] + [KnownSubType(3, typeof(Dog))] + public class Animal + { + public string? Name { get; set; } + } + + [GenerateShape] + public partial class Cow : Animal { } + [GenerateShape] + public partial class Horse : Animal { } + [GenerateShape] + public partial class Dog : Animal { } + #endregion +#endif + + #region HorsePen + public class HorsePen + { + public List? Horses { get; set; } + } + #endregion + +#if NET + #region HorseBreedsNET + [KnownSubType(1)] + [KnownSubType(2)] + public partial class Horse : Animal { } + + [GenerateShape] + public partial class QuarterHorse : Horse { } + [GenerateShape] + public partial class Thoroughbred : Horse { } + #endregion +#else + #region HorseBreedsNETFX + [KnownSubType(1, typeof(QuarterHorse))] + [KnownSubType(2, typeof(Thoroughbred))] + public partial class Horse : Animal { } + + [GenerateShape] + public partial class QuarterHorse : Horse { } + [GenerateShape] + public partial class Thoroughbred : Horse { } + #endregion +#endif } namespace GenericSubTypes { - #region ClosedGenericSubTypes - [KnownSubType(1)] - [KnownSubType, Witness>(2)] - [KnownSubType, Witness>(3)] - class Animal - { - public string? Name { get; set; } - } +#if NET + #region ClosedGenericSubTypesNET + [KnownSubType(1)] + [KnownSubType, Witness>(2)] + [KnownSubType, Witness>(3)] + class Animal + { + public string? Name { get; set; } + } + + [GenerateShape] + partial class Horse : Animal { } + + partial class Cow : Animal { } + + [GenerateShape>] + [GenerateShape>] + partial class Witness; + + class SolidHoof { } + + class ClovenHoof { } + #endregion +#else + #region ClosedGenericSubTypesNETFX + [KnownSubType(1, typeof(Horse))] + [KnownSubType(2, typeof(Cow))] + [KnownSubType(3, typeof(Cow))] + class Animal + { + public string? Name { get; set; } + } - [GenerateShape] - partial class Horse : Animal { } + [GenerateShape] + partial class Horse : Animal { } - partial class Cow : Animal { } + partial class Cow : Animal { } - [GenerateShape>] - [GenerateShape>] - partial class Witness; + [GenerateShape>] + [GenerateShape>] + partial class Witness; - class SolidHoof { } + class SolidHoof { } - class ClovenHoof { } - #endregion + class ClovenHoof { } + #endregion +#endif } diff --git a/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs b/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs index 1837d8b9..0c73f74d 100644 --- a/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs +++ b/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs @@ -15,6 +15,8 @@ public class ConverterAnalyzers : DiagnosticAnalyzer public const string NotExactlyOneStructureDiagnosticId = "NBMsgPack031"; public const string OverrideGetJsonSchemaDiagnosticId = "NBMsgPack032"; + //// NBMsgPack033 | Usage | Error | Async converters should return the MessagePackWriter + public static readonly DiagnosticDescriptor CallbackToTopLevelSerializerDescriptor = new( CallbackToTopLevelSerializerDiagnosticId, title: Strings.NBMsgPack030_Title, diff --git a/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs b/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs index 20f5bda8..b91eeba9 100644 --- a/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs +++ b/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs @@ -82,13 +82,16 @@ public override void Initialize(AnalysisContext context) context => { INamedTypeSymbol appliedSymbol = (INamedTypeSymbol)context.Symbol; - AttributeData[] attributeDatas = context.Symbol.FindAttributes(referenceSymbols.IKnownSubTypeAttribute).ToArray(); + AttributeData[] attributeDatas = context.Symbol.FindAttributes(referenceSymbols.KnownSubTypeAttribute).ToArray(); Dictionary? typesByAlias = null; Dictionary? aliasesByType = null; foreach (AttributeData att in attributeDatas) { - int? alias = att.ConstructorArguments is [{ Value: int a }] ? a : null; - ITypeSymbol? subType = att.AttributeClass?.TypeArguments[0]; + int? alias = att.ConstructorArguments is [{ Value: int a }, ..] ? a : null; + (ITypeSymbol? subType, Location? subTypeLocation) = + att.AttributeClass?.TypeArguments.Length >= 1 ? (att.AttributeClass?.TypeArguments[0], GetTypeArgumentLocation(0)) : + att.ConstructorArguments.Length >= 2 ? ((ITypeSymbol?)att.ConstructorArguments[1].Value, GetArgumentLocation(1)) : + (null, null); if (alias is not null) { @@ -112,7 +115,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( NonUniqueTypeDescriptor, - GetTypeArgumentLocation(0), + subTypeLocation, existingAlias)); } else @@ -125,7 +128,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( NonDerivedTypeDescriptor, - GetTypeArgumentLocation(0), + subTypeLocation, subType.Name)); } @@ -133,7 +136,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( OpenGenericTypeDescriptor, - GetTypeArgumentLocation(0))); + subTypeLocation)); } Location? GetArgumentLocation(int argumentIndex) diff --git a/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs b/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs index 32d80e9d..d10cfba1 100644 --- a/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs +++ b/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs @@ -12,7 +12,7 @@ public record ReferenceSymbols( INamedTypeSymbol MessagePackReader, INamedTypeSymbol MessagePackWriter, INamedTypeSymbol KeyAttribute, - INamedTypeSymbol IKnownSubTypeAttribute, + INamedTypeSymbol KnownSubTypeAttribute, INamedTypeSymbol GenerateShapeAttribute, INamedTypeSymbol PropertyShapeAttribute, INamedTypeSymbol ConstructorShapeAttribute) @@ -79,8 +79,8 @@ public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out Re return false; } - INamedTypeSymbol? iknownSubTypeAttribute = libraryAssembly.GetTypeByMetadataName("Nerdbank.MessagePack.IKnownSubTypeAttribute"); - if (iknownSubTypeAttribute is null) + INamedTypeSymbol? knownSubTypeAttribute = libraryAssembly.GetTypeByMetadataName("Nerdbank.MessagePack.KnownSubTypeAttribute"); + if (knownSubTypeAttribute is null) { referenceSymbols = null; return false; @@ -121,7 +121,7 @@ public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out Re messagePackReader, messagePackWriter, keyAttribute, - iknownSubTypeAttribute, + knownSubTypeAttribute, generateShapeAttribute, propertyShapeAttribute, constructorShapeAttribute); diff --git a/src/Nerdbank.MessagePack/BufferWriter.cs b/src/Nerdbank.MessagePack/BufferWriter.cs index cadf3b82..bfe6922d 100644 --- a/src/Nerdbank.MessagePack/BufferWriter.cs +++ b/src/Nerdbank.MessagePack/BufferWriter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // This file was originally derived from https://github.com/MessagePack-CSharp/MessagePack-CSharp/ +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft; @@ -13,13 +14,19 @@ namespace Nerdbank.MessagePack; /// internal ref struct BufferWriter { + private readonly bool usingBufferMemoryWriter; + +#if NET + private ref BufferMemoryWriter memoryWriter; +#else + private BufferMemoryWriter memoryWriter; +#endif + /// /// The underlying . /// private IBufferWriter? output; - private ref BufferMemoryWriter memoryWriter; - /// /// The result of the last call to , less any bytes already "consumed" with . /// Backing field for the property. @@ -60,8 +67,13 @@ internal BufferWriter(IBufferWriter output) /// The async compatible equivalent to this struct, that this struct will temporarily wrap. internal BufferWriter(ref BufferMemoryWriter memoryWriter) { +#if NET this.memoryWriter = ref memoryWriter; - this.span = memoryWriter.GetSpan(); +#else + this.memoryWriter = memoryWriter; +#endif + this.usingBufferMemoryWriter = true; + this.span = this.memoryWriter.GetSpan(); } /// @@ -93,6 +105,12 @@ internal BufferWriter(SequencePool sequencePool, byte[] array) /// internal SequencePool.Rental SequenceRental => this.rental; + /// + /// Gets the underlying this writer. + /// + [UnscopedRef] + internal ref BufferMemoryWriter BufferMemoryWriter => ref this.memoryWriter; + /// internal Span GetSpan(int sizeHint = 0) { @@ -130,7 +148,7 @@ internal void Commit() int buffered = this.buffered; if (buffered > 0) { - if (!Unsafe.IsNullRef(ref this.memoryWriter)) + if (this.usingBufferMemoryWriter) { this.memoryWriter.Advance(buffered); } @@ -222,7 +240,7 @@ private void EnsureMore(int sizeHint = 0) this.MigrateToSequence(); } - if (!Unsafe.IsNullRef(ref this.memoryWriter)) + if (this.usingBufferMemoryWriter) { this.span = this.memoryWriter.GetSpan(sizeHint); } diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs index 8179d5b5..fdbe055a 100644 --- a/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs +++ b/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs @@ -7,9 +7,59 @@ namespace Nerdbank.MessagePack; /// -/// A non-generic class for caching equality comparers. +/// Provides deep by-value implementations of for arbitrary data types. /// -internal static class ByValueEqualityComparer +/// +/// +/// The deep walking of the object graph for deep by-value equality and hashing is based on the same +/// that is used to generate MessagePack serializers. +/// The implementation therefore considers all the same properties for equality and hashing that would +/// be included in a serialized copy. +/// +/// +/// This implementation is not suitable for all types. Specifically, it is not suitable for types that +/// have multiple memory representations that are considered equal. +/// An invariant for behavior must be that if +/// x.Equals(y) then x.GetHashCode() == y.GetHashCode(). +/// For an auto-generated implementation of these methods for arbitrary types such as this, +/// no specialization for multiple values that are considered equal is possible. +/// +/// +/// For example, a value has distinct memory representations for 0.0 and -0.0, +/// yet these two values are considered equal and must have the same hash code. +/// In this case and for several other common data types included with .NET, special consideration is built-in +/// for correct operation. +/// But this cannot be done automatically for any user-defined types. +/// +/// +/// When using user-defined types for which this implementation is inappropriate, +/// a custom implementation of may be used if the type is used directly. +/// But if the type is referenced in a type reference graph such that is used for by-value comparison, +/// implementing on that type will allow the type to take control +/// of just its contribution to the hash code and equality comparison. +/// +/// +/// Types that define no (public or opted in) properties and do not implement will throw a when attempting to create an equality comparer. +/// +/// +/// This implementation should only be used for acyclic graphs, since cyclic graphs will cause a +/// while performing the comparison. +/// +/// +/// Another consideration is that types used as keys in collections should generally not have a changing hash code +/// or the collections internal data structures may become corrupted by a key that is stored in the wrong hash bucket. +/// Keys should generally be immutable to prevent this, or at least immutable in the elements that contribute to the hash code. +/// In an automated equality comparer such as the one produced by this class, all public properties contribute to the hash code, +/// even if they are mutable. +/// Care should therefore be taken to not mutate properties on objects used as keys in collections. +/// +/// +/// The values are compared by their declared types rather than polymorphism. +/// If some type has a property of type Foo, and the actual value at runtime derives from Foo, only the properties on Foo will be considered. +/// If between two object graphs being equality checked, their runtime types do not match, the equality check will return . +/// +/// +public static class ByValueEqualityComparer { /// /// Cache for generated by-value comparers. @@ -28,4 +78,100 @@ internal static class ByValueEqualityComparer DelayedValueFactory = new SecureVisitor.DelayedEqualityComparerFactory(), ValueBuilderFactory = ctx => new SecureVisitor(ctx), }; + +#if NET + /// + /// Gets a deep by-value equality comparer for the type , without hash collision resistance. + /// + /// The type to be compared. + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetDefault() + where T : IShapeable => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(T.GetShape())!; + + /// + /// Gets a deep by-value equality comparer for the type , with hash collision resistance. + /// + /// The type to be compared. + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetHashResistant() + where T : IShapeable => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(T.GetShape())!; + + /// + /// Gets a deep by-value equality comparer for the type , without hash collision resistance. + /// + /// The type to be compared. + /// The witness type that provides the for . + /// An equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetDefault() + where TProvider : IShapeable => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(TProvider.GetShape())!; + + /// + /// Gets a deep by-value equality comparer for the type , with hash collision resistance. + /// + /// The type to be compared. + /// The witness type that provides the for . + /// An equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetHashResistant() + where TProvider : IShapeable => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(TProvider.GetShape())!; +#endif + + /// + /// Gets a deep by-value equality comparer for the type , without hash collision resistance. + /// + /// The type to be compared. + /// + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetDefault(ITypeShapeProvider provider) + => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAddOrThrow(typeof(T), provider); + + /// + /// Gets a deep by-value equality comparer for the type , with hash collision resistance. + /// + /// The type to be compared. + /// + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetHashResistant(ITypeShapeProvider provider) + => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAddOrThrow(typeof(T), provider); + + /// + /// Gets a deep by-value equality comparer for the type , without hash collision resistance. + /// + /// The type to be compared. + /// The type shape. + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetDefault(ITypeShape shape) + => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(shape)!; + + /// + /// Gets a deep by-value equality comparer for the type , with hash collision resistance. + /// + /// The type to be compared. + /// The type shape. + /// The equality comparer. + /// + /// See the remarks on the class for important notes about correctness of this implementation. + /// + public static IEqualityComparer GetHashResistant(ITypeShape shape) + => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(shape)!; } diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs deleted file mode 100644 index ae972c81..00000000 --- a/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Andrew Arnott. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Nerdbank.MessagePack; - -/// -public static class ByValueEqualityComparer - where T : IShapeable -{ - /// - public static IEqualityComparer Default => ByValueEqualityComparer.Default; - - /// - public static IEqualityComparer HashResistant => ByValueEqualityComparer.HashResistant; -} diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs deleted file mode 100644 index 2d90269e..00000000 --- a/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Andrew Arnott. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Nerdbank.MessagePack; - -/// -/// Provides deep by-value implementations of for arbitrary data types. -/// -/// The data type to be hashed or compared by value. -/// The provider of the type shape. -/// -/// -/// The deep walking of the object graph for deep by-value equality and hashing is based on the same -/// that is used to generate MessagePack serializers. -/// The implementation therefore considers all the same properties for equality and hashing that would -/// be included in a serialized copy. -/// -/// -/// This implementation is not suitable for all types. Specifically, it is not suitable for types that -/// have multiple memory representations that are considered equal. -/// An invariant for behavior must be that if -/// x.Equals(y) then x.GetHashCode() == y.GetHashCode(). -/// For an auto-generated implementation of these methods for arbitrary types such as this, -/// no specialization for multiple values that are considered equal is possible. -/// -/// -/// For example, a value has distinct memory representations for 0.0 and -0.0, -/// yet these two values are considered equal and must have the same hash code. -/// In this case and for several other common data types included with .NET, special consideration is built-in -/// for correct operation. -/// But this cannot be done automatically for any user-defined types. -/// -/// -/// When using user-defined types for which this implementation is inappropriate, -/// a custom implementation of may be used if the type is used directly. -/// But if the type is referenced in a type reference graph such that is used for by-value comparison, -/// implementing on that type will allow the type to take control -/// of just its contribution to the hash code and equality comparison. -/// -/// -/// Types that define no (public or opted in) properties and do not implement will throw a when attempting to create an equality comparer. -/// -/// -/// This implementation should only be used for acyclic graphs, since cyclic graphs will cause a -/// while performing the comparison. -/// -/// -/// Another consideration is that types used as keys in collections should generally not have a changing hash code -/// or the collections internal data structures may become corrupted by a key that is stored in the wrong hash bucket. -/// Keys should generally be immutable to prevent this, or at least immutable in the elements that contribute to the hash code. -/// In an automated equality comparer such as the one produced by this class, all public properties contribute to the hash code, -/// even if they are mutable. -/// Care should therefore be taken to not mutate properties on objects used as keys in collections. -/// -/// -/// The values are compared by their declared types rather than polymorphism. -/// If some type has a property of type Foo, and the actual value at runtime derives from Foo, only the properties on Foo will be considered. -/// If between two object graphs being equality checked, their runtime types do not match, the equality check will return . -/// -/// -public static class ByValueEqualityComparer - where TProvider : IShapeable -{ - private static IEqualityComparer? defaultEqualityComparer; - - private static IEqualityComparer? hashResistantEqualityComparer; - - /// - /// Gets a deep by-value equality comparer for the type , without hash collision resistance. - /// - /// - /// See the remarks on the class for important notes about correctness of this implementation. - /// - public static IEqualityComparer Default => defaultEqualityComparer ??= (IEqualityComparer)ByValueEqualityComparer.DefaultEqualityComparerCache.GetOrAdd(TProvider.GetShape())!; - - /// - /// Gets a deep by-value equality comparer for the type , with hash collision resistance. - /// - /// - /// See the remarks on the class for important notes about correctness of this implementation. - /// - public static IEqualityComparer HashResistant => hashResistantEqualityComparer ??= (IEqualityComparer)ByValueEqualityComparer.HashResistantEqualityComparerCache.GetOrAdd(TProvider.GetShape())!; -} diff --git a/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs index 003f3217..359ed4d1 100644 --- a/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs +++ b/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs @@ -84,7 +84,7 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, TEleme context.CancellationToken.ThrowIfCancellationRequested(); } - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false); } while (progress < value.Length); diff --git a/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs b/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs index 7c09a9f3..12cd88b7 100644 --- a/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs +++ b/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs @@ -1,6 +1,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET + using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -118,6 +120,8 @@ public override void Write(ref MessagePackWriter writer, in TArray? value, Seria /// /// The array. /// The span of all elements. - private static Span AsSpan(Array array) => - MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length); + private static Span AsSpan(Array array) + => MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length); } + +#endif diff --git a/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs b/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs index d670ad39..7cc1882f 100644 --- a/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs +++ b/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs @@ -1,6 +1,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -78,8 +80,8 @@ public override void Write(ref MessagePackWriter writer, in TArray? value, Seria /// /// The array. /// The span of all elements. - private static Span AsSpan(Array array) => - MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length); + private static Span AsSpan(Array array) + => MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length); /// /// Writes an array containing one dimension of an array, and its children, recursively. @@ -155,3 +157,5 @@ private void PeekDimensionsLength(MessagePackReader reader, int[] dimensions) } } } + +#endif diff --git a/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs b/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs index 403b5e22..f8d5ad0b 100644 --- a/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs +++ b/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs @@ -61,7 +61,7 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value, else { IEnumerable enumerable = getEnumerable(value); - if (Enumerable.TryGetNonEnumeratedCount(enumerable, out int count)) + if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerable, out int count)) { writer.WriteArrayHeader(count); Span span = writer.GetSpan(count * MsgPackBufferLengthFactor); @@ -145,9 +145,11 @@ private static bool TryGetSpan(TEnumerable enumerable, out ReadOnlySpan list: span = CollectionsMarshal.AsSpan(list); return true; +#endif case ReadOnlyMemory rom: span = rom.Span; return true; diff --git a/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs index 718985c7..1b9e15ef 100644 --- a/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs +++ b/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs @@ -29,11 +29,15 @@ public override void Write(ref MessagePackWriter writer, in TEnum value, Seriali JsonObject schema = new JsonObject { ["type"] = "integer" }; StringBuilder description = new(); +#if NET Array enumValuesUntyped = typeof(TEnum).GetEnumValuesAsUnderlyingType(); +#else + Array enumValuesUntyped = typeof(TEnum).GetEnumValues(); +#endif JsonNode[] enumValueNodes = new JsonNode[enumValuesUntyped.Length]; for (int i = 0; i < enumValueNodes.Length; i++) { - object ordinalValue = enumValuesUntyped.GetValue(i)!; + TUnderlyingType ordinalValue = (TUnderlyingType)enumValuesUntyped.GetValue(i)!; if (description.Length > 0) { description.Append(", "); diff --git a/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs index 192dba63..6ffc5182 100644 --- a/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs +++ b/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs @@ -46,9 +46,17 @@ public EnumAsStringConverter(MessagePackConverter primitiveConv bool TryPopulateDictionary() { bool nonUniqueNameDetected = false; +#if NET foreach (TEnum value in Enum.GetValues()) +#else + foreach (TEnum value in Enum.GetValues(typeof(TEnum))) +#endif { +#if NET if (Enum.GetName(value) is string name) +#else + if (Enum.GetName(typeof(TEnum), value) is string name) +#endif { if (!this.valueByName.TryAdd(name, value)) { @@ -70,10 +78,18 @@ bool TryPopulateDictionary() if (nonUniqueNameDetected) { // Enumerate values and ensure we have all the names indexed so we can deserialize them. +#if NET foreach (string name in Enum.GetNames()) { this.valueByName.TryAdd(name, Enum.Parse(name)); } +#else + foreach (string name in Enum.GetNames(typeof(TEnum))) + { + Assumes.True(Enum.TryParse(name, out TEnum value)); + this.valueByName.TryAdd(name, value); + } +#endif } return true; diff --git a/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs index 2a1a3b33..3bcb0f70 100644 --- a/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs +++ b/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs @@ -43,7 +43,7 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value, context.DepthStep(); IEnumerable enumerable = getEnumerable(value); - if (Enumerable.TryGetNonEnumeratedCount(enumerable, out int count)) + if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerable, out int count)) { writer.WriteArrayHeader(count); foreach (TElement element in enumerable) diff --git a/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs b/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs index 41530a29..a27eabad 100644 --- a/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs +++ b/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs @@ -1,6 +1,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET + using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Numerics; @@ -995,3 +997,5 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value, }; } } + +#endif diff --git a/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs index a9081ea5..bf70e0e9 100644 --- a/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs +++ b/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs @@ -227,7 +227,7 @@ static async ValueTask WriteAsMapAsync(MessagePackAsyncWriter writer, T value, R allProperties.Span[properties.Span[i]]!.Value.MsgPackWriters!.Value.Serialize(value, ref syncWriter, context); } - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false); } @@ -290,7 +290,7 @@ static async ValueTask WriteAsArrayAsync(MessagePackAsyncWriter writer, T value, } } - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false); } diff --git a/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs index 54cd59d8..1bbe7d2c 100644 --- a/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs +++ b/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs @@ -105,7 +105,7 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, T? val syncWriter.WriteRaw(property.RawPropertyNameString.Span); if (property.PreferAsyncSerialization) { - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); await property.WriteAsync(value, writer, context).ConfigureAwait(false); syncWriter = writer.CreateWriter(); } @@ -116,13 +116,13 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, T? val if (writer.IsTimeToFlush(context, syncWriter)) { - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false); syncWriter = writer.CreateWriter(); } } - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); } finally { diff --git a/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs b/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs index 63d3fe0c..7b6badc5 100644 --- a/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs +++ b/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs @@ -150,6 +150,8 @@ internal class UriConverter : MessagePackConverter }; } +#if NET + /// /// Serializes a . /// @@ -170,6 +172,8 @@ internal class HalfConverter : MessagePackConverter }; } +#endif + /// /// Serializes a . /// @@ -307,6 +311,8 @@ public override void Write(ref MessagePackWriter writer, in decimal value, Seria }; } +#if NET + /// /// Serializes a value. /// @@ -463,6 +469,8 @@ public override void Write(ref MessagePackWriter writer, in UInt128 value, Seria }; } +#endif + /// /// Serializes a value. /// @@ -474,10 +482,15 @@ public override BigInteger Read(ref MessagePackReader reader, SerializationConte ReadOnlySequence bytes = reader.ReadBytes() ?? throw MessagePackSerializationException.ThrowUnexpectedNilWhileDeserializing(); if (bytes.IsSingleSegment) { +#if NET return new BigInteger(bytes.First.Span); +#else + return new BigInteger(bytes.First.ToArray()); +#endif } else { +#if NET byte[] bytesArray = ArrayPool.Shared.Rent((int)bytes.Length); try { @@ -488,17 +501,24 @@ public override BigInteger Read(ref MessagePackReader reader, SerializationConte { ArrayPool.Shared.Return(bytesArray); } +#else + return new BigInteger(bytes.ToArray()); +#endif } } /// public override void Write(ref MessagePackWriter writer, in BigInteger value, SerializationContext context) { +#if NET int byteCount = value.GetByteCount(); writer.WriteBinHeader(byteCount); Span span = writer.GetSpan(byteCount); Assumes.True(value.TryWriteBytes(span, out int written)); writer.Advance(written); +#else + writer.Write(value.ToByteArray()); +#endif } /// @@ -559,6 +579,8 @@ public override void Write(ref MessagePackWriter writer, in DateTimeOffset value }; } +#if NET + /// /// Serializes values. /// @@ -601,6 +623,8 @@ internal class TimeOnlyConverter : MessagePackConverter }; } +#endif + /// /// Serializes values. /// @@ -622,6 +646,8 @@ internal class TimeSpanConverter : MessagePackConverter }; } +#if NET + /// /// Serializes values. /// @@ -641,6 +667,8 @@ internal class RuneConverter : MessagePackConverter }; } +#endif + /// /// Serializes values. /// @@ -757,13 +785,21 @@ public override Guid Read(ref MessagePackReader reader, SerializationContext con if (bytes.IsSingleSegment) { +#if NET return new Guid(bytes.FirstSpan); +#else + return PolyfillExtensions.CreateGuid(bytes.First.Span); +#endif } else { Span guidValue = stackalloc byte[GuidLength]; bytes.CopyTo(guidValue); +#if NET return new Guid(guidValue); +#else + return PolyfillExtensions.CreateGuid(guidValue); +#endif } } diff --git a/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs b/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs index b1886549..04705947 100644 --- a/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs +++ b/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs @@ -12,7 +12,7 @@ namespace Nerdbank.MessagePack; /// /// When a type implements this interface, and /// is used to determine equality and hash codes for the type by the -/// equality comparer +/// equality comparer /// instead of the deep by-value automatic implementation. /// /// @@ -24,7 +24,7 @@ public interface IDeepSecureEqualityComparer /// The other object. /// if the two objects are deeply equal. /// - /// An implementation may use to obtain equality comparers for any sub-values that must be tested. + /// An implementation may use to obtain equality comparers for any sub-values that must be tested. /// bool DeepEquals(T? other); @@ -42,5 +42,10 @@ public interface IDeepSecureEqualityComparer /// /// The default implementation of this method is to truncate the result of . /// - int GetHashCode() => unchecked((int)this.GetSecureHashCode()); + int GetHashCode() +#if NET + => unchecked((int)this.GetSecureHashCode()); +#else + ; +#endif } diff --git a/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs b/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs index 08c8aa73..fbb36c40 100644 --- a/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs +++ b/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs @@ -12,13 +12,21 @@ public interface IMessagePackSerializationCallbacks /// Performs any additional operations on an object before it is serialized. /// void OnBeforeSerialize() +#if NET { } +#else + ; +#endif /// /// Performs any additional operations on an object after it has been deserialized. /// void OnAfterDeserialize() +#if NET { } +#else + ; +#endif } diff --git a/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs b/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs index df0af17a..58b63f43 100644 --- a/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs +++ b/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs @@ -8,21 +8,7 @@ namespace Nerdbank.MessagePack; -/// -/// A non-generic interface that allows access to the members of the generic attributes that implement it. -/// -internal interface IKnownSubTypeAttribute -{ - /// - /// Gets a value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class. - /// - int Alias { get; } - - /// - /// Gets the shape that describes the subtype. - /// - ITypeShape Shape { get; } -} +#if NET /// /// Specifies that where the class to which this attribute is applied is the declared type in an object graph @@ -44,14 +30,13 @@ internal interface IKnownSubTypeAttribute /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] -public class KnownSubTypeAttribute(int alias) : Attribute, IKnownSubTypeAttribute +#pragma warning disable CS0618 // Type or member is obsolete +public class KnownSubTypeAttribute(int alias) : KnownSubTypeAttribute(alias, typeof(TSubType)) +#pragma warning restore CS0618 // Type or member is obsolete where TShapeProvider : IShapeable { /// - public int Alias => alias; - - /// - ITypeShape IKnownSubTypeAttribute.Shape => TShapeProvider.GetShape(); + public override ITypeShape? Shape => TShapeProvider.GetShape(); /// /// Gets the value for the . @@ -67,13 +52,57 @@ public class KnownSubTypeAttribute(int alias) : KnownSubTypeAttribute< { } +#endif + /// -/// A non-generic type for getting the name of the attribute, for use in error messages. +/// Specifies that where the class to which this attribute is applied is the declared type in an object graph +/// that certain derived types are recorded in the serialized data as well and allowed to be deserialized back +/// as their derived types. /// -internal static class KnownSubTypeAttribute +/// +/// +/// A type with one or more of these attributes applied serializes to a different schema than the same type +/// without any attributes applied. The serialized data will include a special header that indicates the runtime type. +/// Consider version compatibility issues when adding the first or removing the last attribute from a type. +/// +/// +/// Each type referenced by this attribute must have applied to it or a witness class. +/// +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] +public class KnownSubTypeAttribute : Attribute { /// /// The name of the type. /// internal const string TypeName = nameof(KnownSubTypeAttribute); + + /// + /// Initializes a new instance of the class. + /// + /// A value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class. + /// The derived-type that the represents. +#if NET + [Obsolete("Use the generic version of this attribute instead.")] +#endif + public KnownSubTypeAttribute(int alias, Type subType) + { + this.Alias = alias; + this.SubType = subType; + } + + /// + /// Gets a value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class. + /// + public int Alias { get; } + + /// + /// Gets the sub-type. + /// + public Type SubType { get; } + + /// + /// Gets the shape that describes the subtype. + /// + public virtual ITypeShape? Shape => null; } diff --git a/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs b/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs index aa2e15fe..f64adf39 100644 --- a/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs +++ b/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs @@ -149,7 +149,7 @@ public async ValueTask ReadMapHeaderAsync() this.AdvanceTo(readTo); return (int)count; case MessagePackPrimitives.DecodeResult.TokenMismatch: - throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.FirstSpan[0]); + throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.First.Span[0]); case MessagePackPrimitives.DecodeResult.InsufficientBuffer when !readResult.IsCompleted: // Fetch more data. this.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); @@ -178,7 +178,7 @@ public async ValueTask ReadArrayHeaderAsync() this.AdvanceTo(readTo); return (int)count; case MessagePackPrimitives.DecodeResult.TokenMismatch: - throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.FirstSpan[0]); + throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.First.Span[0]); case MessagePackPrimitives.DecodeResult.InsufficientBuffer when !readResult.IsCompleted: // Fetch more data. this.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); diff --git a/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs b/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs index 4ce2d3e8..e5ebf695 100644 --- a/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs +++ b/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs @@ -37,13 +37,35 @@ public class MessagePackAsyncWriter(PipeWriter pipeWriter) /// /// The writer. /// - /// The caller must take care to call before discarding the writer. + /// The caller must take care to call before discarding the writer. /// public MessagePackWriter CreateWriter() { +#if !NET + // ref fields are not supported on .NET Framework, so we have to prepare to copy the struct. + this.bufferWriter.Commit(); +#endif + return new(new BufferWriter(ref this.bufferWriter)); } + /// + /// Applies the bytes written with a writer previously obtained from back to this object. + /// + /// The writer to return. It should not be used after this. + public void ReturnWriter(ref MessagePackWriter writer) + { + writer.Flush(); + +#if !NET + // ref fields are not supported on .NET Framework, so we have to copy the struct since it'll disappear. + this.bufferWriter = writer.Writer.BufferMemoryWriter; +#endif + + // Help prevent misuse of the writer after it's been returned. + writer = default; + } + /// /// Ensures everything previously written has been flushed to the underlying . /// @@ -61,7 +83,7 @@ public void Write(SyncWriter writer, TState state) MessagePackWriter syncWriter = this.CreateWriter(); writer(ref syncWriter, state); - syncWriter.Flush(); + this.ReturnWriter(ref syncWriter); } /// @@ -69,7 +91,7 @@ public void WriteNil() { MessagePackWriter writer = this.CreateWriter(); writer.WriteNil(); - writer.Flush(); + this.ReturnWriter(ref writer); } /// @@ -99,7 +121,7 @@ public void WriteRaw(ReadOnlySpan bytes) { MessagePackWriter writer = this.CreateWriter(); writer.WriteRaw(bytes); - writer.Flush(); + this.ReturnWriter(ref writer); } /// @@ -107,7 +129,7 @@ public void WriteRaw(ReadOnlySequence bytes) { MessagePackWriter writer = this.CreateWriter(); writer.WriteRaw(bytes); - writer.Flush(); + this.ReturnWriter(ref writer); } /// diff --git a/src/Nerdbank.MessagePack/MessagePackConverter`1.cs b/src/Nerdbank.MessagePack/MessagePackConverter`1.cs index f837355b..3ad2f8f9 100644 --- a/src/Nerdbank.MessagePack/MessagePackConverter`1.cs +++ b/src/Nerdbank.MessagePack/MessagePackConverter`1.cs @@ -21,7 +21,7 @@ namespace Nerdbank.MessagePack; /// /// Read or write exactly one msgpack structure. Use an array or map header for multiple values. /// Call before any significant work. -/// Delegate serialization of sub-values to a converter obtained using rather than making a top-level call back to . +/// Delegate serialization of sub-values to a converter obtained using rather than making a top-level call back to . /// /// /// @@ -87,7 +87,7 @@ public virtual ValueTask WriteAsync(MessagePackAsyncWriter writer, T? value, Ser MessagePackWriter syncWriter = writer.CreateWriter(); this.Write(ref syncWriter, value, context); - syncWriter.Flush(); + writer.ReturnWriter(ref syncWriter); // On our way out, pause to flush the pipe if a lot of data has accumulated in the buffer. return writer.FlushIfAppropriateAsync(context); diff --git a/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs b/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs index fbe35a64..61b70d52 100644 --- a/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs +++ b/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs @@ -39,11 +39,15 @@ public override string ConvertName(string name) return name; } +#if NET return string.Create(name.Length, name, static (span, name) => { span[0] = char.ToLowerInvariant(name[0]); name.AsSpan(1).CopyTo(span.Slice(1)); }); +#else + return char.ToLowerInvariant(name[0]) + name.Substring(1); +#endif } } @@ -61,11 +65,15 @@ public override string ConvertName(string name) return name; } +#if NET return string.Create(name.Length, name, static (span, name) => { span[0] = char.ToUpperInvariant(name[0]); name.AsSpan(1).CopyTo(span.Slice(1)); }); +#else + return char.ToUpperInvariant(name[0]) + name.Substring(1); +#endif } } } diff --git a/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs b/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs index 8f4f1f17..e74206c1 100644 --- a/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs +++ b/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs @@ -141,7 +141,7 @@ public static DecodeResult TryReadArrayHeader(ReadOnlySpan source, out uin public static DecodeResult TryReadArrayHeader(ReadOnlySequence source, out uint count, out SequencePosition readTo) { // Fast and preferred path is that we have enough data all in one contiguous buffer. - DecodeResult result = TryReadArrayHeader(source.FirstSpan, out count, out int tokenSize); + DecodeResult result = TryReadArrayHeader(source.First.Span, out count, out int tokenSize); if (result == DecodeResult.Success) { readTo = source.GetPosition(tokenSize); @@ -245,7 +245,7 @@ public static DecodeResult TryReadMapHeader(ReadOnlySpan source, out uint public static DecodeResult TryReadMapHeader(ReadOnlySequence source, out uint count, out SequencePosition readTo) { // Fast and preferred path is that we have enough data all in one contiguous buffer. - DecodeResult result = TryReadMapHeader(source.FirstSpan, out count, out int tokenSize); + DecodeResult result = TryReadMapHeader(source.First.Span, out count, out int tokenSize); if (result == DecodeResult.Success) { readTo = source.GetPosition(tokenSize); diff --git a/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs b/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs index f3aa431a..a3c41fad 100644 --- a/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs +++ b/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs @@ -667,7 +667,12 @@ public static unsafe bool TryWrite(Span destination, float value, out int } destinationRef = MessagePackCode.Float32; - WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), Unsafe.BitCast(value)); +#if NET + int valueAsInt = Unsafe.BitCast(value); +#else + int valueAsInt = *(int*)&value; +#endif + WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), valueAsInt); return true; } @@ -694,7 +699,12 @@ public static unsafe bool TryWrite(Span destination, double value, out int } destinationRef = MessagePackCode.Float64; - WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), Unsafe.BitCast(value)); +#if NET + long valueAsLong = Unsafe.BitCast(value); +#else + long valueAsLong = *(long*)&value; +#endif + WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), valueAsLong); return true; } diff --git a/src/Nerdbank.MessagePack/MessagePackReader.cs b/src/Nerdbank.MessagePack/MessagePackReader.cs index 34925b3d..fbb03b68 100644 --- a/src/Nerdbank.MessagePack/MessagePackReader.cs +++ b/src/Nerdbank.MessagePack/MessagePackReader.cs @@ -1061,17 +1061,19 @@ private string ReadStringSlow(uint byteLength) int bytesRead = Math.Min(remainingByteLength, this.reader.UnreadSpan.Length); remainingByteLength -= bytesRead; bool flush = remainingByteLength == 0; -#if NETCOREAPP +#if NET initializedChars += decoder.GetChars(this.reader.UnreadSpan.Slice(0, bytesRead), charArray.AsSpan(initializedChars), flush); #else - unsafe - { - fixed (byte* pUnreadSpan = this.reader.UnreadSpan) - fixed (char* pCharArray = &charArray[initializedChars]) - { - initializedChars += decoder.GetChars(pUnreadSpan, bytesRead, pCharArray, charArray.Length - initializedChars, flush); - } - } + unsafe + { + fixed (byte* pUnreadSpan = this.reader.UnreadSpan) + { + fixed (char* pCharArray = &charArray[initializedChars]) + { + initializedChars += decoder.GetChars(pUnreadSpan, bytesRead, pCharArray, charArray.Length - initializedChars, flush); + } + } + } #endif this.reader.Advance(bytesRead); } diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs index caccfe0d..102d80d6 100644 --- a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs +++ b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs @@ -1,6 +1,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET + #pragma warning disable RS0026 // optional parameter on a method with overloads using System.Diagnostics.CodeAnalysis; @@ -126,3 +128,6 @@ public ValueTask SerializeAsync(Stream stream, in T? value, Cancel #pragma warning restore RS0027 // optional parameter on a method with overloads where TProvider : IShapeable => this.SerializeAsync(stream, value, TProvider.GetShape(), cancellationToken); } + +#endif + diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt index ec7f1d1e..1b45c9ab 100644 --- a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt +++ b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt @@ -2,6 +2,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET + #pragma warning disable RS0026 // optional parameter on a method with overloads using System.Diagnostics.CodeAnalysis; @@ -113,6 +115,9 @@ public partial record MessagePackSerializer } #> } + +#endif + <#+ enum SerializeTransport { diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs index 3b578841..3e2ba1a5 100644 --- a/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs +++ b/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs @@ -84,6 +84,71 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape await pipeWriter.CompleteAsync().ConfigureAwait(false); } + /// + /// A byte array containing the serialized msgpack. + public byte[] Serialize(in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + Requires.NotNull(provider); + + // Although the static array is thread-local, we still want to null it out while using it + // to avoid any potential issues with re-entrancy due to a converter that makes a (bad) top-level call to the serializer. + (byte[] array, scratchArray) = (scratchArray ?? new byte[65536], null); + try + { + MessagePackWriter writer = new(SequencePool.Shared, array); + this.Serialize(ref writer, value, provider, cancellationToken); + return writer.FlushAndGetArray(); + } + finally + { + scratchArray = array; + } + } + + /// + public void Serialize(IBufferWriter writer, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + MessagePackWriter msgpackWriter = new(writer); + this.Serialize(ref msgpackWriter, value, provider, cancellationToken); + msgpackWriter.Flush(); + } + + /// + /// The stream to write to. +#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + public void Serialize(Stream stream, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) +#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + { + Requires.NotNull(stream); + this.Serialize(new StreamBufferWriter(stream), value, provider, cancellationToken); + } + + /// + /// Serializes a value to a . + /// + /// + /// The stream to write to. + /// + /// + /// + /// +#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) +#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + { + // Fast path for MemoryStream. + if (stream is MemoryStream ms) + { + this.Serialize(stream, value, provider, cancellationToken); + return; + } + + PipeWriter pipeWriter = PipeWriter.Create(stream, PipeWriterOptions); + await this.SerializeAsync(pipeWriter, value, provider, cancellationToken).ConfigureAwait(false); + await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + await pipeWriter.CompleteAsync().ConfigureAwait(false); + } + /// public T? Deserialize(ReadOnlyMemory buffer, ITypeShape shape, CancellationToken cancellationToken = default) { @@ -132,7 +197,7 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape } while (bytesLastRead > 0); - return this.Deserialize(rental.Value, shape, cancellationToken); + return this.Deserialize(rental.Value, shape, cancellationToken); } } @@ -159,4 +224,80 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape await pipeReader.CompleteAsync().ConfigureAwait(false); return result; } + + /// + public T? Deserialize(ReadOnlyMemory buffer, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + MessagePackReader reader = new(buffer); + return this.Deserialize(ref reader, provider, cancellationToken); + } + + /// + /// The msgpack to deserialize from. +#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + public T? Deserialize(scoped in ReadOnlySequence buffer, ITypeShapeProvider provider, CancellationToken cancellationToken = default) +#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + { + MessagePackReader reader = new(buffer); + return this.Deserialize(ref reader, provider, cancellationToken); + } + + /// + /// The stream to deserialize from. If this stream contains more than one top-level msgpack structure, it may be positioned beyond its end after deserialization due to buffering. + /// + /// The implementation of this method currently is to buffer the entire content of the into memory before deserializing. + /// This is for simplicity and perf reasons. + /// Callers should only provide streams that are known to be small enough to fit in memory and contain only msgpack content. + /// +#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + public T? Deserialize(Stream stream, ITypeShapeProvider provider, CancellationToken cancellationToken = default) +#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + { + Requires.NotNull(stream); + + // Fast path for MemoryStream. + if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment buffer)) + { + return this.Deserialize(buffer.AsMemory(), provider, cancellationToken); + } + else + { + // We don't have a streaming msgpack reader, so buffer it all into memory instead and read from there. + using SequencePool.Rental rental = SequencePool.Shared.Rent(); + int bytesLastRead; + do + { + Span span = rental.Value.GetSpan(0); + bytesLastRead = stream.Read(span); + rental.Value.Advance(bytesLastRead); + } + while (bytesLastRead > 0); + + return this.Deserialize(rental.Value, provider, cancellationToken); + } + } + + /// + /// Deserializes a value from a . + /// + /// + /// The stream to deserialize from. If this stream contains more than one top-level msgpack structure, it may be positioned beyond its end after deserialization due to buffering. + /// + /// + /// +#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + public async ValueTask DeserializeAsync(Stream stream, ITypeShapeProvider provider, CancellationToken cancellationToken = default) +#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) + { + // Fast path for MemoryStream. + if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment buffer)) + { + return this.Deserialize(buffer.AsMemory(), provider, cancellationToken); + } + + PipeReader pipeReader = PipeReader.Create(stream, PipeReaderOptions); + T? result = await this.DeserializeAsync(pipeReader, provider, cancellationToken).ConfigureAwait(false); + await pipeReader.CompleteAsync().ConfigureAwait(false); + return result; + } } diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs index a3da9ea7..86d83389 100644 --- a/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs +++ b/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs @@ -8,6 +8,7 @@ namespace Nerdbank.MessagePack; public partial record MessagePackSerializer { +#if NET /// /// /// @@ -24,6 +25,19 @@ public JsonObject GetJsonSchema() /// public JsonObject GetJsonSchema() where TProvider : IShapeable => this.GetJsonSchema(TProvider.GetShape()); +#endif + + /// + /// + /// + /// The type whose schema should be produced. + /// + /// + public JsonObject GetJsonSchema(ITypeShapeProvider provider) + { + Requires.NotNull(provider); + return this.GetJsonSchema(provider.GetShape(typeof(T)) ?? throw new ArgumentException($"This provider had no type shape for {typeof(T)}.", nameof(provider))); + } /// /// Creates a JSON Schema that describes the msgpack serialization of the given type's shape. diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.cs index c84acdbd..e9b1530e 100644 --- a/src/Nerdbank.MessagePack/MessagePackSerializer.cs +++ b/src/Nerdbank.MessagePack/MessagePackSerializer.cs @@ -37,11 +37,15 @@ public partial record MessagePackSerializer private MultiProviderTypeCache? cachedConverters; private bool preserveReferences; +#if NET + /// /// Gets the format to use when serializing multi-dimensional arrays. /// public MultiDimensionalArrayFormat MultiDimensionalArrayFormat { get; init; } = MultiDimensionalArrayFormat.Nested; +#endif + /// /// Gets the transformation function to apply to property names before serializing them. /// @@ -241,24 +245,58 @@ public void RegisterConverter(MessagePackConverter converter) /// A cancellation token. public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShape shape, CancellationToken cancellationToken = default) { - using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken); + Requires.NotNull(shape); + using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken); this.GetOrAddConverter(shape).Write(ref writer, value, context.Value); } + /// + /// Serializes a value. + /// + /// The type to be serialized. + /// The msgpack writer to use. + /// The value to serialize. + /// + /// A cancellation token. + public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken); + this.GetOrAddConverter(provider).Write(ref writer, value, context.Value); + } + /// /// Deserializes a value. /// /// The type of value to deserialize. /// The msgpack reader to deserialize from. - /// The shape provider of . This may be the same as when the data type is attributed with , or it may be another "witness" partial class that was annotated with where T for the attribute is the same as the used here. + /// The shape of . /// A cancellation token. /// The deserialized value. public T? Deserialize(ref MessagePackReader reader, ITypeShape shape, CancellationToken cancellationToken = default) { - using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken); + Requires.NotNull(shape); + using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken); return this.GetOrAddConverter(shape).Read(ref reader, context.Value); } + /// + /// Deserializes a value. + /// + /// The type of value to deserialize. + /// The msgpack reader to deserialize from. + /// + /// The shape provider of . + /// This will typically be obtained by calling the ShapeProvider static property on a witness class + /// (a class on which has been applied). + /// + /// A cancellation token. + /// The deserialized value. + public T? Deserialize(ref MessagePackReader reader, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken); + return this.GetOrAddConverter(provider).Read(ref reader, context.Value); + } + /// /// Serializes a value using the given . /// @@ -271,16 +309,39 @@ public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShape(PipeWriter writer, T? value, ITypeShape shape, CancellationToken cancellationToken = default) { Requires.NotNull(writer); + Requires.NotNull(shape); cancellationToken.ThrowIfCancellationRequested(); #pragma warning disable NBMsgPackAsync MessagePackAsyncWriter asyncWriter = new(writer); - using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken); + using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken); await this.GetOrAddConverter(shape).WriteAsync(asyncWriter, value, context.Value).ConfigureAwait(false); asyncWriter.Flush(); #pragma warning restore NBMsgPackAsync } + /// + /// Serializes a value using the given . + /// + /// The type to be serialized. + /// The writer to use. + /// The value to serialize. + /// + /// A cancellation token. + /// A task that tracks the async serialization. + public async ValueTask SerializeAsync(PipeWriter writer, T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + Requires.NotNull(writer); + cancellationToken.ThrowIfCancellationRequested(); + +#pragma warning disable NBMsgPackAsync + MessagePackAsyncWriter asyncWriter = new(writer); + using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken); + await this.GetOrAddConverter(provider).WriteAsync(asyncWriter, value, context.Value).ConfigureAwait(false); + asyncWriter.Flush(); +#pragma warning restore NBMsgPackAsync + } + /// /// Deserializes a value from a . /// @@ -291,13 +352,31 @@ public async ValueTask SerializeAsync(PipeWriter writer, T? value, ITypeShape /// The deserialized value. public ValueTask DeserializeAsync(PipeReader reader, ITypeShape shape, CancellationToken cancellationToken = default) { + Requires.NotNull(shape); cancellationToken.ThrowIfCancellationRequested(); - using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken); + using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken); #pragma warning disable NBMsgPackAsync return this.GetOrAddConverter(shape).ReadAsync(new MessagePackAsyncReader(reader) { CancellationToken = cancellationToken }, context.Value); #pragma warning restore NBMsgPackAsync } + /// + /// Deserializes a value from a . + /// + /// The type of value to deserialize. + /// The reader to deserialize from. + /// + /// A cancellation token. + /// The deserialized value. + public ValueTask DeserializeAsync(PipeReader reader, ITypeShapeProvider provider, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken); +#pragma warning disable NBMsgPackAsync + return this.GetOrAddConverter(provider).ReadAsync(new MessagePackAsyncReader(reader) { CancellationToken = cancellationToken }, context.Value); +#pragma warning restore NBMsgPackAsync + } + /// public static string ConvertToJson(ReadOnlyMemory msgpack) => ConvertToJson(new ReadOnlySequence(msgpack)); @@ -474,6 +553,17 @@ internal MessagePackConverter GetOrAddConverter(ITypeShape shape) internal IMessagePackConverter GetOrAddConverter(ITypeShape shape) => (IMessagePackConverter)this.CachedConverters.GetOrAdd(shape)!; + /// + /// Gets a converter for the given type shape. + /// An existing converter is reused if one is found in the cache. + /// If a converter must be created, it is added to the cache for lookup next time. + /// + /// The type to convert. + /// The type shape provider. + /// A msgpack converter. + internal MessagePackConverter GetOrAddConverter(ITypeShapeProvider provider) + => (MessagePackConverter)this.CachedConverters.GetOrAddOrThrow(typeof(T), provider); + /// /// Gets a user-defined converter for the specified type if one is available. /// @@ -517,15 +607,17 @@ internal string GetSerializedPropertyName(string name, ICustomAttributeProvider? /// /// Creates a new serialization context that is ready to process a serialization job. /// + /// /// A cancellation token for the operation. /// The serialization context. /// /// Callers should be sure to always call when done with the context. /// - protected DisposableSerializationContext CreateSerializationContext(CancellationToken cancellationToken = default) + protected DisposableSerializationContext CreateSerializationContext(ITypeShapeProvider provider, CancellationToken cancellationToken = default) { + Requires.NotNull(provider); this.configurationLocked = true; - return new(this.StartingContext.Start(this, cancellationToken)); + return new(this.StartingContext.Start(this, provider, cancellationToken)); } /// diff --git a/src/Nerdbank.MessagePack/MessagePackWriter.cs b/src/Nerdbank.MessagePack/MessagePackWriter.cs index 75d68866..14f3b487 100644 --- a/src/Nerdbank.MessagePack/MessagePackWriter.cs +++ b/src/Nerdbank.MessagePack/MessagePackWriter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // This file was originally derived from https://github.com/MessagePack-CSharp/MessagePack-CSharp/ +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft; @@ -64,6 +65,12 @@ internal MessagePackWriter(BufferWriter writer) /// public bool OldSpec { get; set; } + /// + /// Gets a reference to the behind this writer. + /// + [UnscopedRef] + internal ref BufferWriter Writer => ref this.writer; + /// /// Get the number of bytes required to encode a value in msgpack. /// diff --git a/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj b/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj index 9a6e051b..c633138c 100644 --- a/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj +++ b/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj @@ -1,11 +1,12 @@  - net8.0 + net8.0;netstandard2.0 true - true + true true $(TargetsForTfmSpecificContentInPackage);PackAnalyzers A fast and more user-friendly MessagePack serialization library for .NET. With ground-up support for trimming and Native AOT. + true @@ -14,6 +15,11 @@ + + + + + TextTemplatingFileGenerator @@ -75,12 +81,15 @@ MessagePackSerializer.AutomatedFriendlyOverloads.tt + + + - + diff --git a/src/Nerdbank.MessagePack/PolyfillExtensions.cs b/src/Nerdbank.MessagePack/PolyfillExtensions.cs new file mode 100644 index 00000000..855b9544 --- /dev/null +++ b/src/Nerdbank.MessagePack/PolyfillExtensions.cs @@ -0,0 +1,305 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable SA1402 // multiple types +#pragma warning disable SA1403 // multiple namespaces + +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft; +using PolyType.Utilities; + +namespace Nerdbank.MessagePack +{ + /// + /// Utility methods to help make up for cross-targeting support. + /// + internal static partial class PolyfillExtensions + { + internal static bool TryGetNonEnumeratedCount(this IEnumerable source, out int count) + { +#if NET + return Enumerable.TryGetNonEnumeratedCount(source, out count); +#else + Requires.NotNull(source); + + switch (source) + { + case ICollection collection: + count = collection.Count; + return true; + case System.Collections.ICollection collection: + count = collection.Count; + return true; + default: + count = 0; + return false; + } +#endif + } + + internal static object GetOrAddOrThrow(this MultiProviderTypeCache cache, Type type, ITypeShapeProvider provider) + => cache.GetOrAdd(type, provider) ?? throw ThrowMissingTypeShape(type, provider); + + internal static ITypeShape GetShapeOrThrow(this ITypeShapeProvider provider, Type type) + => provider.GetShape(type) ?? throw ThrowMissingTypeShape(type, provider); + + private static Exception ThrowMissingTypeShape(Type type, ITypeShapeProvider provider) + => new ArgumentException($"The {provider.GetType().FullName} provider had no type shape for {type.FullName}.", nameof(provider)); + } + +#if !NET + /// + /// Polyfills specifically for .NET Standard targeting. + /// + internal static partial class PolyfillExtensions + { + internal static unsafe int GetChars(this Encoding encoding, ReadOnlySpan source, Span destination) + { + fixed (byte* pSource = source) + { + fixed (char* pDestination = destination) + { + return encoding.GetChars(pSource, source.Length, pDestination, destination.Length); + } + } + } + + internal static unsafe int GetChars(this Encoding encoding, ReadOnlySequence source, Span destination) + { + if (source.IsSingleSegment) + { + return GetChars(encoding, source.First.Span, destination); + } + + Decoder decoder = encoding.GetDecoder(); + int charsWritten = 0; + bool completed = true; + foreach (ReadOnlyMemory sourceSegment in source) + { + fixed (byte* pSource = sourceSegment.Span) + { + fixed (char* pDestination = destination) + { + decoder.Convert(pSource, sourceSegment.Length, pDestination, destination.Length, false, out _, out int charsUsed, out completed); + charsWritten += charsUsed; + destination = destination[charsUsed..]; + } + } + } + + if (!completed) + { + fixed (char* pDest = destination) + { + decoder.Convert(null, 0, pDest, destination.Length, flush: true, out _, out int charsUsed, out _); + charsWritten += charsUsed; + } + } + + return charsWritten; + } + + internal static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan source, Span destination) + { + fixed (char* pSource = source) + { + fixed (byte* pDestination = destination) + { + return encoding.GetBytes(pSource, source.Length, pDestination, destination.Length); + } + } + } + + internal static unsafe string GetString(this Encoding encoding, ReadOnlySpan bytes) + { + if (bytes.IsEmpty) + { + return string.Empty; + } + + fixed (byte* pBytes = bytes) + { + return encoding.GetString(pBytes, bytes.Length); + } + } + + /// + /// Reads from the stream into a memory buffer. + /// + /// The stream to read from. + /// The buffer to read directly into. + /// The number of bytes actually read. + internal static int Read(this Stream stream, Span buffer) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + byte[] array = ArrayPool.Shared.Rent(buffer.Length); + try + { + int bytesRead = stream.Read(array, 0, buffer.Length); + new Span(array, 0, bytesRead).CopyTo(buffer); + return bytesRead; + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + /// + /// Reads from the stream into a memory buffer. + /// + /// The stream to read from. + /// The buffer to read directly into. + /// A cancellation token. + /// The number of bytes actually read. + /// + /// This method shamelessly copied from the .NET Core 2.1 Stream class: https://github.com/dotnet/coreclr/blob/a113b1c803783c9d64f1f0e946ff9a853e3bc140/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L366-L391. + /// + internal static ValueTask ReadAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + return new ValueTask(stream.ReadAsync(array.Array, array.Offset, array.Count, cancellationToken)); + } + else + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + return FinishReadAsync(stream.ReadAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer, buffer); + + async ValueTask FinishReadAsync(Task readTask, byte[] localBuffer, Memory localDestination) + { + try + { + int result = await readTask.ConfigureAwait(false); + new Span(localBuffer, 0, result).CopyTo(localDestination.Span); + return result; + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + } + } + + internal static bool TryWriteBytes(this Guid value, Span destination) + { + if (destination.Length < 16) + { + return false; + } + + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.TryWrite(destination, ref value); + } + else + { + // slower path for BigEndian + Span guidSpan = stackalloc GuidBits[1]; + guidSpan[0] = value; + GuidBits endianSwitched = new(MemoryMarshal.AsBytes(guidSpan), false); + MemoryMarshal.TryWrite(destination, ref endianSwitched); + } + + return true; + } + + internal static Guid CreateGuid(ReadOnlySpan bytes) => new GuidBits(bytes, bigEndian: false); + + internal static bool IsAssignableTo(this Type left, Type right) => right.IsAssignableFrom(left); + + internal static bool TryAdd(this Dictionary dictionary, TKey key, TValue value) + { + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, value); + return true; + } + + return false; + } + + internal static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T value) + { + if (stack.Count > 0) + { + value = stack.Pop(); + return true; + } + + value = default; + return false; + } + + internal static Exception ThrowNotSupportedOnNETFramework() => throw new PlatformNotSupportedException("This functionality is only supported on .NET."); + + private readonly struct GuidBits + { + private readonly int a; + private readonly short b; + private readonly short c; +#pragma warning disable CS0169 // The field is never used + private readonly byte d; + private readonly byte e; + private readonly byte f; + private readonly byte g; + private readonly byte h; + private readonly byte i; + private readonly byte j; + private readonly byte k; +#pragma warning restore CS0169 // The field is never used + + internal GuidBits(ReadOnlySpan b, bool bigEndian = false) + { + if (b.Length != 16) + { + throw new ArgumentException(); + } + + this = MemoryMarshal.Read(b); + + if (bigEndian == BitConverter.IsLittleEndian) + { + this.a = BinaryPrimitives.ReverseEndianness(this.a); + this.b = BinaryPrimitives.ReverseEndianness(this.b); + this.c = BinaryPrimitives.ReverseEndianness(this.c); + } + } + + public static implicit operator Guid(GuidBits value) => Unsafe.As(ref value); + + public static implicit operator GuidBits(Guid value) => Unsafe.As(ref value); + } + } +#endif +} + +#if !NET + +namespace System.Diagnostics +{ + internal class UnreachableException : Exception + { + internal UnreachableException() + : base("This code path should be unreachable.") + { + } + } +} + +#endif diff --git a/src/Nerdbank.MessagePack/RawMessagePack.cs b/src/Nerdbank.MessagePack/RawMessagePack.cs index 3e7ccdde..dc52880c 100644 --- a/src/Nerdbank.MessagePack/RawMessagePack.cs +++ b/src/Nerdbank.MessagePack/RawMessagePack.cs @@ -112,6 +112,9 @@ public RawMessagePack ToOwned() } private static bool SequenceEqual(in ReadOnlySequence a, in ReadOnlySequence b) +#if !NET + where T : IEquatable +#endif { if (a.Length != b.Length) { @@ -120,7 +123,11 @@ private static bool SequenceEqual(in ReadOnlySequence a, in ReadOnlySequen if (a.IsSingleSegment && b.IsSingleSegment) { +#if NET return a.FirstSpan.SequenceEqual(b.FirstSpan); +#else + return a.First.Span.SequenceEqual(b.First.Span); +#endif } ReadOnlySequence.Enumerator aEnumerator = a.GetEnumerator(); diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs index 11c6f204..d34550b0 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs index f6a66a17..48a9765b 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; -using System.Security.AccessControl; namespace Nerdbank.MessagePack.SecureHash; @@ -27,7 +26,14 @@ private ByValueByteArrayEqualityComparer() public int GetHashCode([DisallowNull] byte[] obj) { HashCode hashCode = default; +#if NET hashCode.AddBytes(obj); +#else + for (int i = 0; i < obj.Length; i++) + { + hashCode.Add(obj[i]); + } +#endif return hashCode.ToHashCode(); } } diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs index 083543c5..47f5684f 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; namespace Nerdbank.MessagePack.SecureHash; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs index e928774d..0fdf5c53 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; namespace Nerdbank.MessagePack.SecureHash; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs index 4c8590a9..d271b9cf 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; @@ -28,8 +33,8 @@ public bool Equals(TEnumerable? x, TEnumerable? y) IEnumerable enumerableX = getEnumerable(x); IEnumerable enumerableY = getEnumerable(y); - if (Enumerable.TryGetNonEnumeratedCount(enumerableX, out int countX) && - Enumerable.TryGetNonEnumeratedCount(enumerableY, out int countY) && + if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerableX, out int countX) && + PolyfillExtensions.TryGetNonEnumeratedCount(enumerableY, out int countY) && countX != countY) { return false; @@ -60,6 +65,11 @@ public int GetHashCode([DisallowNull] TEnumerable obj) hashes.Add(element is null ? 0 : equalityComparer.GetHashCode(element)); } - return unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(CollectionsMarshal.AsSpan(hashes)))); +#if NET + Span span = CollectionsMarshal.AsSpan(hashes); +#else + Span span = hashes.ToArray(); +#endif + return unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(span))); } } diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs index 007e1d36..dac80878 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs index 10fba6fc..4c1043b4 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; namespace Nerdbank.MessagePack.SecureHash; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs index 6b732596..e09a3702 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Diagnostics.CodeAnalysis; namespace Nerdbank.MessagePack.SecureHash; diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs index ae5d7553..cc78de44 100644 --- a/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs +++ b/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs @@ -86,6 +86,11 @@ internal class DelayedEqualityComparerFactory : IDelayedValueFactory public DelayedValue Create(ITypeShape typeShape) => new DelayedValue>(self => new DelayedEqualityComparer(self)); +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + private class DelayedEqualityComparer(DelayedValue> self) : IEqualityComparer { public bool Equals(T? x, T? y) => self.Result.Equals(x, y); diff --git a/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs b/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs index 81d55679..54bc7d23 100644 --- a/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs +++ b/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs @@ -1,6 +1,10 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS1574 // unresolvable cref +#endif + using System.Runtime.InteropServices; namespace Nerdbank.MessagePack.SecureHash; @@ -24,8 +28,25 @@ internal class CollisionResistantHasherUnmanaged : SecureEqualityComparer { /// public override bool Equals(T x, T y) - => MemoryMarshal.Cast(new Span(ref x)).SequenceEqual(MemoryMarshal.Cast(new Span(ref y))); + { +#if NET + Span ySpan = new(ref y); + Span xSpan = new(ref x); +#else + Span ySpan = stackalloc T[1] { y }; + Span xSpan = stackalloc T[1] { x }; +#endif + return MemoryMarshal.Cast(xSpan).SequenceEqual(MemoryMarshal.Cast(ySpan)); + } /// - public override long GetSecureHashCode(T value) => SipHash.Default.Compute(MemoryMarshal.Cast(new Span(ref value))); + public override long GetSecureHashCode(T value) + { +#if NET + Span span = new(ref value); +#else + Span span = stackalloc T[1] { value }; +#endif + return SipHash.Default.Compute(MemoryMarshal.Cast(span)); + } } diff --git a/src/Nerdbank.MessagePack/SecureHash/HashResistantPrimitives.cs b/src/Nerdbank.MessagePack/SecureHash/HashResistantPrimitives.cs index 33aac0af..fd5a1bd1 100644 --- a/src/Nerdbank.MessagePack/SecureHash/HashResistantPrimitives.cs +++ b/src/Nerdbank.MessagePack/SecureHash/HashResistantPrimitives.cs @@ -3,8 +3,15 @@ #pragma warning disable SA1600 // Elements should be documented +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Collections; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -16,7 +23,15 @@ internal static class HashResistantPrimitives { private static int SecureHash(T value) where T : unmanaged - => unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(new Span(ref value)))); + { +#if NET + Span span = new Span(ref value); +#else + Span span = stackalloc T[1] { value }; +#endif + + return unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(span))); + } private static int SecureHash(ReadOnlySpan data) => unchecked((int)SipHash.Default.Compute(data)); @@ -27,6 +42,8 @@ internal class BooleanEqualityComparer : CollisionResistantHasherUnmanaged public override bool Equals(bool x, bool y) => x is true == y is true; } +#if NET + internal class HalfEqualityComparer : CollisionResistantHasherUnmanaged { /// @@ -37,6 +54,8 @@ public override unsafe long GetSecureHashCode(Half value) value); } +#endif + internal class SingleEqualityComparer : CollisionResistantHasherUnmanaged { /// @@ -75,6 +94,8 @@ internal class DateTimeOffsetEqualityComparer : CollisionResistantHasherUnmanage internal class StringEqualityComparer : SecureEqualityComparer { + internal static readonly StringEqualityComparer Instance = new(); + /// public override long GetSecureHashCode(string value) { @@ -106,9 +127,13 @@ internal class BigIntegerEqualityComparer : SecureEqualityComparer /// public override long GetSecureHashCode([DisallowNull] BigInteger obj) { +#if NET Span bytes = stackalloc byte[obj.GetByteCount()]; Assumes.True(obj.TryWriteBytes(bytes, out _)); return SecureHash(bytes); +#else + return SecureHash(obj.ToByteArray()); +#endif } } @@ -120,6 +145,7 @@ internal class DecimalEqualityComparer : SecureEqualityComparer /// public override long GetSecureHashCode([DisallowNull] decimal obj) { +#if NET Span bytes = stackalloc int[500]; if (!decimal.TryGetBits(obj, bytes, out int length)) { @@ -127,6 +153,9 @@ public override long GetSecureHashCode([DisallowNull] decimal obj) } return SecureHash(MemoryMarshal.Cast(bytes[..length])); +#else + return StringEqualityComparer.Instance.GetSecureHashCode(obj.ToString(CultureInfo.InvariantCulture)); +#endif } } diff --git a/src/Nerdbank.MessagePack/SecureHash/SecureEnumerableEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/SecureEnumerableEqualityComparer`2.cs index dcffe5c1..d8b8a175 100644 --- a/src/Nerdbank.MessagePack/SecureHash/SecureEnumerableEqualityComparer`2.cs +++ b/src/Nerdbank.MessagePack/SecureHash/SecureEnumerableEqualityComparer`2.cs @@ -28,8 +28,8 @@ public override bool Equals(TEnumerable? x, TEnumerable? y) IEnumerable enumerableX = getEnumerable(x); IEnumerable enumerableY = getEnumerable(y); - if (Enumerable.TryGetNonEnumeratedCount(enumerableX, out int countX) && - Enumerable.TryGetNonEnumeratedCount(enumerableY, out int countY) && + if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerableX, out int countX) && + PolyfillExtensions.TryGetNonEnumeratedCount(enumerableY, out int countY) && countX != countY) { return false; @@ -54,12 +54,17 @@ public override long GetSecureHashCode([DisallowNull] TEnumerable obj) IEnumerable enumerable = getEnumerable(obj); // Ideally we could switch this to a SIP hash implementation that can process additional data in chunks with a constant amount of memory. - List hashes = Enumerable.TryGetNonEnumeratedCount(enumerable, out int count) ? new(count) : new(); + List hashes = PolyfillExtensions.TryGetNonEnumeratedCount(enumerable, out int count) ? new(count) : new(); foreach (TElement element in enumerable) { hashes.Add(element is null ? 0 : equalityComparer.GetSecureHashCode(element)); } - return SipHash.Default.Compute(MemoryMarshal.Cast(CollectionsMarshal.AsSpan(hashes))); +#if NET + Span span = CollectionsMarshal.AsSpan(hashes); +#else + Span span = hashes.ToArray(); +#endif + return SipHash.Default.Compute(MemoryMarshal.Cast(span)); } } diff --git a/src/Nerdbank.MessagePack/SecureHash/SecureEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/SecureEqualityComparer`1.cs index 26f41802..e2ce69b1 100644 --- a/src/Nerdbank.MessagePack/SecureHash/SecureEqualityComparer`1.cs +++ b/src/Nerdbank.MessagePack/SecureHash/SecureEqualityComparer`1.cs @@ -1,12 +1,13 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Collections; -using System.Collections.Frozen; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Text; namespace Nerdbank.MessagePack.SecureHash; diff --git a/src/Nerdbank.MessagePack/SecureHash/SecureVisitor.cs b/src/Nerdbank.MessagePack/SecureHash/SecureVisitor.cs index e2dfb392..9c254821 100644 --- a/src/Nerdbank.MessagePack/SecureHash/SecureVisitor.cs +++ b/src/Nerdbank.MessagePack/SecureHash/SecureVisitor.cs @@ -21,7 +21,6 @@ internal class SecureVisitor(TypeGenerationContext context) : TypeShapeVisitor, internal static readonly FrozenDictionary HashResistantPrimitiveEqualityComparers = new Dictionary() { { typeof(char), new CollisionResistantHasherUnmanaged() }, - { typeof(Rune), new CollisionResistantHasherUnmanaged() }, { typeof(byte), new CollisionResistantHasherUnmanaged() }, { typeof(ushort), new CollisionResistantHasherUnmanaged() }, { typeof(uint), new CollisionResistantHasherUnmanaged() }, @@ -31,22 +30,25 @@ internal class SecureVisitor(TypeGenerationContext context) : TypeShapeVisitor, { typeof(int), new CollisionResistantHasherUnmanaged() }, { typeof(long), new CollisionResistantHasherUnmanaged() }, { typeof(BigInteger), new HashResistantPrimitives.BigIntegerEqualityComparer() }, - { typeof(Int128), new CollisionResistantHasherUnmanaged() }, - { typeof(UInt128), new CollisionResistantHasherUnmanaged() }, { typeof(string), new HashResistantPrimitives.StringEqualityComparer() }, { typeof(bool), new HashResistantPrimitives.BooleanEqualityComparer() }, { typeof(Version), new HashResistantPrimitives.VersionEqualityComparer() }, { typeof(Uri), new HashResistantPrimitives.AlreadySecureEqualityComparer() }, - { typeof(Half), new HashResistantPrimitives.HalfEqualityComparer() }, { typeof(float), new HashResistantPrimitives.SingleEqualityComparer() }, { typeof(double), new HashResistantPrimitives.DoubleEqualityComparer() }, { typeof(decimal), new HashResistantPrimitives.DecimalEqualityComparer() }, - { typeof(TimeOnly), new CollisionResistantHasherUnmanaged() }, - { typeof(DateOnly), new CollisionResistantHasherUnmanaged() }, { typeof(DateTime), new HashResistantPrimitives.DateTimeEqualityComparer() }, { typeof(DateTimeOffset), new HashResistantPrimitives.DateTimeOffsetEqualityComparer() }, { typeof(TimeSpan), new CollisionResistantHasherUnmanaged() }, { typeof(Guid), new CollisionResistantHasherUnmanaged() }, +#if NET + { typeof(Int128), new CollisionResistantHasherUnmanaged() }, + { typeof(UInt128), new CollisionResistantHasherUnmanaged() }, + { typeof(Rune), new CollisionResistantHasherUnmanaged() }, + { typeof(Half), new HashResistantPrimitives.HalfEqualityComparer() }, + { typeof(TimeOnly), new CollisionResistantHasherUnmanaged() }, + { typeof(DateOnly), new CollisionResistantHasherUnmanaged() }, +#endif }.ToFrozenDictionary(); /// diff --git a/src/Nerdbank.MessagePack/SequenceReader`1.cs b/src/Nerdbank.MessagePack/SequenceReader`1.cs new file mode 100644 index 00000000..947be4ef --- /dev/null +++ b/src/Nerdbank.MessagePack/SequenceReader`1.cs @@ -0,0 +1,507 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Nerdbank.MessagePack; + +/// +/// A .NET Framework implementation of the SequenceReader from .NET. +/// +/// The type of element to be read. +internal ref struct SequenceReader + where T : unmanaged, IEquatable +{ + /// + /// A value indicating whether we're using (as opposed to . + /// + private bool usingSequence; + + /// + /// Backing for the entire sequence when we're not using . + /// + private ReadOnlySequence sequence; + + /// + /// The position at the start of the . + /// + private SequencePosition currentPosition; + + /// + /// The position at the end of the . + /// + private SequencePosition nextPosition; + + /// + /// Backing for the entire sequence when we're not using . + /// + private ReadOnlyMemory memory; + + /// + /// A value indicating whether there is unread data remaining. + /// + private bool moreData; + + /// + /// The total number of elements in the sequence. + /// + private long length; + + /// + /// Initializes a new instance of the struct + /// over the given . + /// + /// The sequence to read. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SequenceReader(scoped in ReadOnlySequence sequence) + { + this.usingSequence = true; + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.sequence = sequence; + this.memory = default; + this.currentPosition = sequence.Start; + this.length = -1; + + ReadOnlySpan first = sequence.First.Span; + this.nextPosition = sequence.GetPosition(first.Length); + this.CurrentSpan = first; + this.moreData = first.Length > 0; + + if (!this.moreData && !sequence.IsSingleSegment) + { + this.moreData = true; + this.GetNextSpan(); + } + } + + /// + /// Initializes a new instance of the struct + /// over the given . + /// + /// The memory to read. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SequenceReader(ReadOnlyMemory memory) + { + this.usingSequence = false; + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.memory = memory; + this.CurrentSpan = memory.Span; + this.length = memory.Length; + this.moreData = memory.Length > 0; + + this.currentPosition = default; + this.nextPosition = default; + this.sequence = default; + } + + /// + /// Gets a value indicating whether there is no more data in the . + /// + public readonly bool End => !this.moreData; + + /// + /// Gets the underlying for the reader. + /// + public ReadOnlySequence Sequence + { + get + { + if (this.sequence.IsEmpty && !this.memory.IsEmpty) + { + // We're in memory mode (instead of sequence mode). + // Lazily fill in the sequence data. + this.sequence = new ReadOnlySequence(this.memory); + this.currentPosition = this.sequence.Start; + this.nextPosition = this.sequence.End; + } + + return this.sequence; + } + } + + /// + /// Gets the current position in the . + /// + public SequencePosition Position + => this.Sequence.GetPosition(this.CurrentSpanIndex, this.currentPosition); + + /// + /// Gets the current segment in the as a span. + /// + public ReadOnlySpan CurrentSpan { get; private set; } + + /// + /// Gets the index in the . + /// + public int CurrentSpanIndex { get; private set; } + + /// + /// Gets the unread portion of the . + /// + public readonly ReadOnlySpan UnreadSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.CurrentSpan.Slice(this.CurrentSpanIndex); + } + + /// + /// Gets the total number of 's processed by the reader. + /// + public long Consumed { get; private set; } + + /// + /// Gets remaining 's in the reader's . + /// + public long Remaining => this.Length - this.Consumed; + + /// + /// Gets count of in the reader's . + /// + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (this.length < 0) + { + // Cache the length + this.length = this.Sequence.Length; + } + + return this.length; + } + } + + /// + /// Peeks at the next value without advancing the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPeek(out T value) + { + if (this.moreData) + { + value = this.CurrentSpan[this.CurrentSpanIndex]; + return true; + } + else + { + value = default; + return false; + } + } + + /// + /// Read the next value and advance the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRead(out T value) + { + if (this.End) + { + value = default; + return false; + } + + value = this.CurrentSpan[this.CurrentSpanIndex]; + this.CurrentSpanIndex++; + this.Consumed++; + + if (this.CurrentSpanIndex >= this.CurrentSpan.Length) + { + if (this.usingSequence) + { + this.GetNextSpan(); + } + else + { + this.moreData = false; + } + } + + return true; + } + + /// + /// Move the reader back the specified number of items. + /// + /// The number of elements to move the reader backwards. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Rewind(long count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + this.Consumed -= count; + + if (this.CurrentSpanIndex >= count) + { + this.CurrentSpanIndex -= (int)count; + this.moreData = true; + } + else if (this.usingSequence) + { + // Current segment doesn't have enough data, scan backward through segments + this.RetreatToPreviousSpan(this.Consumed); + } + else + { + throw new ArgumentOutOfRangeException("Rewind went past the start of the memory."); + } + } + + /// + /// Move the reader ahead the specified number of items. + /// + /// The number of elements to move the reader beyond. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(long count) + { + const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); + if ((count & TooBigOrNegative) == 0 && this.CurrentSpan.Length - this.CurrentSpanIndex > (int)count) + { + this.CurrentSpanIndex += (int)count; + this.Consumed += count; + } + else if (this.usingSequence) + { + // Can't satisfy from the current span + this.AdvanceToNextSpan(count); + } + else if (this.CurrentSpan.Length - this.CurrentSpanIndex == (int)count) + { + this.CurrentSpanIndex += (int)count; + this.Consumed += count; + this.moreData = false; + } + else + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + /// + /// Copies data from the current to the given span. + /// + /// Destination to copy to. + /// True if there is enough data to copy to the . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryCopyTo(Span destination) + { + ReadOnlySpan firstSpan = this.UnreadSpan; + if (firstSpan.Length >= destination.Length) + { + firstSpan.Slice(0, destination.Length).CopyTo(destination); + return true; + } + + return !this.sequence.IsEmpty && this.TryCopyMultisegment(destination); + } + + /// + /// Unchecked helper to avoid unnecessary checks where you know count is valid. + /// + /// The number of elements to move the reader beyond. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceCurrentSpan(long count) + { + Debug.Assert(count >= 0, "count >= 0"); + + this.Consumed += count; + this.CurrentSpanIndex += (int)count; + if (this.usingSequence && this.CurrentSpanIndex >= this.CurrentSpan.Length) + { + this.GetNextSpan(); + } + } + + /// + /// Only call this helper if you know that you are advancing in the current span + /// with valid count and there is no need to fetch the next one. + /// + /// The number of elements to move the reader beyond. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceWithinSpan(long count) + { + Debug.Assert(count >= 0, "count >= 0"); + + this.Consumed += count; + this.CurrentSpanIndex += (int)count; + + Debug.Assert(this.CurrentSpanIndex < this.CurrentSpan.Length, "this.CurrentSpanIndex < this.CurrentSpan.Length"); + } + + /// + /// Move the reader ahead the specified number of items + /// if there are enough elements remaining in the sequence. + /// + /// The number of elements to move the reader beyond. + /// if there were enough elements to advance; otherwise . + internal bool TryAdvance(long count) + { + if (this.Remaining < count) + { + return false; + } + + this.Advance(count); + return true; + } + + private void AdvanceToNextSpan(long count) + { + Debug.Assert(this.usingSequence, "usingSequence"); + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + this.Consumed += count; + while (this.moreData) + { + int remaining = this.CurrentSpan.Length - this.CurrentSpanIndex; + + if (remaining > count) + { + this.CurrentSpanIndex += (int)count; + count = 0; + break; + } + + // As there may not be any further segments we need to + // push the current index to the end of the span. + this.CurrentSpanIndex += remaining; + count -= remaining; + Debug.Assert(count >= 0, "count >= 0"); + + this.GetNextSpan(); + + if (count == 0) + { + break; + } + } + + if (count != 0) + { + // Not enough data left- adjust for where we actually ended and throw + this.Consumed -= count; + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RetreatToPreviousSpan(long consumed) + { + Debug.Assert(this.usingSequence, "usingSequence"); + this.ResetReader(); + this.Advance(consumed); + } + + private void ResetReader() + { + Debug.Assert(this.usingSequence, "usingSequence"); + this.CurrentSpanIndex = 0; + this.Consumed = 0; + this.currentPosition = this.Sequence.Start; + this.nextPosition = this.currentPosition; + + if (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true)) + { + this.moreData = true; + + if (memory.Length == 0) + { + this.CurrentSpan = default; + + // No data in the first span, move to one with data + this.GetNextSpan(); + } + else + { + this.CurrentSpan = memory.Span; + } + } + else + { + // No data in any spans and at end of sequence + this.moreData = false; + this.CurrentSpan = default; + } + } + + /// + /// Get the next segment with available data, if any. + /// + private void GetNextSpan() + { + Debug.Assert(this.usingSequence, "usingSequence"); + if (!this.Sequence.IsSingleSegment) + { + SequencePosition previousNextPosition = this.nextPosition; + while (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true)) + { + this.currentPosition = previousNextPosition; + if (memory.Length > 0) + { + this.CurrentSpan = memory.Span; + this.CurrentSpanIndex = 0; + return; + } + else + { + this.CurrentSpan = default; + this.CurrentSpanIndex = 0; + previousNextPosition = this.nextPosition; + } + } + } + + this.moreData = false; + } + + private readonly bool TryCopyMultisegment(Span destination) + { + long length = this.length < 0 ? this.sequence.Length : this.length; + long remaining = length - this.Consumed; + if (remaining < destination.Length) + { + return false; + } + + ReadOnlySpan firstSpan = this.UnreadSpan; + Debug.Assert(firstSpan.Length < destination.Length, "firstSpan.Length < destination.Length"); + firstSpan.CopyTo(destination); + int copied = firstSpan.Length; + + SequencePosition next = this.nextPosition; + while (this.sequence.TryGet(ref next, out ReadOnlyMemory nextSegment, true)) + { + if (nextSegment.Length > 0) + { + ReadOnlySpan nextSpan = nextSegment.Span; + int toCopy = Math.Min(nextSpan.Length, destination.Length - copied); + nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); + copied += toCopy; + if (copied >= destination.Length) + { + break; + } + } + } + + return true; + } +} + +#endif diff --git a/src/Nerdbank.MessagePack/SerializationContext.cs b/src/Nerdbank.MessagePack/SerializationContext.cs index 1bbc7fbe..0d1c99b8 100644 --- a/src/Nerdbank.MessagePack/SerializationContext.cs +++ b/src/Nerdbank.MessagePack/SerializationContext.cs @@ -50,6 +50,11 @@ public SerializationContext() /// public CancellationToken CancellationToken { get; init; } + /// + /// Gets the type shape provider that applies to the serialization operation. + /// + public ITypeShapeProvider? TypeShapeProvider { get; internal init; } + /// /// Gets the that owns this context. /// @@ -77,38 +82,53 @@ public void DepthStep() } } +#if NET + /// + public MessagePackConverter GetConverter() + where T : IShapeable + { + Verify.Operation(this.Owner is not null, "No serialization operation is in progress."); + MessagePackConverter result = this.Owner.GetOrAddConverter(T.GetShape()); + return this.ReferenceEqualityTracker is null ? result : result.WrapWithReferencePreservation(); + } + /// /// Gets a converter for a specific type. /// /// The type to be converted. + /// The type that provides the shape of the type to be converted. /// The converter. /// Thrown if no serialization operation is in progress. /// /// This method is intended only for use by custom converters in order to delegate conversion of sub-values. /// - public MessagePackConverter GetConverter() - where T : IShapeable + public MessagePackConverter GetConverter() + where TProvider : IShapeable { Verify.Operation(this.Owner is not null, "No serialization operation is in progress."); - MessagePackConverter result = this.Owner.GetOrAddConverter(T.GetShape()); + MessagePackConverter result = this.Owner.GetOrAddConverter(TProvider.GetShape()); return this.ReferenceEqualityTracker is null ? result : result.WrapWithReferencePreservation(); } +#endif /// /// Gets a converter for a specific type. /// /// The type to be converted. - /// The type that provides the shape of the type to be converted. + /// + /// + /// It can also come from . + /// A null value will be filled in with . + /// /// The converter. /// Thrown if no serialization operation is in progress. /// /// This method is intended only for use by custom converters in order to delegate conversion of sub-values. /// - public MessagePackConverter GetConverter() - where TProvider : IShapeable + public MessagePackConverter GetConverter(ITypeShapeProvider? provider) { Verify.Operation(this.Owner is not null, "No serialization operation is in progress."); - MessagePackConverter result = this.Owner.GetOrAddConverter(TProvider.GetShape()); + MessagePackConverter result = this.Owner.GetOrAddConverter(provider ?? this.TypeShapeProvider ?? throw new UnreachableException()); return this.ReferenceEqualityTracker is null ? result : result.WrapWithReferencePreservation(); } @@ -116,14 +136,16 @@ public MessagePackConverter GetConverter() /// Starts a new serialization operation. /// /// The owning serializer. + /// /// A cancellation token to associate with this serialization operation. /// The new context for the operation. - internal SerializationContext Start(MessagePackSerializer owner, CancellationToken cancellationToken) + internal SerializationContext Start(MessagePackSerializer owner, ITypeShapeProvider provider, CancellationToken cancellationToken) { return this with { Owner = owner, ReferenceEqualityTracker = owner.PreserveReferences ? ReusableObjectPool.Take(owner) : null, + TypeShapeProvider = provider, CancellationToken = cancellationToken, }; } diff --git a/src/Nerdbank.MessagePack/StandardVisitor.cs b/src/Nerdbank.MessagePack/StandardVisitor.cs index b4ed7b06..ab769b59 100644 --- a/src/Nerdbank.MessagePack/StandardVisitor.cs +++ b/src/Nerdbank.MessagePack/StandardVisitor.cs @@ -21,7 +21,6 @@ internal class StandardVisitor : TypeShapeVisitor, ITypeShapeFunc private static readonly FrozenDictionary PrimitiveConverters = new Dictionary() { { typeof(char), new CharConverter() }, - { typeof(Rune), new RuneConverter() }, { typeof(byte), new ByteConverter() }, { typeof(ushort), new UInt16Converter() }, { typeof(uint), new UInt32Converter() }, @@ -31,18 +30,13 @@ internal class StandardVisitor : TypeShapeVisitor, ITypeShapeFunc { typeof(int), new Int32Converter() }, { typeof(long), new Int64Converter() }, { typeof(BigInteger), new BigIntegerConverter() }, - { typeof(Int128), new Int128Converter() }, - { typeof(UInt128), new UInt128Converter() }, { typeof(string), new StringConverter() }, { typeof(bool), new BooleanConverter() }, { typeof(Version), new VersionConverter() }, { typeof(Uri), new UriConverter() }, - { typeof(Half), new HalfConverter() }, { typeof(float), new SingleConverter() }, { typeof(double), new DoubleConverter() }, { typeof(decimal), new DecimalConverter() }, - { typeof(TimeOnly), new TimeOnlyConverter() }, - { typeof(DateOnly), new DateOnlyConverter() }, { typeof(DateTime), new DateTimeConverter() }, { typeof(DateTimeOffset), new DateTimeOffsetConverter() }, { typeof(TimeSpan), new TimeSpanConverter() }, @@ -50,6 +44,14 @@ internal class StandardVisitor : TypeShapeVisitor, ITypeShapeFunc { typeof(byte[]), ByteArrayConverter.Instance }, { typeof(Memory), new MemoryOfByteConverter() }, { typeof(ReadOnlyMemory), new ReadOnlyMemoryOfByteConverter() }, +#if NET + { typeof(Rune), new RuneConverter() }, + { typeof(Int128), new Int128Converter() }, + { typeof(UInt128), new UInt128Converter() }, + { typeof(Half), new HalfConverter() }, + { typeof(TimeOnly), new TimeOnlyConverter() }, + { typeof(DateOnly), new DateOnlyConverter() }, +#endif }.ToFrozenDictionary(); private static readonly FrozenDictionary PrimitiveReferencePreservingConverters = PrimitiveConverters.ToFrozenDictionary( @@ -228,7 +230,7 @@ internal StandardVisitor(MessagePackSerializer owner, TypeGenerationContext cont defaultValue = attributeDefaultValue; } - shouldSerialize = obj => !eq.Equals(getter(ref obj), defaultValue); + shouldSerialize = obj => !eq.Equals(getter(ref obj), defaultValue!); } SerializeProperty serialize = (in TDeclaringType container, ref MessagePackWriter writer, SerializationContext context) => @@ -426,21 +428,28 @@ internal StandardVisitor(MessagePackSerializer owner, TypeGenerationContext cont if (enumerableShape.Type.IsArray) { + MessagePackConverter? converter; if (enumerableShape.Rank > 1) { +#if NET return this.owner.MultiDimensionalArrayFormat switch { MultiDimensionalArrayFormat.Nested => new ArrayWithNestedDimensionsConverter(elementConverter, enumerableShape.Rank), MultiDimensionalArrayFormat.Flat => new ArrayWithFlattenedDimensionsConverter(elementConverter), _ => throw new NotSupportedException(), }; +#else + throw PolyfillExtensions.ThrowNotSupportedOnNETFramework(); +#endif } +#if NET else if (!this.owner.DisableHardwareAcceleration && enumerableShape.ConstructionStrategy == CollectionConstructionStrategy.Span && - HardwareAccelerated.TryGetConverter(out MessagePackConverter? converter)) + HardwareAccelerated.TryGetConverter(out converter)) { return converter; } +#endif else if (enumerableShape.ConstructionStrategy == CollectionConstructionStrategy.Span && ArraysOfPrimitivesConverters.TryGetConverter(enumerableShape.GetGetEnumerable(), enumerableShape.GetSpanConstructor(), out converter)) { @@ -457,7 +466,9 @@ internal StandardVisitor(MessagePackSerializer owner, TypeGenerationContext cont { CollectionConstructionStrategy.None => new EnumerableConverter(getEnumerable, elementConverter), CollectionConstructionStrategy.Mutable => new MutableEnumerableConverter(getEnumerable, elementConverter, enumerableShape.GetAddElement(), enumerableShape.GetDefaultConstructor()), +#if NET CollectionConstructionStrategy.Span when !this.owner.DisableHardwareAcceleration && HardwareAccelerated.TryGetConverter(out MessagePackConverter? converter) => converter, +#endif CollectionConstructionStrategy.Span when ArraysOfPrimitivesConverters.TryGetConverter(getEnumerable, enumerableShape.GetSpanConstructor(), out MessagePackConverter? converter) => converter, CollectionConstructionStrategy.Span => new SpanEnumerableConverter(getEnumerable, elementConverter, enumerableShape.GetSpanConstructor()), CollectionConstructionStrategy.Enumerable => new EnumerableEnumerableConverter(getEnumerable, elementConverter, enumerableShape.GetEnumerableConstructor()), @@ -503,7 +514,7 @@ protected IMessagePackConverter GetConverter(ITypeShape shape, object? state = n /// Thrown if has any that violates rules. private SubTypes? DiscoverUnionTypes(IObjectTypeShape objectShape) { - IKnownSubTypeAttribute[]? unionAttributes = objectShape.AttributeProvider?.GetCustomAttributes(typeof(IKnownSubTypeAttribute), false).Cast().ToArray(); + KnownSubTypeAttribute[]? unionAttributes = objectShape.AttributeProvider?.GetCustomAttributes(typeof(KnownSubTypeAttribute), false).Cast().ToArray(); if (unionAttributes is null or { Length: 0 }) { return null; @@ -511,10 +522,10 @@ protected IMessagePackConverter GetConverter(ITypeShape shape, object? state = n Dictionary deserializerData = new(); Dictionary serializerData = new(); - foreach (IKnownSubTypeAttribute unionAttribute in unionAttributes) + foreach (KnownSubTypeAttribute unionAttribute in unionAttributes) { - ITypeShape subtypeShape = unionAttribute.Shape; - Verify.Operation(objectShape.Type.IsAssignableFrom(subtypeShape.Type), $"The type {objectShape.Type.FullName} has a {KnownSubTypeAttribute.TypeName} that references non-derived {unionAttribute.Shape.Type.FullName}."); + ITypeShape subtypeShape = unionAttribute.Shape ?? objectShape.Provider.GetShapeOrThrow(unionAttribute.SubType); + Verify.Operation(objectShape.Type.IsAssignableFrom(subtypeShape.Type), $"The type {objectShape.Type.FullName} has a {KnownSubTypeAttribute.TypeName} that references non-derived {subtypeShape.Type.FullName}."); IMessagePackConverter converter = this.GetConverter(subtypeShape); Verify.Operation(deserializerData.TryAdd(unionAttribute.Alias, converter), $"The type {objectShape.Type.FullName} has more than one {KnownSubTypeAttribute.TypeName} with a duplicate alias: {unionAttribute.Alias}."); diff --git a/src/Nerdbank.MessagePack/StreamBufferWriter.cs b/src/Nerdbank.MessagePack/StreamBufferWriter.cs index b8253cca..e609d41f 100644 --- a/src/Nerdbank.MessagePack/StreamBufferWriter.cs +++ b/src/Nerdbank.MessagePack/StreamBufferWriter.cs @@ -23,7 +23,7 @@ public void Advance(int count) Verify.NotDisposed(!this.disposed, this); Assumes.NotNull(this.buffer); - stream.Write(this.buffer[..count]); + stream.Write(this.buffer, 0, count); this.advanceNext = false; } diff --git a/src/Nerdbank.MessagePack/StringEncoding.cs b/src/Nerdbank.MessagePack/StringEncoding.cs index 0d17c610..3a52fa68 100644 --- a/src/Nerdbank.MessagePack/StringEncoding.cs +++ b/src/Nerdbank.MessagePack/StringEncoding.cs @@ -27,10 +27,10 @@ internal static class StringEncoding /// internal static void GetEncodedStringBytes(string value, out ReadOnlyMemory utf8Bytes, out ReadOnlyMemory msgpackEncoded) { - int byteCount = StringEncoding.UTF8.GetByteCount(value); + int byteCount = UTF8.GetByteCount(value); Memory bytes = new byte[byteCount + 5]; Assumes.True(MessagePackPrimitives.TryWriteStringHeader(bytes.Span, (uint)byteCount, out int msgpackHeaderLength)); - StringEncoding.UTF8.GetBytes(value, bytes.Span[msgpackHeaderLength..]); + UTF8.GetBytes(value.AsSpan(), bytes.Span[msgpackHeaderLength..]); utf8Bytes = bytes.Slice(msgpackHeaderLength, byteCount); msgpackEncoded = bytes.Slice(0, byteCount + msgpackHeaderLength); } diff --git a/src/Nerdbank.MessagePack/Utilities/SpanEqualityComparer.cs b/src/Nerdbank.MessagePack/Utilities/SpanEqualityComparer.cs index c9230964..77c6c504 100644 --- a/src/Nerdbank.MessagePack/Utilities/SpanEqualityComparer.cs +++ b/src/Nerdbank.MessagePack/Utilities/SpanEqualityComparer.cs @@ -6,6 +6,8 @@ #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1649 // File name should match first type name +using System.Globalization; + namespace Nerdbank.MessagePack.Utilities; /// Defines a span-based equality comparer. @@ -38,7 +40,14 @@ public bool Equals(ReadOnlySpan x, ReadOnlySpan y) public int GetHashCode(ReadOnlySpan buffer) { var hc = default(HashCode); +#if NET hc.AddBytes(buffer); +#else + for (int i = 0; i < buffer.Length; i++) + { + hc.Add(buffer[i]); + } +#endif return hc.ToHashCode(); } } @@ -56,7 +65,19 @@ internal static class CharSpanEqualityComparer private sealed class StringComparisonEqualityComparer(StringComparison comparison) : ISpanEqualityComparer { public int GetHashCode(ReadOnlySpan buffer) - => string.GetHashCode(buffer, comparison); + { +#if NET + return string.GetHashCode(buffer, comparison); +#else + var hc = default(HashCode); + foreach (char c in buffer) + { + hc.Add(comparison == StringComparison.OrdinalIgnoreCase ? char.ToLowerInvariant(c) : c); + } + + return hc.ToHashCode(); +#endif + } public bool Equals(ReadOnlySpan x, ReadOnlySpan y) => x.Equals(y, comparison); diff --git a/src/Nerdbank.MessagePack/PublicAPI.Shipped.txt b/src/Nerdbank.MessagePack/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Nerdbank.MessagePack/PublicAPI.Shipped.txt rename to src/Nerdbank.MessagePack/net8.0/PublicAPI.Shipped.txt diff --git a/src/Nerdbank.MessagePack/PublicAPI.Unshipped.txt b/src/Nerdbank.MessagePack/net8.0/PublicAPI.Unshipped.txt similarity index 88% rename from src/Nerdbank.MessagePack/PublicAPI.Unshipped.txt rename to src/Nerdbank.MessagePack/net8.0/PublicAPI.Unshipped.txt index edb939bc..63c8c243 100644 --- a/src/Nerdbank.MessagePack/PublicAPI.Unshipped.txt +++ b/src/Nerdbank.MessagePack/net8.0/PublicAPI.Unshipped.txt @@ -44,8 +44,7 @@ const Nerdbank.MessagePack.MessagePackCode.UInt32 = 206 -> byte const Nerdbank.MessagePack.MessagePackCode.UInt64 = 207 -> byte const Nerdbank.MessagePack.MessagePackCode.UInt8 = 204 -> byte const Nerdbank.MessagePack.ReservedMessagePackExtensionTypeCode.DateTime = -1 -> sbyte -Nerdbank.MessagePack.ByValueEqualityComparer -Nerdbank.MessagePack.ByValueEqualityComparer +Nerdbank.MessagePack.ByValueEqualityComparer Nerdbank.MessagePack.Extension Nerdbank.MessagePack.Extension.Data.get -> System.Buffers.ReadOnlySequence Nerdbank.MessagePack.Extension.Data.set -> void @@ -74,8 +73,11 @@ Nerdbank.MessagePack.JsonSchemaContext.GetJsonSchema(PolyType.Abstractions.IType Nerdbank.MessagePack.KeyAttribute Nerdbank.MessagePack.KeyAttribute.Index.get -> int Nerdbank.MessagePack.KeyAttribute.KeyAttribute(int index) -> void +Nerdbank.MessagePack.KnownSubTypeAttribute +Nerdbank.MessagePack.KnownSubTypeAttribute.Alias.get -> int +Nerdbank.MessagePack.KnownSubTypeAttribute.KnownSubTypeAttribute(int alias, System.Type! subType) -> void +Nerdbank.MessagePack.KnownSubTypeAttribute.SubType.get -> System.Type! Nerdbank.MessagePack.KnownSubTypeAttribute -Nerdbank.MessagePack.KnownSubTypeAttribute.Alias.get -> int Nerdbank.MessagePack.KnownSubTypeAttribute.KnownSubTypeAttribute(int alias) -> void Nerdbank.MessagePack.KnownSubTypeAttribute Nerdbank.MessagePack.KnownSubTypeAttribute.KnownSubTypeAttribute(int alias) -> void @@ -104,6 +106,7 @@ Nerdbank.MessagePack.MessagePackAsyncWriter.FlushIfAppropriateAsync(Nerdbank.Mes Nerdbank.MessagePack.MessagePackAsyncWriter.IsTimeToFlush(Nerdbank.MessagePack.SerializationContext context) -> bool Nerdbank.MessagePack.MessagePackAsyncWriter.IsTimeToFlush(Nerdbank.MessagePack.SerializationContext context, in Nerdbank.MessagePack.MessagePackWriter syncWriter) -> bool Nerdbank.MessagePack.MessagePackAsyncWriter.MessagePackAsyncWriter(System.IO.Pipelines.PipeWriter! pipeWriter) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.ReturnWriter(ref Nerdbank.MessagePack.MessagePackWriter writer) -> void Nerdbank.MessagePack.MessagePackAsyncWriter.SyncWriter Nerdbank.MessagePack.MessagePackAsyncWriter.Write(Nerdbank.MessagePack.MessagePackAsyncWriter.SyncWriter! writer, TState state) -> void Nerdbank.MessagePack.MessagePackAsyncWriter.WriteArrayHeader(int count) -> void @@ -175,22 +178,28 @@ Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationE Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationException(string? message) -> void Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationException(string? message, System.Exception? innerException) -> void Nerdbank.MessagePack.MessagePackSerializer -Nerdbank.MessagePack.MessagePackSerializer.CreateSerializationContext(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext +Nerdbank.MessagePack.MessagePackSerializer.CreateSerializationContext(PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(ref Nerdbank.MessagePack.MessagePackReader reader, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(ref Nerdbank.MessagePack.MessagePackReader reader, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence buffer, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence buffer, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory buffer, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory buffer, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory bytes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.DisposableSerializationContext() -> void @@ -200,6 +209,7 @@ Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.Value. Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema(PolyType.Abstractions.ITypeShape! typeShape) -> System.Text.Json.Nodes.JsonObject! Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema() -> System.Text.Json.Nodes.JsonObject! Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema() -> System.Text.Json.Nodes.JsonObject! +Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema(PolyType.ITypeShapeProvider! provider) -> System.Text.Json.Nodes.JsonObject! Nerdbank.MessagePack.MessagePackSerializer.InternStrings.get -> bool Nerdbank.MessagePack.MessagePackSerializer.InternStrings.init -> void Nerdbank.MessagePack.MessagePackSerializer.LibraryExtensionTypeCodes.get -> Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode! @@ -215,18 +225,24 @@ Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> byte[]! +Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> byte[]! Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> byte[]! Nerdbank.MessagePack.MessagePackSerializer.Serialize(ref Nerdbank.MessagePack.MessagePackWriter writer, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(ref Nerdbank.MessagePack.MessagePackWriter writer, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, in T? value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Nerdbank.MessagePack.MessagePackSerializer.SerializeDefaultValues.get -> bool Nerdbank.MessagePack.MessagePackSerializer.SerializeDefaultValues.init -> void Nerdbank.MessagePack.MessagePackSerializer.SerializeEnumValuesByName.get -> bool @@ -310,15 +326,22 @@ Nerdbank.MessagePack.SerializationContext.CancellationToken.init -> void Nerdbank.MessagePack.SerializationContext.DepthStep() -> void Nerdbank.MessagePack.SerializationContext.GetConverter() -> Nerdbank.MessagePack.MessagePackConverter! Nerdbank.MessagePack.SerializationContext.GetConverter() -> Nerdbank.MessagePack.MessagePackConverter! +Nerdbank.MessagePack.SerializationContext.GetConverter(PolyType.ITypeShapeProvider? provider) -> Nerdbank.MessagePack.MessagePackConverter! Nerdbank.MessagePack.SerializationContext.MaxDepth.get -> int Nerdbank.MessagePack.SerializationContext.MaxDepth.set -> void Nerdbank.MessagePack.SerializationContext.SerializationContext() -> void +Nerdbank.MessagePack.SerializationContext.TypeShapeProvider.get -> PolyType.ITypeShapeProvider? Nerdbank.MessagePack.SerializationContext.UnflushedBytesThreshold.get -> int Nerdbank.MessagePack.SerializationContext.UnflushedBytesThreshold.init -> void -static Nerdbank.MessagePack.ByValueEqualityComparer.Default.get -> System.Collections.Generic.IEqualityComparer! -static Nerdbank.MessagePack.ByValueEqualityComparer.HashResistant.get -> System.Collections.Generic.IEqualityComparer! -static Nerdbank.MessagePack.ByValueEqualityComparer.Default.get -> System.Collections.Generic.IEqualityComparer! -static Nerdbank.MessagePack.ByValueEqualityComparer.HashResistant.get -> System.Collections.Generic.IEqualityComparer! +override Nerdbank.MessagePack.KnownSubTypeAttribute.Shape.get -> PolyType.Abstractions.ITypeShape? +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault() -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault() -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault(PolyType.Abstractions.ITypeShape! shape) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault(PolyType.ITypeShapeProvider! provider) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant() -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant() -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant(PolyType.Abstractions.ITypeShape! shape) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant(PolyType.ITypeShapeProvider! provider) -> System.Collections.Generic.IEqualityComparer! static Nerdbank.MessagePack.MessagePackCode.ToFormatName(byte code) -> string! static Nerdbank.MessagePack.MessagePackCode.ToMessagePackType(byte code) -> Nerdbank.MessagePack.MessagePackType static Nerdbank.MessagePack.MessagePackConverter.ApplyJsonSchemaNullability(System.Text.Json.Nodes.JsonObject! schema) -> System.Text.Json.Nodes.JsonObject! @@ -388,6 +411,7 @@ static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePac static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePack.RawMessagePack(System.ReadOnlyMemory msgpack) -> Nerdbank.MessagePack.RawMessagePack static Nerdbank.MessagePack.RawMessagePack.implicit operator System.Buffers.ReadOnlySequence(Nerdbank.MessagePack.RawMessagePack msgpack) -> System.Buffers.ReadOnlySequence static readonly Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode.Default -> Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode! +virtual Nerdbank.MessagePack.KnownSubTypeAttribute.Shape.get -> PolyType.Abstractions.ITypeShape? virtual Nerdbank.MessagePack.MessagePackConverter.GetJsonSchema(Nerdbank.MessagePack.JsonSchemaContext! context, PolyType.Abstractions.ITypeShape! typeShape) -> System.Text.Json.Nodes.JsonObject? virtual Nerdbank.MessagePack.MessagePackConverter.PreferAsyncSerialization.get -> bool virtual Nerdbank.MessagePack.MessagePackConverter.ReadAsync(Nerdbank.MessagePack.MessagePackAsyncReader! reader, Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask diff --git a/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Shipped.txt b/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000..7dc5c581 --- /dev/null +++ b/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Unshipped.txt b/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..7865f95f --- /dev/null +++ b/src/Nerdbank.MessagePack/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,383 @@ +abstract Nerdbank.MessagePack.MessagePackConverter.Read(ref Nerdbank.MessagePack.MessagePackReader reader, Nerdbank.MessagePack.SerializationContext context) -> T? +abstract Nerdbank.MessagePack.MessagePackConverter.Write(ref Nerdbank.MessagePack.MessagePackWriter writer, in T? value, Nerdbank.MessagePack.SerializationContext context) -> void +abstract Nerdbank.MessagePack.MessagePackNamingPolicy.ConvertName(string! name) -> string! +const Nerdbank.MessagePack.MessagePackCode.Array16 = 220 -> byte +const Nerdbank.MessagePack.MessagePackCode.Array32 = 221 -> byte +const Nerdbank.MessagePack.MessagePackCode.Bin16 = 197 -> byte +const Nerdbank.MessagePack.MessagePackCode.Bin32 = 198 -> byte +const Nerdbank.MessagePack.MessagePackCode.Bin8 = 196 -> byte +const Nerdbank.MessagePack.MessagePackCode.Ext16 = 200 -> byte +const Nerdbank.MessagePack.MessagePackCode.Ext32 = 201 -> byte +const Nerdbank.MessagePack.MessagePackCode.Ext8 = 199 -> byte +const Nerdbank.MessagePack.MessagePackCode.False = 194 -> byte +const Nerdbank.MessagePack.MessagePackCode.FixExt1 = 212 -> byte +const Nerdbank.MessagePack.MessagePackCode.FixExt16 = 216 -> byte +const Nerdbank.MessagePack.MessagePackCode.FixExt2 = 213 -> byte +const Nerdbank.MessagePack.MessagePackCode.FixExt4 = 214 -> byte +const Nerdbank.MessagePack.MessagePackCode.FixExt8 = 215 -> byte +const Nerdbank.MessagePack.MessagePackCode.Float32 = 202 -> byte +const Nerdbank.MessagePack.MessagePackCode.Float64 = 203 -> byte +const Nerdbank.MessagePack.MessagePackCode.Int16 = 209 -> byte +const Nerdbank.MessagePack.MessagePackCode.Int32 = 210 -> byte +const Nerdbank.MessagePack.MessagePackCode.Int64 = 211 -> byte +const Nerdbank.MessagePack.MessagePackCode.Int8 = 208 -> byte +const Nerdbank.MessagePack.MessagePackCode.Map16 = 222 -> byte +const Nerdbank.MessagePack.MessagePackCode.Map32 = 223 -> byte +const Nerdbank.MessagePack.MessagePackCode.MaxFixArray = 159 -> byte +const Nerdbank.MessagePack.MessagePackCode.MaxFixInt = 127 -> byte +const Nerdbank.MessagePack.MessagePackCode.MaxFixMap = 143 -> byte +const Nerdbank.MessagePack.MessagePackCode.MaxFixStr = 191 -> byte +const Nerdbank.MessagePack.MessagePackCode.MaxNegativeFixInt = 255 -> byte +const Nerdbank.MessagePack.MessagePackCode.MinFixArray = 144 -> byte +const Nerdbank.MessagePack.MessagePackCode.MinFixInt = 0 -> byte +const Nerdbank.MessagePack.MessagePackCode.MinFixMap = 128 -> byte +const Nerdbank.MessagePack.MessagePackCode.MinFixStr = 160 -> byte +const Nerdbank.MessagePack.MessagePackCode.MinNegativeFixInt = 224 -> byte +const Nerdbank.MessagePack.MessagePackCode.NeverUsed = 193 -> byte +const Nerdbank.MessagePack.MessagePackCode.Nil = 192 -> byte +const Nerdbank.MessagePack.MessagePackCode.Str16 = 218 -> byte +const Nerdbank.MessagePack.MessagePackCode.Str32 = 219 -> byte +const Nerdbank.MessagePack.MessagePackCode.Str8 = 217 -> byte +const Nerdbank.MessagePack.MessagePackCode.True = 195 -> byte +const Nerdbank.MessagePack.MessagePackCode.UInt16 = 205 -> byte +const Nerdbank.MessagePack.MessagePackCode.UInt32 = 206 -> byte +const Nerdbank.MessagePack.MessagePackCode.UInt64 = 207 -> byte +const Nerdbank.MessagePack.MessagePackCode.UInt8 = 204 -> byte +const Nerdbank.MessagePack.ReservedMessagePackExtensionTypeCode.DateTime = -1 -> sbyte +Nerdbank.MessagePack.ByValueEqualityComparer +Nerdbank.MessagePack.Extension +Nerdbank.MessagePack.Extension.Data.get -> System.Buffers.ReadOnlySequence +Nerdbank.MessagePack.Extension.Data.set -> void +Nerdbank.MessagePack.Extension.Extension() -> void +Nerdbank.MessagePack.Extension.Extension(sbyte TypeCode, System.Buffers.ReadOnlySequence Data) -> void +Nerdbank.MessagePack.Extension.Extension(sbyte typeCode, System.Memory data) -> void +Nerdbank.MessagePack.Extension.Header.get -> Nerdbank.MessagePack.ExtensionHeader +Nerdbank.MessagePack.Extension.TypeCode.get -> sbyte +Nerdbank.MessagePack.Extension.TypeCode.set -> void +Nerdbank.MessagePack.ExtensionHeader +Nerdbank.MessagePack.ExtensionHeader.ExtensionHeader() -> void +Nerdbank.MessagePack.ExtensionHeader.ExtensionHeader(sbyte TypeCode, uint Length) -> void +Nerdbank.MessagePack.ExtensionHeader.Length.get -> uint +Nerdbank.MessagePack.ExtensionHeader.Length.set -> void +Nerdbank.MessagePack.ExtensionHeader.TypeCode.get -> sbyte +Nerdbank.MessagePack.ExtensionHeader.TypeCode.set -> void +Nerdbank.MessagePack.IDeepSecureEqualityComparer +Nerdbank.MessagePack.IDeepSecureEqualityComparer.DeepEquals(T? other) -> bool +Nerdbank.MessagePack.IDeepSecureEqualityComparer.GetHashCode() -> int +Nerdbank.MessagePack.IDeepSecureEqualityComparer.GetSecureHashCode() -> long +Nerdbank.MessagePack.IMessagePackSerializationCallbacks +Nerdbank.MessagePack.IMessagePackSerializationCallbacks.OnAfterDeserialize() -> void +Nerdbank.MessagePack.IMessagePackSerializationCallbacks.OnBeforeSerialize() -> void +Nerdbank.MessagePack.JsonSchemaContext +Nerdbank.MessagePack.JsonSchemaContext.GetJsonSchema(PolyType.Abstractions.ITypeShape! typeShape) -> System.Text.Json.Nodes.JsonObject! +Nerdbank.MessagePack.KeyAttribute +Nerdbank.MessagePack.KeyAttribute.Index.get -> int +Nerdbank.MessagePack.KeyAttribute.KeyAttribute(int index) -> void +Nerdbank.MessagePack.KnownSubTypeAttribute +Nerdbank.MessagePack.KnownSubTypeAttribute.Alias.get -> int +Nerdbank.MessagePack.KnownSubTypeAttribute.KnownSubTypeAttribute(int alias, System.Type! subType) -> void +Nerdbank.MessagePack.KnownSubTypeAttribute.SubType.get -> System.Type! +Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode +Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode.ObjectReference.get -> sbyte +Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode.ObjectReference.init -> void +Nerdbank.MessagePack.MessagePackAsyncReader +Nerdbank.MessagePack.MessagePackAsyncReader.AdvanceTo(System.SequencePosition consumed) -> void +Nerdbank.MessagePack.MessagePackAsyncReader.AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined) -> void +Nerdbank.MessagePack.MessagePackAsyncReader.BufferNextStructureAsync(Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncReader.CancellationToken.get -> System.Threading.CancellationToken +Nerdbank.MessagePack.MessagePackAsyncReader.CancellationToken.init -> void +Nerdbank.MessagePack.MessagePackAsyncReader.MessagePackAsyncReader(System.IO.Pipelines.PipeReader! pipeReader) -> void +Nerdbank.MessagePack.MessagePackAsyncReader.ReadArrayHeaderAsync() -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncReader.ReadMapHeaderAsync() -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncReader.ReadNextStructureAsync(Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask> +Nerdbank.MessagePack.MessagePackAsyncReader.ReadNextStructuresAsync(int minimumDesiredBufferedStructures, int countUpTo, Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask<(System.Buffers.ReadOnlySequence Buffer, int IncludedStructures)> +Nerdbank.MessagePack.MessagePackAsyncReader.ReadNextStructuresAsync(int minimumDesiredBufferedStructures, Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask> +Nerdbank.MessagePack.MessagePackAsyncReader.SkipAsync(Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncReader.TryPeekNextMessagePackTypeAsync() -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncReader.TryReadNilAsync() -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncWriter +Nerdbank.MessagePack.MessagePackAsyncWriter.CreateWriter() -> Nerdbank.MessagePack.MessagePackWriter +Nerdbank.MessagePack.MessagePackAsyncWriter.Flush() -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.FlushIfAppropriateAsync(Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackAsyncWriter.IsTimeToFlush(Nerdbank.MessagePack.SerializationContext context) -> bool +Nerdbank.MessagePack.MessagePackAsyncWriter.IsTimeToFlush(Nerdbank.MessagePack.SerializationContext context, in Nerdbank.MessagePack.MessagePackWriter syncWriter) -> bool +Nerdbank.MessagePack.MessagePackAsyncWriter.MessagePackAsyncWriter(System.IO.Pipelines.PipeWriter! pipeWriter) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.ReturnWriter(ref Nerdbank.MessagePack.MessagePackWriter writer) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.SyncWriter +Nerdbank.MessagePack.MessagePackAsyncWriter.Write(Nerdbank.MessagePack.MessagePackAsyncWriter.SyncWriter! writer, TState state) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteArrayHeader(int count) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteArrayHeader(uint count) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteMapHeader(int count) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteMapHeader(uint count) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteNil() -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteRaw(System.Buffers.ReadOnlySequence bytes) -> void +Nerdbank.MessagePack.MessagePackAsyncWriter.WriteRaw(System.ReadOnlySpan bytes) -> void +Nerdbank.MessagePack.MessagePackCode +Nerdbank.MessagePack.MessagePackConverter +Nerdbank.MessagePack.MessagePackConverter.MessagePackConverter() -> void +Nerdbank.MessagePack.MessagePackConverterAttribute +Nerdbank.MessagePack.MessagePackConverterAttribute.ConverterType.get -> System.Type! +Nerdbank.MessagePack.MessagePackConverterAttribute.MessagePackConverterAttribute(System.Type! converterType) -> void +Nerdbank.MessagePack.MessagePackNamingPolicy +Nerdbank.MessagePack.MessagePackNamingPolicy.MessagePackNamingPolicy() -> void +Nerdbank.MessagePack.MessagePackPrimitives +Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult.EmptyBuffer = 2 -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult.InsufficientBuffer = 3 -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult.Success = 0 -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult.TokenMismatch = 1 -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +Nerdbank.MessagePack.MessagePackReader +Nerdbank.MessagePack.MessagePackReader.Clone(scoped in System.Buffers.ReadOnlySequence readOnlySequence) -> Nerdbank.MessagePack.MessagePackReader +Nerdbank.MessagePack.MessagePackReader.Consumed.get -> long +Nerdbank.MessagePack.MessagePackReader.CreatePeekReader() -> Nerdbank.MessagePack.MessagePackReader +Nerdbank.MessagePack.MessagePackReader.End.get -> bool +Nerdbank.MessagePack.MessagePackReader.IsNil.get -> bool +Nerdbank.MessagePack.MessagePackReader.MessagePackReader() -> void +Nerdbank.MessagePack.MessagePackReader.MessagePackReader(scoped in System.Buffers.ReadOnlySequence msgpack) -> void +Nerdbank.MessagePack.MessagePackReader.MessagePackReader(System.ReadOnlyMemory msgpack) -> void +Nerdbank.MessagePack.MessagePackReader.NextCode.get -> byte +Nerdbank.MessagePack.MessagePackReader.NextMessagePackType.get -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackReader.Position.get -> System.SequencePosition +Nerdbank.MessagePack.MessagePackReader.ReadArrayHeader() -> int +Nerdbank.MessagePack.MessagePackReader.ReadBoolean() -> bool +Nerdbank.MessagePack.MessagePackReader.ReadByte() -> byte +Nerdbank.MessagePack.MessagePackReader.ReadBytes() -> System.Buffers.ReadOnlySequence? +Nerdbank.MessagePack.MessagePackReader.ReadChar() -> char +Nerdbank.MessagePack.MessagePackReader.ReadDateTime() -> System.DateTime +Nerdbank.MessagePack.MessagePackReader.ReadDateTime(Nerdbank.MessagePack.ExtensionHeader header) -> System.DateTime +Nerdbank.MessagePack.MessagePackReader.ReadDouble() -> double +Nerdbank.MessagePack.MessagePackReader.ReadExtension() -> Nerdbank.MessagePack.Extension +Nerdbank.MessagePack.MessagePackReader.ReadExtensionHeader() -> Nerdbank.MessagePack.ExtensionHeader +Nerdbank.MessagePack.MessagePackReader.ReadInt16() -> short +Nerdbank.MessagePack.MessagePackReader.ReadInt32() -> int +Nerdbank.MessagePack.MessagePackReader.ReadInt64() -> long +Nerdbank.MessagePack.MessagePackReader.ReadMapHeader() -> int +Nerdbank.MessagePack.MessagePackReader.ReadNil() -> void +Nerdbank.MessagePack.MessagePackReader.ReadRaw(long length) -> System.Buffers.ReadOnlySequence +Nerdbank.MessagePack.MessagePackReader.ReadRaw(Nerdbank.MessagePack.SerializationContext context) -> System.Buffers.ReadOnlySequence +Nerdbank.MessagePack.MessagePackReader.ReadSByte() -> sbyte +Nerdbank.MessagePack.MessagePackReader.ReadSingle() -> float +Nerdbank.MessagePack.MessagePackReader.ReadString() -> string? +Nerdbank.MessagePack.MessagePackReader.ReadStringSequence() -> System.Buffers.ReadOnlySequence? +Nerdbank.MessagePack.MessagePackReader.ReadUInt16() -> ushort +Nerdbank.MessagePack.MessagePackReader.ReadUInt32() -> uint +Nerdbank.MessagePack.MessagePackReader.ReadUInt64() -> ulong +Nerdbank.MessagePack.MessagePackReader.Sequence.get -> System.Buffers.ReadOnlySequence +Nerdbank.MessagePack.MessagePackReader.Skip(Nerdbank.MessagePack.SerializationContext context) -> void +Nerdbank.MessagePack.MessagePackReader.TryReadArrayHeader(out int count) -> bool +Nerdbank.MessagePack.MessagePackReader.TryReadExtensionHeader(out Nerdbank.MessagePack.ExtensionHeader extensionHeader) -> bool +Nerdbank.MessagePack.MessagePackReader.TryReadMapHeader(out int count) -> bool +Nerdbank.MessagePack.MessagePackReader.TryReadNil() -> bool +Nerdbank.MessagePack.MessagePackReader.TryReadStringSpan(out System.ReadOnlySpan span) -> bool +Nerdbank.MessagePack.MessagePackSerializationException +Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationException() -> void +Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationException(string? message) -> void +Nerdbank.MessagePack.MessagePackSerializationException.MessagePackSerializationException(string? message, System.Exception? innerException) -> void +Nerdbank.MessagePack.MessagePackSerializer +Nerdbank.MessagePack.MessagePackSerializer.CreateSerializationContext(PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(ref Nerdbank.MessagePack.MessagePackReader reader, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(ref Nerdbank.MessagePack.MessagePackReader reader, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence buffer, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(scoped in System.Buffers.ReadOnlySequence buffer, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.IO.Stream! stream, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory buffer, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.Deserialize(System.ReadOnlyMemory buffer, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> T? +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Pipelines.PipeReader! reader, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DeserializeAsync(System.IO.Stream! stream, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext +Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.DisposableSerializationContext() -> void +Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.DisposableSerializationContext(Nerdbank.MessagePack.SerializationContext context) -> void +Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.Dispose() -> void +Nerdbank.MessagePack.MessagePackSerializer.DisposableSerializationContext.Value.get -> Nerdbank.MessagePack.SerializationContext +Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema(PolyType.Abstractions.ITypeShape! typeShape) -> System.Text.Json.Nodes.JsonObject! +Nerdbank.MessagePack.MessagePackSerializer.GetJsonSchema(PolyType.ITypeShapeProvider! provider) -> System.Text.Json.Nodes.JsonObject! +Nerdbank.MessagePack.MessagePackSerializer.InternStrings.get -> bool +Nerdbank.MessagePack.MessagePackSerializer.InternStrings.init -> void +Nerdbank.MessagePack.MessagePackSerializer.LibraryExtensionTypeCodes.get -> Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode! +Nerdbank.MessagePack.MessagePackSerializer.LibraryExtensionTypeCodes.init -> void +Nerdbank.MessagePack.MessagePackSerializer.PreserveReferences.get -> bool +Nerdbank.MessagePack.MessagePackSerializer.PreserveReferences.init -> void +Nerdbank.MessagePack.MessagePackSerializer.PropertyNamingPolicy.get -> Nerdbank.MessagePack.MessagePackNamingPolicy? +Nerdbank.MessagePack.MessagePackSerializer.PropertyNamingPolicy.init -> void +Nerdbank.MessagePack.MessagePackSerializer.RegisterConverter(Nerdbank.MessagePack.MessagePackConverter! converter) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> byte[]! +Nerdbank.MessagePack.MessagePackSerializer.Serialize(in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> byte[]! +Nerdbank.MessagePack.MessagePackSerializer.Serialize(ref Nerdbank.MessagePack.MessagePackWriter writer, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(ref Nerdbank.MessagePack.MessagePackWriter writer, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.Buffers.IBufferWriter! writer, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.Serialize(System.IO.Stream! stream, in T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Pipelines.PipeWriter! writer, T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, T? value, PolyType.Abstractions.ITypeShape! shape, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeAsync(System.IO.Stream! stream, T? value, PolyType.ITypeShapeProvider! provider, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Nerdbank.MessagePack.MessagePackSerializer.SerializeDefaultValues.get -> bool +Nerdbank.MessagePack.MessagePackSerializer.SerializeDefaultValues.init -> void +Nerdbank.MessagePack.MessagePackSerializer.SerializeEnumValuesByName.get -> bool +Nerdbank.MessagePack.MessagePackSerializer.SerializeEnumValuesByName.init -> void +Nerdbank.MessagePack.MessagePackSerializer.StartingContext.get -> Nerdbank.MessagePack.SerializationContext +Nerdbank.MessagePack.MessagePackSerializer.StartingContext.init -> void +Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Array = 7 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Binary = 6 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Boolean = 3 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Extension = 9 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Float = 4 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Integer = 1 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Map = 8 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Nil = 2 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.String = 5 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackType.Unknown = 0 -> Nerdbank.MessagePack.MessagePackType +Nerdbank.MessagePack.MessagePackWriter +Nerdbank.MessagePack.MessagePackWriter.Advance(int length) -> void +Nerdbank.MessagePack.MessagePackWriter.Clone(System.Buffers.IBufferWriter! writer) -> Nerdbank.MessagePack.MessagePackWriter +Nerdbank.MessagePack.MessagePackWriter.Flush() -> void +Nerdbank.MessagePack.MessagePackWriter.GetSpan(int length) -> System.Span +Nerdbank.MessagePack.MessagePackWriter.MessagePackWriter() -> void +Nerdbank.MessagePack.MessagePackWriter.MessagePackWriter(System.Buffers.IBufferWriter! writer) -> void +Nerdbank.MessagePack.MessagePackWriter.OldSpec.get -> bool +Nerdbank.MessagePack.MessagePackWriter.OldSpec.set -> void +Nerdbank.MessagePack.MessagePackWriter.UnflushedBytes.get -> int +Nerdbank.MessagePack.MessagePackWriter.Write(bool value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(byte value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(byte[]? src) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(char value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(double value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(float value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(in System.Buffers.ReadOnlySequence src) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(int value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(long value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(Nerdbank.MessagePack.Extension extensionData) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(Nerdbank.MessagePack.ExtensionHeader extensionHeader) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(sbyte value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(short value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(string? value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(System.DateTime dateTime) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(System.ReadOnlySpan src) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(System.ReadOnlySpan value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(uint value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(ulong value) -> void +Nerdbank.MessagePack.MessagePackWriter.Write(ushort value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteArrayHeader(int count) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteArrayHeader(uint count) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteBinHeader(int length) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteInt16(short value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteInt32(int value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteInt64(long value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteInt8(sbyte value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteMapHeader(int count) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteMapHeader(uint count) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteNil() -> void +Nerdbank.MessagePack.MessagePackWriter.WriteRaw(in System.Buffers.ReadOnlySequence rawMessagePackBlock) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteRaw(System.ReadOnlySpan rawMessagePackBlock) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteString(in System.Buffers.ReadOnlySequence utf8stringBytes) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteString(System.ReadOnlySpan utf8stringBytes) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteStringHeader(int byteCount) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteUInt16(ushort value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteUInt32(uint value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteUInt64(ulong value) -> void +Nerdbank.MessagePack.MessagePackWriter.WriteUInt8(byte value) -> void +Nerdbank.MessagePack.MultiDimensionalArrayFormat +Nerdbank.MessagePack.MultiDimensionalArrayFormat.Flat = 1 -> Nerdbank.MessagePack.MultiDimensionalArrayFormat +Nerdbank.MessagePack.MultiDimensionalArrayFormat.Nested = 0 -> Nerdbank.MessagePack.MultiDimensionalArrayFormat +Nerdbank.MessagePack.RawMessagePack +Nerdbank.MessagePack.RawMessagePack.Equals(Nerdbank.MessagePack.RawMessagePack other) -> bool +Nerdbank.MessagePack.RawMessagePack.IsOwned.get -> bool +Nerdbank.MessagePack.RawMessagePack.MsgPack.get -> System.Buffers.ReadOnlySequence +Nerdbank.MessagePack.RawMessagePack.RawMessagePack() -> void +Nerdbank.MessagePack.RawMessagePack.RawMessagePack(System.Buffers.ReadOnlySequence msgpack) -> void +Nerdbank.MessagePack.RawMessagePack.ToOwned() -> Nerdbank.MessagePack.RawMessagePack +Nerdbank.MessagePack.ReservedMessagePackExtensionTypeCode +Nerdbank.MessagePack.SerializationContext +Nerdbank.MessagePack.SerializationContext.CancellationToken.get -> System.Threading.CancellationToken +Nerdbank.MessagePack.SerializationContext.CancellationToken.init -> void +Nerdbank.MessagePack.SerializationContext.DepthStep() -> void +Nerdbank.MessagePack.SerializationContext.GetConverter(PolyType.ITypeShapeProvider? provider) -> Nerdbank.MessagePack.MessagePackConverter! +Nerdbank.MessagePack.SerializationContext.MaxDepth.get -> int +Nerdbank.MessagePack.SerializationContext.MaxDepth.set -> void +Nerdbank.MessagePack.SerializationContext.SerializationContext() -> void +Nerdbank.MessagePack.SerializationContext.TypeShapeProvider.get -> PolyType.ITypeShapeProvider? +Nerdbank.MessagePack.SerializationContext.UnflushedBytesThreshold.get -> int +Nerdbank.MessagePack.SerializationContext.UnflushedBytesThreshold.init -> void +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault(PolyType.Abstractions.ITypeShape! shape) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetDefault(PolyType.ITypeShapeProvider! provider) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant(PolyType.Abstractions.ITypeShape! shape) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.ByValueEqualityComparer.GetHashResistant(PolyType.ITypeShapeProvider! provider) -> System.Collections.Generic.IEqualityComparer! +static Nerdbank.MessagePack.MessagePackCode.ToFormatName(byte code) -> string! +static Nerdbank.MessagePack.MessagePackCode.ToMessagePackType(byte code) -> Nerdbank.MessagePack.MessagePackType +static Nerdbank.MessagePack.MessagePackConverter.ApplyJsonSchemaNullability(System.Text.Json.Nodes.JsonObject! schema) -> System.Text.Json.Nodes.JsonObject! +static Nerdbank.MessagePack.MessagePackConverter.CreateJsonValue(object? value) -> System.Text.Json.Nodes.JsonValue? +static Nerdbank.MessagePack.MessagePackConverter.CreateMsgPackBinarySchema(string? description = null) -> System.Text.Json.Nodes.JsonObject! +static Nerdbank.MessagePack.MessagePackConverter.CreateMsgPackExtensionSchema(sbyte extensionCode) -> System.Text.Json.Nodes.JsonObject! +static Nerdbank.MessagePack.MessagePackConverter.CreateUndocumentedSchema(System.Type! undocumentingConverter) -> System.Text.Json.Nodes.JsonObject! +static Nerdbank.MessagePack.MessagePackNamingPolicy.CamelCase.get -> Nerdbank.MessagePack.MessagePackNamingPolicy! +static Nerdbank.MessagePack.MessagePackNamingPolicy.PascalCase.get -> Nerdbank.MessagePack.MessagePackNamingPolicy! +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, Nerdbank.MessagePack.ExtensionHeader header, out System.DateTime value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out bool value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out byte value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out char value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out double value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out float value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out int value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out long value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out sbyte value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out short value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out System.DateTime value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out uint value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out ulong value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryRead(System.ReadOnlySpan source, out ushort value, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadArrayHeader(System.Buffers.ReadOnlySequence source, out uint count, out System.SequencePosition readTo) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadArrayHeader(System.ReadOnlySpan source, out uint count, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadBinHeader(System.ReadOnlySpan source, out uint length, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadExtensionHeader(System.ReadOnlySpan source, out Nerdbank.MessagePack.ExtensionHeader extensionHeader, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadMapHeader(System.Buffers.ReadOnlySequence source, out uint count, out System.SequencePosition readTo) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadMapHeader(System.ReadOnlySpan source, out uint count, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadNil(System.ReadOnlySpan source, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryReadStringHeader(System.ReadOnlySpan source, out uint length, out int tokenSize) -> Nerdbank.MessagePack.MessagePackPrimitives.DecodeResult +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, bool value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, byte value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, char value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, double value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, float value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, int value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, long value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, sbyte value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, short value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, System.DateTime value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, uint value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, ulong value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWrite(System.Span destination, ushort value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteArrayHeader(System.Span destination, uint count, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteBinHeader(System.Span destination, uint length, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteExtensionHeader(System.Span destination, Nerdbank.MessagePack.ExtensionHeader extensionHeader, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteInt16(System.Span destination, short value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteInt32(System.Span destination, int value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteInt64(System.Span destination, long value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteInt8(System.Span destination, sbyte value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteMapHeader(System.Span destination, uint count, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteNil(System.Span destination, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteStringHeader(System.Span destination, uint byteCount, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteUInt16(System.Span destination, ushort value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteUInt32(System.Span destination, uint value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteUInt64(System.Span destination, ulong value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackPrimitives.TryWriteUInt8(System.Span destination, byte value, out int bytesWritten) -> bool +static Nerdbank.MessagePack.MessagePackSerializer.ConvertToJson(in System.Buffers.ReadOnlySequence msgpack) -> string! +static Nerdbank.MessagePack.MessagePackSerializer.ConvertToJson(ref Nerdbank.MessagePack.MessagePackReader reader, System.IO.TextWriter! jsonWriter) -> void +static Nerdbank.MessagePack.MessagePackSerializer.ConvertToJson(System.ReadOnlyMemory msgpack) -> string! +static Nerdbank.MessagePack.MessagePackWriter.GetEncodedLength(long value) -> int +static Nerdbank.MessagePack.MessagePackWriter.GetEncodedLength(ulong value) -> int +static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePack.RawMessagePack(byte[]! msgpack) -> Nerdbank.MessagePack.RawMessagePack +static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePack.RawMessagePack(System.Buffers.ReadOnlySequence msgpack) -> Nerdbank.MessagePack.RawMessagePack +static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePack.RawMessagePack(System.Memory msgpack) -> Nerdbank.MessagePack.RawMessagePack +static Nerdbank.MessagePack.RawMessagePack.implicit operator Nerdbank.MessagePack.RawMessagePack(System.ReadOnlyMemory msgpack) -> Nerdbank.MessagePack.RawMessagePack +static Nerdbank.MessagePack.RawMessagePack.implicit operator System.Buffers.ReadOnlySequence(Nerdbank.MessagePack.RawMessagePack msgpack) -> System.Buffers.ReadOnlySequence +static readonly Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode.Default -> Nerdbank.MessagePack.LibraryReservedMessagePackExtensionTypeCode! +virtual Nerdbank.MessagePack.KnownSubTypeAttribute.Shape.get -> PolyType.Abstractions.ITypeShape? +virtual Nerdbank.MessagePack.MessagePackConverter.GetJsonSchema(Nerdbank.MessagePack.JsonSchemaContext! context, PolyType.Abstractions.ITypeShape! typeShape) -> System.Text.Json.Nodes.JsonObject? +virtual Nerdbank.MessagePack.MessagePackConverter.PreferAsyncSerialization.get -> bool +virtual Nerdbank.MessagePack.MessagePackConverter.ReadAsync(Nerdbank.MessagePack.MessagePackAsyncReader! reader, Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask +virtual Nerdbank.MessagePack.MessagePackConverter.WriteAsync(Nerdbank.MessagePack.MessagePackAsyncWriter! writer, T? value, Nerdbank.MessagePack.SerializationContext context) -> System.Threading.Tasks.ValueTask \ No newline at end of file diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/ConverterAnalyzersTests.cs b/test/Nerdbank.MessagePack.Analyzers.Tests/ConverterAnalyzersTests.cs index 51a5f5dc..5bfa26dd 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/ConverterAnalyzersTests.cs +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/ConverterAnalyzersTests.cs @@ -140,10 +140,16 @@ public class MyType public SomeOtherType SomeField; } + #if NET public class SomeOtherType : IShapeable { public static ITypeShape GetShape() => throw new System.NotImplementedException(); } + #else + public class SomeOtherType + { + } + #endif public class MyTypeConverter : MessagePackConverter { @@ -157,7 +163,11 @@ public override MyType Read(ref MessagePackReader reader, SerializationContext c { return new MyType { + #if NET SomeField = context.GetConverter().Read(ref reader, context), + #else + SomeField = context.GetConverter(context.TypeShapeProvider).Read(ref reader, context), + #endif }; } } @@ -170,7 +180,11 @@ public override void Write(ref MessagePackWriter writer, in MyType value, Serial return; } + #if NET context.GetConverter().Write(ref writer, value.SomeField, context); + #else + context.GetConverter(context.TypeShapeProvider).Write(ref writer, value.SomeField, context); + #endif } public override System.Text.Json.Nodes.JsonObject GetJsonSchema(JsonSchemaContext context, PolyType.Abstractions.ITypeShape typeShape) => throw new System.NotImplementedException(); @@ -311,7 +325,7 @@ public async Task NoIssues_ReadHasAttribute() internal class ArrayWithFlattenedDimensionsConverter : MessagePackConverter { - [UnconditionalSuppressMessage("AOT", "IL3050")] + [My] public override int Read(ref MessagePackReader reader, SerializationContext context) { return reader.ReadInt32(); @@ -320,6 +334,9 @@ public override int Read(ref MessagePackReader reader, SerializationContext cont public override void Write(ref MessagePackWriter writer, in int value, SerializationContext context) => throw new NotImplementedException(); public override System.Text.Json.Nodes.JsonObject GetJsonSchema(JsonSchemaContext context, PolyType.Abstractions.ITypeShape typeShape) => throw new System.NotImplementedException(); } + + [AttributeUsage(AttributeTargets.Method)] + internal class MyAttribute : Attribute { } """; await VerifyCS.VerifyAnalyzerAsync(source); @@ -422,18 +439,29 @@ public async Task CreatesNewSerializer() using PolyType.Abstractions; using Nerdbank.MessagePack; + #if NET public partial class MyType : IShapeable { public static ITypeShape GetShape() => throw new System.NotImplementedException(); } - + #else + public partial class MyType + { + } + #endif + public class MyTypeConverter : MessagePackConverter { public override MyType Read(ref MessagePackReader reader, SerializationContext context) => throw new System.NotImplementedException(); public override void Write(ref MessagePackWriter writer, in MyType value, SerializationContext context) { var serializer = {|NBMsgPack030:new MessagePackSerializer()|}; - {|NBMsgPack030:serializer.Serialize(value)|}; + {|NBMsgPack030:serializer.Serialize( + value + #if !NET + , (ITypeShape)null! + #endif + )|}; throw new System.NotImplementedException(); } public override System.Text.Json.Nodes.JsonObject GetJsonSchema(JsonSchemaContext context, PolyType.Abstractions.ITypeShape typeShape) => throw new System.NotImplementedException(); diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/KnownSubTypeAnalyzersTests.cs b/test/Nerdbank.MessagePack.Analyzers.Tests/KnownSubTypeAnalyzersTests.cs index f7216384..44708391 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/KnownSubTypeAnalyzersTests.cs +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/KnownSubTypeAnalyzersTests.cs @@ -8,6 +8,7 @@ public class KnownSubTypeAnalyzersTests [Fact] public async Task NoIssues_Interface() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -21,13 +22,27 @@ public class DerivedType : IMyType, PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType))] + public interface IMyType + { + } + public class DerivedType : IMyType + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } [Fact] public async Task NoIssues_Subclass() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -41,6 +56,20 @@ public class DerivedType : MyType, PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType))] + public class MyType + { + } + + public class DerivedType : MyType + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } @@ -48,6 +77,7 @@ public class DerivedType : MyType, PolyType.IShapeable [Fact] public async Task NonDerivedType() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -61,6 +91,20 @@ public class NonDerivedType : PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, {|NBMsgPack010:typeof(NonDerivedType)|})] + public class MyType + { + } + + public class NonDerivedType + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } @@ -68,6 +112,7 @@ public class NonDerivedType : PolyType.IShapeable [Fact] public async Task NoIssues_NonUniqueAlias_AcrossTypes() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -91,6 +136,29 @@ public class DerivedType2 : MyType2, PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType1))] + public class MyType + { + } + + public class DerivedType1 : MyType + { + } + + [KnownSubType(1, typeof(DerivedType2))] + public class MyType2 + { + } + + public class DerivedType2 : MyType2 + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } @@ -98,6 +166,7 @@ public class DerivedType2 : MyType2, PolyType.IShapeable [Fact] public async Task NonUniqueAlias() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -117,6 +186,25 @@ public class DerivedType2 : MyType, PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType1))] + [KnownSubType({|NBMsgPack011:1|}, typeof(DerivedType2))] + public class MyType + { + } + + public class DerivedType1 : MyType + { + } + + public class DerivedType2 : MyType + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } @@ -124,6 +212,7 @@ public class DerivedType2 : MyType, PolyType.IShapeable [Fact] public async Task NonUniqueSubType() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -138,6 +227,21 @@ public class DerivedType1 : MyType, PolyType.IShapeable static PolyType.Abstractions.ITypeShape PolyType.IShapeable.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType1))] + [KnownSubType(2, {|NBMsgPack012:typeof(DerivedType1)|})] + public class MyType + { + } + + public class DerivedType1 : MyType + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } @@ -145,6 +249,7 @@ public class DerivedType1 : MyType, PolyType.IShapeable [Fact] public async Task NoIssues_ClosedGenericSubType() { +#if NET string source = /* lang=c#-test */ """ using Nerdbank.MessagePack; @@ -164,6 +269,25 @@ internal class Witness : PolyType.IShapeable>, PolyType.IShapea static PolyType.Abstractions.ITypeShape> PolyType.IShapeable>.GetShape() => throw new System.NotImplementedException(); } """; +#else + string source = /* lang=c#-test */ """ + using Nerdbank.MessagePack; + + [KnownSubType(1, typeof(DerivedType))] + [KnownSubType(2, typeof(DerivedType))] + public class MyType + { + } + + public class DerivedType : MyType + { + } + + internal class Witness + { + } + """; +#endif await VerifyCS.VerifyAnalyzerAsync(source); } diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/MigrationAnalyzerTests.cs b/test/Nerdbank.MessagePack.Analyzers.Tests/MigrationAnalyzerTests.cs index f53b91aa..9386412b 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/MigrationAnalyzerTests.cs +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/MigrationAnalyzerTests.cs @@ -382,7 +382,7 @@ private Task VerifyCodeFixAsync([StringSyntax("c#-test")] string source, [String { TestCode = source.Replace("\t", " "), FixedCode = fixedSource.Replace("\t", " "), - ReferenceAssemblies = ReferencesHelper.DefaultTargetFrameworkReferences.WithPackages([ + ReferenceAssemblies = ReferencesHelper.References.WithPackages([ new PackageIdentity("MessagePack", "2.5.187"), ]), NumberOfFixAllInDocumentIterations = iterations, diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/Nerdbank.MessagePack.Analyzers.Tests.csproj b/test/Nerdbank.MessagePack.Analyzers.Tests/Nerdbank.MessagePack.Analyzers.Tests.csproj index 658b47ac..1d2f371c 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/Nerdbank.MessagePack.Analyzers.Tests.csproj +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/Nerdbank.MessagePack.Analyzers.Tests.csproj @@ -1,7 +1,8 @@  - net8.0 + net8.0 + $(TargetFrameworks);net472 diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/CodeFixVerifier`2.cs b/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/CodeFixVerifier`2.cs index e8bb655b..762a044a 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/CodeFixVerifier`2.cs +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/CodeFixVerifier`2.cs @@ -68,7 +68,7 @@ internal class Test : CSharpCodeFixTest { internal Test() { - this.ReferenceAssemblies = ReferencesHelper.DefaultTargetFrameworkReferences; + this.ReferenceAssemblies = ReferencesHelper.References; this.CompilerDiagnostics = CompilerDiagnostics.Warnings; this.TestState.AdditionalReferences.AddRange(ReferencesHelper.GetReferences()); this.FixedState.AdditionalReferences.AddRange(ReferencesHelper.GetReferences()); @@ -85,7 +85,11 @@ where resourceName.StartsWith(additionalFilePrefix, StringComparison.Ordinal) protected override ParseOptions CreateParseOptions() { - return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion.CSharp12); + return ((CSharpParseOptions)base.CreateParseOptions()) +#if NET + .WithPreprocessorSymbols("NET") +#endif + .WithLanguageVersion(LanguageVersion.CSharp12); } protected override CompilationOptions CreateCompilationOptions() @@ -96,6 +100,7 @@ protected override CompilationOptions CreateCompilationOptions() .WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions .SetItem("CS1591", ReportDiagnostic.Suppress) // documentation required .SetItem("CS1701", ReportDiagnostic.Suppress) // binding redirects + .SetItem("CS1702", ReportDiagnostic.Suppress) // binding redirects .SetItem("CS0169", ReportDiagnostic.Suppress) // unused field .SetItem("CS0414", ReportDiagnostic.Suppress)); // field assigned but never used } diff --git a/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/ReferencesHelper.cs b/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/ReferencesHelper.cs index 21126204..70d336a0 100644 --- a/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/ReferencesHelper.cs +++ b/test/Nerdbank.MessagePack.Analyzers.Tests/Verifiers/ReferencesHelper.cs @@ -5,7 +5,15 @@ internal static class ReferencesHelper { - internal static ReferenceAssemblies DefaultTargetFrameworkReferences = ReferenceAssemblies.Net.Net80; +#if NET + internal static ReferenceAssemblies References = ReferenceAssemblies.Net.Net80; +#else + internal static ReferenceAssemblies References = ReferenceAssemblies.NetStandard.NetStandard20 + .WithPackages([ + new PackageIdentity("System.Memory", "4.6.0"), + new PackageIdentity("System.Text.Json", "9.0.0"), + ]); +#endif internal static IEnumerable GetReferences() { diff --git a/test/Nerdbank.MessagePack.Tests/ArraysOfPrimitivesTests.cs b/test/Nerdbank.MessagePack.Tests/ArraysOfPrimitivesTests.cs index 55b98738..31946031 100644 --- a/test/Nerdbank.MessagePack.Tests/ArraysOfPrimitivesTests.cs +++ b/test/Nerdbank.MessagePack.Tests/ArraysOfPrimitivesTests.cs @@ -6,6 +6,12 @@ public partial class ArraysOfPrimitivesTests(ITestOutputHelper logger) : MessagePackSerializerTestBase(logger) { +#if NET + private static readonly Random Random = Random.Shared; +#else + private static readonly Random Random = new Random(); +#endif + [Theory, PairwiseData] public void BoolArray([CombinatorialMemberData(nameof(GetInterestingLengths), typeof(byte))] int length) { @@ -13,7 +19,7 @@ public void BoolArray([CombinatorialMemberData(nameof(GetInterestingLengths), ty if (length >= 0) { byte[] random = new byte[length]; - Random.Shared.NextBytes(random); + Random.NextBytes(random); values = random.Select(b => b % 2 == 0).ToArray(); } @@ -27,7 +33,7 @@ public void Boolean([CombinatorialMemberData(nameof(GetInterestingLengths), type if (length >= 0) { byte[]? random = new byte[length]; - Random.Shared.NextBytes(random); + Random.NextBytes(random); values = random.Select(b => b % 2 == 0).ToArray(); } @@ -75,7 +81,11 @@ public void Single([CombinatorialMemberData(nameof(GetInterestingLengths), typeo values = new float[length]; for (int i = 0; i < values.Length; i++) { - values[i] = Random.Shared.NextSingle(); +#if NET + values[i] = Random.NextSingle(); +#else + values[i] = (float)Random.NextDouble(); +#endif } } @@ -91,7 +101,7 @@ public void Double([CombinatorialMemberData(nameof(GetInterestingLengths), typeo values = new double[length]; for (int i = 0; i < values.Length; i++) { - values[i] = Random.Shared.NextDouble(); + values[i] = Random.NextDouble(); } } @@ -107,7 +117,7 @@ public void Double([CombinatorialMemberData(nameof(GetInterestingLengths), typeo } byte[] random = new byte[length * sizeof(T)]; - Random.Shared.NextBytes(random); + Random.NextBytes(random); T[] values = new T[length]; fixed (byte* pSource = random) { @@ -122,7 +132,11 @@ public void Double([CombinatorialMemberData(nameof(GetInterestingLengths), typeo private static int[] GetInterestingLengths(Type type) => (int[])typeof(ArraysOfPrimitivesTests).GetMethod(nameof(GetInterestingLengthsHelper), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(type).Invoke(null, null)!; +#if NET private static int[] GetInterestingLengthsHelper() => [-1, 0, 4, Vector.Count - 1, Vector.Count, Vector.Count + 1, (Vector.Count * 2) + 2, 10_000]; +#else + private static int[] GetInterestingLengthsHelper() => [-1, 0, 4, 100, 10_000]; +#endif [GenerateShape] [GenerateShape>] diff --git a/test/Nerdbank.MessagePack.Tests/BuiltInConverterTests.cs b/test/Nerdbank.MessagePack.Tests/BuiltInConverterTests.cs index 361c06ef..a2934173 100644 --- a/test/Nerdbank.MessagePack.Tests/BuiltInConverterTests.cs +++ b/test/Nerdbank.MessagePack.Tests/BuiltInConverterTests.cs @@ -8,12 +8,16 @@ public partial class BuiltInConverterTests(ITestOutputHelper logger) : MessagePa [Fact] public void ImmutableDictionary() => this.AssertRoundtrip(new HasImmutableDictionary() { Map = { { "a", 1 } } }); +#if NET + [Fact] public void Int128() => this.AssertRoundtrip(new HasInt128(new Int128(1, 2))); [Fact] public void UInt128() => this.AssertRoundtrip(new HasUInt128(new UInt128(1, 2))); +#endif + [Fact] public void Decimal() => this.AssertRoundtrip(new HasDecimal(1.2m)); @@ -37,12 +41,16 @@ public partial class HasImmutableDictionary : IEquatable public bool Equals(HasImmutableDictionary? other) => ByValueEquality.Equal(this.Map, other?.Map); } +#if NET + [GenerateShape] public partial record HasInt128(Int128 Value); [GenerateShape] public partial record HasUInt128(UInt128 Value); +#endif + [GenerateShape] public partial record HasDecimal(decimal Value); diff --git a/test/Nerdbank.MessagePack.Tests/ByValueEqualityComparerTests.cs b/test/Nerdbank.MessagePack.Tests/ByValueEqualityComparerTests.cs index c4ede6b2..27651488 100644 --- a/test/Nerdbank.MessagePack.Tests/ByValueEqualityComparerTests.cs +++ b/test/Nerdbank.MessagePack.Tests/ByValueEqualityComparerTests.cs @@ -1,6 +1,11 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8767 // null ref annotations +#endif + using System.Numerics; using PolyType.Tests; @@ -47,27 +52,33 @@ public void ReadOnlyMemoryOfByte() => this.AssertEqualityComparerBehavior( [SkippableTheory(typeof(NotSupportedException))] [MemberData(nameof(TestTypes.GetTestCases), MemberType = typeof(TestTypes))] +#if NET public void Equals_Exhaustive(TestCase testCase) where TProvider : IShapeable +#else + public void Equals_Exhaustive(TestCase testCase) +#endif { // We do not expect these cases to work. Skip.If(typeof(T) == typeof(object)); - ITypeShape shape = TProvider.GetShape(); - IEqualityComparer equalityComparer = this.GetEqualityComparer(); - Assert.True(equalityComparer.Equals(testCase.Value, testCase.Value)); + IEqualityComparer equalityComparer = this.GetEqualityComparer(testCase.DefaultShape); + Assert.True(equalityComparer.Equals(testCase.Value!, testCase.Value!)); } [SkippableTheory(typeof(NotSupportedException))] [MemberData(nameof(TestTypes.GetTestCases), MemberType = typeof(TestTypes))] +#if NET public void GetHashCode_Exhaustive(TestCase testCase) where TProvider : IShapeable +#else + public void GetHashCode_Exhaustive(TestCase testCase) +#endif { // We do not expect these cases to work. Skip.If(typeof(T) == typeof(object)); - ITypeShape shape = TProvider.GetShape(); - IEqualityComparer equalityComparer = this.GetEqualityComparer(); + IEqualityComparer equalityComparer = this.GetEqualityComparer(testCase.DefaultShape); // We don't really have anything useful to check the return value against, but // at least verify it doesn't throw. @@ -77,13 +88,29 @@ public void GetHashCode_Exhaustive(TestCase testCase } } - protected abstract IEqualityComparer GetEqualityComparer() - where TProvider : IShapeable; + protected IEqualityComparer GetEqualityComparer() +#if NET + where TProvider : IShapeable => this.GetEqualityComparer(TProvider.GetShape()); +#else + => this.GetEqualityComparer(MessagePackSerializerTestBase.GetShape()); +#endif + + protected IEqualityComparer GetEqualityComparer() +#if NET + where T : IShapeable => this.GetEqualityComparer(T.GetShape()); +#else + => this.GetEqualityComparer(MessagePackSerializerPolyfill.Witness.ShapeProvider.GetShape()); +#endif + + protected abstract IEqualityComparer GetEqualityComparer(ITypeShape shape); /// private void AssertEqualityComparerBehavior(T[] equivalent, T[] different) - where T : notnull, IShapeable - => this.AssertEqualityComparerBehavior(equivalent, different); +#if NET + where T : notnull, IShapeable => this.AssertEqualityComparerBehavior(equivalent, different); +#else + where T : notnull => this.AssertEqualityComparerBehavior(equivalent, different); +#endif /// /// Asserts that hash codes and equality checks match or mismatch for various values. @@ -93,7 +120,9 @@ private void AssertEqualityComparerBehavior(T[] equivalent, T[] different) /// An array of values that should all be considered equivalent. /// An array of values that are each distinct from any of the values in the array. private void AssertEqualityComparerBehavior(T[] equivalent, T[] different) +#if NET where TProvider : IShapeable +#endif where T : notnull { IEqualityComparer eq = this.GetEqualityComparer(); @@ -128,11 +157,11 @@ public class DefaultByValue(ITestOutputHelper logger) : ByValueEqualityComparerT public override void CustomHash() { CustomHasher obj = new(); - Assert.Equal(obj.SpecialCode, this.GetEqualityComparer().GetHashCode(obj)); + Assert.Equal(obj.SpecialCode, this.GetEqualityComparer().GetHashCode(obj)); } - protected override IEqualityComparer GetEqualityComparer() - => ByValueEqualityComparer.Default; + protected override IEqualityComparer GetEqualityComparer(ITypeShape shape) + => ByValueEqualityComparer.GetDefault(shape); } public class HashCollisionResistant(ITestOutputHelper logger) : ByValueEqualityComparerTests(logger) @@ -141,15 +170,16 @@ public class HashCollisionResistant(ITestOutputHelper logger) : ByValueEqualityC public override void CustomHash() { CustomHasher obj = new(); - Assert.Equal(obj.SpecialCode * 2, this.GetEqualityComparer().GetHashCode(obj)); + Assert.Equal(obj.SpecialCode * 2, this.GetEqualityComparer().GetHashCode(obj)); } - protected override IEqualityComparer GetEqualityComparer() - => ByValueEqualityComparer.HashResistant; + protected override IEqualityComparer GetEqualityComparer(ITypeShape shape) + => ByValueEqualityComparer.GetHashResistant(shape); } [GenerateShape] [GenerateShape] + [GenerateShape] internal partial class Witness; [GenerateShape] diff --git a/test/Nerdbank.MessagePack.Tests/ConvertToJsonTests.cs b/test/Nerdbank.MessagePack.Tests/ConvertToJsonTests.cs index c6db9584..acbbb48b 100644 --- a/test/Nerdbank.MessagePack.Tests/ConvertToJsonTests.cs +++ b/test/Nerdbank.MessagePack.Tests/ConvertToJsonTests.cs @@ -24,7 +24,9 @@ public partial class ConvertToJsonTests(ITestOutputHelper logger) : MessagePackS public void ConvertToJson_Sequence() => Assert.Equal("null", MessagePackSerializer.ConvertToJson(new([0xc0]))); private void AssertConvertToJson([StringSyntax("json")] string expected, T? value) +#if NET where T : IShapeable +#endif { string json = this.ConvertToJson(value); this.Logger.WriteLine(json); @@ -32,10 +34,16 @@ private void AssertConvertToJson([StringSyntax("json")] string expected, T? v } private string ConvertToJson(T? value) +#if NET where T : IShapeable +#endif { Sequence sequence = new(); +#if NET this.Serializer.Serialize(sequence, value); +#else + this.Serializer.Serialize(sequence, value, Witness.ShapeProvider); +#endif StringWriter jsonWriter = new(); MessagePackReader reader = new(sequence); MessagePackSerializer.ConvertToJson(ref reader, jsonWriter); @@ -47,4 +55,7 @@ public partial record Primitives(int Seeds, bool Is, double Number); [GenerateShape] public partial record ArrayWrapper(params int[] IntArray); + + [GenerateShape] + private partial class Witness; } diff --git a/test/Nerdbank.MessagePack.Tests/CustomConverterTests.cs b/test/Nerdbank.MessagePack.Tests/CustomConverterTests.cs index 95314df3..06c03dac 100644 --- a/test/Nerdbank.MessagePack.Tests/CustomConverterTests.cs +++ b/test/Nerdbank.MessagePack.Tests/CustomConverterTests.cs @@ -38,13 +38,13 @@ private partial class CustomTypeConverter : MessagePackConverter { public override CustomType? Read(ref MessagePackReader reader, SerializationContext context) { - string? value = context.GetConverter().Read(ref reader, context); + string? value = context.GetConverter(ShapeProvider).Read(ref reader, context); return new CustomType { InternalProperty = value }; } public override void Write(ref MessagePackWriter writer, in CustomType? value, SerializationContext context) { - context.GetConverter().Write(ref writer, value?.InternalProperty, context); + context.GetConverter(ShapeProvider).Write(ref writer, value?.InternalProperty, context); } } } diff --git a/test/Nerdbank.MessagePack.Tests/EnumTests.cs b/test/Nerdbank.MessagePack.Tests/EnumTests.cs index c0ab5566..f9131d96 100644 --- a/test/Nerdbank.MessagePack.Tests/EnumTests.cs +++ b/test/Nerdbank.MessagePack.Tests/EnumTests.cs @@ -92,7 +92,9 @@ private static ReadOnlySequence SerializeEnumName(string name) private void AssertEnum(T value) where T : struct, Enum +#if NET where TWitness : IShapeable +#endif { ReadOnlySequence msgpack = this.AssertRoundtrip(value); this.AssertType(msgpack, this.ExpectedType); diff --git a/test/Nerdbank.MessagePack.Tests/KnownSubTypeTests.cs b/test/Nerdbank.MessagePack.Tests/KnownSubTypeTests.cs index 466fb848..2b8ee3ff 100644 --- a/test/Nerdbank.MessagePack.Tests/KnownSubTypeTests.cs +++ b/test/Nerdbank.MessagePack.Tests/KnownSubTypeTests.cs @@ -96,11 +96,19 @@ public void UnknownDerivedType() internal partial class Witness; [GenerateShape] +#if NET [KnownSubType(1)] [KnownSubType(2)] [KnownSubType(3)] [KnownSubType(4)] [KnownSubType, Witness>(5)] +#else + [KnownSubType(1, typeof(DerivedA))] + [KnownSubType(2, typeof(DerivedAA))] + [KnownSubType(3, typeof(DerivedB))] + [KnownSubType(4, typeof(EnumerableDerived))] + [KnownSubType(5, typeof(DerivedGeneric))] +#endif public partial record BaseClass { public int BaseClassProperty { get; set; } diff --git a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerPolyfill.cs b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerPolyfill.cs new file mode 100644 index 00000000..86ce1529 --- /dev/null +++ b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerPolyfill.cs @@ -0,0 +1,59 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET + +using System.Text.Json.Nodes; + +internal static partial class MessagePackSerializerPolyfill +{ + internal static byte[] Serialize(this MessagePackSerializer serializer, in T? value) + => serializer.Serialize(value, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static byte[] Serialize(this MessagePackSerializer serializer, in T? value) + => serializer.Serialize(value, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static void Serialize(this MessagePackSerializer serializer, IBufferWriter writer, in T? value) + => serializer.Serialize(writer, value, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static void Serialize(this MessagePackSerializer serializer, Stream stream, in T? value) + => serializer.Serialize(stream, value, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static ValueTask SerializeAsync(this MessagePackSerializer serializer, Stream writer, in T? value) + => serializer.SerializeAsync(writer, value, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static T? Deserialize(this MessagePackSerializer serializer, byte[] sequence) + => serializer.Deserialize(sequence, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static T? Deserialize(this MessagePackSerializer serializer, ReadOnlySequence sequence) + => serializer.Deserialize(sequence, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static T? Deserialize(this MessagePackSerializer serializer, ReadOnlySequence sequence) + => serializer.Deserialize(sequence, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static T? Deserialize(this MessagePackSerializer serializer, byte[] buffer) + => serializer.Deserialize(buffer, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static T? Deserialize(this MessagePackSerializer serializer, Stream stream) + => serializer.Deserialize(stream, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static ValueTask DeserializeAsync(this MessagePackSerializer serializer, PipeReader pipeReader) + => serializer.DeserializeAsync(pipeReader, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static ValueTask DeserializeAsync(this MessagePackSerializer serializer, Stream pipeReader) + => serializer.DeserializeAsync(pipeReader, MessagePackSerializerTestBase.GetShapeProvider()); + + internal static MessagePackConverter GetConverter(this SerializationContext context) + => context.GetConverter(MessagePackSerializerTestBase.GetShapeProvider()); + + internal static MessagePackConverter GetConverter(this SerializationContext context) + => context.GetConverter(MessagePackSerializerTestBase.GetShapeProvider()); + + internal static JsonObject GetJsonSchema(this MessagePackSerializer serializer) + => serializer.GetJsonSchema(MessagePackSerializerTestBase.GetShapeProvider()); + + [GenerateShape] + internal partial class Witness; +} + +#endif diff --git a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTestBase.cs b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTestBase.cs index 8bdae877..258d9e53 100644 --- a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTestBase.cs +++ b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTestBase.cs @@ -1,6 +1,8 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Reflection; + public abstract class MessagePackSerializerTestBase(ITestOutputHelper logger) { protected ReadOnlySequence lastRoundtrippedMsgpack; @@ -14,6 +16,27 @@ public abstract class MessagePackSerializerTestBase(ITestOutputHelper logger) protected ITestOutputHelper Logger => logger; +#if !NET + internal static ITypeShapeProvider GetShapeProvider() + { + PropertyInfo shapeProperty = typeof(TProvider).GetProperty("ShapeProvider", BindingFlags.Public | BindingFlags.Static) ?? throw new InvalidOperationException($"{typeof(TProvider).FullName} is not a witness class."); + Assert.NotNull(shapeProperty); + return (ITypeShapeProvider)shapeProperty.GetValue(null)!; + } +#endif + + internal static ITypeShape GetShape() +#if NET + where TProvider : IShapeable +#endif + { +#if NET + return TProvider.GetShape(); +#else + return (ITypeShape?)GetShapeProvider().GetShape(typeof(T)) ?? throw new InvalidOperationException("Shape not found."); +#endif + } + protected static void CapturePipe(PipeReader reader, PipeWriter forwardTo, Sequence logger) { _ = Task.Run(async delegate @@ -50,25 +73,44 @@ protected static void CapturePipe(PipeReader reader, PipeWriter forwardTo, Seque } protected ReadOnlySequence AssertRoundtrip(T? value) - where T : IShapeable => this.AssertRoundtrip(value); +#if NET + where T : IShapeable + => this.AssertRoundtrip(value); +#else + => this.AssertRoundtrip(value); +#endif protected ReadOnlySequence AssertRoundtrip(T? value) +#if NET where TProvider : IShapeable +#endif { +#if NET T? roundtripped = this.Roundtrip(value, TProvider.GetShape()); +#else + T? roundtripped = this.Roundtrip(value, GetShape()); +#endif Assert.Equal(value, roundtripped); return this.lastRoundtrippedMsgpack; } protected async Task> AssertRoundtripAsync(T? value) +#if NET where T : IShapeable +#endif { +#if NET await this.AssertRoundtripAsync(value); +#else + await this.AssertRoundtripAsync(value); +#endif return this.lastRoundtrippedMsgpack; } protected async Task> AssertRoundtripAsync(T? value) +#if NET where TProvider : IShapeable +#endif { T? roundtripped = await this.RoundtripAsync(value); Assert.Equal(value, roundtripped); @@ -76,10 +118,19 @@ protected async Task> AssertRoundtripAsync( } protected T? Roundtrip(T? value) +#if NET where T : IShapeable => this.Roundtrip(value, T.GetShape()); +#else + => this.Roundtrip(value, GetShape()); +#endif protected T? Roundtrip(T? value) - where TProvider : IShapeable => this.Roundtrip(value, TProvider.GetShape()); +#if NET + where TProvider : IShapeable + => this.Roundtrip(value, TProvider.GetShape()); +#else + => this.Roundtrip(value, GetShape()); +#endif protected T? Roundtrip(T? value, ITypeShape shape) { @@ -91,22 +142,34 @@ protected async Task> AssertRoundtripAsync( } protected ValueTask RoundtripAsync(T? value) - where T : IShapeable => this.RoundtripAsync(value); +#if NET + where T : IShapeable + => this.RoundtripAsync(value, T.GetShape()); +#else + => this.RoundtripAsync(value, GetShape()); +#endif - protected async ValueTask RoundtripAsync(T? value) + protected ValueTask RoundtripAsync(T? value) +#if NET where TProvider : IShapeable + => this.RoundtripAsync(value, TProvider.GetShape()); +#else + => this.RoundtripAsync(value, GetShape()); +#endif + + protected async ValueTask RoundtripAsync(T? value, ITypeShape shape) { Pipe pipeForSerializing = new(); Pipe pipeForDeserializing = new(); // Arrange the reader first to avoid deadlocks if the Pipe gets full. - ValueTask resultTask = this.Serializer.DeserializeAsync(pipeForDeserializing.Reader); + ValueTask resultTask = this.Serializer.DeserializeAsync(pipeForDeserializing.Reader, shape); // Log along the way. Sequence loggingSequence = new(); CapturePipe(pipeForSerializing.Reader, pipeForDeserializing.Writer, loggingSequence); - await this.Serializer.SerializeAsync(pipeForSerializing.Writer, value); + await this.Serializer.SerializeAsync(pipeForSerializing.Writer, value, shape); await pipeForSerializing.Writer.FlushAsync(); // The deserializer should complete even *without* our completing the writer. diff --git a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs index 9763b88f..5ab6f877 100644 --- a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs +++ b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs @@ -52,6 +52,7 @@ public enum SomeEnum [Fact] public void Array_Null() => this.AssertRoundtrip(new ClassWithArray { IntArray = null }); +#if NET #pragma warning disable SA1500 // Braces for multi-line statements should not share line [Theory, PairwiseData] public void MultidimensionalArray(MultiDimensionalArrayFormat format) @@ -72,8 +73,9 @@ public void MultidimensionalArray(MultiDimensionalArrayFormat format) }); } #pragma warning restore SA1500 // Braces for multi-line statements should not share line +#endif - [Fact] + [SkippableFact(typeof(PlatformNotSupportedException))] public void MultidimensionalArray_Null() => this.AssertRoundtrip(new HasMultiDimensionalArray()); [Fact] @@ -184,7 +186,7 @@ public void ByteMemorySerializedOptimally() { Memory original = new byte[] { 1, 2, 3 }; Memory deserialized = this.Roundtrip, Witness>(original); - Assert.Equal(original, deserialized); + Assert.Equal(original.ToArray(), deserialized.ToArray()); MessagePackReader reader = new(this.lastRoundtrippedMsgpack); Assert.Equal(MessagePackType.Binary, reader.NextMessagePackType); Assert.NotNull(reader.ReadBytes()); @@ -195,7 +197,7 @@ public void ByteReadOnlyMemorySerializedOptimally() { ReadOnlyMemory original = new byte[] { 1, 2, 3 }; ReadOnlyMemory deserialized = this.Roundtrip, Witness>(original); - Assert.Equal(original, deserialized); + Assert.Equal(original.ToArray(), deserialized.ToArray()); MessagePackReader reader = new(this.lastRoundtrippedMsgpack); Assert.Equal(MessagePackType.Binary, reader.NextMessagePackType); Assert.NotNull(reader.ReadBytes()); diff --git a/test/Nerdbank.MessagePack.Tests/NamingPolicyApplicationTests.cs b/test/Nerdbank.MessagePack.Tests/NamingPolicyApplicationTests.cs index ad0947d1..7a8d1ff5 100644 --- a/test/Nerdbank.MessagePack.Tests/NamingPolicyApplicationTests.cs +++ b/test/Nerdbank.MessagePack.Tests/NamingPolicyApplicationTests.cs @@ -43,7 +43,9 @@ public void PolicyNotAppliedToExplicitPropertyNames_NonDefaultCtor() } private void PolicyAppliedToInferredPropertyNamesHelper(T value) +#if NET where T : IShapeable +#endif { Sequence sequence = new(); this.Serializer = this.Serializer with { PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase }; @@ -58,7 +60,9 @@ private void PolicyAppliedToInferredPropertyNamesHelper(T value) } private void PolicyNotAppliedToExplicitPropertyNamesHelper(T value) +#if NET where T : IShapeable +#endif { Sequence sequence = new(); this.Serializer = this.Serializer with { PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase }; diff --git a/test/Nerdbank.MessagePack.Tests/Nerdbank.MessagePack.Tests.csproj b/test/Nerdbank.MessagePack.Tests/Nerdbank.MessagePack.Tests.csproj index 61ff9b4a..8a8001a2 100644 --- a/test/Nerdbank.MessagePack.Tests/Nerdbank.MessagePack.Tests.csproj +++ b/test/Nerdbank.MessagePack.Tests/Nerdbank.MessagePack.Tests.csproj @@ -1,7 +1,8 @@  - net8.0 + net8.0 + $(TargetFrameworks);net472 true diff --git a/test/Nerdbank.MessagePack.Tests/ObjectsAsArraysTests.cs b/test/Nerdbank.MessagePack.Tests/ObjectsAsArraysTests.cs index c648b75c..814c149d 100644 --- a/test/Nerdbank.MessagePack.Tests/ObjectsAsArraysTests.cs +++ b/test/Nerdbank.MessagePack.Tests/ObjectsAsArraysTests.cs @@ -349,7 +349,14 @@ public void PropertyGetterWithCtorParam() } private static ITypeShape GetShape() +#if NET where T : IShapeable => T.GetShape(); +#else + => GetShape(); +#endif + + [GenerateShape] + private partial class Witness; [GenerateShape] public partial record Person diff --git a/test/Nerdbank.MessagePack.Tests/SchemaTests.cs b/test/Nerdbank.MessagePack.Tests/SchemaTests.cs index 27fd1f99..88b20a07 100644 --- a/test/Nerdbank.MessagePack.Tests/SchemaTests.cs +++ b/test/Nerdbank.MessagePack.Tests/SchemaTests.cs @@ -136,7 +136,16 @@ public void ReferencePreservationGraphReset() Assert.DoesNotContain("ReferencePreservingConverter", schemaString); } - private static string SchemaToString(JsonObject schema) => schema.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + private static string SchemaToString(JsonObject schema) + { + string schemaString = schema.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + +#if NETFRAMEWORK + schemaString = schemaString.Replace("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"); +#endif + + return schemaString; + } private static void Record(JsonObject schema, string testName) { @@ -201,7 +210,9 @@ private bool CheckMatchWithLKG(JsonObject schema, string testName) } private JSchema AssertSchema(T?[]? sampleData = null, [CallerMemberName] string? testName = null) +#if NET where T : IShapeable +#endif { Requires.NotNull(testName!); @@ -323,7 +334,11 @@ internal partial class HasDateTime } [GenerateShape] +#if NET [KnownSubType(1)] +#else + [KnownSubType(1, typeof(SubType))] +#endif internal partial class BaseType { public string? Message { get; set; } diff --git a/test/Nerdbank.MessagePack.Tests/SerializationContextTests.cs b/test/Nerdbank.MessagePack.Tests/SerializationContextTests.cs index a7ed98b0..5c032a15 100644 --- a/test/Nerdbank.MessagePack.Tests/SerializationContextTests.cs +++ b/test/Nerdbank.MessagePack.Tests/SerializationContextTests.cs @@ -1,10 +1,14 @@ // Copyright (c) Andrew Arnott. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NET +#pragma warning disable CS1574 // unresolvable cref +#endif + public partial class SerializationContextTests { /// - /// Verifies that the method throws when not within a serialization operation. + /// Verifies that the method throws when not within a serialization operation. /// [Fact] public void GetConverterThrows() diff --git a/test/Nerdbank.MessagePack.Tests/SharedTestCases.cs b/test/Nerdbank.MessagePack.Tests/SharedTestCases.cs index 39cd1aa2..906a9626 100644 --- a/test/Nerdbank.MessagePack.Tests/SharedTestCases.cs +++ b/test/Nerdbank.MessagePack.Tests/SharedTestCases.cs @@ -5,12 +5,16 @@ public class SharedTestCases(ITestOutputHelper logger) : MessagePackSerializerTestBase(logger) { - [Theory] + [SkippableTheory(typeof(PlatformNotSupportedException))] [MemberData(nameof(TestTypes.GetTestCases), MemberType = typeof(TestTypes))] +#if NET public void Roundtrip_Value(TestCase testCase) where TProvider : IShapeable +#else + public void Roundtrip_Value(TestCase testCase) +#endif { - ITypeShape shape = TProvider.GetShape(); + ITypeShape shape = testCase.DefaultShape; byte[] msgpack = this.Serializer.Serialize(testCase.Value, shape); if (IsDeserializable(testCase)) From 481e2bb1059a88deb6119e028e48052cd4642b7c Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 7 Dec 2024 16:50:35 -0700 Subject: [PATCH 2/2] Bump version to 0.3-alpha --- .../Resources/KnownGoodSchemas/Complex.schema.json | 4 ++-- test/Nerdbank.MessagePack.Tests/SchemaTests.cs | 4 +++- version.json | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test/Nerdbank.MessagePack.Tests/Resources/KnownGoodSchemas/Complex.schema.json b/test/Nerdbank.MessagePack.Tests/Resources/KnownGoodSchemas/Complex.schema.json index 821810a7..91606ab6 100644 --- a/test/Nerdbank.MessagePack.Tests/Resources/KnownGoodSchemas/Complex.schema.json +++ b/test/Nerdbank.MessagePack.Tests/Resources/KnownGoodSchemas/Complex.schema.json @@ -48,7 +48,7 @@ ], "description": "A human person." }, - "System.Collections.Generic.List\u00601[[SchemaTests\u002BPerson, Nerdbank.MessagePack.Tests, Version=0.2.0.0, Culture=neutral, PublicKeyToken=d4b172c8050bbbc3]]": { + "System.Collections.Generic.List\u00601[[SchemaTests\u002BPerson, Nerdbank.MessagePack.Tests, Version=x.x.x.x, Culture=neutral, PublicKeyToken=d4b172c8050bbbc3]]": { "type": "array", "items": { "$ref": "#/definitions/SchemaTests\u002BPerson" @@ -79,7 +79,7 @@ ] }, "progeny": { - "$ref": "#/definitions/System.Collections.Generic.List\u00601[[SchemaTests\u002BPerson, Nerdbank.MessagePack.Tests, Version=0.2.0.0, Culture=neutral, PublicKeyToken=d4b172c8050bbbc3]]" + "$ref": "#/definitions/System.Collections.Generic.List\u00601[[SchemaTests\u002BPerson, Nerdbank.MessagePack.Tests, Version=x.x.x.x, Culture=neutral, PublicKeyToken=d4b172c8050bbbc3]]" } } } diff --git a/test/Nerdbank.MessagePack.Tests/SchemaTests.cs b/test/Nerdbank.MessagePack.Tests/SchemaTests.cs index 88b20a07..e5614489 100644 --- a/test/Nerdbank.MessagePack.Tests/SchemaTests.cs +++ b/test/Nerdbank.MessagePack.Tests/SchemaTests.cs @@ -138,7 +138,9 @@ public void ReferencePreservationGraphReset() private static string SchemaToString(JsonObject schema) { - string schemaString = schema.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + string schemaString = schema + .ToJsonString(new JsonSerializerOptions { WriteIndented = true }) + .Replace("Nerdbank.MessagePack.Tests, Version=0.3.0.0", "Nerdbank.MessagePack.Tests, Version=x.x.x.x"); #if NETFRAMEWORK schemaString = schemaString.Replace("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"); diff --git a/version.json b/version.json index f0751ee3..df7b3a05 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.2-alpha", + "version": "0.3-alpha", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/v\\d+(?:\\.\\d+)?$", @@ -9,4 +9,4 @@ "cloudBuild": { "setVersionVariables": false } -} \ No newline at end of file +}