-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Foreach processing for types implementing IEnumerable<T> is problematic #1188
Comments
The specific commit that is linked as "current for v9" has not appeared in the v9 branch, but this would be relevant work for v8 which is still in draft. For your first point, I'm curious how much interest there would be in making this a compiler bug. I can see the rationale in allowing iteration here under the assumption that there is really no confusion about "which sequence" you're getting. The more-derived sequence should be the same instances as the less-derived. If there is any confusion, that's poor type authoring. This text is word for word the same as the v5 spec, but the compiler implements the behavior that matches the v4 spec. Like, the spec was changed in v5 here, but the compiler seems to have ignored this change. v4 text:
For your second point, it certainly seems the compiler's behavior is the desirable one. It's not actually possible to implement This raises further questions for me. Let's say we fix
It's an interesting suggestion to talk about interfaces implemented by the type instead of conversions. I'm curious what others think who are more familiar with what this change would entail. If I'm following, the benefit of this change would come from saying that |
I raised dotnet/roslyn#76598 making the case for following the v5 (to present) spec on point 1. |
Response from the compiler is that it's been a decade with next to no customer complaints, so perhaps the best course of action is to change the spec, as @svick originally suggested. |
The quoted text from draft-v9 is the same in v7 and draft-v8 so this is a current issue. This is not the most elegant of algorithms, partly of course due to maintaining backward compatibility when generics were introduced, and contains a surprise or more… For example: if a type implements both
For both implementers and consumers of such a type that might cause surprise… A Note might not go amiss. Looking at the second issue raised, I think this is a simple spec error. The bullet point starting “If among all the types Ti…” breaks if there is only one Suggestion:
|
At first glance, Nigel's proposal sounds reasonable. I suspect there may be more subtlety that I'm missing though. Looking forward to the discussion. |
I believe the GetEnumerator cases are handled in depth by the previous bullet point ("Otherwise, determine whether the type X has an appropriate GetEnumerator method:") before reaching the part of the spec in question ("Otherwise, check for an enumerable interface:"). So we do need to deal with the first point, after already having ruled out an implicitly implemented GetEnumerator. |
If I run svick's example through Nigel's suggestion, I believe it is specified to allow foreaching as |
In discussion in the meeting:
|
@svick I don't think so. Here's a type that actually implements both class Base : IEnumerable<int>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw new System.NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => throw new System.NotImplementedException();
}
class Derived<T> : Base, IEnumerable<T>
{
IEnumerator<T> IEnumerable<T>.GetEnumerator() => throw new System.NotImplementedException();
} |
Let's look at what Roslyn does (possibly via testing rather than code inspection) and go from there. We suspect that instead of considering implicit conversions, it's walking the type hierarchy looking for implementations, and aim to confirm that suspicion. (And we agree with Nigel's suggestion of a note for the explicit/implicit implementation aspect.) Assigned to @gafter to work out next steps. |
§13.9.5 The foreach statement of the current draft-v9 says that when processing
foreach
, and after not finding aGetEnumerable
method (in practice, this happens with explicit interface implementation), we do the following:I think relying on implicit conversions here is problematic. Also, it's not what Roslyn actually does, resulting in differences between the specification and the implementations.
In particular:
The specification allows implementing
IEnumerable<T>
multiple times and directly using such type inforeach
, when one of the type parameters inherits from the other:According to the spec, the unique type
T
here should bestring
, since there is an implicit covariant conversion fromIEnumerable<string>
toIEnumerable<object>
. The compiler does not allow this code:The specification results in the wrong iteration type for a type that implements
IEnumerable<dynamic>
:According to the spec, the unique type
T
here should beobject
, since the type is implicitly convertible to bothIEnumerable<object>
andIEnumerable<dynamic>
, butdynamic
is explicitly forbidden. The compiler determines the iteration type to bedynamic
, as expected.It seems to me that this could be resolved by following the compiler, and specifying this in terms of the interfaces actually implemented by the type in question, not based on implicit conversions. But I don't know whether that's viable.
The text was updated successfully, but these errors were encountered: