csharpstandard icon indicating copy to clipboard operation
csharpstandard copied to clipboard

Method group conversion rules could not be implemented

Open jcouv opened this issue 2 years ago • 1 comments

The current rules for "method group conversions" say that it should bind like a method invocation: "A single method M is selected corresponding to a method invocation of the form E(A)".

The current rules for "member lookup" know to ignore non-invokable members when we're dealing with an invocation:

  1. "If a member is a method or event, or if it is a constant, field or property of either a delegate type or the type dynamic (, then the member is said to be invocable."
  2. "Next, if the member is invoked, all non-invocable members are removed from the set."

Those two rules cannot be implemented as-is, and the compiler does not in fact implement them. The problem is that when binding a delegate conversion like Action a = o.M; we don't know that we're in a method group conversion until we've bound the right-hand side, but we can't bind the right-hand side without knowing whether we're being invoked or not.

Here are two scenarios to illustrate the problem. Per the spec, I would expect System.Action a = c.M; and c.M() to bind the same way, without error, in both of those scenarios. But in fact, only the invocation syntax c.M() binds successfully.

C c = null;
System.Action a = c.M; // error CS0029: Cannot implicitly convert type 'int' to 'System.Action'
c.M(); // okay

public class Base
{
    public void M() { }
}

public class C : Base
{
    public int M; // non-invokable field
}
C c = null;
System.Action a = c.M; // error CS0029: Cannot implicitly convert type 'int' to 'System.Action'
c.M(); // okay

public static class E
{
    public static void M(this C c) { }
}

public class C
{
    public int M; // non-invokable field
}

Tagging @MadsTorgersen @BillWagner @alekseyts @jjones

jcouv avatar Jun 22 '23 19:06 jcouv

Can this not be fixed if “member is invoked” is replaced by “member is in a context requiring a function” – to know the member is invoked requires knowledge of the context of the member lookup, so such a change would “just” require a compiler to look a little more into the context. (Note some compilers for other languages already do this, but it's “just” as I expect it might be non-trivial for the current C# compiler(s)).

The actual error produced in the example is down to hiding, there are two bullets in play:

  • Next, if the member is invoked, all non-invocable members are removed from the set.
  • Next, members that are hidden by other members are removed from the set. For every member S.M in the set, where S is the type in which the member M is declared, the following rules are applied:

If the M's in Base & C are switched around the example compiles with just a warning there is no new on the M in C even though the current compiler does not know that the member lookup is happening in a context requiring a function.

Here be dragons? The above suggest fix is really a patch on what could well be more issues in this area as C# has expanded, it may cause other issues – I’ve not checked.

Nigel-Ecma avatar Jun 30 '23 02:06 Nigel-Ecma