dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.74k stars 3.99k forks source link

Implementing IOptionMonad<T, Self> from the dotNext project in a type in another project is impossible #68767

Closed carpe7 closed 1 year ago

carpe7 commented 1 year ago

Version Used:

Visual Studio 2022 Professional 17.6.4 targeting .NET 6.0.
Visual Studio 2022 Professional 17.6.4 targeting .NET 7.0.
Visual Studio 2022 Professional 17.7.0 Preview 2.0 targeting .NET 8.0.

Steps to Reproduce:

  1. In a Visual Studio 2022 project (e.g., library or console) reference latest version of the dotNext NuGet package. You can target a wide variety of .NET versions or SDKs, including the preview latest.
  2. Try to implement IOptionMonad<T, Self>; e.g., in a readonly struct.
  3. In .NET 6.0, you will need to opt in for preview features, which is not a smooth task (EnablePreviewFeatures, RequiresPreviewFeaturesAttribute, and if none works then disabling CA2252).
  4. Ask Visual Studio to fill a draft implementation of the interface for you (the one that throws NotImplementedException from every method).

Expected Behavior:

The program should compile without errors.

Actual Behavior:

I get these three compilation errors:

Thank you very much.

svick commented 1 year ago

Since static virtual interface members are only fully supported starting with .Net 7, have you tried using that?

Preview features are just that: a preview, and they may be buggy or incomplete.

carpe7 commented 1 year ago

I have tried not only .NET 7, but also .NET 8, to no avail.

jcouv commented 1 year ago

I'm able to repro the issue (see code snippet below with errors). Based on the description in OP, it looks like we get different behavior whether IOptionMonad<T, TSelf> is defined in source or in metadata. Assigned to @AlekseyTs to confirm expected behavior and triage.

// Project includes reference to DotNext nuget package
using DotNext;

class C
{
    static void Main()
    {
        System.Console.Write(42);
    }
}

readonly struct S<T> : IOptionMonad<T, S<T>>
{
    bool IOptionMonad<T>.HasValue => throw new NotImplementedException();

    T? IOptionMonad<T>.Or(T? defaultValue)
    {
        throw new NotImplementedException();
    }

    T? IOptionMonad<T>.OrDefault()
    {
        throw new NotImplementedException();
    }

    T IOptionMonad<T>.OrInvoke(Func<T> defaultFunc)
    {
        throw new NotImplementedException();
    }

    bool IOptionMonad<T>.TryGet(out T? value)
    {
        throw new NotImplementedException();
    }

    static bool IOptionMonad<T, S<T>>.operator true(in S<T> container) // Error CS0570  'IOptionMonad<T, TSelf>.operator true(in TSelf)' is not supported by the language
    {
        throw new NotImplementedException();
    }

    static bool IOptionMonad<T, S<T>>.operator false(in S<T> container) // Error CS0570 'IOptionMonad<T, TSelf>.operator false(in TSelf)' is not supported by the language
    {
        throw new NotImplementedException();
    }

    public static implicit operator S<T>(T value)
    {
        throw new NotImplementedException();
    }

    public static explicit operator T(in S<T> container) // Error CS0570    'IOptionMonad<T, TSelf>.explicit operator T(in TSelf)' is not supported by the language
    {
        throw new NotImplementedException();
    }
}
AlekseyTs commented 1 year ago

It looks like the reason for the error is the fact that compiler is failing to emit modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) for in parameters of virtual operators:

    public interface IOptionMonad<T, TSelf> where TSelf : struct, IOptionMonad<T, TSelf>
    {
        static abstract bool operator true(in TSelf container);
        static abstract bool operator false(in TSelf container);
        static abstract bool M(in TSelf container);
    }

IL:

.class interface public auto ansi abstract IOptionMonad`2<T, valuetype .ctor (class IOptionMonad`2<!T, !TSelf>, [System.Runtime]System.ValueType) TSelf>
{
    .param type T
        .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
            01 00 02 00 00
        )
    .param constraint TSelf, class IOptionMonad`2<!T, !TSelf>
        .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = (
            01 00 03 00 00 00 01 01 00 00 00
        )
    // Methods
    .method public hidebysig specialname abstract virtual static 
        bool op_True (
            [in] !TSelf& container
        ) cil managed 
    {
        .param [1]
            .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
                01 00 00 00
            )
    } // end of method IOptionMonad`2::op_True

    .method public hidebysig specialname abstract virtual static 
        bool op_False (
            [in] !TSelf& container
        ) cil managed 
    {
        .param [1]
            .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
                01 00 00 00
            )
    } // end of method IOptionMonad`2::op_False

    .method public hidebysig abstract virtual static 
        bool M (
            [in] !TSelf& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) container
        ) cil managed 
    {
        .param [1]
            .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = (
                01 00 00 00
            )
    } // end of method IOptionMonad`2::M

} // end of class IOptionMonad`2
jcouv commented 1 year ago

Thanks for confirming the issue. Assigned to @jjonescz to try and fix in .NET 8 timeframe since he's working in the area with ref readonly feature.

jcouv commented 1 year ago

Moved it back to @AlekseyTs since he's already invested in the investigation and is working on a fix already.