Home > Blockchain >  Overload resolution with generics and implicit conversion
Overload resolution with generics and implicit conversion

Time:10-25

This code is not compiled:

static class Foo
{
    public static Bar Do(Func<Bar> f) => null;
    public static Bar<TOut> Do<TOut>(Func<Bar<TOut>> f) => null;
}

public class Bar
{
}

public class Bar<TOut>
{
    public static implicit operator Bar<TOut>(TOut i) => null;
}

// Here compiler complains:
// CS0029 Cannot implicitly convert type 'int' to 'Bar'
// CS1662 Cannot convert lambda expression to intended delegate type 
// because some of the return types in the block
// are not implicitly convertible to the delegate return type
Foo.Do(() => 1);

My expectation would be that compiler sees the return type of the lambda and that no valid overload can be selected unless int is converted to Bar<int>. However, I see that compiler resolves to the first method.

Which part of spec defines this behavior?

CodePudding user response:

This is specified in Method Invocations, when the spec is talking about what method declarations count as a candidate for overload resolution, for an invocation of the form M(A):

The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M:

  • If F is non-generic, F is a candidate when:
    • M has no type argument list, and
    • F is applicable with respect to A.
  • If F is generic and M has no type argument list, F is a candidate when:
    • Type inference succeeds, inferring a list of type arguments for the call, and
    • [...]

Just from those rules, we can see that the non-generic Do is a candidate, and the generic Do is not, because type inference fails. Try commenting out the non-generic Do, and you will see that it says something like "type arguments cannot be inferred".

CodePudding user response:

I do not have the full answer, but I have some observations:

Observation 1: With removing the non-generic version of Do:

static class Foo
{
    public static Bar Do(Func<Bar> f) => null;
}

public class Bar
{
    public static implicit operator Bar(int i) => null;
}

// CS0411: The type arguments for method 'Foo.Do<TOut>(Func<Bar<TOut>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Foo.Do(() => 1);

, still the compiler can't resolve Func<Bar<TOut>> f. So it does not seem that the issue is about picking the wrong overload, but rather compiler not being able to match () => 1 to Func<Bar<TOut>> f implicitly at all.

Observation 2: The code below works

static class Foo
{
    public static Bar Do(Func<Bar> f) => null;
}

public class Bar
{
    public static implicit operator Bar(int i) => null;
}

Foo.Do(() => 1);

which shows the compiler is checking implicit conversions.

Observation 3: Making the implicit cast operator to take int as in input, eventhough makes the operator not practically usable as TOut will not be resolved, makes the compiler find the operator:

static class Foo
{
    public static Bar<TOut> Do<TOut>(Func<Bar<TOut>> f) => null;
}

public class Bar<TOut>
{
    public static implicit operator Bar<TOut>(int i) => null; // Input is `int` now
}
// CS0411: The type arguments for method 'Foo.Do<TOut>(Func<Bar<TOut>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Foo.Do(() => 1);

So all of this combined makes me think that the compiler is just not trying to which generic types would cause the implicit conversion to work.

  • Related