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); + } }