From fdf76f721ed91b72bcf4c74a0bf65d4feac58bcb Mon Sep 17 00:00:00 2001 From: Ali Makki Date: Mon, 16 Sep 2024 10:23:03 -0400 Subject: [PATCH 1/2] MPC String Value From Key Attribute Fix - when a string key is defined as a `Key`'s attribute, ensure that `mpc` generates the Span attributes based on the key defined and not the property name - add `StringKeyAttributeMapModeTest` in `GeneratedStringKeyFormatterTest.cs` --- .../CodeAnalysis/TypeCollector.cs | 6 +- .../GenerateStringKeyedFormatterTest.cs | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs index eaf9ef1f6..3083416d8 100644 --- a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs +++ b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs @@ -635,7 +635,8 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + var stringKey = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0].Value as string ?? item.Name; + var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); this.CollectCore(item.Type); // recursive collect @@ -661,7 +662,8 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + var stringKey = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0].Value as string ?? item.Name; + var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); this.CollectCore(item.Type); // recursive collect } diff --git a/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs b/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs index 1719fb5fa..2815b8636 100644 --- a/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs +++ b/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs @@ -863,5 +863,67 @@ await compiler.GenerateFileAsync( ((string)result.B).Should().Be("foobar"); // default value }); } + + [Fact] + public async Task StringKeyAttributeMapModeTest() + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyMessagePackObject + { + [Key(""a"")] + public int A { get; set; } + [Key(""b"")] + public string B { get; set; } + } +} + "; + tempWorkarea.AddFileToTargetProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.GetOutputCompilation().Compilation, + tempWorkarea.OutputDirectory, + "TempProjectResolver", + "TempProject.Generated", + true, + string.Empty, + Array.Empty()); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Should().NotContain(x => x.Severity == DiagnosticSeverity.Error); + + // Run tests with the generated resolver/formatter assembly. + compilation.ExecuteWithGeneratedAssembly((ctx, assembly) => + { + var mpoType = assembly.GetType("TempProject.MyMessagePackObject"); + var options = MessagePackSerializerOptions.Standard + .WithResolver(CompositeResolver.Create( + StandardResolver.Instance, + TestUtilities.GetResolverInstance(assembly, "TempProject.Generated.Resolvers.TempProjectResolver"))); + + // Build `{ "a": -1, "b": "foo" }` + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(2); + writer.Write("a"); + writer.Write(-1); + writer.Write("b"); + writer.Write("foo"); + writer.Flush(); + + // Verify deserialization + dynamic result = MessagePackSerializer.Deserialize(mpoType, seq, options); + ((int)result.A).Should().Be(-1); // from ctor + ((string)result.B).Should().Be("foo"); // default value + }); + } } } From 62238b3d8b7c6b66c4263cf97a88af5895e11a03 Mon Sep 17 00:00:00 2001 From: Ali Makki Date: Wed, 25 Sep 2024 12:22:06 -0400 Subject: [PATCH 2/2] - perform a length check on `keyAttribute.ConstructorArguments` before indexing to `0`, otherwise default back to `item.Name` --- .../CodeAnalysis/TypeCollector.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs index 3083416d8..5f991d514 100644 --- a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs +++ b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs @@ -635,7 +635,13 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var stringKey = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0].Value as string ?? item.Name; + var keyAttribute = item.GetAttributes() + .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + + var stringKey = keyAttribute?.ConstructorArguments.Length > 0 + ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name + : item.Name; + var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); @@ -662,7 +668,13 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var stringKey = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0].Value as string ?? item.Name; + var keyAttribute = item.GetAttributes() + .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + + var stringKey = keyAttribute?.ConstructorArguments.Length > 0 + ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name + : item.Name; + var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); this.CollectCore(item.Type); // recursive collect