Repository: MessagePack-CSharp/MessagePack-CSharp

Issue:
MsgPack003 is not reported on generic typed members
#### Bug description

The Analyzer does not detect properly missing attributes on generic types.

#### Repro steps

```csharp
public class Foo<T>
{
    public T Member { get; set; }
}

[MessagePack.MessagePackObject]
public class Bar
{
    [MessagePack.Key(0)]
    public Foo<int> MemberUserGeneric { get; set; }

    [MessagePack.Key(1)]
    public System.Collections.Generic.List<int> MemberKnownGeneric { get; set; }
}

static void Test()
{
    MessagePackSerializer.Serialize(new Bar());
}

```

#### Expected behavior

The analyzer should detect an error and propose according changes.

#### Actual behavior

Analyzer does not detect any issue nor propose code change, issue is generated at runtime only as FormatterNotRegisteredException


Hints:

Patch:
diff --git a/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs b/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs
index 63a7851f8..eb915c795 100644
--- a/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs
+++ b/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs
@@ -32,7 +32,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
     private const string InvalidMessagePackObjectTitle = "MessagePackObject validation";
     private const DiagnosticSeverity InvalidMessagePackObjectSeverity = DiagnosticSeverity.Error;
 
-    internal static readonly DiagnosticDescriptor TypeMustBeMessagePackObject = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor TypeMustBeMessagePackObject = new DiagnosticDescriptor(
         id: UseMessagePackObjectAttributeId,
         title: "Use MessagePackObjectAttribute",
         category: Category,
@@ -42,7 +42,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(UseMessagePackObjectAttributeId));
 
-    internal static readonly DiagnosticDescriptor MessageFormatterMustBeMessagePackFormatter = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor MessageFormatterMustBeMessagePackFormatter = new DiagnosticDescriptor(
         id: MessagePackFormatterMustBeMessagePackFormatterId,
         title: "Must be IMessageFormatter",
         category: Category,
@@ -52,7 +52,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(MessagePackFormatterMustBeMessagePackFormatterId));
 
-    internal static readonly DiagnosticDescriptor PublicMemberNeedsKey = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor PublicMemberNeedsKey = new DiagnosticDescriptor(
         id: AttributeMessagePackObjectMembersId,
         title: "Attribute properties and fields of MessagePack objects",
         category: Category,
@@ -62,7 +62,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(AttributeMessagePackObjectMembersId));
 
-    internal static readonly DiagnosticDescriptor BaseTypeContainsUnattributedPublicMembers = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor BaseTypeContainsUnattributedPublicMembers = new DiagnosticDescriptor(
         id: AttributeMessagePackObjectMembersId,
         title: "Attribute properties and fields of MessagePack objects",
         category: Category,
@@ -72,7 +72,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(AttributeMessagePackObjectMembersId));
 
-    internal static readonly DiagnosticDescriptor InvalidMessagePackObject = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor InvalidMessagePackObject = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -82,7 +82,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId));
 
-    internal static readonly DiagnosticDescriptor BothStringAndIntKeyAreNull = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor BothStringAndIntKeyAreNull = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -92,7 +92,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId));
 
-    internal static readonly DiagnosticDescriptor DoNotMixStringAndIntKeys = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor DoNotMixStringAndIntKeys = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -102,7 +102,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId));
 
-    internal static readonly DiagnosticDescriptor KeysMustBeUnique = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor KeysMustBeUnique = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -112,7 +112,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId));
 
-    internal static readonly DiagnosticDescriptor UnionAttributeRequired = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor UnionAttributeRequired = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -124,7 +124,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
 
     // This is important because [Key] on a private member still will not be serialized, which is very confusing until
     // one realizes the type is serializing in map mode.
-    internal static readonly DiagnosticDescriptor KeyAnnotatedMemberInMapMode = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor KeyAnnotatedMemberInMapMode = new DiagnosticDescriptor(
         id: InvalidMessagePackObjectId,
         title: InvalidMessagePackObjectTitle,
         category: Category,
@@ -134,7 +134,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId));
 
-    internal static readonly DiagnosticDescriptor NoDeserializingConstructor = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor NoDeserializingConstructor = new DiagnosticDescriptor(
         id: DeserializingConstructorId,
         title: "Deserializing constructors",
         category: Category,
@@ -144,7 +144,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId));
 
-    internal static readonly DiagnosticDescriptor DeserializingConstructorParameterTypeMismatch = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor DeserializingConstructorParameterTypeMismatch = new DiagnosticDescriptor(
         id: DeserializingConstructorId,
         title: "Deserializing constructors",
         category: Category,
@@ -154,7 +154,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId));
 
-    internal static readonly DiagnosticDescriptor DeserializingConstructorParameterIndexMissing = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor DeserializingConstructorParameterIndexMissing = new DiagnosticDescriptor(
         id: DeserializingConstructorId,
         title: "Deserializing constructors",
         category: Category,
@@ -164,7 +164,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId));
 
-    internal static readonly DiagnosticDescriptor DeserializingConstructorParameterNameMissing = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor DeserializingConstructorParameterNameMissing = new DiagnosticDescriptor(
         id: DeserializingConstructorId,
         title: "Deserializing constructors",
         category: Category,
@@ -174,7 +174,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId));
 
-    internal static readonly DiagnosticDescriptor DeserializingConstructorParameterNameDuplicate = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor DeserializingConstructorParameterNameDuplicate = new DiagnosticDescriptor(
         id: DeserializingConstructorId,
         title: "Deserializing constructors",
         category: Category,
@@ -184,7 +184,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId));
 
-    internal static readonly DiagnosticDescriptor AotUnionAttributeRequiresTypeArg = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor AotUnionAttributeRequiresTypeArg = new DiagnosticDescriptor(
         id: AOTLimitationsId,
         title: "AOT limitations",
         category: Category,
@@ -194,7 +194,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTLimitationsId));
 
-    internal static readonly DiagnosticDescriptor AotArrayRankTooHigh = new DiagnosticDescriptor(
+    public static readonly DiagnosticDescriptor AotArrayRankTooHigh = new DiagnosticDescriptor(
         id: AOTLimitationsId,
         title: "AOT limitations",
         category: Category,
@@ -204,7 +204,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTLimitationsId));
 
-    internal static readonly DiagnosticDescriptor CollidingFormatters = new(
+    public static readonly DiagnosticDescriptor CollidingFormatters = new(
         id: CollidingFormattersId,
         title: "Colliding formatters",
         category: Category,
@@ -234,7 +234,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterId));
 
-    internal static readonly DiagnosticDescriptor PartialTypeRequired = new(
+    public static readonly DiagnosticDescriptor PartialTypeRequired = new(
         id: PartialTypeRequiredId,
         title: "Partial type required",
         category: Category,
@@ -244,7 +244,7 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
         isEnabledByDefault: true,
         helpLinkUri: AnalyzerUtilities.GetHelpLink(PartialTypeRequiredId));
 
-    internal static readonly DiagnosticDescriptor InaccessibleDataType = new(
+    public static readonly DiagnosticDescriptor InaccessibleDataType = new(
         id: InaccessibleDataTypeId,
         title: "Internally accessible data type required",
         category: Category,
diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
index 46469e3f2..215b190b3 100644
--- a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
+++ b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs
@@ -574,8 +574,7 @@ private bool CollectGeneric(INamedTypeSymbol type)
         if (type.IsDefinition)
         {
             this.CollectGenericUnion(type);
-            this.CollectObject(type);
-            return true;
+            return this.CollectObject(type);
         }
         else
         {
@@ -585,7 +584,10 @@ private bool CollectGeneric(INamedTypeSymbol type)
             this.GetObjectInfo(type);
 
             // Collect generic type definition, that is not collected when it is defined outside target project.
-            this.CollectCore(type.OriginalDefinition);
+            if (!this.CollectCore(type.OriginalDefinition))
+            {
+                return false;
+            }
         }
 
         // Collect substituted types for the type parameters (e.g. Bar in Foo<Bar>)


Test patch:
diff --git a/tests/MessagePack.SourceGenerator.Tests/MessagePackAnalyzerTests.cs b/tests/MessagePack.SourceGenerator.Tests/MessagePackAnalyzerTests.cs
index c77bb52ab..e356fe63a 100644
--- a/tests/MessagePack.SourceGenerator.Tests/MessagePackAnalyzerTests.cs
+++ b/tests/MessagePack.SourceGenerator.Tests/MessagePackAnalyzerTests.cs
@@ -1,6 +1,7 @@
 ﻿// Copyright (c) All contributors. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
+using MessagePack.SourceGenerator.Analyzers;
 using Microsoft.CodeAnalysis.Testing;
 using VerifyCS = CSharpCodeFixVerifier<MessagePack.SourceGenerator.Analyzers.MsgPack00xMessagePackAnalyzer, MessagePack.Analyzers.CodeFixes.MessagePackCodeFixProvider>;
 
@@ -354,4 +355,47 @@ public class Bar : Foo
             MarkupOptions = MarkupOptions.UseFirstDescriptor,
         }.RunAsync();
     }
+
+    [Fact]
+    public async Task AddAttributeToGenericType()
+    {
+        string input = Preamble + """
+            public class Foo<T>
+            {
+                public T Member { get; set; }
+            }
+
+            [MessagePackObject]
+            public class Bar
+            {
+                [Key(0)]
+                public {|MsgPack003:Foo<int>|} MemberUserGeneric { get; set; }
+
+                [Key(1)]
+                public System.Collections.Generic.List<int> MemberKnownGeneric { get; set; }
+            }
+            """;
+
+        string output = Preamble + """
+
+            [MessagePackObject]
+            public class Foo<T>
+            {
+                [Key(0)]
+                public T Member { get; set; }
+            }
+
+            [MessagePackObject]
+            public class Bar
+            {
+                [Key(0)]
+                public Foo<int> MemberUserGeneric { get; set; }
+
+                [Key(1)]
+                public System.Collections.Generic.List<int> MemberKnownGeneric { get; set; }
+            }
+            """;
+
+        await VerifyCS.VerifyCodeFixAsync(input, output);
+    }
 }