Skip to content

Commit

Permalink
Async-iterator method can return IAsyncEnumerator<T> (#31114)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored Nov 28, 2018
1 parent 232ecc8 commit 259cae0
Show file tree
Hide file tree
Showing 24 changed files with 487 additions and 93 deletions.
12 changes: 9 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2482,10 +2482,16 @@ protected bool IsGenericTaskReturningAsyncMethod()
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsGenericTaskReturningAsync(this.Compilation);
}

protected bool IsIAsyncEnumerableReturningAsyncMethod()
protected bool IsIAsyncEnumerableOrIAsyncEnumeratorReturningAsyncMethod()
{
var symbol = this.ContainingMemberOrLambda;
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsIAsyncEnumerableReturningAsync(this.Compilation);
if (symbol?.Kind == SymbolKind.Method)
{
var method = (MethodSymbol)symbol;
return method.IsIAsyncEnumerableReturningAsync(this.Compilation) ||
method.IsIAsyncEnumeratorReturningAsync(this.Compilation);
}
return false;
}

protected virtual TypeSymbol GetCurrentReturnType(out RefKind refKind)
Expand Down Expand Up @@ -2547,7 +2553,7 @@ private BoundStatement BindReturn(ReturnStatementSyntax syntax, DiagnosticBag di
diagnostics.Add(ErrorCode.ERR_MustNotHaveRefReturn, syntax.ReturnKeyword.GetLocation());
hasErrors = true;
}
else if (IsIAsyncEnumerableReturningAsyncMethod())
else if (IsIAsyncEnumerableOrIAsyncEnumeratorReturningAsyncMethod())
{
diagnostics.Add(ErrorCode.ERR_ReturnInIterator, syntax.ReturnKeyword.GetLocation());
hasErrors = true;
Expand Down
8 changes: 5 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ internal override TypeSymbol GetIteratorElementType(YieldStatementSyntax node, D
}
else if (!returnType.IsErrorType())
{
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturn, _methodSymbol.Locations[0], _methodSymbol, returnType);
Error(elementTypeDiagnostics, ErrorCode.ERR_BadIteratorReturn, _methodSymbol.Locations[0], _methodSymbol, returnType);
}
elementType = CreateErrorType();
}
Expand Down Expand Up @@ -184,7 +184,8 @@ internal static TypeSymbolWithAnnotations GetIteratorElementTypeFromReturnType(C
{
if (refKind == RefKind.None && returnType.Kind == SymbolKind.NamedType)
{
switch (returnType.OriginalDefinition.SpecialType)
TypeSymbol originalDefinition = returnType.OriginalDefinition;
switch (originalDefinition.SpecialType)
{
case SpecialType.System_Collections_IEnumerable:
case SpecialType.System_Collections_IEnumerator:
Expand All @@ -200,7 +201,8 @@ internal static TypeSymbolWithAnnotations GetIteratorElementTypeFromReturnType(C
return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
}

if (returnType.OriginalDefinition == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T))
if (originalDefinition == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T) ||
originalDefinition == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T))
{
return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
}
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3630,7 +3630,7 @@ Give the compiler some way to differentiate the methods. For example, you can gi
<value>Since '{0}' is an async method that returns 'Task', a return keyword must not be followed by an object expression. Did you intend to return 'Task&lt;T&gt;'?</value>
</data>
<data name="ERR_BadAsyncReturn" xml:space="preserve">
<value>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</value>
<value>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</value>
</data>
<data name="ERR_CantReturnVoid" xml:space="preserve">
<value>Cannot return an expression of type 'void'</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ private sealed class AsyncIteratorRewriter : AsyncRewriter
private FieldSymbol _promiseOfValueOrEndField; // this struct implements the IValueTaskSource logic
private FieldSymbol _currentField; // stores the current/yielded value

// true if the iterator implements IAsyncEnumerable<T>,
// false if it implements IAsyncEnumerator<T>
private readonly bool _isEnumerable;

internal AsyncIteratorRewriter(
BoundStatement body,
MethodSymbol method,
Expand All @@ -29,14 +33,21 @@ internal AsyncIteratorRewriter(
: base(body, method, methodOrdinal, stateMachineType, slotAllocatorOpt, compilationState, diagnostics)
{
Debug.Assert(method.IteratorElementType != null);

_isEnumerable = method.IsIAsyncEnumerableReturningAsync(method.DeclaringCompilation);
}

protected override void VerifyPresenceOfRequiredAPIs(DiagnosticBag bag)
{
base.VerifyPresenceOfRequiredAPIs(bag);
EnsureWellKnownMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, bag);

if (_isEnumerable)
{
EnsureWellKnownMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, bag);
}
EnsureWellKnownMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__MoveNextAsync, bag);
EnsureWellKnownMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__get_Current, bag);

EnsureWellKnownMember(WellKnownMember.System_IAsyncDisposable__DisposeAsync, bag);
EnsureWellKnownMember(WellKnownMember.System_Threading_Tasks_ValueTask_T__ctor, bag);

Expand All @@ -62,8 +73,11 @@ protected override void GenerateMethodImplementations()
// IAsyncStateMachine methods and constructor
base.GenerateMethodImplementations();

// IAsyncEnumerable
GenerateIAsyncEnumerableImplementation_GetAsyncEnumerator();
if (_isEnumerable)
{
// IAsyncEnumerable
GenerateIAsyncEnumerableImplementation_GetAsyncEnumerator();
}

// IAsyncEnumerator
GenerateIAsyncEnumeratorImplementation_MoveNextAsync();
Expand All @@ -81,6 +95,9 @@ protected override void GenerateMethodImplementations()
GenerateIAsyncDisposable_DisposeAsync();
}

protected override bool PreserveInitialParameterValuesAndThreadId
=> _isEnumerable;

protected override void GenerateControlFields()
{
// the fields are initialized from entry-point method (which replaces the async-iterator method), so they need to be public
Expand Down Expand Up @@ -153,8 +170,8 @@ protected override void GenerateConstructor()

protected override void InitializeStateMachine(ArrayBuilder<BoundStatement> bodyBuilder, NamedTypeSymbol frameType, LocalSymbol stateMachineLocal)
{
// var stateMachineLocal = new {StateMachineType}(FinishedStateMachine)
int initialState = StateMachineStates.FinishedStateMachine;
// var stateMachineLocal = new {StateMachineType}({initialState})
int initialState = _isEnumerable ? StateMachineStates.FinishedStateMachine : StateMachineStates.NotStartedStateMachine;
bodyBuilder.Add(
F.Assignment(
F.Local(stateMachineLocal),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ private Symbol EnsureWellKnownMember(WellKnownMember member, DiagnosticBag bag)
return Binder.GetWellKnownTypeMember(F.Compilation, member, bag, body.Syntax.Location);
}

// Should only be true for async-enumerables, not async-enumerators. Tracked by https://github.com/dotnet/roslyn/issues/31057
protected override bool PreserveInitialParameterValuesAndThreadId
=> method.IsIterator;
=> false;

protected override void GenerateControlFields()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ public AsyncStateMachine(VariableSlotAllocator variableAllocatorOpt, TypeCompila
var elementType = TypeMap.SubstituteType(asyncMethod.IteratorElementType).TypeSymbol;
this.IteratorElementType = elementType;

// IAsyncEnumerable<TResult>
interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T).Construct(elementType));
bool isEnumerable = asyncMethod.IsIAsyncEnumerableReturningAsync(compilation);
if (isEnumerable)
{
// IAsyncEnumerable<TResult>
interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T).Construct(elementType));
}

// IAsyncEnumerator<TResult>
interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T).Construct(elementType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ public static bool IsIAsyncEnumerableReturningAsync(this MethodSymbol method, CS
&& method.ReturnType.TypeSymbol.IsIAsyncEnumerableType(compilation);
}

/// <summary>
/// Returns whether this method is async and returns an IAsyncEnumerator`1.
/// </summary>
public static bool IsIAsyncEnumeratorReturningAsync(this MethodSymbol method, CSharpCompilation compilation)
{
return method.IsAsync
&& method.ReturnType.TypeSymbol.IsIAsyncEnumeratorType(compilation);
}

internal static CSharpSyntaxNode ExtractReturnTypeSyntax(this MethodSymbol method)
{
method = method.PartialDefinitionPart ?? method;
Expand Down
17 changes: 15 additions & 2 deletions src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,17 @@ internal static bool IsIAsyncEnumerableType(this TypeSymbol type, CSharpCompilat
return (object)namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T);
}

internal static bool IsIAsyncEnumeratorType(this TypeSymbol type, CSharpCompilation compilation)
{
var namedType = type as NamedTypeSymbol;
if ((object)namedType == null || namedType.Arity != 1)
{
return false;
}

return (object)namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T);
}

/// <summary>
/// Returns true if the type is generic or non-generic custom task-like type due to the
/// [AsyncMethodBuilder(typeof(B))] attribute. It returns the "B".
Expand Down Expand Up @@ -1694,10 +1705,12 @@ private static bool IsWellKnownInteropServicesTopLevelType(this ITypeSymbol type
public static bool IsBadAsyncReturn(this TypeSymbol returnType, CSharpCompilation declaringCompilation)
{
// Note: we're passing the return type explicitly (rather than using `method.ReturnType`) to avoid cycles
return returnType.SpecialType != SpecialType.System_Void &&
return !returnType.IsErrorType() &&
returnType.SpecialType != SpecialType.System_Void &&
!returnType.IsNonGenericTaskType(declaringCompilation) &&
!returnType.IsGenericTaskType(declaringCompilation) &&
!returnType.IsIAsyncEnumerableType(declaringCompilation);
!returnType.IsIAsyncEnumerableType(declaringCompilation) &&
!returnType.IsIAsyncEnumeratorType(declaringCompilation);
}
}
}
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Poskytněte kompilátoru nějaký způsob, jak metody rozlišit. Můžete např
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Návratový typ asynchronní metody musí být void, Task nebo Task&lt;T&gt;.</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Unterstützen Sie den Compiler bei der Unterscheidung zwischen den Methoden. Daz
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Der Rückgabetyp einer Async-Methode muss "void", "Task" oder "Task&lt;T&gt;" sein</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Indique al compilador alguna forma de diferenciar los métodos. Por ejemplo, pue
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">El tipo de valor devuelto de un método asincrónico debe ser void, Task o Task&lt;T&gt;</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Permettez au compilateur de différencier les méthodes. Par exemple, vous pouve
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Le type de retour d'une méthode async doit être void, Task ou Task&lt;T&gt;</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Impostare il compilatore in modo tale da distinguere i metodi, ad esempio assegn
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Il tipo restituito di un metodo asincrono deve essere void, Task o Task&lt;T&gt;</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ C# では out と ref を区別しますが、CLR では同じと認識します
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">非同期メソッドの戻り値の型は、void、Task、または Task&lt;T&gt; であることが必要です</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ C#에서는 out과 ref를 구분하지만 CLR에서는 동일한 것으로 간
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">비동기 메서드의 반환 형식은 void, Task 또는 Task&lt;T&gt;여야 합니다.</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Musisz umożliwić kompilatorowi rozróżnienie metod. Możesz na przykład nada
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Zwracany typ metody asynchronicznej musi mieć wartość „void”, „Task” lub „Task&lt;T&gt;”.</target>
<note />
</trans-unit>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Forneça ao compilador alguma forma de diferenciar os métodos. Por exemplo, voc
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">O tipo de retorno de um método assíncrono deve ser void, Task ou Task&lt;T&gt;</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Give the compiler some way to differentiate the methods. For example, you can gi
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Возвращаемым типом асинхронного метода должен быть void, Task или Task&lt;T&gt;</target>
<note />
</trans-unit>
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -6419,7 +6419,7 @@ Derleyiciye yöntemleri ayrıştırma yolu verin. Örneğin, bunlara farklı adl
<note />
</trans-unit>
<trans-unit id="ERR_BadAsyncReturn">
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, or IAsyncEnumerable&lt;T&gt;</source>
<source>The return type of an async method must be void, Task, Task&lt;T&gt;, a task-like type, IAsyncEnumerable&lt;T&gt;, or IAsyncEnumerator&lt;T&gt;</source>
<target state="needs-review-translation">Zaman uyumsuz bir yöntemin dönüş türü void, Task ve Task&lt;T&gt; olmalıdır&gt;</target>
<note />
</trans-unit>
Expand Down
Loading

0 comments on commit 259cae0

Please sign in to comment.