-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Test plan for "Extension GetEnumerator" #43184
Comments
@YairHalberstadt I've updated the test plan. I haven't yet gone through the existing tests to check the boxes, and we'll need to do a compiler review of the plan to see what's blocking. I'll try to go through the existing tests either this afternoon or tomorrow so we have an idea of what's outstanding. |
@YairHalberstadt I've filled in the checkboxes. First check is for synchronous, second is for async. |
Thanks. Going through them 1 by 1. Will comment here as I have issues: It looks like __arglist isn't allowed currently for an instance GetAsyncEnumerator either. |
Extension methods on a nullable value type don't work on a non-nullable valuetype and vice versa. Therefore I would expect extension foreach to fail in both cases. |
Do you mean an extension method on int, when you the expression is object? |
That's correct. Many of these are error scenarios, we just need to make sure we don't crash.
Yep. We do have a good error message for the regular extension method case, I would expect a similar message here.
From |
I removed the |
Actually, for an instance GetEnumerator, we unwrap nullable value types. SHould we do the same for extension GetEnumerator? |
This goes straight to foreach on a string (which is what we want) |
Do you want to add delegate conversion to the test plan? |
As a result of the nullable unwrapping this doesn't work: using System;
public class C
{
public static void Main()
{
foreach (var i in (int?)null)
{
Console.Write(i);
}
}
public sealed class Enumerator
{
public int Current { get; private set; }
public bool MoveNext() => Current++ != 3;
}
}
public static class Extensions
{
public static C.Enumerator GetEnumerator(this int? self) => new C.Enumerator();
} Is this what we want? |
Do you mean method group/lambda to delegate type?
No. I would expect that example to work, given that you can do |
But we should still unwrap nullable types? I'm guessing this will require two passes of overload resolution, one with the type and one with the unwrapped type. |
Although even then I'm not sure that's correct. Which of them would win if the extension method on the nullable type is in a further namespace than the extension method on the unwrapped type? |
You mean should we unbox the nullable type? Absolutely not. You can't call an extension method defined on |
The following currently works: public struct Program {
public static void Main() {
foreach (var a in (Program?)null)
{
}
}
public IEnumerator<int> GetEnumerator() => throw null;
} So it makes sense for instance members and extension members to be consistent in this regard. |
roslyn/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs Lines 576 to 578 in d04ebc2
|
Given that text, I'd rather not take what is effectively a "the native compiler did this weird thing so let's replicate" into a new feature. I'll double check with the LDM on this, but I'd expect the rules for calling an extension method to be followed. |
We're not going to replicate the behavior. This should behave the same as regular extension methods. |
Another case I've discovered: Any object known to be null is invalid in a foreach (e.g. I'm going to make an exception to this when we use an extension GetEnumerator, since they may be able to handle nulls. |
Implementation of this testplan ongoing at #45160 |
Can you show some examples of where we have these tests in the codebase |
What am I testing for in each of these cases? |
Look for tests that use the
Generally speaking, that they work. For |
PR available at #45348 Seperate PR since it's IDE and a separate feature. |
I'm not sure that makes much sense. Extension GetEnumerator only applies if somethings not IEnumerable, and hence it will not be convertible to linq. Similiarly linq to foreach will never require use of extension GetEnumerator since it will implement IEnumerable. |
I've added a test for find all references for extension GetEnumerator. I discovered that find all references doesn't work at all for GetAsyncEnumerator. I've opened an issue for that (#45352) and will hopefully deal with it at some point, but I presume it is not critical for now. All I've got left to do are the following:
Though as I said above, I'm not sure what there is to test in foreach to linq/linq to foreach I think it's probably worth starting to review my PR now. |
@YairHalberstadt I've updated the test plan with new cases from test plan review, and I'll go through the latest tests in the branch next to make sure the status is fully up to date. |
@YairHalberstadt I've updated the test plan. It looks like we're in very good shape, just another couple of tests to add:
|
The pattern for all existing ForEach tests is to call |
@YairHalberstadt I see this for the sync version, is there an async version I'm missing? |
Test plan is complete and the feature is merged. |
Language proposal: dotnet/csharplang#3194
General compiler test plan: https://github.com/dotnet/roslyn/blob/master/docs/contributing/Compiler%20Test%20Plan.md
Spec:
Compiler (Both sync and async for each of these):
private
extension methods when inside a static class (TestGetEnumeratorPatternViaAccessiblePrivateExtension, TestGetAsyncEnumeratorPatternViaAccessiblePrivateExtension)out
/ref
disallowed.out
(, )ref
(TestGetEnumeratorPatternViaRefExtensionOnNonAssignableVariable, TestGetAsyncEnumeratorPatternViaExtensionWithParams)in
allowed in the same places asDeconstruct
. (TestGetEnumeratorPatternViaInExtensionOnNonAssignableVariable, TestGetAsyncEnumeratorPatternViaInExtensionOnAssignableVariable)params
arrays (TestGetEnumeratorPatternViaExtensionWithParams, TestGetAsyncEnumeratorPatternViaExtensionWithParams)__arglist
parameter (TestGetEnumeratorPatternViaExtensionWithArgList, TestGetAsyncEnumeratorPatternViaExtensionWithArgList)dynamic
prefers existing attempt to call instanceGetEnumerator
, not an extension method, or does not work on async (TestPreferIEnumeratorInterfaceOnDynamicThanViaExtension, TestCannotUseExtensionGetAsyncEnumeratorOnDynamic)this
types:class
(a lot)interface
(TestGetEnumeratorPatternViaExtensionsOnInterface, TestGetAsyncEnumeratorPatternViaExtensionsOnInterface)delegate
(TestGetEnumeratorPatternViaExtensionsOnDelegate, TestGetAsyncEnumeratorPatternViaExtensionsOnDelegate)struct
(, TestGetAsyncEnumeratorPatternOnRange)enum
(TestGetEnumeratorPatternViaExtensionsOnEnum, TestGetAsyncEnumeratorPatternViaExtensionsOnEnum)nullable
(TestGetEnumeratorPatternViaExtensionsOnNullable, TestGetAsyncEnumeratorPatternViaExtensionsOnNullable)GetEnumerator
is defined on the class, extensions are not searched, even if return is incorrect. (TestPreferEnumeratorPatternFromInstanceThanViaExtensionEvenWhenInvalid, TestPreferAsyncEnumeratorPatternFromInstanceThanViaExtensionEvenWhenInvalid)ref
returns (TestGetEnumeratorPatternViaExtensionWithRefReturn, TestGetAsyncEnumeratorPatternViaExtensionWithRefReturn)Obsolete
extension methods (TestGetEnumeratorPatternViaObsoleteExtension, TestWithObsoletePatternMethodsViaExtension)GetEnumerator()
returningIDisposable
types (TestForEachViaExtensionExplicitlyDisposableStruct, TestAwaitForEachViaExtensionExplicitlyDisposableStruct)Dispose
method on async, not on sync (TestForEachViaExtensionDisposeStruct, TestAwaitForEachViaExtensionAsyncDisposeStruct)Productivity:
GetEnumerator
GetEnumerator
into scopeforeach
to Linqforeach
The text was updated successfully, but these errors were encountered: