diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs
index 337955c14..d607c995c 100644
--- a/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs
+++ b/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs
@@ -189,6 +189,8 @@ public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out Cust
CodeAnalysisUtilities.FindInaccessibleTypes(type).Any() ? MsgPack00xMessagePackAnalyzer.InaccessibleFormatterType :
instanceProvidingMember is null ? MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstance :
null,
+ ExcludeFromSourceGeneratedResolver =
+ type.GetAttributes().Any(a => a.AttributeClass?.Name == Constants.ExcludeFormatterFromSourceGeneratedResolverAttributeName && a.AttributeClass?.ContainingNamespace.Name == Constants.AttributeNamespace),
};
return true;
@@ -196,6 +198,8 @@ public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out Cust
public DiagnosticDescriptor? InaccessibleDescriptor { get; init; }
+ public bool ExcludeFromSourceGeneratedResolver { get; init; }
+
public virtual bool Equals(CustomFormatter other)
{
return this.Name.Equals(other.Name)
diff --git a/src/MessagePack.SourceGenerator/MessagePackGenerator.cs b/src/MessagePack.SourceGenerator/MessagePackGenerator.cs
index fc412a67f..f9b413f6d 100644
--- a/src/MessagePack.SourceGenerator/MessagePackGenerator.cs
+++ b/src/MessagePack.SourceGenerator/MessagePackGenerator.cs
@@ -31,7 +31,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
predicate: static (node, ct) => node is ClassDeclarationSyntax { BaseList.Types.Count: > 0 },
transform: (ctxt, ct) =>
{
- return ctxt.SemanticModel.GetDeclaredSymbol(ctxt.Node, ct) is INamedTypeSymbol symbol && CustomFormatter.TryCreate(symbol, out CustomFormatter? formatter)
+ return ctxt.SemanticModel.GetDeclaredSymbol(ctxt.Node, ct) is INamedTypeSymbol symbol
+ && CustomFormatter.TryCreate(symbol, out CustomFormatter? formatter)
+ && !formatter.ExcludeFromSourceGeneratedResolver
? formatter
: null;
}).Collect();
diff --git a/src/MessagePack.SourceGenerator/Utils/Constants.cs b/src/MessagePack.SourceGenerator/Utils/Constants.cs
index b2d26c6cd..28ea3456c 100644
--- a/src/MessagePack.SourceGenerator/Utils/Constants.cs
+++ b/src/MessagePack.SourceGenerator/Utils/Constants.cs
@@ -9,6 +9,7 @@ internal static class Constants
internal const string CompositeResolverAttributeName = "CompositeResolverAttribute";
internal const string GeneratedMessagePackResolverAttributeName = "GeneratedMessagePackResolverAttribute";
internal const string MessagePackKnownFormatterAttributeName = "MessagePackKnownFormatterAttribute";
+ internal const string ExcludeFormatterFromSourceGeneratedResolverAttributeName = "ExcludeFormatterFromSourceGeneratedResolverAttribute";
internal const string MessagePackAssumedFormattableAttributeName = "MessagePackAssumedFormattableAttribute";
internal const string MessagePackObjectAttributeName = "MessagePackObjectAttribute";
internal const string MessagePackUnionAttributeName = "UnionAttribute";
diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/AnalyzerAttributes.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/AnalyzerAttributes.cs
index 2b6f6099f..4a5c8888f 100644
--- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/AnalyzerAttributes.cs
+++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/AnalyzerAttributes.cs
@@ -55,4 +55,18 @@ public MessagePackAssumedFormattableAttribute(Type formattableType)
///
public Type FormattableType { get; }
}
+
+ ///
+ /// Causes the source generated resolver, which typically includes all implementations of IMessagePackFormatter<T>,
+ /// to exclude this particular formatter.
+ ///
+ ///
+ /// This is useful when the formatter is intended for special case members,
+ /// which may apply the to select the private formatter.
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ [Conditional("NEVERDEFINED")]
+ public class ExcludeFormatterFromSourceGeneratedResolverAttribute : Attribute
+ {
+ }
}
diff --git a/tests/MessagePack.SourceGenerator.ExecutionTests/ExcludedCustomFormatter.cs b/tests/MessagePack.SourceGenerator.ExecutionTests/ExcludedCustomFormatter.cs
new file mode 100644
index 000000000..6b0dcd0f8
--- /dev/null
+++ b/tests/MessagePack.SourceGenerator.ExecutionTests/ExcludedCustomFormatter.cs
@@ -0,0 +1,34 @@
+// 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.Formatters;
+
+public class ExcludedCustomFormatter
+{
+ [Fact]
+ public void ExcludedFormatterIsIgnored()
+ {
+ // This would normally succeed because of our custom formatter and the auto-generated resolver,
+ // but because of the attribute applied to the formatter, it should not be included in the resolver,
+ // and thus our custom type should fail to serialize as an unknown type.
+ Assert.Throws(
+ () => MessagePackSerializer.Serialize(default(CustomType), MessagePackSerializerOptions.Standard));
+ }
+
+ internal struct CustomType;
+
+ [ExcludeFormatterFromSourceGeneratedResolver]
+ internal class CustomFormatter : IMessagePackFormatter
+ {
+ public CustomType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
+ {
+ reader.Skip();
+ return default;
+ }
+
+ public void Serialize(ref MessagePackWriter writer, CustomType value, MessagePackSerializerOptions options)
+ {
+ writer.WriteNil();
+ }
+ }
+}
diff --git a/tests/MessagePack.SourceGenerator.Tests/GenerationTests.cs b/tests/MessagePack.SourceGenerator.Tests/GenerationTests.cs
index d83633313..187b4cf2e 100644
--- a/tests/MessagePack.SourceGenerator.Tests/GenerationTests.cs
+++ b/tests/MessagePack.SourceGenerator.Tests/GenerationTests.cs
@@ -545,4 +545,22 @@ public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializ
""";
await VerifyCS.Test.RunDefaultAsync(this.testOutputHelper, testSource);
}
+
+ [Fact]
+ public async Task ExcludeFormatterFromSourceGeneratedResolver()
+ {
+ string testSource = """
+ using MessagePack;
+ using MessagePack.Formatters;
+ class A {}
+ [ExcludeFormatterFromSourceGeneratedResolver]
+ class F : IMessagePackFormatter {
+ public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializerOptions options) {}
+ public A Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => default;
+ }
+ """;
+
+ // This test is *not* expected to produce any generated files.
+ await VerifyCS.Test.RunDefaultAsync(this.testOutputHelper, testSource);
+ }
}