Skip to content

Conversation

@mayuki
Copy link
Contributor

@mayuki mayuki commented Aug 17, 2020

When generating the formatter, the generator generates a formatter for each closed generics type.

In this PR will change this behavior to generate the formatter for open types. And resolver will use closed generics type formatter with type parameters.

The current generator generates the following close type formatters and resolver.

...
internal static class GeneratedResolverGetFormatterHelper
{
    ...
    internal static object GetFormatter(Type t)
    {
        ...
        switch (key)
        {
            case 0: return new MessagePack.Formatters.TempProject.MyGenericObject_int_Formatter();
            case 1: return new MessagePack.Formatters.TempProject.MyGenericObject_long_Formatter();
            ...
        }
        ...
    }
    ...
}
...
public sealed class MyGenericObject_int_Formatter : global::MessagePack.Formatters.IMessagePackFormatter<global::TempProject.MyGenericObject<int>>
{
    public void Serialize(ref MessagePackWriter writer, global::TempProject.MyGenericObject<int> value, global::MessagePack.MessagePackSerializerOptions options)
    ...
}
...
public sealed class MyGenericObject_long_Formatter : global::MessagePack.Formatters.IMessagePackFormatter<global::TempProject.MyGenericObject<long>>
{
    public void Serialize(ref MessagePackWriter writer, global::TempProject.MyGenericObject<long> value, global::MessagePack.MessagePackSerializerOptions options)
    ...
}

This PR changes the generation of an open generics type formatter as follows.

...
internal static class GeneratedResolverGetFormatterHelper
{
    ...
    internal static object GetFormatter(Type t)
    {
        ...
        switch (key)
        {
            case 0: return new MessagePack.Formatters.TempProject.MyGenericObjectFormatter<int>();
            case 1: return new MessagePack.Formatters.TempProject.MyGenericObjectFormatter<long>();
            ...
        }
        ...
    }
    ...
}
...
public sealed class MyGenericObjectFormatter<T> : global::MessagePack.Formatters.IMessagePackFormatter<global::TempProject.MyGenericObject<T>>
{
    public void Serialize(ref MessagePackWriter writer, global::TempProject.MyGenericObject<T> value, global::MessagePack.MessagePackSerializerOptions options)
    ...
}

@AArnott
Copy link
Collaborator

AArnott commented Aug 19, 2020

That's very interesting, and looks like a good idea. Since there is static code demonstrating use of each closed generic type even in the new generated code, I hope that AOT compilers would still produce the closed generic types in the machine code. @neuecc do you know if that's the case?

@neuecc
Copy link
Member

neuecc commented Aug 19, 2020

Yes, this is an idea that I originally introduced to mayuki, so I think it should work fine.
T[] and List<>,Dictionary<> were originally implemented that way.

@neuecc neuecc merged commit d89dbdf into MessagePack-CSharp:master Aug 19, 2020
@mayuki mayuki deleted the feature/Generics branch August 19, 2020 07:23
@thorgeirk11
Copy link
Contributor

thorgeirk11 commented Sep 16, 2020

@AArnott @neuecc @mayuki
This PR seems to have reintroduced this bug
#994 (comment)

Also, another issue I found is that generic type constraints are not translated and will result in a compile error:

    [MessagePackObject]
    public class MyGenericClass<T> where T : struct 
    {
        [Key(0)]
        public T Value { get; set; }   
    }

Will produce

    public sealed class MyGenericClassFormatter<T> : IMessagePackFormatter<MyGenericClass<T>>
    { ... }

Which will result in a compile error because T is not type constrained to be a struct.

Sorry for the late response, I only saw this now after updating my mpc to the latest version

@AArnott
Copy link
Collaborator

AArnott commented Sep 16, 2020

Bummer. It's too bad we don't have many regression tests for mpc. Some were introduced recently though, so we ought to be able to take PRs that add features or fix bugs and add tests to make sure they don't regress in the future. @thorgeirk11 can you file a bug to track the regression? Would you feel up to sending another PR to fix it, perhaps with tests?

@mayuki
Copy link
Contributor Author

mayuki commented Sep 16, 2020

Oh, I'm sorry.... I failed to consider such a case, which caused a regression.
I would like to create a PR to add a fix and test.

@mayuki
Copy link
Contributor Author

mayuki commented Sep 17, 2020

@thorgeirk11
The original issue noted that there is a problem when generating Formatters from code like this.

[MessagePackObject]
public class TreeNode<T> 
{
    [Key(0)]
    public TreeNode<T> Parent { get; set; }   
}

[MessagePackObject]
public class Leaf : TreeNode<Leaf> { }

[MessagePackObject]
public class Leaf2 : TreeNode<Leaf2> { }

However, the Generator, as modified by this PR, generates the following Formatters and a Resolver codes.

public sealed class LeafFormatter : IMessagePackFormatter<Leaf> { ... }
public sealed class Leaf2Formatter : IMessagePackFormatter<Leaf2> { ... }
public sealed class TreeNodeFormatter<T> : IMessagePackFormatter<TreeNode<T>> { ... }
switch (key)
{
    case 0: return new TreeNodeFormatter<Leaf>();
    case 1: return new TreeNodeFormatter<Leaf2>();
    case 2: return new LeafFormatter();
    case 3: return new Leaf2Formatter();
    default: return null;
}

Only one TreeNodeFormatter<T> was generated like this, and the problem was not reproduced.
If you have the code to reproduce it, can you show it to me?

Another problem with generic type parameter constraints is reported, I could reproduce it and will work on it separately.

@thorgeirk11
Copy link
Contributor

thorgeirk11 commented Sep 18, 2020

@mayuki Thanks for the quick response! Yeah on second look that bug doesn't seem to be happening with the latest version.
I mistakenly thought it was another bug I encountered:

[MessagePackObject]
[Union(0, typeof(ADataModel<int>))]
public class ADataModel<T>
{
    [Key(1)]
    public List<T> Content { get; set; }
}

This code will result in a compile error

static GeneratedResolverGetFormatterHelper()
{
    lookup = new global::System.Collections.Generic.Dictionary<Type, int>(2)
    {
        { typeof(global::MessagePackDemo.ADataModel<int>), 0 },
        { typeof(global::System.Collections.Generic.List<T>), 1 }, // Compie Error: T is not defined in this context
    };
}

Amazing work! Those tests will help a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants