SteveDunn / Vogen

A semi-opinionated library which is a source generator and a code analyser. It Source generates Value Objects
Apache License 2.0
782 stars 46 forks source link

Multiple IVogen interface issue with dependent projects #646

Closed Uriel6575 closed 1 month ago

Uriel6575 commented 1 month ago

Describe the bug

Let's say we have a project A that uses Vogen with static abstractions generation enabled. It will add it's own IVogen interface with configured members. Then we add project B, that depends on project A and in it's turn also defines value objects. So it will generate IVogen inteface too. With probably different members. And we have a project C that has a method accepting IVogen interface as a parameter. Which one should be used? What members would be available? Now compiler gives following warning:

ClassLibrary2\obj\Debug\net8.0\Vogen\Vogen.ValueObjectGenerator_Bar.g.cs(36,284): warning CS0436: The type 'IVogen<TSelf, TPrimitive>' in 'ClassLibrary2\obj\Debug\net8.0\Vogen\Vogen.ValueObjectGenerator\VogenInterfaces_g.cs' conflicts with the imported type 'IVogen<TSelf, TPrimitive>' in 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in 'ClassLibrary2\obj\Debug\net8.0\Vogen\Vogen.ValueObjectGenerator\VogenInterfaces_g.cs'. [ClassLibrary2\ClassLibrary2.csproj]

I solved this issue by disabling inteface generation and defining it explicitly in project A. But anyway it's a thing to think about in the future.

Steps to reproduce

Create two dependent projects with static abstractions generation enabled.

Expected behaviour

No good idea so far for the generic solution. But I think it's not an uncommon scenario.

SteveDunn commented 1 month ago

Hi @Uriel6575 - thanks for the bug report. This is to be expected; it's like defining a type in the same namespace in multiple projects. The correct way to handle this is exactly what you've done.

I can't think of a reasonable way to get around this. We do want the interface generated, and it must be of a single, known type. So, in effect, we must pick which project (assembly) will generate that interface.

I'll look to see if there's some way that the source generator can see if it's already generated it. I know that they can't be ordered or depend on each other, but maybe there's another way. I'll investigate.

Uriel6575 commented 1 month ago

So, in effect, we must pick which project (assembly) will generate that interface.

But as far as I can see, WriteHeaderIfNeeded will skip implementation part of the definition if this generation is omitted. I may be mistaken, but there is no existing option to disable IVogen generation inside a project, but still make value object from this assembly to implement "some well-known" IVogen.

SteveDunn commented 1 month ago

Ah, yes, of course! Sorry, I should have thought of that. I think we need a new entry to say

  1. implement the interface on value objects
  2. generate the interface for value objects

I've just added this entry to the enum:

    /// <summary>
    /// Value objects that are generated derive from `: IVogen`. Use just this flag if you have several projects and only wish to
    /// generate the definition in one of them, or if you want to define it yourself.
    /// </summary>
    ValueObjectsDeriveFromTheInterface = 1 << 7,

    /// <summary>
    /// The most common usage; generates the definition, containing equals, explicit casts, and factory methods.
    /// </summary>
    MostCommon = ValueObjectsDeriveFromTheInterface |
                 EqualsOperators |
                 ExplicitCastFromPrimitive |
                 ExplicitCastToPrimitive |
                 FactoryMethods

I'll see how that works out. I'm thinking of adding a MostCommonWithoutGeneratingTheInterface as the '2nd' most common usage... I'm beginning to regret adding most common at all now though!

Uriel6575 commented 1 month ago

Great! Thanks a lot.