Peculiar Overload Resolution with While (True)

Peculiar overload resolution with while (true)

So to start with, the first expression can only possibly call the first overload. It is not a valid expression for a Func<Task> because there is a code path that returns an invalid value (void instead of Task).

() => while(true) is actually a valid method for either signature. (It, along with implementations such as () => throw new Expression(); are valid bodies of methods that return any possible type, including void, an interesting point of trivia, and why auto generated methods from an IDE typically just throw an exception; it'll compile regardless of the signature of the method.) A method that loops infinitely is a method in which there are no code paths that don't return the correct value (and that's true whether the "correct value" is void, Task, or literally anything else). This is of course because it never returns a value, and it does so in a way that the compiler can prove. (If it did so in a way that the compiler couldn't prove, as it hasn't solved the halting problem after all, then we'd be in the same boat as A.)

So, for our infinite loop, which is better, given that both overload are applicable. This brings us to our betterness section of the C# specs.

If we go to section 7.4.3.3, bullet 4, we see:

If E is an anonymous function, T1 and T2 are delegate types or expression tree types with identical parameter lists, and an inferred return type X exists for E in the context of that parameter list (§7.4.2.11):

[...]

if T1 has a return type Y, and T2 is void returning, then C1 is the better conversion.

So when converting from an anonymous delegate, which is what we're doing, it will prefer the conversion that returns a value over one that is void, so it chooses Func<Task>.

Lambda conversions with unclear return type and overload resolution

I should've looked one section lower: §7.5.3.3 Better conversion from expression explains that:

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if at least one of the following holds:

  • E is an anonymous function, T1 is either a delegate type D1 or an expression tree type Expression<D1>, T2 is either a delegate type D2 or an expression tree type Expression<D2> and one of the following holds:

    • D1 and D2 have identical parameter lists, and one of the following holds:

      • D1 has a return type Y, and D2 is void returning

Method overload resolution with regards to generics and IEnumerable

The first part of your question (without the List-specific overload) is easy. Let's consider the Array call, because it works the same for both calls:

First, type inference produces two possible generic implementations of the call: Print<Person[]>(Person[] items) and Print<Person>(IEnumerable<Person> items).

Then overload resolution kicks in and the first one wins, because the second requires an implicit conversion, where the first one does not (see §7.4.2.3 of the C# spec). The same mechanism works for the List variant.

With the added overload, a third possible overload is generated with the List call: Print<Person>(List<Person> items). The argument is the same as with the Print<List<Person>>(List<Person> items) but again, section 7.4.3.2 provides the resolution with the language

Recursively, a constructed type is more specific than another constructed type (with the same number of type arguments) if at least one type argument is more specific and no type argument is less specific than the corresponding type argument in the other.

So the Print<Person> overload is more specific than the Print<List<Person>> overload and the List version wins over the IEnumerable because it requires no implicit conversion.

Weird extension method overload resolution

Oh, I got it after re-reading my own answer! Nice question =)
The overload does not work because it does not take constraint where T:IMaker into account while resolving overload (constraint is not a part of method signature). When you reference a parameter in lambda you (can) add a hint to the compiler:

  1. This works:

    new Container<A>().Foo(a => a.AProp == 0);

    because here we do hint that a:A;

  2. This does not work even with a reference to parameter:

    new Container<A>().Foo(a => a != null);

    because there is still not enough information to infer the type.

As far as I understand the specification, in "Foo scenario" the inference can fail on the second (Func) argument thus making the call ambiguous.

Here's what spec (25.6.4) says:

Type inference occurs as part of the compile-time processing of a method invocation (§14.5.5.1) and takes place before the overload resolution step of the invocation. When a particular method group is specified in a method invocation, and no type arguments are specified as part of the method invocation, type inference is applied to each generic method in the method group. If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution.

If overload resolution chooses a generic method as the one to invoke, then the inferred type arguments are used as the runtime type arguments for the invocation. If type inference for a particular method fails, that method does not participate in overload resolution. The failure of type inference, in and of itself, does not cause a compile-time error. However, it often leads to a compile-time error when overload resolution then fails to find any applicable methods.

Now lets get to pretty straightforward "Bar scenario". After type inference we will get only one method, because only one is applicable:

  1. Bar(Container<A>) for new Container<A>() (does not implement IMaker)
  2. Bar(A) for new A()(is not a Container)

And here's the ECMA-334 specification, just in case.
P.s. I'm not 100% sure that I got it right, but I prefer to think that I grasped the essential part.

Overload resolution error with DrawText

This doesn't seem to be a normal overload resolution situation. DrawText is defined twice as :

function DrawText(hDC: HDC; 
lpString: PWideChar;
nCount: Integer;
var lpRect: TRect;
uFormat: UINT): Integer; external user32 name 'DrawTextW';

function DrawText(hDC: HDC;
const lpString: UnicodeString;
nCount: Integer;
var lpRect: TRect; uFormat: UINT): Integer;
begin
Result := Winapi.Windows.DrawText(hDC,
PWideChar(lpString),
nCount,
lpRect,
uFormat);
end;

With {$TYPEDADDRESS OFF} it seems that a ^Char is interpreted by the compiler as an untyped pointer that is never compatible with a declared type of PChar while @c does seem to resolve to a PChar ok. This seems at odds with the notion that {$TYPEDADDRESS OFF} is meant to make all pointers type-agnostic. It seems that PChar and ^Char are somehow treated differently in the compiler than other pointers.

With {$TYPEDADDRESS ON} both @c and ^Char become equivalent, but curiously are accepted as arguments so long as there is no overload resolution to sort out.

In both cases it seems that overload resolutions are finalized before type-compatibility is fully established. I'm not sure I'd call it a bug, though... it seems like behaviour that would be tricky to change without causing problems.

SSCCE

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

{$DEFINE OVLD}

{$IFDEF OVLD}
procedure Test(s:string); overload;
begin
end;
{$ENDIF}
procedure Test(x:PChar); {$IFDEF OVLD}overload; {$ENDIF}
begin
end;

var
c : Char;
pc : ^Char;
begin
{$TYPEDADDRESS OFF}
Test(@c);
Test(pc); //OVLD - Incompatible types : 'string'-'pointer'
//No OVLD - Incompat. types : 'PWideChar'-'pointer'
{$TYPEDADDRESS ON}
Test(@c); //OVLD - Incompatible types : 'string'-'pointer'
//No OVLD - OK
Test(pc); //OVLD - Incompatible types : 'string'-'pointer'
//No OVLD - OK
end.

C# method overload resolution issues in Visual Studio 2013

This is a fun one :) There are multiple aspects to it. To start with, let's simplify it very significantly by removing Rx and actual overload resolution from the picture. Overload resolution is handled at the very end of the answer.

Anonymous function to delegate conversions, and reachability

The difference here is whether the end-point of the lambda expression is reachable. If it is, then that lambda expression doesn't return anything, and the lambda expression can only be converted to a Func<Task>. If the end-point of the lambda expression isn't reachable, then it can be converted to any Func<Task<T>>.

The form of the while statement makes a difference because of this part of the C# specification. (This is from the ECMA C# 5 standard; other versions may have slightly different wording for the same concept.)

The end point of a while statement is reachable if at least one of the following is true:

  • The while statement contains a reachable break statement that exits the while statement.
  • The while statement is reachable and the Boolean expression does not have the constant value true.

When you have a while (true) loop with no break statements, neither bullet is true, so the end point of the while statement (and therefore the lambda expression in your case) is not reachable.

Here's a short but complete example without any Rx involved:

using System;
using System.Threading.Tasks;

public class Test
{
static void Main()
{
// Valid
Func<Task> t1 = async () => { while(true); };

// Valid: end of lambda is unreachable, so it's fine to say
// it'll return an int when it gets to that end point.
Func<Task<int>> t2 = async () => { while(true); };

// Valid
Func<Task> t3 = async () => { while(false); };

// Invalid
Func<Task<int>> t4 = async () => { while(false); };
}
}

We can simplify even further by removing async from the equation. If we have a synchronous parameterless lambda expression with no return statements, that's always convertible to Action, but it's also convertible to Func<T> for any T if the end of the lambda expression isn't reachable. Slight change to the above code:

using System;

public class Test
{
static void Main()
{
// Valid
Action t1 = () => { while(true); };

// Valid: end of lambda is unreachable, so it's fine to say
// it'll return an int when it gets to that end point.
Func<int> t2 = () => { while(true); };

// Valid
Action t3 = () => { while(false); };

// Invalid
Func<int> t4 = () => { while(false); };
}
}

We can look at this in a slightly different way by removing delegates and lambda expressions from the mix. Consider these methods:

void Method1()
{
while (true);
}

// Valid: end point is unreachable
int Method2()
{
while (true);
}

void Method3()
{
while (false);
}

// Invalid: end point is reachable
int Method4()
{
while (false);
}

Although the error method for Method4 is "not all code paths return a value" the way this is detected is "the end of the method is reachable". Now imagine those method bodies are lambda expressions trying to satisfy a delegate with the same signature as the method signature, and we're back to the second example...

Fun with overload resolution

As Panagiotis Kanavos noted, the original error around overload resolution isn't reproducible in Visual Studio 2017. So what's going on? Again, we don't actually need Rx involved to test this. But we can see some very odd behavior. Consider this:

using System;
using System.Threading.Tasks;

class Program
{
static void Foo(Func<Task> func) => Console.WriteLine("Foo1");
static void Foo(Func<Task<int>> func) => Console.WriteLine("Foo2");

static void Bar(Action action) => Console.WriteLine("Bar1");
static void Bar(Func<int> action) => Console.WriteLine("Bar2");

static void Main(string[] args)
{
Foo(async () => { while (true); });
Bar(() => { while (true) ; });
}
}

That issues a warning (no await operators) but it compiles with the C# 7 compiler. The output surprised me:

Foo1
Bar2

So the resolution for Foo is determining that the conversion to Func<Task> is better than the conversion to Func<Task<int>>, whereas the resolution for Bar is determining that the conversion to Func<int> is better than the conversion to Action. All the conversions are valid - if you comment out the Foo1 and Bar2 methods, it still compiles, but gives output of Foo2, Bar1.

With the C# 5 compiler, the Foo call is ambiguous by the Bar call resolves to Bar2, just like with the C# 7 compiler.

With a bit more research, the synchronous form is specified in 12.6.4.4 of the ECMA C# 5 specification:

C1 is a better conversion than C2 if at least one of the following holds:

  • ...
  • E is an anonymous function, T1 is either a delegate type D1 or an expression tree type Expression, T2 is either a delegate type D2 or an expression tree type Expression and one of the following holds:

    • D1 is a better conversion target than D2 (irrelevant for us)
    • D1 and D2 have identical parameter lists, and one of the following holds:
    • D1 has a return type Y1, and D2 has a return type Y2, an inferred return type X exists for E in the context of that parameter list (§12.6.3.13), and the conversion from X to Y1 is better than the conversion from X to Y2
    • E is async, D1 has a return type Task<Y1>, and D2 has a return type Task<Y2>, an inferred return type Task<X> exists for E in the context of that parameter list (§12.6.3.13), and the conversion from X to Y1 is better than the conversion from X to Y2
    • D1 has a return type Y, and D2 is void returning

So that makes sense for the non-async case - and it also makes sense for how the C# 5 compiler isn't able to resolve the ambiguity, because those rules don't break the tie.

We don't have a full C# 6 or C# 7 specification yet, but there's a draft one available. Its overload resolution rules are expressed somewhat differently, and the change may be there somewhere.

If it's going to compile to anything though, I'd expect the Foo overload accepting a Func<Task<int>> to be chosen over the overload accepting Func<Task> - because it's a more specific type. (There's a reference conversion from Func<Task<int>> to Func<Task>, but not vice versa.)

Note that the inferred return type of the lambda expression would just be Func<Task> in both the C# 5 and draft C# 6 specifications.

Ultimately, overload resolution and type inference are really hard bits of the specification. This answer explains why the while(true) loop makes a difference (because without it, the overload accepting a func returning a Task<T> isn't even applicable) but I've reached the end of what I can work out about the choice the C# 7 compiler makes.

Very strange overload failure

What goes wrong?

Bar A({1,2});

Can be interpreted as:

Bar A(Bar{Foo<std::size_t>(1), (bool)2 /*, true*/ });

or

Bar A(Foo<std::size_t>{1,2} /*, true, true*/);

so ambiguous call.

How can I solve it?

It depends of which result you expect, adding explicit might help for example.

Making explicit Foo(size_t n) would allow only:

Bar A(B{Foo<std::size_t>(1), (bool)2 /*, true*/ });


Related Topics



Leave a reply



Submit