dotnet / csharpstandard

Working space for ECMA-TC49-TG2, the C# standard committee.
Creative Commons Attribution 4.0 International
715 stars 84 forks source link

Bug in example in 17.4.6, Interface member access #638

Open RexJaeschke opened 2 years ago

RexJaeschke commented 2 years ago

The first example in this section has a bug. Here is that example and surrounding narrative:

Interface members are accessed through member access (§11.7.6) and indexer access (§11.7.10.3) expressions of the form I.M and I[A], where I is an interface type, M is a method, property, or event of that interface type, and A is an indexer argument list.

For interfaces that are strictly single-inheritance (each interface in the inheritance chain has exactly zero or one direct base interface), the effects of the member lookup (§11.5), method invocation (§11.7.8.2), and indexer access (§11.7.10.3) rules are exactly the same as for classes and structs: More derived members hide less derived members with the same name or signature. However, for multiple-inheritance interfaces, ambiguities can occur when two or more unrelated base interfaces declare members with the same name or signature. This subclause shows several examples, some of which lead to ambiguities and others which don’t. In all cases, explicit casts can be used to resolve the ambiguities.

Example: In the following code

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    void Count(int i);
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
/*17*/  x.Count(1);             // Error
/*18*/  x.Count = 1;            // Error
/*19*/  ((IList)x).Count = 1;   // Ok, invokes IList.Count.set
/*20*/  ((ICounter)x).Count(1); // Ok, invokes ICounter.Count
    }
}

the first two statements cause compile-time errors because the member lookup (§11.5) of Count in IListCounter is ambiguous. As illustrated by the example, the ambiguity is resolved by casting x to the appropriate base interface type. Such casts have no run-time costs—they merely consist of viewing the instance as a less derived type at compile-time.

The only compilation error reported is for Line 18:

Library.cs(18,9): error CS1656: Cannot assign to 'Count' because it is a 'method group'

If I disable that line, no error results from Line 17, in any csc compiler version since V1!

BTW, this example has been in the Ecma spec since V1. Perhaps this case was an error in a pre-V1 release when the first MS spec was first written!

jskeet commented 2 years ago

Good find. And eek!

RexJaeschke commented 3 weeks ago

VS 17.11.2 accepts x.Count(1); and calls the method member, which I think is the right thing to do, so there is no error. Here's why:

12.5 Member lookup|12.5.1 General states:

A member lookup is the process whereby the meaning of a name in the context of a type is determined. A member lookup can occur as part of evaluating a simple_name (§12.8.4) or a member_access (§12.8.7) in an expression. If the simple_name or member_access occurs as the primary_expression of an invocation_expression (§12.8.9.2), the member is said to be invoked.

From that I deduce that x.Count(1); is an invocation_expression, and that member Count is being invoked.

If a member is a method or event, or if it is a constant, field or property of either a delegate type (§20) or the type dynamic (§8.2.4), then the member is said to be invocable.

This indicates that the method count is invocable, and the property Count is not.

Further down, in the bullet list, we have:

  • Next, if the member is invoked, all non-invocable members are removed from the set.

which causes the property to be removed, leaving only the method, making the use unambiguous.

If my analysis is correct, we need to remove lines 17 and 19, and adjust the description accordingly.

Lines 18 involves the following spec text, with the final bullet prevailing:

  • Finally, having removed hidden members, the result of the lookup is determined:
    • If the set consists of a single member that is not a method, then this member is the result of the lookup.
    • Otherwise, if the set contains only methods, then this group of methods is the result of the lookup.
    • Otherwise, the lookup is ambiguous, and a binding-time error occurs.

@BillWagner A compiler-message/doc issue: The line 18 error is

Library.cs(xx,yy): error CS1656: Cannot assign to 'Count' because it is a 'method group'

however, the online help text for CS1656 says:

Cannot assign to 'variable' because it is a 'read-only variable type'

which is quite different.

Also, the situation this example presents is nothing like the two examples shown, so maybe this scenario could be added there. If you agree, I can write up some words for that and submit a PR; let me know.

BTW, when I compile the 2 examples shown for that error message, I get two other/different messages, so this error number has at least 3 different messages associated with it.

jskeet commented 2 weeks ago

Happy to be assigned this, but I'm unlikely to get to look at it any time soon.