Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client source generator known message pack generic formatters list requires update #918

Open
LB-AhmadFarahani opened this issue Feb 7, 2025 · 0 comments

Comments

@LB-AhmadFarahani
Copy link

LB-AhmadFarahani commented Feb 7, 2025

Just what the title says. Known message pack formatters saved in formatter mapper is old making types like HashSet result in faulty generated code.
src/MagicOnion.Client.SourceGenerator/CodeAnalysis/SerializationFormatterNameMapper.cs:

    class MessagePackWellKnownSerializationTypes : IWellKnownSerializationTypes
    {
        MessagePackWellKnownSerializationTypes() {}
        public static IWellKnownSerializationTypes Instance { get; } = new MessagePackWellKnownSerializationTypes();

        public IReadOnlyDictionary<string, string> GenericFormattersMap { get; } = new Dictionary<string, string>
        {
            {"global::System.Nullable<>", "global::MessagePack.Formatters.NullableFormatter" },
            {"global::System.Collections.Generic.List<>", "global::MessagePack.Formatters.ListFormatter" },
            {"global::System.Collections.Generic.IList<>", "global::MessagePack.Formatters.InterfaceListFormatter2" },
            {"global::System.Collections.Generic.IReadOnlyList<>", "global::MessagePack.Formatters.InterfaceReadOnlyListFormatter" },
            {"global::System.Collections.Generic.Dictionary<,>", "global::MessagePack.Formatters.DictionaryFormatter"},
            {"global::System.Collections.Generic.IDictionary<,>", "global::MessagePack.Formatters.InterfaceDictionaryFormatter"},
            {"global::System.Collections.Generic.IReadOnlyDictionary<,>", "global::MessagePack.Formatters.InterfaceReadOnlyDictionaryFormatter"},
            {"global::System.Collections.Generic.ICollection<>", "global::MessagePack.Formatters.InterfaceCollectionFormatter2" },
            {"global::System.Collections.Generic.IReadOnlyCollection<>", "global::MessagePack.Formatters.InterfaceReadOnlyCollectionFormatter" },
            {"global::System.Collections.Generic.IEnumerable<>", "global::MessagePack.Formatters.InterfaceEnumerableFormatter" },
            {"global::System.Collections.Generic.KeyValuePair<,>", "global::MessagePack.Formatters.KeyValuePairFormatter" },
            {"global::System.Linq.ILookup<,>", "global::MessagePack.Formatters.InterfaceLookupFormatter" },
            {"global::System.Linq.IGrouping<,>", "global::MessagePack.Formatters.InterfaceGroupingFormatter" },
            {"global::System.Tuple<>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,,,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,,,,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.Tuple<,,,,,,,>", "global::MessagePack.Formatters.TupleFormatter" },
            {"global::System.ValueTuple<>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
            {"global::System.ValueTuple<,,,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" },
        };

This is the current code and as you see this does not contain HashSet formatter (might be missing other formatters as well but HashSet is the first one I noticed). This in turn makes the following code in the same file output invalid formatter type.

    public bool TryMapGeneric(MagicOnionTypeInfo type, [NotNullWhen(true)] out string? formatterName, [NotNullWhen(true)] out string? formatterConstructorArgs)
    {
        formatterName = null;
        formatterConstructorArgs = null;

        var genericTypeArgs = string.Join(", ", type.GenericArguments.Select(x => x.FullName));
        if (type is { Namespace: "MagicOnion", Name: "DynamicArgumentTuple" })
        {
            // MagicOnion.DynamicArgumentTuple
            var ctorArguments = string.Join(", ", type.GenericArguments.Select(x => $"default({x.FullName})"));
            formatterName = $"global::MagicOnion.Serialization.MessagePack.DynamicArgumentTupleFormatter<{genericTypeArgs}>";
            formatterConstructorArgs = $"({ctorArguments})";
        }
        else if (MessagePackWellKnownSerializationTypes.Instance.GenericFormattersMap.TryGetValue(type.FullNameOpenType, out var mappedFormatterName))
        {
            // Well-known generic types (Nullable<T>, IList<T>, List<T>, Dictionary<TKey, TValue> ...)
            formatterName = $"{mappedFormatterName}<{genericTypeArgs}>";
            formatterConstructorArgs = "()";
        }
        else if (allowToMapUserDefinedFormatter)
        {
            // User-defined generic types
            formatterName = $"{userDefinedFormatterNamespace}{(string.IsNullOrWhiteSpace(userDefinedFormatterNamespace) ? "" : ".")}{type.ToDisplayName(MagicOnionTypeInfo.DisplayNameFormat.Namespace | MagicOnionTypeInfo.DisplayNameFormat.WithoutGenericArguments)}Formatter<{genericTypeArgs}>";
            formatterConstructorArgs = "()";
        }

        return formatterName != null;
    }

For example having a service with return type UnaryTask<HashSet> will make the formatter created for MessagePackGeneratedGetFormatterHelper look like this:

     case 6: return new global::MessagePack.Formatters.System.Collections.Generic.HashSetFormatter<global::System.UInt32>();
  // Correct output would be this:
  //  case 6: return new global::MessagePack.Formatters.HashSetFormatter<global::System.UInt32>();

Adding missing formatters to MessagePackWellKnownSerializationTypes should fix the issue.


For those who are looking for a quick fix without waiting for an official fix, copy pasting messagepack formatters somewhere in your project accessible to the auto generated code should fix the problem. For example this code (copy pasted from messagepack with only namespace changed) will fix the above mentioned problem:

namespace MessagePack.Formatters.System.Collections.Generic
{
    public sealed class HashSetFormatter<T> : CollectionFormatterBase<T, HashSet<T>, HashSet<T>.Enumerator, HashSet<T>>
    {
        protected override int? GetCount(HashSet<T> sequence)
        {
            return sequence.Count;
        }

        protected override void Add(HashSet<T> collection, int index, T value, MessagePackSerializerOptions options)
        {
            collection.Add(value);
        }

        protected override HashSet<T> Complete(HashSet<T> intermediateCollection)
        {
            return intermediateCollection;
        }

        protected override HashSet<T> Create(int count, MessagePackSerializerOptions options)
        {
            return new HashSet<T>(options.Security.GetEqualityComparer<T>());
        }

        protected override HashSet<T>.Enumerator GetSourceEnumerator(HashSet<T> source)
        {
            return source.GetEnumerator();
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant