diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 4a8ff5348bebf..8fb295e4e7ef2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -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) @@ -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; diff --git a/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs b/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs index 6a76ef8c943d8..2ab1dd8218a19 100644 --- a/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs @@ -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(); } @@ -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: @@ -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]; } diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index 2c300afb05e72..d65901f2909e6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -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, + // false if it implements IAsyncEnumerator + private readonly bool _isEnumerable; + internal AsyncIteratorRewriter( BoundStatement body, MethodSymbol method, @@ -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); @@ -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(); @@ -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 @@ -153,8 +170,8 @@ protected override void GenerateConstructor() protected override void InitializeStateMachine(ArrayBuilder 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), diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs index cfef83c811223..af41d21af7650 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs @@ -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() { diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs index b6cf218295b5a..22bfddd73e42a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncStateMachine.cs @@ -31,8 +31,12 @@ public AsyncStateMachine(VariableSlotAllocator variableAllocatorOpt, TypeCompila var elementType = TypeMap.SubstituteType(asyncMethod.IteratorElementType).TypeSymbol; this.IteratorElementType = elementType; - // IAsyncEnumerable - interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T).Construct(elementType)); + bool isEnumerable = asyncMethod.IsIAsyncEnumerableReturningAsync(asyncMethod.DeclaringCompilation); + if (isEnumerable) + { + // IAsyncEnumerable + interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T).Construct(elementType)); + } // IAsyncEnumerator interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T).Construct(elementType)); diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs index 506f064dbe1b5..b2d106aa98d46 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs @@ -314,6 +314,15 @@ public static bool IsIAsyncEnumerableReturningAsync(this MethodSymbol method, CS && method.ReturnType.TypeSymbol.IsIAsyncEnumerableType(compilation); } + /// + /// Returns whether this method is async and returns an IAsyncEnumerator`1. + /// + 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; diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index edc7efcd5dbb6..0bde6566e9551 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -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); + } + /// /// 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". @@ -1697,7 +1708,8 @@ public static bool IsBadAsyncReturn(this TypeSymbol returnType, CSharpCompilatio return returnType.SpecialType != SpecialType.System_Void && !returnType.IsNonGenericTaskType(declaringCompilation) && !returnType.IsGenericTaskType(declaringCompilation) && - !returnType.IsIAsyncEnumerableType(declaringCompilation); + !returnType.IsIAsyncEnumerableType(declaringCompilation) && + !returnType.IsIAsyncEnumeratorType(declaringCompilation); } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index 69c0a8d88c03d..b48b95e09735a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { + using System.Linq; using static Instruction; internal enum Instruction { @@ -22,18 +23,32 @@ internal enum Instruction [CompilerTrait(CompilerFeature.AsyncStreams)] public class CodeGenAsyncIteratorTests : EmitMetadataTestBase { - private void VerifyMissingMember(WellKnownMember member, params DiagnosticDescription[] expected) - { - var lib = CreateCompilationWithAsyncIterator(""); - var lib_ref = lib.EmitToImageReference(); - - string source = @" + private const string _enumerable = @" using System.Threading.Tasks; class C { async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } } "; + private const string _enumerator = @" +using System.Threading.Tasks; +class C +{ + async System.Collections.Generic.IAsyncEnumerator M() { await Task.CompletedTask; yield return 3; } +} +"; + private void VerifyMissingMember(WellKnownMember member, params DiagnosticDescription[] expected) + { + foreach (var source in new[] { _enumerable, _enumerator }) + { + VerifyMissingMember(source, member, expected); + } + } + + private static void VerifyMissingMember(string source, WellKnownMember member, params DiagnosticDescription[] expected) + { + var lib = CreateCompilationWithTasksExtensions(AsyncStreamsTypes); + var lib_ref = lib.EmitToImageReference(); var comp = CreateCompilationWithTasksExtensions(source, references: new[] { lib_ref }); comp.MakeMemberMissing(member); comp.VerifyEmitDiagnostics(expected); @@ -41,16 +56,16 @@ class C private void VerifyMissingType(WellKnownType type, params DiagnosticDescription[] expected) { - var lib = CreateCompilationWithAsyncIterator(""); - var lib_ref = lib.EmitToImageReference(); + foreach (var source in new[] { _enumerable, _enumerator }) + { + VerifyMissingType(source, type, expected); + } + } - string source = @" -using System.Threading.Tasks; -class C -{ - async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } -} -"; + private static void VerifyMissingType(string source, WellKnownType type, params DiagnosticDescription[] expected) + { + var lib = CreateCompilationWithTasksExtensions(AsyncStreamsTypes); + var lib_ref = lib.EmitToImageReference(); var comp = CreateCompilationWithTasksExtensions(source, references: new[] { lib_ref }); comp.MakeTypeMissing(type); comp.VerifyEmitDiagnostics(expected); @@ -389,49 +404,73 @@ static async System.Collections.Generic.IAsyncEnumerable M() Assert.True(m.IsIterator); } - [ConditionalFact(typeof(WindowsDesktopOnly))] - public void AttributesSynthesized() + [Fact] + public void ReturnAfterAwait() { string source = @" -public class C +class C { - public static async System.Collections.Generic.IAsyncEnumerable M() + async System.Collections.Generic.IAsyncEnumerable M() { await System.Threading.Tasks.Task.CompletedTask; - yield return 4; + return null; } }"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugDll); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, symbolValidator: module => - { - var method = module.GlobalNamespace.GetMember("C.M"); - AssertEx.SetEqual(new[] { "AsyncStateMachineAttribute", "IteratorStateMachineAttribute" }, - GetAttributeNames(method.GetAttributes())); - }); + comp.VerifyDiagnostics( + // (7,9): error CS1622: Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. + // return null; + Diagnostic(ErrorCode.ERR_ReturnInIterator, "return").WithLocation(7, 9) + ); } [Fact] - public void ReturnIAsyncEnumerator() + public void AwaitAfterReturn() { string source = @" -public class C +class C { - public static async System.Collections.Generic.IAsyncEnumerator M() + async System.Collections.Generic.IAsyncEnumerable M() { + return null; await System.Threading.Tasks.Task.CompletedTask; - yield return 4; } }"; var comp = CreateCompilationWithAsyncIterator(source); comp.VerifyDiagnostics( - // (4,74): error CS1983: The return type of an async method must be void, Task, Task, a task-like type, or IAsyncEnumerable - // public static async System.Collections.Generic.IAsyncEnumerator M() - Diagnostic(ErrorCode.ERR_BadAsyncReturn, "M").WithLocation(4, 74), - // (4,74): error CS1624: The body of 'C.M()' cannot be an iterator block because 'IAsyncEnumerator' is not an iterator interface type - // public static async System.Collections.Generic.IAsyncEnumerator M() - Diagnostic(ErrorCode.ERR_BadIteratorReturn, "M").WithArguments("C.M()", "System.Collections.Generic.IAsyncEnumerator").WithLocation(4, 74) + // (6,9): error CS1622: Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. + // return null; + Diagnostic(ErrorCode.ERR_ReturnInIterator, "return").WithLocation(6, 9), + // (7,9): warning CS0162: Unreachable code detected + // await System.Threading.Tasks.Task.CompletedTask; + Diagnostic(ErrorCode.WRN_UnreachableCode, "await").WithLocation(7, 9) ); + + var m = comp.GlobalNamespace.GetMember("C.M"); + Assert.True(m.IsAsync); + Assert.False(m.IsIterator); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + public void AttributesSynthesized() + { + string source = @" +public class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + await System.Threading.Tasks.Task.CompletedTask; + yield return 4; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugDll); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, symbolValidator: module => + { + var method = module.GlobalNamespace.GetMember("C.M"); + AssertEx.SetEqual(new[] { "AsyncStateMachineAttribute", "IteratorStateMachineAttribute" }, + GetAttributeNames(method.GetAttributes())); + }); } [Fact] @@ -601,13 +640,15 @@ public void MissingTypeAndMembers_IAsyncStateMachine() [Fact] public void MissingTypeAndMembers_IAsyncEnumerable() { - VerifyMissingMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, + VerifyMissingMember(_enumerable, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, // (5,64): error CS0656: Missing compiler required member 'System.Collections.Generic.IAsyncEnumerable`1.GetAsyncEnumerator' // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Collections.Generic.IAsyncEnumerable`1", "GetAsyncEnumerator").WithLocation(5, 64) ); - VerifyMissingType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T, + VerifyMissingMember(_enumerator, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator); + + VerifyMissingType(_enumerable, WellKnownType.System_Collections_Generic_IAsyncEnumerable_T, // (5,60): error CS1983: The return type of an async method must be void, Task, Task, a task-like type, or IAsyncEnumerable // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } Diagnostic(ErrorCode.ERR_BadAsyncReturn, "M").WithLocation(5, 60), @@ -615,6 +656,8 @@ public void MissingTypeAndMembers_IAsyncEnumerable() // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } Diagnostic(ErrorCode.ERR_BadIteratorReturn, "M").WithArguments("C.M()", "System.Collections.Generic.IAsyncEnumerable").WithLocation(5, 60) ); + + VerifyMissingType(_enumerator, WellKnownType.System_Collections_Generic_IAsyncEnumerable_T); } [Fact] @@ -702,7 +745,16 @@ public void MissingTypeAndMembers_IAsyncEnumerator() Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Collections.Generic.IAsyncEnumerator`1", "get_Current").WithLocation(5, 64) ); - VerifyMissingType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T, + VerifyMissingType(_enumerator, WellKnownType.System_Collections_Generic_IAsyncEnumerator_T, + // (5,60): error CS1983: The return type of an async method must be void, Task, Task, a task-like type, or IAsyncEnumerable + // async System.Collections.Generic.IAsyncEnumerator M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "M").WithLocation(5, 60), + // (5,60): error CS1624: The body of 'C.M()' cannot be an iterator block because 'IAsyncEnumerator' is not an iterator interface type + // async System.Collections.Generic.IAsyncEnumerator M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_BadIteratorReturn, "M").WithArguments("C.M()", "System.Collections.Generic.IAsyncEnumerator").WithLocation(5, 60) + ); + + VerifyMissingType(_enumerable, WellKnownType.System_Collections_Generic_IAsyncEnumerator_T, // (5,64): error CS0656: Missing compiler required member 'System.Collections.Generic.IAsyncEnumerable`1.GetAsyncEnumerator' // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Collections.Generic.IAsyncEnumerable`1", "GetAsyncEnumerator").WithLocation(5, 64), @@ -932,6 +984,248 @@ async System.Collections.Generic.IAsyncEnumerable M2() ); } + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator() + { + string source = @" +using static System.Console; +class C +{ + static async System.Collections.Generic.IAsyncEnumerator M(int value) + { + yield return value; + value = 5; + Write(""1 ""); + await System.Threading.Tasks.Task.CompletedTask; + Write(""2 ""); + yield return 3; + Write(""4 ""); + yield return value; + } + static async System.Threading.Tasks.Task Main() + { + await using (var enumerator = M(0)) + { + if (!await enumerator.MoveNextAsync()) throw null; + Write($""Value:{enumerator.Current} ""); + if (!await enumerator.MoveNextAsync()) throw null; + Write($""Value:{enumerator.Current} ""); + if (!await enumerator.MoveNextAsync()) throw null; + Write($""Value:{enumerator.Current} ""); + if (await enumerator.MoveNextAsync()) throw null; + if (await enumerator.MoveNextAsync()) throw null; + + Write(""Done""); + } + } +}"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "Value:0 1 2 Value:3 4 Value:5 Done"); + + var comp2 = CreateCompilationWithTasksExtensions("", references: new[] { comp.EmitToImageReference() }); + var type = (NamedTypeSymbol)comp2.GetMember("C.d__0"); + AssertEx.SetEqual(new[] { + "System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.System.Runtime.CompilerServices.IStrongBox>.Value { get; }", + "System.Runtime.CompilerServices.AsyncVoidMethodBuilder C.d__0.<>t__builder", + "System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.<>v__promiseOfValueOrEnd", + "System.Int32 C.d__0.value", + "C.d__0..ctor(System.Int32 <>1__state)", + "void C.d__0.MoveNext()", + "void C.d__0.SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)", + "System.Threading.Tasks.ValueTask C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Boolean C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(System.Int16 token)", + "System.Threading.Tasks.Sources.ValueTaskSourceStatus C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(System.Int16 token)", + "void C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(System.Action continuation, System.Object state, System.Int16 token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)", + "ref System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.System.Runtime.CompilerServices.IStrongBox>.get_Value()", + "System.Threading.Tasks.ValueTask C.d__0.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__0.<>1__state" }, + type.GetMembersUnordered().Select(m => m.ToTestDisplayString())); + + AssertEx.SetEqual(new[] { + "System.Runtime.CompilerServices.IStrongBox>", + "System.Runtime.CompilerServices.IAsyncStateMachine", + "System.IAsyncDisposable", + "System.Threading.Tasks.Sources.IValueTaskSource", + "System.Collections.Generic.IAsyncEnumerator" }, + type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Select(m => m.ToTestDisplayString())); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_CSharp73() + { + string source = @" +class C +{ + async System.Collections.Generic.IAsyncEnumerator M() + { + yield return 1; + await System.Threading.Tasks.Task.CompletedTask; + } +}"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, parseOptions: TestOptions.Regular7_3); + comp.VerifyDiagnostics( + // (4,60): error CS8370: Feature 'async streams' is not available in C# 7.3. Please use language version 8.0 or greater. + // async System.Collections.Generic.IAsyncEnumerator M() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "M").WithArguments("async streams", "8.0").WithLocation(4, 60), + // (23,2): error CS8370: Feature 'nullable reference types' is not available in C# 7.3. Please use language version 8.0 or greater. + // #nullable disable + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "nullable").WithArguments("nullable reference types", "8.0").WithLocation(23, 2) + ); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_WithReturnOnly() + { + string source = @" +class C +{ + async System.Collections.Generic.IAsyncEnumerator M() + { + return null; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (4,60): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async System.Collections.Generic.IAsyncEnumerator M() + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "M").WithLocation(4, 60), + // (6,9): error CS1622: Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. + // return null; + Diagnostic(ErrorCode.ERR_ReturnInIterator, "return").WithLocation(6, 9) + ); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void ReturningIAsyncEnumerator_WithReturn() + { + string source = @" +class C +{ + System.Collections.Generic.IAsyncEnumerator M() + { + return null; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics(); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_WithReturnAndAwait() + { + string source = @" +class C +{ + static async System.Collections.Generic.IAsyncEnumerator M(int value) + { + return value; + await System.Threading.Tasks.Task.CompletedTask; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (6,9): error CS1622: Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. + // return value; + Diagnostic(ErrorCode.ERR_ReturnInIterator, "return").WithLocation(6, 9), + // (7,9): warning CS0162: Unreachable code detected + // await System.Threading.Tasks.Task.CompletedTask; + Diagnostic(ErrorCode.WRN_UnreachableCode, "await").WithLocation(7, 9) + ); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + [WorkItem(31113, "https://github.com/dotnet/roslyn/issues/31113")] + public void AsyncIteratorReturningEnumerator_WithoutAsync() + { + string source = @" +class C +{ + static System.Collections.Generic.IAsyncEnumerator M(int value) + { + yield return value; + await System.Threading.Tasks.Task.CompletedTask; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (7,9): error CS4032: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task>'. + // await System.Threading.Tasks.Task.CompletedTask; + Diagnostic(ErrorCode.ERR_BadAwaitWithoutAsyncMethod, "await System.Threading.Tasks.Task.CompletedTask").WithArguments("System.Collections.Generic.IAsyncEnumerator").WithLocation(7, 9) + ); + + // This error message is rather poor. Tracked by https://github.com/dotnet/roslyn/issues/31113 + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_WithReturnAfterAwait() + { + string source = @" +class C +{ + static async System.Collections.Generic.IAsyncEnumerator M(int value) + { + await System.Threading.Tasks.Task.CompletedTask; + return value; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (7,9): error CS1622: Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration. + // return value; + Diagnostic(ErrorCode.ERR_ReturnInIterator, "return").WithLocation(7, 9) + ); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_WithoutAwait() + { + string source = @" +class C +{ + static async System.Collections.Generic.IAsyncEnumerator M(int value) + { + yield return value; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (4,67): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // static async System.Collections.Generic.IAsyncEnumerator M(int value) + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "M").WithLocation(4, 67) + ); + } + + [ConditionalFact(typeof(WindowsDesktopOnly))] + [WorkItem(31057, "https://github.com/dotnet/roslyn/issues/31057")] + public void AsyncIteratorReturningEnumerator_WithoutYield() + { + string source = @" +class C +{ + static async System.Collections.Generic.IAsyncEnumerator M(int value) + { + await System.Threading.Tasks.Task.CompletedTask; + } +}"; + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics( + // (4,67): error CS0161: 'C.M(int)': not all code paths return a value + // static async System.Collections.Generic.IAsyncEnumerator M(int value) + Diagnostic(ErrorCode.ERR_ReturnExpected, "M").WithArguments("C.M(int)").WithLocation(4, 67) + ); + } + [ConditionalFact(typeof(WindowsDesktopOnly))] public void CallingMoveNextAsyncTwice() { @@ -1008,6 +1302,37 @@ static async System.Threading.Tasks.Task Main() var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe); comp.VerifyDiagnostics(); CompileAndVerify(comp, expectedOutput: "1 2 Stream1:3 1 2 Stream2:3 4 2 4 2 Done"); + + var comp2 = CreateCompilationWithTasksExtensions("", references: new[] { comp.EmitToImageReference() }); + var type = (NamedTypeSymbol)comp2.GetMember("C.d__0"); + AssertEx.SetEqual(new[] { + "System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.System.Runtime.CompilerServices.IStrongBox>.Value { get; }", + "System.Runtime.CompilerServices.AsyncVoidMethodBuilder C.d__0.<>t__builder", + "System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.<>v__promiseOfValueOrEnd", + "System.Int32 C.d__0.<>3__value", + "C.d__0..ctor(System.Int32 <>1__state)", + "void C.d__0.MoveNext()", + "void C.d__0.SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)", + "System.Collections.Generic.IAsyncEnumerator C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator()", + "System.Threading.Tasks.ValueTask C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Boolean C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(System.Int16 token)", + "System.Threading.Tasks.Sources.ValueTaskSourceStatus C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(System.Int16 token)", + "void C.d__0.System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(System.Action continuation, System.Object state, System.Int16 token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)", + "ref System.Threading.Tasks.ManualResetValueTaskSourceLogic C.d__0.System.Runtime.CompilerServices.IStrongBox>.get_Value()", + "System.Threading.Tasks.ValueTask C.d__0.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__0.<>1__state" }, + type.GetMembersUnordered().Select(m => m.ToTestDisplayString())); + + AssertEx.SetEqual(new[] { + "System.Runtime.CompilerServices.IStrongBox>", + "System.Runtime.CompilerServices.IAsyncStateMachine", + "System.IAsyncDisposable", + "System.Threading.Tasks.Sources.IValueTaskSource", + "System.Collections.Generic.IAsyncEnumerable", + "System.Collections.Generic.IAsyncEnumerator" }, + type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Select(m => m.ToTestDisplayString())); } [ConditionalFact(typeof(WindowsDesktopOnly))] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index cfe771cbf2d63..99a3106fd0f95 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -28641,7 +28641,7 @@ async IAsyncEnumerable local() ); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/31057"), CompilerTrait(CompilerFeature.AsyncStreams)] + [Fact, CompilerTrait(CompilerFeature.AsyncStreams)] public void Yield_IAsyncEnumerator() { var source = @" @@ -28680,7 +28680,8 @@ async IAsyncEnumerator local() } } }"; - CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: WithNonNullTypesTrue()).VerifyDiagnostics( + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( // (8,22): warning CS8603: Possible null reference return. // yield return null; // 1 Diagnostic(ErrorCode.WRN_NullReferenceReturn, "null").WithLocation(8, 22),