Repository: MessagePack-CSharp/MessagePack-CSharp

Issue:
Array of arrays member in MessagePackObject-annotated class causes build failure with NuGet package version >=v3.0.111-alpha
#### Bug description

Trying to build a project that has a class with an array of arrays member fails when the >=v3.0.111-alpha NuGet package is used. v3.0.54-alpha is the last working v3 version. What's strange to me, is that I tried to bisect the commit where this broke, checked out the v3.0.134-beta tag, added it as a `ProjectReference`, and the build succeeded. So this happens only with the NuGet package, and I have no idea how to dig further. To summarize what I observerd:

 - v3.0.54-alpha is the last version not affected by this.
 - v2.x.x versions are not affected.
 - The build error happens only with the NuGet package.
 - I have observed the build failure only with array of arrays (simple (`[]`), and multidimensional (`[,]`) arrays work as expected).

#### Repro steps

The following code can be used to reproduce the build failure:

Program.cs:
```c#
using MessagePack;

A a = new();

[MessagePackObject]
public class A
{
    public int[][]? M { get; set; }
};
```

MessagePackTest.csproj:
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!--  Build fails with the NuGet package -->
    <PackageReference Include="MessagePack" Version="3.0.134-beta" />

    <!--  Build succeeds with referencing the checked-out 3.0.134-beta tag -->
    <!-- <ProjectReference Include="..\MessagePack-CSharp\src\MessagePack\MessagePack.csproj" /> -->
  </ItemGroup>
</Project>
```

#### Expected behavior

`dotnet build` succeeds.

#### Actual behavior

`dotnet build` fails with the following error message:

```
/home/sghctoma/projects/misc/dotnet/MessagePackTest/obj/Debug/net8.0/MessagePack.SourceGenerator/MessagePack.SourceGenerator.MessagePackGenerator/MessagePack.GeneratedMessagePackResolver.g.cs(47,21): error CS1001: Identifier expected [/home/sghctoma/projects/misc/dotnet/MessagePackTest/MessagePackTest.csproj]
```

The file the message refers to does not even exist. In fact, there's no `obj/Debug/net8.0/MessagePack.SourceGenerator` folder.

- Version used: v3.0.111-alpha, v3.0.129-beta, v3.0.134-beta
- Runtime: .NET Core 8.0


Hints:
This seems to repro on 3.0.54-alpha too, for me. 

Patch:
diff --git a/src/MessagePack.Annotations/Attributes.cs b/src/MessagePack.Annotations/Attributes.cs
index 0d5ad5d2d..5072776fd 100644
--- a/src/MessagePack.Annotations/Attributes.cs
+++ b/src/MessagePack.Annotations/Attributes.cs
@@ -14,7 +14,7 @@ public class MessagePackObjectAttribute : Attribute
         /// <summary>
         /// Gets a value indicating whether to automatically serialize all internal and public fields and properties using their property name as the key in a map.
         /// </summary>
-        public bool KeyAsPropertyName { get; private set; }
+        public bool KeyAsPropertyName { get; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MessagePackObjectAttribute"/> class.
@@ -46,9 +46,9 @@ public MessagePackObjectAttribute(bool keyAsPropertyName = false)
     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
     public class KeyAttribute : Attribute
     {
-        public int? IntKey { get; private set; }
+        public int? IntKey { get; }
 
-        public string? StringKey { get; private set; }
+        public string? StringKey { get; }
 
         public KeyAttribute(int x)
         {
@@ -72,12 +72,12 @@ public class UnionAttribute : Attribute
         /// <summary>
         /// Gets the distinguishing value that identifies a particular subtype.
         /// </summary>
-        public int Key { get; private set; }
+        public int Key { get; }
 
         /// <summary>
         /// Gets the derived or implementing type.
         /// </summary>
-        public Type SubType { get; private set; }
+        public Type SubType { get; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="UnionAttribute"/> class.
@@ -110,9 +110,9 @@ public class SerializationConstructorAttribute : Attribute
     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
     public class MessagePackFormatterAttribute : Attribute
     {
-        public Type FormatterType { get; private set; }
+        public Type FormatterType { get; }
 
-        public object?[]? Arguments { get; private set; }
+        public object?[]? Arguments { get; }
 
         public MessagePackFormatterAttribute(Type formatterType)
         {
diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
index d70aab0c4..aad9b3c6d 100644
--- a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
+++ b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
@@ -721,7 +721,8 @@ private bool CheckValidMessagePackFormatterAttribute(AttributeData formatterAttr
 
             foreach (IPropertySymbol item in formattedType.GetAllMembers().OfType<IPropertySymbol>())
             {
-                if (item.GetAttributes().Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) ||
+                ImmutableArray<AttributeData> attributes = item.GetAttributes();
+                if (attributes.Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) ||
                     item.IsStatic ||
                     item.IsOverride ||
                     item.IsImplicitlyDeclared ||
@@ -737,10 +738,13 @@ private bool CheckValidMessagePackFormatterAttribute(AttributeData formatterAttr
                     continue;
                 }
 
+                AttributeData? keyAttribute = attributes.FirstOrDefault(attributes => attributes.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute));
+                string stringKey = keyAttribute?.ConstructorArguments.Length == 1 && keyAttribute.ConstructorArguments[0].Value is string name ? name : item.Name;
+
                 includesPrivateMembers |= item.GetMethod is not null && !IsAllowedAccessibility(item.GetMethod.DeclaredAccessibility);
                 includesPrivateMembers |= item.SetMethod is not null && !IsAllowedAccessibility(item.SetMethod.DeclaredAccessibility);
                 FormatterDescriptor? specialFormatter = GetSpecialFormatter(item);
-                var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter);
+                MemberSerializationInfo member = new(true, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter);
                 stringMembers.Add(member.StringKey, (member, item.Type));
 
                 if (specialFormatter is null)
@@ -751,7 +755,8 @@ private bool CheckValidMessagePackFormatterAttribute(AttributeData formatterAttr
 
             foreach (IFieldSymbol item in formattedType.GetAllMembers().OfType<IFieldSymbol>())
             {
-                if (item.GetAttributes().Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) ||
+                ImmutableArray<AttributeData> attributes = item.GetAttributes();
+                if (attributes.Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) ||
                     item.IsStatic ||
                     item.IsImplicitlyDeclared ||
                     item.DeclaredAccessibility < minimumAccessibility)
@@ -759,8 +764,11 @@ private bool CheckValidMessagePackFormatterAttribute(AttributeData formatterAttr
                     continue;
                 }
 
+                AttributeData? keyAttribute = attributes.FirstOrDefault(attributes => attributes.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute));
+                string stringKey = keyAttribute?.ConstructorArguments.Length == 1 && keyAttribute.ConstructorArguments[0].Value is string name ? name : item.Name;
+
                 FormatterDescriptor? specialFormatter = GetSpecialFormatter(item);
-                var member = new MemberSerializationInfo(false, IsWritable: !item.IsReadOnly, IsReadable: true, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter);
+                MemberSerializationInfo member = new(false, IsWritable: !item.IsReadOnly, IsReadable: true, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter);
                 stringMembers.Add(member.StringKey, (member, item.Type));
                 if (specialFormatter is null)
                 {


Test patch:
diff --git a/tests/MessagePack.SourceGenerator.MapModeExecutionTests/StringKeyOverrideTests.cs b/tests/MessagePack.SourceGenerator.MapModeExecutionTests/StringKeyOverrideTests.cs
new file mode 100644
index 000000000..48858e4d3
--- /dev/null
+++ b/tests/MessagePack.SourceGenerator.MapModeExecutionTests/StringKeyOverrideTests.cs
@@ -0,0 +1,83 @@
+﻿// Copyright (c) All contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+public class StringKeyOverrideTests(ITestOutputHelper logger)
+{
+    [Fact]
+    public void KeyOnSettableProperty()
+    {
+        SettableProperty original = new() { TheMessage = "hi" };
+        var deserialized = AssertSerializedName(original);
+        Assert.Equal(original.TheMessage, deserialized.TheMessage);
+    }
+
+    [Fact]
+    public void KeyOnReadOnlyProperty()
+    {
+        ReadOnlyProperty original = new("hi");
+        var deserialized = AssertSerializedName(original);
+        Assert.Equal(original.TheMessage, deserialized.TheMessage);
+    }
+
+    [Fact]
+    public void KeyOnReadOnlyField()
+    {
+        ReadOnlyField original = new("hi");
+        var deserialized = AssertSerializedName(original);
+        Assert.Equal(original.TheMessage, deserialized.TheMessage);
+    }
+
+    [Fact]
+    public void KeyOnSettableField()
+    {
+        SettableField original = new() { TheMessage = "hi" };
+        var deserialized = AssertSerializedName(original);
+        Assert.Equal(original.TheMessage, deserialized.TheMessage);
+    }
+
+    private T AssertSerializedName<T>(T value)
+    {
+        byte[] msgpack = MessagePackSerializer.Serialize(value, MessagePackSerializerOptions.Standard);
+
+        logger.WriteLine(MessagePackSerializer.ConvertToJson(msgpack));
+
+        MessagePackReader reader = new(msgpack);
+        Assert.Equal(1, reader.ReadMapHeader());
+        Assert.Equal("message", reader.ReadString());
+        return MessagePackSerializer.Deserialize<T>(msgpack, MessagePackSerializerOptions.Standard);
+    }
+
+    [MessagePackObject]
+    public struct ReadOnlyProperty
+    {
+        [Key("message")]
+        public string TheMessage { get; }
+
+        [SerializationConstructor]
+        public ReadOnlyProperty(string theMessage) => this.TheMessage = theMessage;
+    }
+
+    [MessagePackObject]
+    public struct SettableProperty
+    {
+        [Key("message")]
+        public string TheMessage { get; set; }
+    }
+
+    [MessagePackObject]
+    public struct ReadOnlyField
+    {
+        [Key("message")]
+        public readonly string TheMessage;
+
+        [SerializationConstructor]
+        public ReadOnlyField(string theMessage) => this.TheMessage = theMessage;
+    }
+
+    [MessagePackObject]
+    public struct SettableField
+    {
+        [Key("message")]
+        public string TheMessage;
+    }
+}