Fix handling of formatters following the singleton pattern by AArnott · Pull Request #1850 · MessagePack-CSharp/MessagePack-CSharp · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace MessagePack.SourceGenerator.CodeAnalysis;
/// </remarks>
public record AnalyzerOptions
{
private readonly ImmutableHashSet<CustomFormatter> knownFormatters = ImmutableHashSet<CustomFormatter>.Empty;
private readonly ImmutableHashSet<FormatterDescriptor> knownFormatters = ImmutableHashSet<FormatterDescriptor>.Empty;

private readonly ImmutableDictionary<QualifiedTypeName, ImmutableArray<FormattableType>> collidingFormatters = ImmutableDictionary<QualifiedTypeName, ImmutableArray<FormattableType>>.Empty;

Expand All @@ -29,17 +29,17 @@ public record AnalyzerOptions
/// <summary>
/// Gets the set of custom formatters that should be considered by the analyzer and included in the generated resolver.
/// </summary>
public ImmutableHashSet<CustomFormatter> KnownFormatters
public ImmutableHashSet<FormatterDescriptor> KnownFormatters
{
get => this.knownFormatters;
init
{
this.knownFormatters = value;
this.KnownFormattersByName = value.ToImmutableDictionary(f => f.Name);

Dictionary<FormattableType, ImmutableArray<CustomFormatter>> formattableTypes = new();
Dictionary<FormattableType, ImmutableArray<FormatterDescriptor>> formattableTypes = new();
bool collisionsEncountered = false;
foreach (CustomFormatter formatter in value)
foreach (FormatterDescriptor formatter in value)
{
foreach (FormattableType dataType in formatter.FormattableTypes)
{
Expand All @@ -58,11 +58,11 @@ public ImmutableHashSet<CustomFormatter> KnownFormatters
var collidingFormatters = ImmutableDictionary<QualifiedTypeName, ImmutableArray<FormattableType>>.Empty;
if (collisionsEncountered)
{
foreach (KeyValuePair<FormattableType, ImmutableArray<CustomFormatter>> kvp in formattableTypes)
foreach (KeyValuePair<FormattableType, ImmutableArray<FormatterDescriptor>> kvp in formattableTypes)
{
if (kvp.Value.Length > 1)
{
foreach (CustomFormatter collidingFormatter in kvp.Value)
foreach (FormatterDescriptor collidingFormatter in kvp.Value)
{
if (collidingFormatters.TryGetValue(collidingFormatter.Name, out ImmutableArray<FormattableType> collidingTypes))
{
Expand All @@ -81,7 +81,7 @@ public ImmutableHashSet<CustomFormatter> KnownFormatters
}
}

public ImmutableDictionary<QualifiedTypeName, CustomFormatter> KnownFormattersByName { get; private init; } = ImmutableDictionary<QualifiedTypeName, CustomFormatter>.Empty;
public ImmutableDictionary<QualifiedTypeName, FormatterDescriptor> KnownFormattersByName { get; private init; } = ImmutableDictionary<QualifiedTypeName, FormatterDescriptor>.Empty;

public GeneratorOptions Generator { get; init; } = new();

Expand All @@ -90,7 +90,7 @@ public ImmutableHashSet<CustomFormatter> KnownFormatters
/// </summary>
public bool IsGeneratingSource { get; init; }

internal AnalyzerOptions WithFormatterTypes(ImmutableArray<FormattableType> formattableTypes, ImmutableHashSet<CustomFormatter> customFormatters)
internal AnalyzerOptions WithFormatterTypes(ImmutableArray<FormattableType> formattableTypes, ImmutableHashSet<FormatterDescriptor> customFormatters)
{
return this with
{
Expand All @@ -107,7 +107,7 @@ internal AnalyzerOptions WithFormatterTypes(ImmutableArray<FormattableType> form
/// <returns>The modified set of options.</returns>
internal AnalyzerOptions WithAssemblyAttributes(ImmutableArray<AttributeData> assemblyAttributes, CancellationToken cancellationToken)
{
ImmutableHashSet<CustomFormatter> customFormatters = AnalyzerUtilities.ParseKnownFormatterAttribute(assemblyAttributes, cancellationToken).Union(this.KnownFormatters);
ImmutableHashSet<FormatterDescriptor> customFormatters = AnalyzerUtilities.ParseKnownFormatterAttribute(assemblyAttributes, cancellationToken).Union(this.KnownFormatters);
ImmutableArray<FormattableType> customFormattedTypes = this.AssumedFormattableTypes.Union(AnalyzerUtilities.ParseAssumedFormattableAttribute(assemblyAttributes, cancellationToken)).ToImmutableArray();
return this.WithFormatterTypes(customFormattedTypes, customFormatters);
}
Expand Down Expand Up @@ -161,12 +161,13 @@ public record GeneratorOptions
/// <summary>
/// Describes a custom formatter.
/// </summary>
/// <param name="Name">The name (without namespace) of the type that implements at least one <c>IMessagePackFormatter</c> interface. If the formatter is a generic type, this should <em>not</em> include any generic type parameters.</param>
/// <param name="Name">The name of the type that implements at least one <c>IMessagePackFormatter</c> interface. If the formatter is a generic type, this should <em>not</em> include any generic type parameters.</param>
/// <param name="InstanceProvidingMember">Either ".ctor" or the name of a static field or property that will return an instance of the formatter.</param>
/// <param name="InstanceTypeName">The type name to use when referring to an instance of the formatter. Usually the same as <paramref name="Name"/> but may be different if <paramref name="InstanceProvidingMember"/> returns a different type.</param>
/// <param name="FormattableTypes">The type arguments that appear in each implemented <c>IMessagePackFormatter</c> interface. When generic, these should be the full name of their type definitions.</param>
public record CustomFormatter(QualifiedTypeName Name, string? InstanceProvidingMember, ImmutableHashSet<FormattableType> FormattableTypes)
public record FormatterDescriptor(QualifiedTypeName Name, string? InstanceProvidingMember, QualifiedTypeName InstanceTypeName, ImmutableHashSet<FormattableType> FormattableTypes)
{
public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out CustomFormatter? formatter)
public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out FormatterDescriptor? formatter)
{
var formattedTypes =
AnalyzerUtilities.SearchTypeForFormatterImplementations(type)
Expand All @@ -178,12 +179,13 @@ public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out Cust
return false;
}

ISymbol? instanceField = type.GetMembers("Instance")
.FirstOrDefault(m => m.IsStatic && m.DeclaredAccessibility == Accessibility.Public && m is IFieldSymbol { IsReadOnly: true });
IFieldSymbol? instanceField = type.GetMembers("Instance").OfType<IFieldSymbol>()
.FirstOrDefault(m => m.IsStatic && m.DeclaredAccessibility == Accessibility.Public && m.IsReadOnly);
IMethodSymbol? ctor = type.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.Length == 0 && ctor.DeclaredAccessibility >= Accessibility.Internal);
string? instanceProvidingMember = instanceField?.Name ?? ctor?.Name ?? null;
QualifiedTypeName instanceTypeName = new(instanceField?.Type ?? type);

formatter = new CustomFormatter(new QualifiedTypeName(type), instanceProvidingMember, formattedTypes)
formatter = new FormatterDescriptor(new QualifiedTypeName(type), instanceProvidingMember, instanceTypeName, formattedTypes)
{
InaccessibleDescriptor =
CodeAnalysisUtilities.FindInaccessibleTypes(type).Any() ? MsgPack00xMessagePackAnalyzer.InaccessibleFormatterType :
Expand All @@ -200,9 +202,14 @@ public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out Cust

public bool ExcludeFromSourceGeneratedResolver { get; init; }

public virtual bool Equals(CustomFormatter other)
public string InstanceExpression => this.InstanceProvidingMember == ".ctor"
? $"new {this.Name.GetQualifiedName()}()"
: $"{this.Name.GetQualifiedName()}.{this.InstanceProvidingMember}";

public virtual bool Equals(FormatterDescriptor? other)
{
return this.Name.Equals(other.Name)
return other is not null
&& this.Name.Equals(other.Name)
&& this.FormattableTypes.SetEquals(other.FormattableTypes)
&& this.InaccessibleDescriptor == other.InaccessibleDescriptor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace MessagePack.SourceGenerator.CodeAnalysis;

public record CustomFormatterRegisterInfo : ResolverRegisterInfo
{
public required CustomFormatter CustomFormatter { get; init; }
public required FormatterDescriptor CustomFormatter { get; init; }

public override string GetFormatterInstanceForResolver()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ public record MemberSerializationInfo(
string Name,
string Type,
string ShortTypeName,
string? CustomFormatterTypeName)
FormatterDescriptor? CustomFormatter)
{
private static readonly IReadOnlyCollection<string> PrimitiveTypes = new HashSet<string>(AnalyzerUtilities.PrimitiveTypes);

public string GetSerializeMethodString()
{
if (CustomFormatterTypeName != null)
if (CustomFormatter is not null)
{
return $"this.__{this.Name}CustomFormatter__.Serialize(ref writer, value.{this.Name}, options)";
}
Expand All @@ -34,7 +34,7 @@ public string GetSerializeMethodString()

public string GetDeserializeMethodString()
{
if (CustomFormatterTypeName != null)
if (CustomFormatter is not null)
{
return $"this.__{this.Name}CustomFormatter__.Deserialize(ref reader, options)";
}
Expand Down
19 changes: 17 additions & 2 deletions src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedTypeName.cs
Loading