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.73k stars 3.99k forks source link

Natural delegate type depends on order of candidates #71333

Open AlekseyTs opened 6 months ago

AlekseyTs commented 6 months ago
class Program
{
    static void Main()
    {
        var x1 = new Program().Test1;
        var x2 = new Program().Test2;

        x1(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action<long[]>'
        x2();
    }
}

static class Params
{
    static public void Test1(this Program p, long[] a) { System.Console.WriteLine(a.Length); }
    static public void Test1(this object p, params long[] a) { System.Console.WriteLine(a.Length); }

    static public void Test2(this object p, params long[] a) { System.Console.WriteLine(a.Length); }
    static public void Test2(this Program p, long[] a) { System.Console.WriteLine(a.Length); }
}

Observed: Type inferred for x1 is Action<long[]>, type inferred for x2 is void <anonymous delegate>(params long[] a)

Expected: The same type is inferred, they differ only by order of candidates.

AlekseyTs commented 6 months ago

CC @cston

AlekseyTs commented 6 months ago

CC @jjonescz

AlekseyTs commented 6 months ago

It looks like the behavior was introduced in https://github.com/dotnet/roslyn/pull/64861. There could be a similar issue around optional parameters as well. Basically any aspect that is not considered as a difference by MemberSignatureComparer.MethodGroupSignatureComparer is a suspect.

jjonescz commented 6 months ago

I think the expected behavior here are errors:

var x1 = new Program().Test1; // error CS8917: The delegate type could not be inferred.
var x2 = new Program().Test2; // error CS8917: The delegate type could not be inferred.

This is what also happens with ref instead of params, for example. And it matches the speclets:

  1. lambda improvements:

    A method group has a natural type if all candidate methods in the method group have a common signature. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.)

  2. params/defaults for lambdas:

    The natural type of an anonymous function expression or method group is a function_type. A function_type represents a method signature: the parameter types, default values, ref kinds, params modifiers, and return type and ref kind. Anonymous function expressions or method groups with the same signature have the same function_type.

I.e., the candidate methods differ in signature, hence the method group has no natural type and CS8917 should be reported.

AlekseyTs commented 6 months ago

@jjonescz I think params modifier and default values are not part of the signature from the language point of view. One cannot overload on them. In my opinion, the fact that function_type mentions them doesn't automatically make them part of the signature for the purpose of the "if all candidate methods in the method group have a common signature" part. I think what you are proposing to do is a viable option, but, judging by the quotes above, that would require a spec change for the feature.