diff --git a/docs/compilers/CSharp/Runtime Async-Streams Design.md b/docs/compilers/CSharp/Runtime Async-Streams Design.md new file mode 100644 index 0000000000000..a4a6a005734da --- /dev/null +++ b/docs/compilers/CSharp/Runtime Async-Streams Design.md @@ -0,0 +1,339 @@ +# Runtime Async-Streams Design + +## Overview + +Async methods that return `IAsyncEnumerable` or `IAsyncEnumerator` are transformed by the compiler into state machines. +States are created for each `await` and `yield`. +Runtime-async support was added in .NET 10 as a preview feature and reduces the overhead of async methods by letting the runtime handling `await` suspensions. +The following design describes how the compiler generates code for async-stream methods when targeting a runtime that supports runtime async. +In short, the compiler generates a state machine similar to async-streams, that implements `IAsyncEnumerable` and `IAsyncEnumerator`. +The states corresponding to `yield` suspensions match those of existing async-streams. +No state is created for `await` expressions, which are lowered to a runtime call instead. + +See `docs/features/async-streams.md` and `Runtime Async Design.md` for more background information. + +## Structure + +For an async-stream method, the compiler generates the following members: +- kickoff method +- state machine class + - fields + - constructor + - `GetAsyncEnumerator` method + - `Current` property + - `DisposeAsync` method + - `MoveNextAsync` method + +Considering a simple async-iterator method: +```csharp +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + Write("1"); + await System.Threading.Tasks.Task.Yield(); + Write("2"); + yield return 3; + Write("4"); + } +} +``` + +The following pseudo-code illustrates the intermediate implementation the compiler generates. +Note that async methods `MoveNextAsync` and `DisposeAsync` will be further lowered following runtime-async design. +```csharp +class C +{ + public static IAsyncEnumerable M() + { + return new M_d__0(-2); + } + + [CompilerGenerated] + private sealed class M_d__0 : IAsyncEnumerable, IAsyncEnumerator, IAsyncDisposable + { + public int 1__state; + private int 2__current; + private bool w__disposeMode; + private int l__initialThreadId; + + [DebuggerHidden] + public M_d__0(int state) + { + 1__state = state; + l__initialThreadId = Environment.CurrentManagedThreadId; + } + + [DebuggerHidden] + IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + M_d__0 result; + + if (1__state == -2 && l__initialThreadId == Environment.CurrentManagedThreadId) + { + 1__state = -3; + w__disposeMode = false; + result = this; + } + else + { + result = new d__0(-3); + } + + return result; + } + + ValueTask IAsyncEnumerator.MoveNextAsync() + { + int temp1 = 1__state; + try + { + switch (temp1) + { + case -4: + goto ; + } + + if (w__disposeMode) + goto ; + + 1__state = temp1 = -1; + Write("1"); + runtime-await Task.Yield(); // `runtime-await` will be lowered to a call to runtime helper method + Write("2"); + + { + // suspension for `yield return 3;` + 2__current = 3; + 1__state = temp1 = -4; + return true; + + :; + 1__state = temp1 = -1; + + if (w__disposeMode) + goto ; + } + + Write("4"); + + w__disposeMode = true; + goto ; + } + catch (Exception) + { + 1__state = -2; + 2__current = default; + throw; + } + + : ; + 1__state = -2; + 2__current = default; + return false; + } + + [DebuggerHidden] + int IAsyncEnumerator.Current + { + get => 2__current; + } + + [DebuggerHidden] + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (<>1__state >= -1) + throw new NotSupportedException(); + + if (<>1__state == -2) + return; + + w__disposeMode = true; + runtime-await MoveNextAsync(); // `runtime-await` will be lowered to a runtime call + } + } +} +``` + +## Lowering details + +The overall lowering strategy is similar to existing async-streams lowering, +except for simplifications since `await` expressions are left to the runtime to handle. +PROTOTYPE overall lifecycle diagram + +### Kickoff method, fields and constructor + +The state machine class contains fields for: +- the state (an `int`), +- the current value (of the yield type of the async iterator), +- the dispose mode (a `bool`), +- the initial thread ID (an `int`), +- the combined cancellation token (a `CancellationTokenSource`) when the `[EnumeratorCancellation]` attribute is applied, +- hoisted variables (parameters and locals) as needed. +- parameter proxies (serve to initialize hoisted parameters when producing an enumerator when the method is declared as enumerable) + +The constructor of the state machine class has the signature `.ctor(int state)`. +Its body is: +``` +{ + this.state = state; + this.initialThreadId = {managedThreadId}; + this.instanceId = LocalStoreTracker.GetNewStateMachineInstanceId(); // when local state tracking is enabled +} +``` + +The kickoff method has the signature of the user's method. It simply creates and returns a new instance of the state machine class, capturing the necessary context. + +### GetAsyncEnumerator + +The signature of this method is `IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken = default)` +where `Y` is the yield type of the async iterator. + +The `GetAsyncEnumerator` method either returns the current instance if it can be reused, +or creates a new instance of the state machine class. + +Assuming that the unspeakble state machine class is named `Unspeakable`, `GetAsyncEnumerator` is emitted as: +``` +{ + Unspeakable result; + if (__state == FinishedState && __initialThreadId == Environment.CurrentManagedThreadId) + { + __state = InitialState; + result = this; + __disposeMode = false; + } + else + { + result = new Unspeakable(InitialState); + } + return result; +} +``` + +### Current property + +The signature of the property is `Y IAsyncEnumerator.Current { get; }` +where `Y` is the yield type of the async iterator. +The getter simply returns the field holding the current value. + +### DisposeAsync + +The signature of this method is `ValueTask IAsyncDisposable.DisposeAsync()`. +This method is emitted with the `async` runtime modifier, so it need only `return;`. + +Its body is: +``` +{ + if (__state >= NotStartedStateMachine) + { + // running + throw new NotSupportedException(); + } + + if (__state == FinishedState) + { + // already disposed + return; + } + + __disposeMode = true; + runtime-await MoveNextAsync(); // `runtime-await` will be lowered to a call to runtime helper method + return; +} +``` + +PROTOTYPE different ways to reach disposal + +### MoveNextAsync + +The signature of this method is `ValueTask IAsyncEnumerator.MoveNextAsync()` +where `Y` is the yield type of the async iterator. +This method is emitted with the `async` runtime modifier, so it need only `return` with a `bool`. + +A number of techniques from existing async-streams lowering are reused here (PROTOTYPE provide more details on these): +- replacement of generic type parameters +- dispatching based on state +- extraction of exception handlers +- dispatching out of try/catch +- replacing cancellation token parameter with one from combined tokens when `[EnumeratorCancellation]` is used + +PROTOTYPE do we still need spilling for `await` expressions? + +#### Lowering of `yield return` + +`yield return` is disallowed in finally, in try with catch and in catch. +`yield return` is lowered as a suspension of the state machine (essentially `__current = ...; return true;` with a way of resuming execution after the return): + +``` +// a `yield return 42;` in user code becomes: +__state = stateForThisYieldReturn; +__current = 42; +return true; // in an ValueTask-returning runtime-async method, we need only return a boolean + +labelForThisYieldReturn: +__state = RunningState; +if (__disposeMode) /* jump to enclosing finally or exit */ +``` + +#### Lowering of `yield break` + +`yield break` is disallowed in finally. +When a `yield break;` is reached, the relevant `finally` blocks should get executed immediately. + +``` +// a `yield break;` in user code becomes: +disposeMode = true; +/* jump to enclosing finally or exit */ +``` + +Note that in this case, the caller will not get a result from `MoveNextAsync()` +until we've reached the end of the method (**finished** state) and so `DisposeAsync()` will have no work left to do. + +#### Lowering of `await` + +`await` is disallowed in lock bodies, and in catch filters. +`await` expressions are lowered to runtime calls (instead of being transformed into state machine logic for regular async-streams), +following the runtime-async design. + +#### Overall method structure + +A catch-all `try` wraps the entire body of the method: + +```csharp +cachedState = __state; +cachedThis = __capturedThis; // if needed + +try +{ + ... dispatch based on cachedState ... + + initialStateResumeLabel: + if (__disposeMode) { goto topLevelDisposeLabel; } + + __state = RunningState; + + ... method body with lowered `await`, `yield return` and `yield break` ... + + __disposeMode = true; + goto topLevelDisposeLabel; +} +catch (Exception) +{ + __state = FinishedState; + ... clear locals ... + if (__combinedTokens != null) { __combinedTokens.Dispose(); __combinedTokens = null; } + __current = default; + throw; +} + +topLevelDisposeLabel: +__state = FinishedState; +... clear locals ... +if (__combinedTokens != null) { __combinedTokens.Dispose(); __combinedTokens = null; } +__current = default; +return false; +``` + +## Open issues + +Question: AsyncIteratorStateMachineAttribute, or IteratorStateMachineAttribute, or other attribute on kickoff method? diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs index a6e1286e3c02e..39faf84eec07c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; @@ -68,7 +70,9 @@ void appendSourceCore(BoundNode node, int indent, Dictionary ">", + BinaryOperatorKind.GreaterThanOrEqual => ">=", + BinaryOperatorKind.LessThan => "<", + BinaryOperatorKind.LessThanOrEqual => "<=", + BinaryOperatorKind.Equal => "==", + BinaryOperatorKind.NotEqual => "!=", + BinaryOperatorKind.Addition => "+", + BinaryOperatorKind.Subtraction => "-", + _ => binary.OperatorKind.ToString() + }; + appendSource(binary.Left); append(" "); - append(binary.OperatorKind.ToString()); + append(relation); append(" "); appendSource(binary.Right); break; @@ -370,11 +384,7 @@ void appendSourceCore(BoundNode node, int indent, Dictionary 0) + { + appendTypeArguments(typeArguments); + } + + append("("); + appendSourceItems(objectCreation.Arguments); + append(")"); + break; + } + case BoundSwitchDispatch switchDispatch: + { + append("switch dispatch("); + appendSource(switchDispatch.Expression); + appendLine(")"); + appendLine("{"); + incrementIndent(); + + foreach (var (value, label) in switchDispatch.Cases) + { + append("case "); + appendConstantValue(value); + append(" => "); + appendLine(label.ToString()); + } + + append("default => "); + appendLine(switchDispatch.DefaultLabel.ToString()); + decrementIndent(); + appendLine("}"); + break; + } + case BoundPropertyAccess propertyAccess: + { + var receiver = propertyAccess.ReceiverOpt; + if (receiver != null) + { + appendSource(receiver); + append("."); + } + append(propertyAccess.PropertySymbol.Name); + break; + } + case BoundParameter parameter: + { + append(parameter.ParameterSymbol.Name); + break; + } + case BoundBaseReference baseReference: + { + append("base"); + break; + } + case BoundPassByCopy passByCopy: + { + append("passByCopy "); + appendSource(passByCopy.Expression); + break; + } + case BoundAsOperator asOperator: + { + appendSource(asOperator.Operand); + append(" as "); + appendSource(asOperator.TargetType); + break; + } default: appendLine(node.Kind.ToString()); break; @@ -490,6 +569,15 @@ void appendSource(BoundNode? n) } } + void appendSourceItems(ImmutableArray nodes) where T : BoundNode + { + for (int i = 0; i < nodes.Length; i++) + { + if (i != 0) append(", "); + appendSource(nodes[i]); + } + } + void append(string? s) { builder.Append(s); @@ -571,6 +659,24 @@ void appendConstantValue(ConstantValue? constantValueOpt) break; } } + + void appendTypeArguments(ImmutableArray typeArguments) + { + append("<"); + bool first = true; + foreach (var typeArgument in typeArguments) + { + if (!first) + { + append(", "); + } + + first = false; + append(typeArgument.Type.Name); // Note: we should handle types more generally + } + + append(">"); + } } } #endif diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 35ee12be9b602..e1ded3b38551e 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -356,14 +356,17 @@ internal bool IsRuntimeAsyncEnabledIn(Symbol? symbol) Debug.Assert(ReferenceEquals(method.ContainingAssembly, Assembly)); - var methodReturn = method.ReturnType.OriginalDefinition; - if (((InternalSpecialType)methodReturn.ExtendedSpecialType) is not ( - InternalSpecialType.System_Threading_Tasks_Task or - InternalSpecialType.System_Threading_Tasks_Task_T or - InternalSpecialType.System_Threading_Tasks_ValueTask or - InternalSpecialType.System_Threading_Tasks_ValueTask_T)) - { - return false; + if (!method.IsAsyncReturningIAsyncEnumerable(this) && !method.IsAsyncReturningIAsyncEnumerator(this)) + { + var methodReturn = method.ReturnType.OriginalDefinition; + if (((InternalSpecialType)methodReturn.ExtendedSpecialType) is not ( + InternalSpecialType.System_Threading_Tasks_Task or + InternalSpecialType.System_Threading_Tasks_Task_T or + InternalSpecialType.System_Threading_Tasks_ValueTask or + InternalSpecialType.System_Threading_Tasks_ValueTask_T)) + { + return false; + } } return symbol switch @@ -2854,8 +2857,7 @@ internal void RecordImport(ExternAliasDirectiveSyntax syntax) private void RecordImportInternal(CSharpSyntaxNode syntax) { - // Note: the suppression will be unnecessary once LazyInitializer is properly annotated - LazyInitializer.EnsureInitialized(ref _lazyImportInfos)!. + LazyInitializer.EnsureInitialized(ref _lazyImportInfos). TryAdd(new ImportInfo(syntax.SyntaxTree, syntax.Kind(), syntax.Span), default); } diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs index 7a4d807a82fcc..4f19b4a1e0261 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Reflection; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -227,6 +228,9 @@ internal override bool SynthesizesLoweredBoundBody get { return true; } } + public override bool IsAsync => false; + internal override MethodImplAttributes ImplementationAttributes => default; + /// /// Given a SynthesizedExplicitImplementationMethod (effectively a tuple (interface method, implementing method, implementing type)), /// construct a BoundBlock body. Consider the tuple (Interface.Goo, Base.Goo, Derived). The generated method will look like: diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 1e0fa5eb5e7a4..c6d1cb77c8a2b 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -769,26 +769,11 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState) try { - // Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async - IteratorStateMachine iteratorStateMachine; - BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine); - StateMachineTypeSymbol stateMachine = iteratorStateMachine; - - if (!loweredBody.HasErrors) - { - AsyncStateMachine asyncStateMachine = null; - if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method)) - { - loweredBody = RuntimeAsyncRewriter.Rewrite(loweredBody, method, compilationState, diagnosticsThisMethod); - } - else - { - loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine); - } - - Debug.Assert((object)iteratorStateMachine == null || (object)asyncStateMachine == null); - stateMachine = stateMachine ?? asyncStateMachine; - } + BoundStatement loweredBody; + StateMachineTypeSymbol stateMachine; + loweredBody = LowerToStateMachineIfNeeded(methodWithBody.Method, methodWithBody.Body, + compilationState, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, diagnosticsThisMethod, + out stateMachine); SetGlobalErrorIfTrue(diagnosticsThisMethod.HasAnyErrors()); @@ -846,6 +831,84 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState) } } +#nullable enable + private static BoundStatement LowerToStateMachineIfNeeded( + MethodSymbol method, + BoundStatement body, + TypeCompilationState compilationState, + int methodOrdinal, + ArrayBuilder stateMachineStateDebugInfoBuilder, + VariableSlotAllocator variableSlotAllocatorOpt, + BindingDiagnosticBag diagnosticsThisMethod, + out StateMachineTypeSymbol? stateMachine) + { + if (method is SynthesizedStateMachineMoveNextMethod { IsAsync: true }) + { + // "MoveNextAsync" is a runtime-async method, but it has already been lowered + Debug.Assert(method.Name is WellKnownMemberNames.MoveNextAsyncMethodName); + stateMachine = null; + return body; + } + + // Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async + IteratorStateMachine? iteratorStateMachine; + BoundStatement loweredBody = IteratorRewriter.Rewrite(body, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine); + stateMachine = iteratorStateMachine; + + if (loweredBody.HasErrors + || reportMissingYieldInAsyncIterator(loweredBody, method, diagnosticsThisMethod)) + { + return loweredBody; + } + + if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method)) + { + if (method.IsAsyncReturningIAsyncEnumerable(method.DeclaringCompilation) + || method.IsAsyncReturningIAsyncEnumerator(method.DeclaringCompilation)) + { + RuntimeAsyncIteratorStateMachine? runtimeAsyncIteratorStateMachine; + loweredBody = RuntimeAsyncIteratorRewriter.Rewrite(loweredBody, method, + methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, + out runtimeAsyncIteratorStateMachine); + + Debug.Assert(runtimeAsyncIteratorStateMachine is not null); + stateMachine = runtimeAsyncIteratorStateMachine; + } + else + { + loweredBody = RuntimeAsyncRewriter.Rewrite(loweredBody, method, compilationState, diagnosticsThisMethod); + } + } + else + { + AsyncStateMachine? asyncStateMachine; + loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine); + Debug.Assert((object?)iteratorStateMachine == null || (object?)asyncStateMachine == null); + stateMachine = stateMachine ?? asyncStateMachine; + } + + return loweredBody; + + static bool reportMissingYieldInAsyncIterator(BoundStatement body, MethodSymbol method, BindingDiagnosticBag diagnostics) + { + CSharpCompilation compilation = method.DeclaringCompilation; + bool isAsyncEnumerableOrEnumerator = method.IsAsyncReturningIAsyncEnumerable(compilation) || + method.IsAsyncReturningIAsyncEnumerator(compilation); + + if (isAsyncEnumerableOrEnumerator && !method.IsIterator) + { + bool containsAwait = AsyncRewriter.AwaitDetector.ContainsAwait(body); + diagnostics.Add(containsAwait ? ErrorCode.ERR_PossibleAsyncIteratorWithoutYield : ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, + method.GetFirstLocation()); + + return true; + } + + return false; + } + } +#nullable disable + /// /// In some circumstances (e.g. implicit implementation of an interface method by a non-virtual method in a /// base type from another assembly) it is necessary for the compiler to generate explicit implementations for @@ -1585,28 +1648,9 @@ internal static BoundStatement LowerBodyOrInitializer( return bodyWithoutLambdas; } - BoundStatement bodyWithoutIterators = IteratorRewriter.Rewrite(bodyWithoutLambdas, method, methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics, - out IteratorStateMachine iteratorStateMachine); - - if (bodyWithoutIterators.HasErrors) - { - return bodyWithoutIterators; - } - - BoundStatement bodyWithoutAsync; - AsyncStateMachine asyncStateMachine = null; - if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method)) - { - bodyWithoutAsync = RuntimeAsyncRewriter.Rewrite(bodyWithoutIterators, method, compilationState, diagnostics); - } - else - { - bodyWithoutAsync = AsyncRewriter.Rewrite(bodyWithoutIterators, method, methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics, - out asyncStateMachine); - } - - Debug.Assert(iteratorStateMachine is null || asyncStateMachine is null); - stateMachineTypeOpt = (StateMachineTypeSymbol)iteratorStateMachine ?? asyncStateMachine; + BoundStatement bodyWithoutAsync = LowerToStateMachineIfNeeded(method, bodyWithoutLambdas, + compilationState, methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, diagnostics, + out stateMachineTypeOpt); return bodyWithoutAsync; } @@ -1666,8 +1710,7 @@ private static MethodBody GenerateMethodBody( bool isAsyncStateMachine; MethodSymbol kickoffMethod; - if (method is SynthesizedStateMachineMethod stateMachineMethod && - method.Name == WellKnownMemberNames.MoveNextMethodName) + if (method is SynthesizedStateMachineMoveNextMethod stateMachineMethod) { kickoffMethod = stateMachineMethod.StateMachineType.KickoffMethod; Debug.Assert(kickoffMethod != null); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs index 26c94761926bf..184766a8b82a2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs @@ -162,18 +162,23 @@ private BoundExpressionStatement GenerateCompleteOnBuilder() } private void AddDisposeCombinedTokensIfNeeded(ArrayBuilder builder) + { + AddDisposeCombinedTokensIfNeeded(builder, _asyncIteratorInfo.CombinedTokensField, F); + } + + internal static void AddDisposeCombinedTokensIfNeeded(ArrayBuilder builder, FieldSymbol combinedTokensField, SyntheticBoundNodeFactory f) { // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only - if (_asyncIteratorInfo.CombinedTokensField is object) + if (combinedTokensField is object) { - var combinedTokens = F.Field(F.This(), _asyncIteratorInfo.CombinedTokensField); + var combinedTokens = f.Field(f.This(), combinedTokensField); TypeSymbol combinedTokensType = combinedTokens.Type; builder.Add( - F.If(F.ObjectNotEqual(combinedTokens, F.Null(combinedTokensType)), - thenClause: F.Block( - F.ExpressionStatement(F.Call(combinedTokens, F.WellKnownMethod(WellKnownMember.System_Threading_CancellationTokenSource__Dispose))), - F.Assignment(combinedTokens, F.Null(combinedTokensType))))); + f.If(f.ObjectNotEqual(combinedTokens, f.Null(combinedTokensType)), + thenClause: f.Block( + f.ExpressionStatement(f.Call(combinedTokens, f.WellKnownMethod(WellKnownMember.System_Threading_CancellationTokenSource__Dispose))), + f.Assignment(combinedTokens, f.Null(combinedTokensType))))); } } @@ -199,7 +204,7 @@ protected override BoundStatement GenerateSetExceptionCall(LocalSymbol exception return F.Block(builder.ToImmutableAndFree()); } - private BoundStatement GenerateJumpToCurrentDisposalLabel() + private BoundStatement GenerateConditionalJumpToCurrentDisposalLabel() { Debug.Assert(_currentDisposalLabel is object); return F.If( @@ -209,7 +214,7 @@ private BoundStatement GenerateJumpToCurrentDisposalLabel() thenClause: F.Goto(_currentDisposalLabel)); } - private BoundStatement AppendJumpToCurrentDisposalLabel(BoundStatement node) + private BoundStatement AppendConditionalJumpToCurrentDisposalLabel(BoundStatement node) { Debug.Assert(_currentDisposalLabel is object); // Append: @@ -217,10 +222,15 @@ private BoundStatement AppendJumpToCurrentDisposalLabel(BoundStatement node) return F.Block( node, - GenerateJumpToCurrentDisposalLabel()); + GenerateConditionalJumpToCurrentDisposalLabel()); } protected override BoundBinaryOperator ShouldEnterFinallyBlock() + { + return ShouldEnterFinallyBlock(cachedState, F); + } + + internal static BoundBinaryOperator ShouldEnterFinallyBlock(LocalSymbol cachedState, SyntheticBoundNodeFactory f) { // We should skip the finally block when: // - the state is 0 or greater (we're suspending on an `await`) @@ -228,7 +238,7 @@ protected override BoundBinaryOperator ShouldEnterFinallyBlock() // We don't care about state = -2 (method already completed) // So we only want to enter the finally when the state is -1 - return F.IntEqual(F.Local(cachedState), F.Literal(StateMachineState.NotStartedOrRunningState)); + return f.IntEqual(f.Local(cachedState), f.Literal(StateMachineState.NotStartedOrRunningState)); } #region Visitors @@ -254,7 +264,7 @@ protected override BoundStatement VisitBody(BoundStatement body) Debug.Assert(_exprReturnLabel.Equals(_currentDisposalLabel)); return F.Block( F.Label(resumeLabel), // initialStateResumeLabel: - GenerateJumpToCurrentDisposalLabel(), // if (disposeMode) goto _exprReturnLabel; + GenerateConditionalJumpToCurrentDisposalLabel(), // if (disposeMode) goto _exprReturnLabel; GenerateSetBothStates(StateMachineState.NotStartedOrRunningState), // this.state = cachedState = -1; rewrittenBody); } @@ -304,7 +314,7 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no Debug.Assert(_currentDisposalLabel is object); // no yield return allowed inside a finally blockBuilder.Add( // if (disposeMode) goto currentDisposalLabel; - GenerateJumpToCurrentDisposalLabel()); + GenerateConditionalJumpToCurrentDisposalLabel()); blockBuilder.Add( F.HiddenSequencePoint()); @@ -322,10 +332,10 @@ public override BoundNode VisitYieldBreakStatement(BoundYieldBreakStatement node Debug.Assert(_currentDisposalLabel is object); // no yield break allowed inside a finally return F.Block( - // disposeMode = true; - SetDisposeMode(true), - // goto currentDisposalLabel; - F.Goto(_currentDisposalLabel)); + // disposeMode = true; + F.Assignment(F.InstanceField(_asyncIteratorInfo.DisposeModeField), F.Literal(true)), + // goto currentDisposalLabel; + F.Goto(_currentDisposalLabel)); } protected override BoundStatement MakeAwaitPreamble() @@ -344,11 +354,6 @@ protected override BoundStatement MakeAwaitPreamble() return null; } - private BoundExpressionStatement SetDisposeMode(bool value) - { - return F.Assignment(F.InstanceField(_asyncIteratorInfo.DisposeModeField), F.Literal(value)); - } - /// /// An async-iterator state machine has a flag indicating "dispose mode". /// We enter dispose mode by calling DisposeAsync() when the state machine is paused on a `yield return`, @@ -394,7 +399,7 @@ public override BoundNode VisitTryStatement(BoundTryStatement node) { // Append: // if (disposeMode) goto currentDisposalLabel; - result = AppendJumpToCurrentDisposalLabel(result); + result = AppendConditionalJumpToCurrentDisposalLabel(result); } // Note: we add this jump to extracted `finally` blocks as well, using `VisitExtractedFinallyBlock` below @@ -425,7 +430,7 @@ public override BoundNode VisitExtractedFinallyBlock(BoundExtractedFinallyBlock if (_currentDisposalLabel is object) { - result = AppendJumpToCurrentDisposalLabel(result); + result = AppendConditionalJumpToCurrentDisposalLabel(result); } return result; diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index 2fbe2b81eacde..d4fdaeea26d73 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -251,7 +251,7 @@ BoundCatchBlock generateExceptionHandling(LocalSymbol exceptionLocal, ImmutableA } } - protected virtual BoundStatement GenerateTopLevelTry(BoundBlock tryBlock, ImmutableArray catchBlocks) + private BoundStatement GenerateTopLevelTry(BoundBlock tryBlock, ImmutableArray catchBlocks) => F.Try(tryBlock, catchBlocks); protected virtual BoundStatement GenerateSetResultCall() diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index c305d9afe130f..bd207ca9c6693 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -20,7 +20,7 @@ internal partial class AsyncRewriter : StateMachineRewriter /// /// This rewriter rewrites an async-iterator method. See async-streams.md for design overview. /// - private sealed class AsyncIteratorRewriter : AsyncRewriter + internal sealed class AsyncIteratorRewriter : AsyncRewriter { private FieldSymbol _promiseOfValueOrEndField; // this struct implements the IValueTaskSource logic private FieldSymbol _currentField; // stores the current/yielded value @@ -169,7 +169,7 @@ protected override void GenerateConstructor() bodyBuilder.Add(F.Assignment(F.InstanceField(stateField), F.Parameter(F.CurrentFunction.Parameters[0]))); // this.state = state; var managedThreadId = MakeCurrentThreadId(); - if (managedThreadId != null && (object)initialThreadIdField != null) + if ((object)initialThreadIdField != null) { // this.initialThreadId = {managedThreadId}; bodyBuilder.Add(F.Assignment(F.InstanceField(initialThreadIdField), managedThreadId)); @@ -209,11 +209,19 @@ protected override void InitializeStateMachine(ArrayBuilder body protected override BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) { + return InitializeParameterField(getEnumeratorMethod, parameter, resultParameter, parameterProxy, _combinedTokensField, F); + } + + internal static BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy, + FieldSymbol combinedTokensField, SyntheticBoundNodeFactory f) + { + Debug.Assert(parameterProxy.Type is not null); + BoundStatement result; - if (_combinedTokensField is object && + if (combinedTokensField is object && !parameter.IsExtensionParameterImplementation() && parameter.HasEnumeratorCancellationAttribute && - parameter.Type.Equals(F.Compilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken), TypeCompareKind.ConsiderEverything)) + parameter.Type.Equals(f.Compilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken), TypeCompareKind.ConsiderEverything)) { // For a parameter of type CancellationToken with [EnumeratorCancellation] // if (this.parameterProxy.Equals(default)) @@ -230,31 +238,31 @@ protected override BoundStatement InitializeParameterField(MethodSymbol getEnume // result.parameter = combinedTokens.Token; // } - BoundParameter tokenParameter = F.Parameter(getEnumeratorMethod.Parameters[0]); - BoundFieldAccess combinedTokens = F.Field(F.This(), _combinedTokensField); - result = F.If( + BoundParameter tokenParameter = f.Parameter(getEnumeratorMethod.Parameters[0]); + BoundFieldAccess combinedTokens = f.Field(f.This(), combinedTokensField); + result = f.If( // if (this.parameterProxy.Equals(default)) - F.Call(parameterProxy, WellKnownMember.System_Threading_CancellationToken__Equals, F.Default(parameterProxy.Type)), + f.Call(parameterProxy, WellKnownMember.System_Threading_CancellationToken__Equals, f.Default(parameterProxy.Type)), // result.parameter = token; - thenClause: F.Assignment(resultParameter, tokenParameter), - elseClauseOpt: F.If( + thenClause: f.Assignment(resultParameter, tokenParameter), + elseClauseOpt: f.If( // else if (token.Equals(this.parameterProxy) || token.Equals(default)) - F.LogicalOr( - F.Call(tokenParameter, WellKnownMember.System_Threading_CancellationToken__Equals, parameterProxy), - F.Call(tokenParameter, WellKnownMember.System_Threading_CancellationToken__Equals, F.Default(tokenParameter.Type))), + f.LogicalOr( + f.Call(tokenParameter, WellKnownMember.System_Threading_CancellationToken__Equals, parameterProxy), + f.Call(tokenParameter, WellKnownMember.System_Threading_CancellationToken__Equals, f.Default(tokenParameter.Type))), // result.parameter = this.parameterProxy; - thenClause: F.Assignment(resultParameter, parameterProxy), - elseClauseOpt: F.Block( + thenClause: f.Assignment(resultParameter, parameterProxy), + elseClauseOpt: f.Block( // result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); - F.Assignment(combinedTokens, F.StaticCall(WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, parameterProxy, tokenParameter)), + f.Assignment(combinedTokens, f.StaticCall(WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, parameterProxy, tokenParameter)), // result.parameter = result.combinedTokens.Token; - F.Assignment(resultParameter, F.Property(combinedTokens, WellKnownMember.System_Threading_CancellationTokenSource__Token))))); + f.Assignment(resultParameter, f.Property(combinedTokens, WellKnownMember.System_Threading_CancellationTokenSource__Token))))); } else { // For parameters that don't have [EnumeratorCancellation], initialize their parameter fields // result.parameter = this.parameterProxy; - result = F.Assignment(resultParameter, parameterProxy); + result = f.Assignment(resultParameter, parameterProxy); } return result; @@ -472,20 +480,26 @@ private void GenerateIAsyncDisposable_DisposeAsync() /// private void GenerateIAsyncEnumeratorImplementation_Current() { + GenerateIAsyncEnumeratorImplementation_Current(_currentField, this, F); + } + + internal static void GenerateIAsyncEnumeratorImplementation_Current(FieldSymbol currentField, StateMachineRewriter stateMachineRewriter, SyntheticBoundNodeFactory f) + { + Debug.Assert(currentField is not null); // Produce the implementation for `T Current { get; }`: // return _current; NamedTypeSymbol IAsyncEnumeratorOfElementType = - F.WellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T) - .Construct(_currentField.Type); + f.WellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T) + .Construct(currentField.Type); MethodSymbol IAsyncEnumerableOfElementType_get_Current = - F.WellKnownMethod(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__get_Current) + f.WellKnownMethod(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__get_Current) .AsMember(IAsyncEnumeratorOfElementType); - OpenPropertyImplementation(IAsyncEnumerableOfElementType_get_Current); + stateMachineRewriter.OpenPropertyImplementation(IAsyncEnumerableOfElementType_get_Current); - F.CloseMethod(F.Block(F.Return(F.InstanceField(_currentField)))); + f.CloseMethod(f.Block(f.Return(f.InstanceField(currentField)))); } private void GenerateIValueTaskSourceBoolImplementation_GetResult() diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs index 1d911bc7155d3..a489b29cad568 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs @@ -56,19 +56,6 @@ internal static BoundStatement Rewrite( return bodyWithAwaitLifted; } - CSharpCompilation compilation = method.DeclaringCompilation; - bool isAsyncEnumerableOrEnumerator = method.IsAsyncReturningIAsyncEnumerable(compilation) || - method.IsAsyncReturningIAsyncEnumerator(compilation); - if (isAsyncEnumerableOrEnumerator && !method.IsIterator) - { - bool containsAwait = AwaitDetector.ContainsAwait(bodyWithAwaitLifted); - diagnostics.Add(containsAwait ? ErrorCode.ERR_PossibleAsyncIteratorWithoutYield : ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, - method.GetFirstLocation()); - - stateMachineType = null; - return bodyWithAwaitLifted; - } - // The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class. // For async-iterators, we also need to generate a class. var typeKind = (compilationState.Compilation.Options.EnableEditAndContinue || method.IsIterator) ? TypeKind.Class : TypeKind.Struct; @@ -76,6 +63,10 @@ internal static BoundStatement Rewrite( stateMachineType = new AsyncStateMachine(slotAllocatorOpt, compilationState, method, methodOrdinal, typeKind); compilationState.ModuleBuilderOpt.CompilationState.SetStateMachineType(method, stateMachineType); + CSharpCompilation compilation = method.DeclaringCompilation; + bool isAsyncEnumerableOrEnumerator = method.IsAsyncReturningIAsyncEnumerable(compilation) || + method.IsAsyncReturningIAsyncEnumerator(compilation); + AsyncRewriter rewriter = isAsyncEnumerableOrEnumerator ? new AsyncIteratorRewriter(bodyWithAwaitLifted, method, methodOrdinal, stateMachineType, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, compilationState, diagnostics) : new AsyncRewriter(bodyWithAwaitLifted, method, methodOrdinal, stateMachineType, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, compilationState, diagnostics); @@ -127,11 +118,6 @@ protected virtual void VerifyPresenceOfRequiredAPIs(BindingDiagnosticBag bag) EnsureWellKnownMember(WellKnownMember.System_Runtime_CompilerServices_IAsyncStateMachine_SetStateMachine, bag); } - private Symbol EnsureWellKnownMember(WellKnownMember member, BindingDiagnosticBag bag) - { - return Binder.GetWellKnownTypeMember(F.Compilation, member, bag, body.Syntax.Location); - } - protected override bool PreserveInitialParameterValuesAndThreadId => false; @@ -299,7 +285,7 @@ protected virtual void GenerateMoveNext(SynthesizedImplementationMethod moveNext /// /// Note: do not use a static/singleton instance of this type, as it holds state. /// - private class AwaitDetector : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator + internal class AwaitDetector : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator { private bool _sawAwait; diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs index 364460a8dca0e..942bedb810051 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs @@ -70,6 +70,28 @@ public static BoundStatement Rewrite( } } + public static BoundStatement RewriteWithoutHoisting( + BoundStatement node, + MethodSymbol method, + TypeCompilationState compilationState, + BindingDiagnosticBag diagnostics) + { + if (!method.IsAsync) + { + return node; + } + + OrderedSet variablesToHoist = new OrderedSet(); + var hoistedLocals = ArrayBuilder.GetInstance(); + var factory = new SyntheticBoundNodeFactory(method, node.Syntax, compilationState, diagnostics); + var rewriter = new RuntimeAsyncRewriter(factory, variablesToHoist, hoistedLocals); + var result = (BoundStatement)rewriter.Visit(node); + + hoistedLocals.Free(); + + return SpillSequenceSpiller.Rewrite(result, method, compilationState, diagnostics); + } + private readonly SyntheticBoundNodeFactory _factory; private readonly Dictionary _placeholderMap; private readonly IReadOnlySet _variablesToHoist; @@ -325,4 +347,7 @@ public override BoundNode VisitLocal(BoundLocal node) return node.Update(expr); } + + public override BoundNode? VisitStateMachineInstanceId(BoundStateMachineInstanceId node) + => throw ExceptionUtilities.Unreachable(); } diff --git a/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs index 3e4d78cbde91b..f911532ee5777 100644 --- a/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/BoundTreeToDifferentEnclosingContextRewriter.cs @@ -33,6 +33,11 @@ internal abstract class BoundTreeToDifferentEnclosingContextRewriter : BoundTree protected abstract MethodSymbol CurrentMethod { get; } + /// + /// Tells the rewriter whether it must create new `LocalSymbol` instances whose `ContainingSymbol` matches the new method context + /// even when a local’s type did not change. + /// That's the correct thing to do, but in some scenarios we can skip it to avoid extra allocations. + /// protected abstract bool EnforceAccurateContainerForLocals { get; } public override BoundNode DefaultVisit(BoundNode node) diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs index 5ce6f07042455..8a09a0007951f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs @@ -35,7 +35,7 @@ internal SynthesizedClosureMethod( : base(containingType, originalMethod, blockSyntax, - originalMethod.DeclaringSyntaxReferences[0].GetLocation(), + originalMethod.GetFirstLocationOrNone(), originalMethod is { MethodKind: MethodKind.LocalFunction } ? MakeName(topLevelMethod.Name, originalMethod.Name, topLevelMethodId, closureKind, lambdaId) : MakeName(topLevelMethod.Name, topLevelMethodId, closureKind, lambdaId), diff --git a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/LocalStateTracingInstrumenter.cs b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/LocalStateTracingInstrumenter.cs index 73358323880e0..69e3d649cc2bc 100644 --- a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/LocalStateTracingInstrumenter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/LocalStateTracingInstrumenter.cs @@ -298,7 +298,7 @@ public override void InstrumentBlock(BoundBlock original, LocalRewriter rewriter Debug.Assert(_factory.TopLevelMethod is not null); Debug.Assert(_factory.CurrentFunction is not null); - var isStateMachine = _factory.CurrentFunction.IsAsync || _factory.CurrentFunction.IsIterator; + var isStateMachine = getIsStateMachine(_factory.CurrentFunction); var prologueBuilder = ArrayBuilder.GetInstance(_factory.CurrentFunction.ParameterCount); @@ -355,6 +355,21 @@ public override void InstrumentBlock(BoundBlock original, LocalRewriter rewriter instrumentation = _factory.CombineInstrumentation(instrumentation, _scope.ContextVariable, instrumentationPrologue, instrumentationEpilogue); _scope.Close(isMethodBody); + + static bool getIsStateMachine(MethodSymbol method) + { + if (method.IsIterator) + { + return true; + } + + if (method.IsAsync) + { + return !method.DeclaringCompilation.IsRuntimeAsyncEnabledIn(method); + } + + return false; + } } public override BoundExpression InstrumentUserDefinedLocalAssignment(BoundAssignmentOperator original) diff --git a/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.MoveNextAsyncRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.MoveNextAsyncRewriter.cs new file mode 100644 index 0000000000000..711f6b012c365 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.MoveNextAsyncRewriter.cs @@ -0,0 +1,462 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp; + +internal sealed partial class RuntimeAsyncIteratorRewriter +{ + /// + /// Generates the MoveNextAsync method for an async-iterator state machine with runtime-async codegen. + /// This means that the generated MoveNextAsync method is itself async and its body contains awaits which will be lowered later on. + /// But the `yield return` and `yield break` statements are lowered away here. + /// The entrypoint is . + /// We use the same state values as for regular async-iterators, minus the states for `await` suspensions. + /// + internal sealed class MoveNextAsyncRewriter : MethodToStateMachineRewriter + { + private readonly FieldSymbol _currentField; + private readonly FieldSymbol _disposeModeField; // whether the state machine is in dispose mode (ie. skipping all logic except that in `catch` and `finally`, yielding no new elements) + private readonly FieldSymbol? _combinedTokensField; // CancellationTokenSource for combining tokens + + /// + /// Where should we jump to to continue the execution of disposal path. + /// + /// Initially, this is the method's top-level disposal label, indicating the end of the method. + /// Inside a `try` or `catch` with a `finally`, we'll use the label directly preceding the `finally`. + /// Inside a `try` or `catch` with an extracted `finally`, we will use the label preceding the extracted `finally`. + /// Inside a `finally`, we'll have no/null label (disposal continues without a jump). + /// See and + /// + private LabelSymbol? _currentDisposalLabel; + + /// + /// States for `yield return` are decreasing from . + /// + private readonly ResumableStateMachineStateAllocator _iteratorStateAllocator; + + internal MoveNextAsyncRewriter( + SyntheticBoundNodeFactory F, + MethodSymbol originalMethod, + FieldSymbol state, + FieldSymbol? instanceIdField, + IReadOnlySet hoistedVariables, + IReadOnlyDictionary nonReusableLocalProxies, + ImmutableArray nonReusableFieldsForCleanup, + SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, + ArrayBuilder stateMachineStateDebugInfoBuilder, + VariableSlotAllocator? slotAllocatorOpt, + int nextFreeHoistedLocalSlot, + BindingDiagnosticBag diagnostics, + FieldSymbol currentField, + FieldSymbol disposeModeField, + FieldSymbol? combinedTokensField) + : base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, + synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) + { + _currentField = currentField; + _disposeModeField = disposeModeField; + _combinedTokensField = combinedTokensField; + + _currentDisposalLabel = F.GenerateLabel("topLevelDisposeLabel"); + _iteratorStateAllocator = new ResumableStateMachineStateAllocator( + slotAllocatorOpt, + firstState: StateMachineState.FirstResumableAsyncIteratorState, + increasing: false); + } + + /// + [SuppressMessage("Style", """VSTHRD200:Use "Async" suffix for async methods""", Justification = "Standard naming convention for generating 'MoveNextAsync'")] + internal BoundStatement GenerateMoveNextAsync(BoundStatement body) + { + var rewrittenBody = visitBody(body); + + var blockBuilder = ArrayBuilder.GetInstance(); + + // { + blockBuilder.Add(F.HiddenSequencePoint()); + + // cachedState = this.state + blockBuilder.Add(F.Assignment(F.Local(cachedState), F.Field(F.This(), stateField))); + + // cachedThis = capturedThis; // if needed + blockBuilder.Add(CacheThisIfNeeded()); + + // try + // { + // switch (cachedState) ... dispatch ... + // ... rewritten body, which ends with `disposeMode = true; goto topLevelDisposeLabel;` ... + // } + // catch (Exception ex) + // { + // _state = finishedState; + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } + // _current = default; + // ... clear locals ... + // throw; + // } + var exceptionLocal = F.SynthesizedLocal(F.WellKnownType(WellKnownType.System_Exception)); + blockBuilder.Add( + GenerateTopLevelTry( + F.Block( + // switch (cachedState) ... dispatch ... + F.HiddenSequencePoint(), + Dispatch(isOutermost: true), + // ... rewritten body ... + rewrittenBody + ), + F.CatchBlocks(generateExceptionHandling(exceptionLocal))) + ); + + // TopLevelDisposeLabel: + Debug.Assert(_currentDisposalLabel is not null); + blockBuilder.Add(F.Label(_currentDisposalLabel)); + + var block = (BlockSyntax)body.Syntax; + + // this.state = finishedState + var stateDone = F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState)); + blockBuilder.Add(F.SequencePointWithSpan(block, block.CloseBraceToken.Span, stateDone)); + blockBuilder.Add(F.HiddenSequencePoint()); + + // We need to clean nested hoisted local variables too (not just top-level ones) + // as they are not cleaned when exiting a block if we exit using a `yield break` + // or if the caller interrupts the enumeration after we reached a `yield return`. + // So we clean both top-level and nested hoisted local variables + + // clear managed hoisted locals + blockBuilder.Add(GenerateAllHoistedLocalsCleanup()); + + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } + addDisposeCombinedTokensIfNeeded(blockBuilder); + + // _current = default; + blockBuilder.Add(GenerateClearCurrent()); + + // Note: we're producing a runtime-async body, so return a bool instead of ValueTask + // return false; + var resultFalse = new BoundReturnStatement(F.Syntax, RefKind.None, F.Literal(false), @checked: false) { WasCompilerGenerated = true }; + blockBuilder.Add(resultFalse); + + var locals = ArrayBuilder.GetInstance(); + locals.Add(cachedState); + if (cachedThis is not null) locals.Add(cachedThis); + + var newBody = F.Block(locals.ToImmutableAndFree(), blockBuilder.ToImmutableAndFree()); + return F.Instrument(newBody, instrumentation); + + BoundCatchBlock generateExceptionHandling(LocalSymbol exceptionLocal) + { + // catch (Exception ex) + // { + // _state = finishedState; + // + // for each hoisted local: + // <>x__y = default + // + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } + // + // _current = default; + // throw; + // } + + var blockBuilder = ArrayBuilder.GetInstance(); + + // _state = finishedState; + BoundStatement assignFinishedState = + F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); + blockBuilder.Add(assignFinishedState); + + blockBuilder.Add(GenerateAllHoistedLocalsCleanup()); + + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } + addDisposeCombinedTokensIfNeeded(blockBuilder); + + // _current = default; + blockBuilder.Add(GenerateClearCurrent()); + + // throw null; + blockBuilder.Add(F.Throw(null)); + + return new BoundCatchBlock( + F.Syntax, + [exceptionLocal], + F.Local(exceptionLocal), + exceptionLocal.Type, + exceptionFilterPrologueOpt: null, + exceptionFilterOpt: null, + body: F.Block(blockBuilder.ToImmutableAndFree()), + isSynthesizedAsyncCatchAll: true); + } + + void addDisposeCombinedTokensIfNeeded(ArrayBuilder builder) + { + AsyncIteratorMethodToStateMachineRewriter.AddDisposeCombinedTokensIfNeeded(builder, _combinedTokensField, F); + } + + // Lower the body, adding an entry state (-3) at the start, + // so that we can differentiate an async-iterator that was never moved forward with MoveNextAsync() + // from one that is running (-1). + // Then we can guard against some bad usages of DisposeAsync. + BoundStatement visitBody(BoundStatement body) + { + // Produce: + // initialStateResumeLabel: + // if (disposeMode) goto _exprReturnLabel; + // this.state = cachedState = -1; + // ... rewritten body, which ends with `disposeMode = true; goto topLevelDisposeLabel;` ... + + AddState(StateMachineState.InitialAsyncIteratorState, out GeneratedLabelSymbol resumeLabel); + + var rewrittenBody = (BoundStatement)Visit(body); + + return F.Block( + F.Label(resumeLabel), // initialStateResumeLabel: + GenerateConditionalJumpToCurrentDisposalLabel(), // if (disposeMode) goto _exprReturnLabel; + GenerateSetBothStates(StateMachineState.NotStartedOrRunningState), // this.state = cachedState = -1; + rewrittenBody); + } + } + + internal BoundStatement GenerateTopLevelTry(BoundBlock tryBlock, ImmutableArray catchBlocks) + => F.Try(tryBlock, catchBlocks); + + private BoundExpressionStatement GenerateClearCurrent() + { + Debug.Assert(_currentField is not null); + + // _current = default; + return F.Assignment(F.InstanceField(_currentField), F.Default(_currentField.Type)); + } + + private BoundStatement GenerateConditionalJumpToCurrentDisposalLabel() + { + Debug.Assert(_currentDisposalLabel is object); + return F.If( + // if (disposeMode) + F.InstanceField(_disposeModeField), + // goto currentDisposalLabel; + thenClause: F.Goto(_currentDisposalLabel)); + } + + private BoundStatement AppendConditionalJumpToCurrentDisposalLabel(BoundStatement node) + { + Debug.Assert(_currentDisposalLabel is object); + // Append: + // if (disposeMode) goto currentDisposalLabel; + + return F.Block( + node, + GenerateConditionalJumpToCurrentDisposalLabel()); + } + + private BoundExpressionStatement SetDisposeMode() + { + return F.Assignment(F.InstanceField(_disposeModeField), F.Literal(true)); + } + + protected override BoundStatement GenerateReturn(bool finished) + { + throw ExceptionUtilities.Unreachable(); + } + + protected override BoundBinaryOperator ShouldEnterFinallyBlock() + { + return AsyncIteratorMethodToStateMachineRewriter.ShouldEnterFinallyBlock(cachedState, F); + } + + protected override StateMachineState FirstIncreasingResumableState + => StateMachineState.FirstResumableIteratorState; + + protected override HotReloadExceptionCode EncMissingStateErrorCode + => HotReloadExceptionCode.CannotResumeSuspendedAsyncMethod; // PROTOTYPE revisit for EnC support + + /// + /// Containing Symbols are not checked after this step - for performance reasons we can allow inaccurate locals + /// + protected override bool EnforceAccurateContainerForLocals => false; + + #region Visitor methods + public override BoundNode? VisitYieldReturnStatement(BoundYieldReturnStatement node) + { + // Produce: + // _current = expression; + // _state = cachedState = ; + // return true; + // : ; + // + // _state = cachedState = NotStartedStateMachine; + // if (disposeMode) goto currentDisposalLabel; + + AddResumableState(_iteratorStateAllocator, node.Syntax, awaitId: default, + out StateMachineState nextState, out GeneratedLabelSymbol nextStateResumeLabel); + + var rewrittenExpression = (BoundExpression)Visit(node.Expression); + var blockBuilder = ArrayBuilder.GetInstance(); + + // _current = expression; + blockBuilder.Add(F.Assignment(F.InstanceField(_currentField), rewrittenExpression)); + + // this.state = cachedState = stateNumber; + // Note: we set cachedState too as it used to skip any finally blocks + blockBuilder.Add(GenerateSetBothStates(nextState)); + + // Note: we're producing a runtime-async method, so return a bool instead of ValueTask + // return true; + var resultTrue = new BoundReturnStatement(F.Syntax, RefKind.None, F.Literal(true), @checked: false) { WasCompilerGenerated = true }; + blockBuilder.Add(resultTrue); + + // : ; + blockBuilder.Add(F.Label(nextStateResumeLabel)); + blockBuilder.Add(F.HiddenSequencePoint()); + + // this.state = cachedState = NotStartedStateMachine + blockBuilder.Add(GenerateSetBothStates(StateMachineState.NotStartedOrRunningState)); + + // if (disposeMode) goto currentDisposalLabel; + Debug.Assert(_currentDisposalLabel is object); // no yield return allowed inside a finally + blockBuilder.Add(GenerateConditionalJumpToCurrentDisposalLabel()); + + blockBuilder.Add(F.HiddenSequencePoint()); + + return F.Block(blockBuilder.ToImmutableAndFree()); + } + + public override BoundNode? VisitYieldBreakStatement(BoundYieldBreakStatement node) + { + var blockBuilder = ArrayBuilder.GetInstance(); + + // disposeMode = true; + blockBuilder.Add(SetDisposeMode()); + + // goto currentDisposalLabel; + Debug.Assert(_currentDisposalLabel is object); + blockBuilder.Add(F.Goto(_currentDisposalLabel)); + + return F.Block(blockBuilder.ToImmutableAndFree()); + } + + public override BoundNode? VisitReturnStatement(BoundReturnStatement node) + { + Debug.Assert(_currentDisposalLabel is not null); + return F.Block(SetDisposeMode(), F.Goto(_currentDisposalLabel)); + } + + /// + public override BoundNode VisitTryStatement(BoundTryStatement node) + { + var savedDisposalLabel = _currentDisposalLabel; + LabelSymbol? afterFinally = null; + if (node.FinallyBlockOpt is object) + { + afterFinally = F.GenerateLabel("afterFinally"); + _currentDisposalLabel = afterFinally; + } + else if (node.FinallyLabelOpt is object) + { + _currentDisposalLabel = node.FinallyLabelOpt; + } + + var result = (BoundStatement)base.VisitTryStatement(node); + + if (afterFinally != null) + { + // Append a label immediately after the try-catch-finally statement, + // which disposal within `try`/`catch` blocks jumps to in order to pass control flow to the `finally` block implicitly: + // tryEnd: + result = F.Block(result, F.Label(afterFinally)); + } + + _currentDisposalLabel = savedDisposalLabel; + + if (node.FinallyBlockOpt != null && _currentDisposalLabel is object) + { + // Append: + // if (disposeMode) goto currentDisposalLabel; + result = AppendConditionalJumpToCurrentDisposalLabel(result); + } + + // Note: we add this jump to extracted `finally` blocks as well, using `VisitExtractedFinallyBlock` below + + return result; + } + + protected override BoundBlock VisitFinally(BoundBlock finallyBlock) + { + // within a finally, continuing disposal doesn't require any jump + var savedDisposalLabel = _currentDisposalLabel; + _currentDisposalLabel = null; + var result = base.VisitFinally(finallyBlock); + _currentDisposalLabel = savedDisposalLabel; + return result; + } + + /// + public override BoundNode VisitExtractedFinallyBlock(BoundExtractedFinallyBlock extractedFinally) + { + // Remove the wrapping and optionally append: + // if (disposeMode) goto currentDisposalLabel; + + BoundStatement result = VisitFinally(extractedFinally.FinallyBlock); + + if (_currentDisposalLabel is object) + { + result = AppendConditionalJumpToCurrentDisposalLabel(result); + } + + return result; + } + + public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node) + { + // We need to clear _current before awaiting when it is a managed type, + // ie. so we may release some references. + BoundExpression? preamble = makeAwaitPreamble(); + var rewrittenAwait = (BoundExpression?)base.VisitAwaitExpression(node); + Debug.Assert(rewrittenAwait is not null); + + if (preamble is null) + { + return rewrittenAwait; + } + + return F.Sequence([], [preamble], rewrittenAwait); + + BoundExpression? makeAwaitPreamble() + { + var useSiteInfo = new CompoundUseSiteInfo(F.Diagnostics, F.Compilation.Assembly); + var field = _currentField; + bool isManaged = field.Type.IsManagedType(ref useSiteInfo); + F.Diagnostics.Add(field.GetFirstLocationOrNone(), useSiteInfo); + + if (isManaged) + { + // _current = default; + return F.AssignmentExpression(F.InstanceField(_currentField), F.Default(_currentField.Type)); + } + + return null; + } + } + + public override BoundNode? VisitLambda(BoundLambda node) + => throw ExceptionUtilities.Unreachable(); + + public override BoundNode? VisitUnboundLambda(UnboundLambda node) + => throw ExceptionUtilities.Unreachable(); + + public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node) + => throw ExceptionUtilities.Unreachable(); + #endregion + } +} diff --git a/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.cs new file mode 100644 index 0000000000000..4d6e164661986 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorRewriter.cs @@ -0,0 +1,428 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.CSharp; + +/// +/// The entrypoint for this rewriter is . +/// It delegates to which drives the process relying on various overrides +/// to produce a nested type with: +/// - fields: +/// - for `current`, `disposeMode`, `initialThreadId` fields +/// - proxies for `this`, parameters and hoisted locals +/// - members including interface implementations: +/// - +/// - (for enumerable case) +/// - which relies heavily on +/// - +/// - +/// before returning the body for the kickoff method (). +/// +/// The state machines uses the same states as regular async-iterator methods, with the exception of `await` states +/// which are no longer needed (handled by the runtime directly). +/// +internal sealed partial class RuntimeAsyncIteratorRewriter : StateMachineRewriter +{ + // true if the iterator implements IAsyncEnumerable, + // false if it implements IAsyncEnumerator + private readonly bool _isEnumerable; + + private FieldSymbol? _currentField; // stores the current/yielded value + private FieldSymbol? _disposeModeField; // whether the state machine is in dispose mode (ie. skipping all logic except that in `catch` and `finally`, yielding no new elements) + private FieldSymbol? _combinedTokensField; // CancellationTokenSource for combining tokens (only set for enumerable async-iterators with [EnumeratorCancellation] parameter) + + public RuntimeAsyncIteratorRewriter( + BoundStatement body, + MethodSymbol method, + SynthesizedContainer stateMachineType, + ArrayBuilder stateMachineStateDebugInfoBuilder, + VariableSlotAllocator? slotAllocatorOpt, + TypeCompilationState compilationState, + BindingDiagnosticBag diagnostics) + : base(body, method, stateMachineType, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, compilationState, diagnostics) + { + Debug.Assert(method.IteratorElementTypeWithAnnotations.Type is not null); + + _isEnumerable = method.IsAsyncReturningIAsyncEnumerable(method.DeclaringCompilation); + Debug.Assert(_isEnumerable != method.IsAsyncReturningIAsyncEnumerator(method.DeclaringCompilation)); + } + + public static BoundStatement Rewrite( + BoundStatement bodyWithAwaitLifted, + MethodSymbol method, + int methodOrdinal, + ArrayBuilder stateMachineStateDebugInfoBuilder, + VariableSlotAllocator? slotAllocatorOpt, + TypeCompilationState compilationState, + BindingDiagnosticBag diagnostics, + out RuntimeAsyncIteratorStateMachine? stateMachineType) + { + Debug.Assert(compilationState.ModuleBuilderOpt != null); + Debug.Assert(method.IsAsyncReturningIAsyncEnumerable(method.DeclaringCompilation) + || method.IsAsyncReturningIAsyncEnumerator(method.DeclaringCompilation)); + + TypeWithAnnotations elementType = method.IteratorElementTypeWithAnnotations; + if (elementType.IsDefault) + { + stateMachineType = null; + return bodyWithAwaitLifted; + } + + bool isEnumerable = method.IsAsyncReturningIAsyncEnumerable(method.DeclaringCompilation); + stateMachineType = new RuntimeAsyncIteratorStateMachine(slotAllocatorOpt, compilationState, method, methodOrdinal, isEnumerable: isEnumerable, elementType); + compilationState.ModuleBuilderOpt.CompilationState.SetStateMachineType(method, stateMachineType); + + var rewriter = new RuntimeAsyncIteratorRewriter(bodyWithAwaitLifted, method, stateMachineType, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, compilationState, diagnostics); + + if (!rewriter.VerifyPresenceOfRequiredAPIs()) + { + return bodyWithAwaitLifted; + } + + try + { + return rewriter.Rewrite(); + } + catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) + { + diagnostics.Add(ex.Diagnostic); + return new BoundBadStatement(bodyWithAwaitLifted.Syntax, [bodyWithAwaitLifted], hasErrors: true); + } + } + + // For async-enumerables, it is possible for an instance to be re-used as enumerator for multiple iterations + // so we need to preserve initial parameter values and thread ID across iterations. + protected override bool PreserveInitialParameterValuesAndThreadId + => _isEnumerable; + + protected override void GenerateControlFields() + { + // the fields are initialized from async method, so they need to be public: + stateField = F.StateMachineField(F.SpecialType(SpecialType.System_Int32), GeneratedNames.MakeStateMachineStateFieldName(), isPublic: true); + + // the element type may contain method type parameters, which are now alpha-renamed into type parameters of the generated class + TypeSymbol elementType = ((RuntimeAsyncIteratorStateMachine)stateMachineType).ElementType.Type; + + // Add a field: T current + _currentField = F.StateMachineField(elementType, GeneratedNames.MakeIteratorCurrentFieldName()); + + // Add a field: bool disposeMode + NamedTypeSymbol boolType = F.SpecialType(SpecialType.System_Boolean); + _disposeModeField = F.StateMachineField(boolType, GeneratedNames.MakeDisposeModeFieldName()); + + if (_isEnumerable && this.method.Parameters.Any(static p => !p.IsExtensionParameterImplementation() && p.HasEnumeratorCancellationAttribute)) + { + // Add a field: CancellationTokenSource combinedTokens + _combinedTokensField = F.StateMachineField( + F.WellKnownType(WellKnownType.System_Threading_CancellationTokenSource), + GeneratedNames.MakeAsyncIteratorCombinedTokensFieldName()); + } + + Debug.Assert(F.ModuleBuilderOpt is not null); + var instrumentations = F.ModuleBuilderOpt.GetMethodBodyInstrumentations(method); + if (instrumentations.Kinds.Contains(InstrumentationKindExtensions.LocalStateTracing)) + { + instanceIdField = F.StateMachineField(F.SpecialType(SpecialType.System_UInt64), GeneratedNames.MakeStateMachineStateIdFieldName(), isPublic: true); + } + } + + /// + /// Returns true if all types and members we need are present and good + /// + private bool VerifyPresenceOfRequiredAPIs() + { + var bag = BindingDiagnosticBag.GetInstance(withDiagnostics: true, diagnostics.AccumulatesDependencies); + + verifyPresenceOfRequiredAPIs(bag); + + bool hasErrors = bag.HasAnyErrors(); + if (!hasErrors) + { + diagnostics.AddDependencies(bag); + } + else + { + diagnostics.AddRange(bag); + } + + bag.Free(); + return !hasErrors; + + void verifyPresenceOfRequiredAPIs(BindingDiagnosticBag bag) + { + if (_isEnumerable) + { + EnsureWellKnownMember(WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, bag); + EnsureWellKnownMember(WellKnownMember.System_Threading_CancellationToken__Equals, bag); + EnsureWellKnownMember(WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, bag); + EnsureWellKnownMember(WellKnownMember.System_Threading_CancellationTokenSource__Token, bag); + EnsureWellKnownMember(WellKnownMember.System_Threading_CancellationTokenSource__Dispose, 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__ctorValue, bag); + EnsureWellKnownMember(WellKnownMember.System_Threading_Tasks_ValueTask__ctor, bag); + + ensureSpecialMember(SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT, bag); + + Symbol ensureSpecialMember(SpecialMember member, BindingDiagnosticBag bag) + { + return Binder.GetSpecialTypeMember(F.Compilation, member, bag, body.Syntax); + } + } + } + + protected override void GenerateMethodImplementations() + { + GenerateConstructor(); + + if (_isEnumerable) + { + // IAsyncEnumerable + GenerateIAsyncEnumerableImplementation_GetAsyncEnumerator(); + } + + // IAsyncEnumerator + GenerateIAsyncEnumeratorImplementation_MoveNextAsync(); + GenerateIAsyncEnumeratorImplementation_Current(); + + // IAsyncDisposable + GenerateIAsyncDisposable_DisposeAsync(); + } + + private void GenerateConstructor() + { + Debug.Assert(stateMachineType.Constructor is IteratorConstructor); + Debug.Assert(stateField is not null); + + // Produces: + // .ctor(int state) + // { + // this.state = state; + // this.initialThreadId = {managedThreadId}; + // this.instanceId = LocalStoreTracker.GetNewStateMachineInstanceId(); + // } + Debug.Assert(stateMachineType.Constructor is IteratorConstructor); + + F.CurrentFunction = stateMachineType.Constructor; + var blockBuilder = ArrayBuilder.GetInstance(); + blockBuilder.Add(F.BaseInitialization()); + + blockBuilder.Add(F.Assignment(F.InstanceField(stateField), F.Parameter(F.CurrentFunction.Parameters[0]))); // this.state = state; + + BoundExpression managedThreadId = MakeCurrentThreadId(); + if (initialThreadIdField is not null) + { + // this.initialThreadId = {managedThreadId}; + blockBuilder.Add(F.Assignment(F.InstanceField(initialThreadIdField), managedThreadId)); + } + + if (instanceIdField is not null && + F.WellKnownMethod(WellKnownMember.Microsoft_CodeAnalysis_Runtime_LocalStoreTracker__GetNewStateMachineInstanceId) is { } getId) + { + // this.instanceId = LocalStoreTracker.GetNewStateMachineInstanceId(); + blockBuilder.Add(F.Assignment(F.InstanceField(instanceIdField), F.Call(receiver: null, getId))); + } + + blockBuilder.Add(F.Return()); + + var block = F.Block(blockBuilder.ToImmutableAndFree()); + F.CloseMethod(block); + } + + /// + /// Generates the GetAsyncEnumerator method. + /// + private void GenerateIAsyncEnumerableImplementation_GetAsyncEnumerator() + { + Debug.Assert(_currentField is not null); + + NamedTypeSymbol IAsyncEnumerableOfElementType = + F.WellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T) + .Construct(_currentField.Type); + + MethodSymbol IAsyncEnumerableOfElementType_GetEnumerator = + F.WellKnownMethod(WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator) + .AsMember(IAsyncEnumerableOfElementType); + + BoundExpression? managedThreadId = null; + GenerateIteratorGetEnumerator(IAsyncEnumerableOfElementType_GetEnumerator, ref managedThreadId, initialState: StateMachineState.InitialAsyncIteratorState); + } + + protected override void GenerateResetInstance(ArrayBuilder builder, StateMachineState initialState) + { + Debug.Assert(stateField is not null); + Debug.Assert(_disposeModeField is not null); + + // this.state = {initialState}; + // this.disposeMode = false; + + builder.Add( + // this.state = {initialState}; + F.Assignment(F.Field(F.This(), stateField), F.Literal(initialState))); + + builder.Add( + // disposeMode = false; + F.Assignment(F.InstanceField(_disposeModeField), F.Literal(false))); + } + + protected override BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) + { + return AsyncRewriter.AsyncIteratorRewriter.InitializeParameterField(getEnumeratorMethod, parameter, resultParameter, parameterProxy, _combinedTokensField, F); + } + + /// + /// Generates the `ValueTask>bool> IAsyncEnumerator>ElementType>.MoveNextAsync()` method as a runtime-async method. + /// + [SuppressMessage("Style", """VSTHRD200:Use "Async" suffix for async methods""", Justification = "Standard naming convention for generating 'IAsyncEnumerator.MoveNextAsync'")] + private void GenerateIAsyncEnumeratorImplementation_MoveNextAsync() + { + Debug.Assert(stateField is not null); + Debug.Assert(_currentField is not null); + Debug.Assert(_disposeModeField is not null); + Debug.Assert(hoistedVariables is not null); + Debug.Assert(nonReusableLocalProxies is not null); + + // Add IAsyncEnumerator<...>.MoveNextAsync() as a runtime-async method. + MethodSymbol IAsyncEnumeratorOfElementType_MoveNextAsync = GetMoveNextAsyncMethod(); + SynthesizedImplementationMethod moveNextAsyncMethod = OpenMoveNextMethodImplementation(IAsyncEnumeratorOfElementType_MoveNextAsync, runtimeAsync: true); + + var rewritter = new MoveNextAsyncRewriter( + F, + method, + stateField, + instanceIdField, + hoistedVariables, + nonReusableLocalProxies, + nonReusableFieldsForCleanup, + synthesizedLocalOrdinals, + stateMachineStateDebugInfoBuilder, + slotAllocatorOpt, + nextFreeHoistedLocalSlot, + diagnostics, + _currentField, + _disposeModeField, + _combinedTokensField); + + BoundStatement runtimeAsyncMoveNextAsyncBody = rewritter.GenerateMoveNextAsync(body); + + Debug.Assert(F.CurrentFunction is not null); + BoundStatement rewrittenBody = RuntimeAsyncRewriter.RewriteWithoutHoisting(runtimeAsyncMoveNextAsyncBody, F.CurrentFunction, F.CompilationState, F.Diagnostics); + + F.CloseMethod(rewrittenBody); + } + + private MethodSymbol GetMoveNextAsyncMethod() + { + Debug.Assert(_currentField is not null); + + MethodSymbol IAsyncEnumerator_MoveNextAsync = F.WellKnownMethod(WellKnownMember.System_Collections_Generic_IAsyncEnumerator_T__MoveNextAsync); + var IAsyncEnumerator = IAsyncEnumerator_MoveNextAsync.ContainingType; + var IAsyncEnumeratorOfElementType = IAsyncEnumerator.Construct(_currentField.Type); + MethodSymbol IAsyncEnumeratorOfElementType_MoveNextAsync = IAsyncEnumerator_MoveNextAsync.AsMember(IAsyncEnumeratorOfElementType); + return IAsyncEnumeratorOfElementType_MoveNextAsync; + } + + /// + /// Generates the Current property. + /// + private void GenerateIAsyncEnumeratorImplementation_Current() + { + AsyncRewriter.AsyncIteratorRewriter.GenerateIAsyncEnumeratorImplementation_Current(_currentField, this, F); + } + + /// + /// Generates the `ValueTask IAsyncDisposable.DisposeAsync()` method as a runtime-async method. + /// + [SuppressMessage("Style", """VSTHRD200:Use "Async" suffix for async methods""", Justification = "Standard naming convention for generating 'IAsyncDisposable.DisposeAsync'")] + private void GenerateIAsyncDisposable_DisposeAsync() + { + // Produce: + // if (state >= StateMachineStates.NotStartedStateMachine /* -1 */) + // { + // throw new NotSupportedException(); + // } + // if (state == StateMachineStates.FinishedStateMachine /* -2 */) + // { + // return; + // } + // disposeMode = true; + // AsyncHelpers.Await(MoveNextAsync()); + // return; + + Debug.Assert(_currentField is not null); + Debug.Assert(stateField is not null); + Debug.Assert(_disposeModeField is not null); + MethodSymbol IAsyncDisposable_DisposeAsync = F.WellKnownMethod(WellKnownMember.System_IAsyncDisposable__DisposeAsync); + + // The implementation doesn't depend on the method body of the iterator method. + OpenMethodImplementation(IAsyncDisposable_DisposeAsync, hasMethodBodyDependency: false, isRuntimeAsync: true); + var blockBuilder = ArrayBuilder.GetInstance(); + + blockBuilder.Add(F.If( + // if (state >= StateMachineStates.NotStartedStateMachine /* -1 */) + F.IntGreaterThanOrEqual(F.InstanceField(stateField), F.Literal(StateMachineState.NotStartedOrRunningState)), + // throw new NotSupportedException(); + thenClause: F.Throw(F.New(F.WellKnownType(WellKnownType.System_NotSupportedException))))); + + blockBuilder.Add(F.If( + // if (state == StateMachineStates.FinishedStateMachine) + F.IntEqual(F.InstanceField(stateField), F.Literal(StateMachineState.FinishedState)), + // return; + thenClause: F.Return())); + + // disposeMode = true; + blockBuilder.Add(F.Assignment(F.InstanceField(_disposeModeField), F.Literal(true))); + + // AsyncHelpers.Await(MoveNextAsync()); + MethodSymbol IAsyncEnumeratorOfElementType_MoveNextAsync = GetMoveNextAsyncMethod(); + NamedTypeSymbol boolType = F.SpecialType(SpecialType.System_Boolean); + var awaitMethod = ((MethodSymbol)F.Compilation.GetSpecialTypeMember(SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT)) + .Construct(boolType); + + BoundCall awaitMoveNextAsync = F.Call( + receiver: null, + awaitMethod, + F.Call(F.This(), IAsyncEnumeratorOfElementType_MoveNextAsync)); + + blockBuilder.Add(F.ExpressionStatement(awaitMoveNextAsync)); + + blockBuilder.Add(F.Return()); + + BoundBlock block = F.Block(blockBuilder.ToImmutableAndFree()); + F.CloseMethod(block); + } + + protected override BoundStatement GenerateStateMachineCreation(LocalSymbol stateMachineVariable, NamedTypeSymbol frameType, IReadOnlyDictionary proxies) + { + var blockBuilder = ArrayBuilder.GetInstance(); + + // result.parameter = this.parameterProxy; // OR more complex initialization for async-iterator parameter marked with [EnumeratorCancellation] + blockBuilder.Add(GenerateParameterStorage(stateMachineVariable, proxies)); + + // return local; + blockBuilder.Add(F.Return(F.Local(stateMachineVariable))); + + return F.Block(blockBuilder.ToImmutableAndFree()); + } + + protected override void InitializeStateMachine(ArrayBuilder blockBuilder, NamedTypeSymbol frameType, LocalSymbol stateMachineLocal) + { + // stateMachineLocal = new {StateMachineType}(initialState); + var initialState = _isEnumerable ? StateMachineState.FinishedState : StateMachineState.InitialAsyncIteratorState; + blockBuilder.Add(F.Assignment(F.Local(stateMachineLocal), F.New(frameType.InstanceConstructors[0], F.Literal(initialState)))); + } +} diff --git a/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorStateMachine.cs b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorStateMachine.cs new file mode 100644 index 0000000000000..330d5fc4639ed --- /dev/null +++ b/src/Compilers/CSharp/Portable/Lowering/RuntimeAsyncIteratorRewriter/RuntimeAsyncIteratorStateMachine.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp; + +/// +/// State machine type for async-iterators implemented with runtime-async. +/// +/// +internal sealed class RuntimeAsyncIteratorStateMachine : StateMachineTypeSymbol +{ + private readonly MethodSymbol _constructor; + private readonly ImmutableArray _interfaces; + + internal readonly TypeWithAnnotations ElementType; + + public RuntimeAsyncIteratorStateMachine( + VariableSlotAllocator? slotAllocatorOpt, + TypeCompilationState compilationState, + MethodSymbol kickoffMethod, + int kickoffMethodOrdinal, + bool isEnumerable, + TypeWithAnnotations elementType) + : base(slotAllocatorOpt, compilationState, kickoffMethod, kickoffMethodOrdinal) + { + this.ElementType = TypeMap.SubstituteType(elementType); + _interfaces = makeInterfaces(isEnumerable); + _constructor = new IteratorConstructor(this); + + ImmutableArray makeInterfaces(bool isEnumerable) + { + var interfaces = ArrayBuilder.GetInstance(); + CSharpCompilation compilation = ContainingAssembly.DeclaringCompilation; + if (isEnumerable) + { + interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T).Construct(ElementType.Type)); + } + + interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T).Construct(ElementType.Type)); + interfaces.Add(compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable)); + + return interfaces.ToImmutableAndFree(); + } + } + + public override TypeKind TypeKind + => TypeKind.Class; + + internal override MethodSymbol Constructor + => _constructor; + + internal override ImmutableArray InterfacesNoUseSiteDiagnostics(ConsList basesBeingResolved) + => _interfaces; + + internal override NamedTypeSymbol BaseTypeNoUseSiteDiagnostics + => ContainingAssembly.GetSpecialType(SpecialType.System_Object); + + internal override bool IsRecord => false; + internal override bool IsRecordStruct => false; + internal override bool HasPossibleWellKnownCloneMethod() => false; +} diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 6c62a86c384bd..c2dccfdce5a4f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -298,11 +298,11 @@ public override BoundNode VisitSequence(BoundSequence node) /// /// The set of locals declared in the original version of this statement /// A delegate to return the translation of the body of this statement - private BoundStatement PossibleIteratorScope(ImmutableArray locals, Func wrapped) + private BoundStatement PossibleIteratorScope(ImmutableArray locals, Func wrapped, BoundBlock node) { if (locals.IsDefaultOrEmpty) { - return wrapped(); + return wrapped(this, node); } var hoistedLocalsWithDebugScopes = ArrayBuilder.GetInstance(); @@ -344,7 +344,7 @@ private BoundStatement PossibleIteratorScope(ImmutableArray locals, } } - var translatedStatement = wrapped(); + var translatedStatement = wrapped(this, node); var variableCleanup = ArrayBuilder.GetInstance(); // produce cleanup code for all fields of locals defined by this block @@ -529,7 +529,10 @@ public override BoundNode VisitBlock(BoundBlock node) instrumentation = (BoundBlockInstrumentation)Visit(node.Instrumentation); } - return PossibleIteratorScope(node.Locals, () => VisitBlock(node, removeInstrumentation: true)); + return PossibleIteratorScope( + node.Locals, + static (MethodToStateMachineRewriter @this, BoundBlock node) => @this.VisitBlock(node, removeInstrumentation: true), + node); } public override BoundNode VisitStateMachineInstanceId(BoundStateMachineInstanceId node) diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 6c5fc05a07aff..9ecd6c32d24de 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -345,15 +345,18 @@ protected BoundStatement GenerateParameterStorage(LocalSymbol stateMachineVariab protected SynthesizedImplementationMethod OpenMethodImplementation( MethodSymbol methodToImplement, string methodName = null, - bool hasMethodBodyDependency = false) + bool hasMethodBodyDependency = false, + bool isRuntimeAsync = false) { - var result = new SynthesizedStateMachineDebuggerHiddenMethod(methodName, methodToImplement, (StateMachineTypeSymbol)F.CurrentType, null, hasMethodBodyDependency); + var result = new SynthesizedStateMachineDebuggerHiddenMethod( + methodName, methodToImplement, (StateMachineTypeSymbol)F.CurrentType, null, hasMethodBodyDependency, isRuntimeAsync); + F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, result.GetCciAdapter()); F.CurrentFunction = result; return result; } - protected MethodSymbol OpenPropertyImplementation(MethodSymbol getterToImplement) + internal MethodSymbol OpenPropertyImplementation(MethodSymbol getterToImplement) { var prop = new SynthesizedStateMachineProperty(getterToImplement, (StateMachineTypeSymbol)F.CurrentType); F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, prop.GetCciAdapter()); @@ -365,9 +368,9 @@ protected MethodSymbol OpenPropertyImplementation(MethodSymbol getterToImplement return getter; } - protected SynthesizedImplementationMethod OpenMoveNextMethodImplementation(MethodSymbol methodToImplement) + protected SynthesizedImplementationMethod OpenMoveNextMethodImplementation(MethodSymbol methodToImplement, bool runtimeAsync = false) { - var result = new SynthesizedStateMachineMoveNextMethod(methodToImplement, (StateMachineTypeSymbol)F.CurrentType); + var result = new SynthesizedStateMachineMoveNextMethod(methodToImplement, (StateMachineTypeSymbol)F.CurrentType, runtimeAsync); F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, result.GetCciAdapter()); F.CurrentFunction = result; return result; @@ -532,5 +535,10 @@ protected bool CanGetThreadId() return (object)F.WellKnownMember(WellKnownMember.System_Threading_Thread__ManagedThreadId, isOptional: true) != null || (object)F.WellKnownMember(WellKnownMember.System_Environment__CurrentManagedThreadId, isOptional: true) != null; } + + protected Symbol EnsureWellKnownMember(WellKnownMember member, BindingDiagnosticBag bag) + { + return Binder.GetWellKnownTypeMember(F.Compilation, member, bag, body.Syntax.Location); + } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineMethod.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineMethod.cs index 880f9759d86a2..0b86bae49ec13 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineMethod.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; @@ -55,16 +56,22 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l } /// - /// Represents a state machine MoveNext method. + /// Represents a state machine MoveNext or MoveNextAsync method. /// Handles special behavior around inheriting some attributes from the original async/iterator method. /// internal sealed class SynthesizedStateMachineMoveNextMethod : SynthesizedStateMachineMethod { private ImmutableArray _attributes; - public SynthesizedStateMachineMoveNextMethod(MethodSymbol interfaceMethod, StateMachineTypeSymbol stateMachineType) - : base(WellKnownMemberNames.MoveNextMethodName, interfaceMethod, stateMachineType, null, generateDebugInfo: true, hasMethodBodyDependency: true) + // Indicates that the method body follows runtime-async conventions and should be emitted with MethodImplAttributes.Async flag + private readonly bool _runtimeAsync; + + public SynthesizedStateMachineMoveNextMethod(MethodSymbol interfaceMethod, StateMachineTypeSymbol stateMachineType, bool runtimeAsync) + : base(interfaceMethod.Name, interfaceMethod, stateMachineType, null, generateDebugInfo: true, hasMethodBodyDependency: true) { + // PROTOTYPE consider reverting to only use "MoveNext" name, as it is expected by various tools that are aware of state machines (EnC, symbols) + Debug.Assert(interfaceMethod.Name is WellKnownMemberNames.MoveNextMethodName or WellKnownMemberNames.MoveNextAsyncMethodName); + _runtimeAsync = runtimeAsync; } public override ImmutableArray GetAttributes() @@ -100,6 +107,22 @@ public override ImmutableArray GetAttributes() return _attributes; } + + public override bool IsAsync => _runtimeAsync; + + internal override MethodImplAttributes ImplementationAttributes + { + get + { + MethodImplAttributes result = default; + if (_runtimeAsync) + { + result |= MethodImplAttributes.Async; + } + + return result; + } + } } /// @@ -108,14 +131,19 @@ public override ImmutableArray GetAttributes() /// internal sealed class SynthesizedStateMachineDebuggerHiddenMethod : SynthesizedStateMachineMethod { + // Indicates that the method should be emitted with MethodImplAttributes.Async flag + private readonly bool _runtimeAsync; + public SynthesizedStateMachineDebuggerHiddenMethod( string name, MethodSymbol interfaceMethod, StateMachineTypeSymbol stateMachineType, PropertySymbol associatedProperty, - bool hasMethodBodyDependency) + bool hasMethodBodyDependency, + bool runtimeAsync) : base(name, interfaceMethod, stateMachineType, associatedProperty, generateDebugInfo: false, hasMethodBodyDependency: hasMethodBodyDependency) { + _runtimeAsync = runtimeAsync; } internal sealed override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) @@ -125,5 +153,21 @@ internal sealed override void AddSynthesizedAttributes(PEModuleBuilder moduleBui base.AddSynthesizedAttributes(moduleBuilder, ref attributes); } + + public override bool IsAsync => _runtimeAsync; + + internal override MethodImplAttributes ImplementationAttributes + { + get + { + MethodImplAttributes result = default; + if (_runtimeAsync) + { + result |= MethodImplAttributes.Async; + } + + return result; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineProperty.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineProperty.cs index 8d02f62939390..69cfc875adede 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineProperty.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/SynthesizedStateMachineProperty.cs @@ -30,7 +30,8 @@ internal SynthesizedStateMachineProperty( interfacePropertyGetter, stateMachineType, associatedProperty: this, - hasMethodBodyDependency: false); + hasMethodBodyDependency: false, + runtimeAsync: false); } public override string Name diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 9a6a623d612d3..1cea6745c8ceb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -990,7 +990,7 @@ public BoundStatement If(BoundExpression condition, ImmutableArray return Block(statements.ToImmutableAndFree()); } - public BoundThrowStatement Throw(BoundExpression e) + public BoundThrowStatement Throw(BoundExpression? e) { return new BoundThrowStatement(Syntax, e) { WasCompilerGenerated = true }; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 6bdf97948dc25..7975a43b7be27 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -1764,7 +1764,7 @@ internal override System.Reflection.MethodImplAttributes ImplementationAttribute protected void AddAsyncImplAttributeIfNeeded(ref System.Reflection.MethodImplAttributes result) { - if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this)) + if (this.IsAsync && !this.IsIterator && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this)) { // When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the // runtime to generate the state machine diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListMethod.cs index a2d113d5a1a96..e7f99e37c6553 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListMethod.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListMethod.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Reflection; + namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal delegate BoundStatement GenerateMethodBodyDelegate(SyntheticBoundNodeFactory factory, MethodSymbol method, MethodSymbol interfaceMethod); @@ -17,6 +19,8 @@ internal SynthesizedReadOnlyListMethod(NamedTypeSymbol containingType, MethodSym } internal override bool SynthesizesLoweredBoundBody => true; + public override bool IsAsync => false; + internal override MethodImplAttributes ImplementationAttributes => default; internal override void GenerateMethodBody(TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs index 52ab0f0f4ab6f..b4c6141b0aa73 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs @@ -168,11 +168,6 @@ public override bool IsStatic get { return false; } } - public sealed override bool IsAsync - { - get { return false; } - } - public sealed override bool IsVirtual { get { return false; } @@ -213,11 +208,6 @@ internal override bool HasSpecialName get { return _interfaceMethod.HasSpecialName; } } - internal sealed override System.Reflection.MethodImplAttributes ImplementationAttributes - { - get { return default(System.Reflection.MethodImplAttributes); } - } - internal sealed override bool RequiresSecurityObject { get { return _interfaceMethod.RequiresSecurityObject; } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index e0e1ebe77a8be..79c7099718619 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -5,6 +5,7 @@ #nullable disable using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; @@ -20,6 +21,15 @@ using Xunit; using static Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.Instruction; +// To run these tests with runtime-async execution enabled, set the DOTNET_RuntimeAsync environment variable to 1. +// +// set DOTNET_RuntimeAsync=1 +// dotnet test Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj -f net10.0 +// +// Here are some example of convenient filters that can be applied: +// --filter "FullyQualifiedName~CodeGenAsyncIteratorTests" +// --filter "DisplayName~RuntimeAsync_" + namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { internal enum Instruction @@ -150,11 +160,9 @@ private CSharpCompilation CreateCompilationWithAsyncIterator(CSharpTestSource so [WorkItem(38961, "https://github.com/dotnet/roslyn/issues/38961")] public void LockInsideFinally() { - var comp = CreateCompilationWithAsyncIterator(@" + var src = @" using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; public class C @@ -179,9 +187,14 @@ public static async Task Main() { await foreach (var i in new C().GetSplits()) { } } -}", options: TestOptions.DebugExe); +}"; + var expectedOutput = "hello world"; + + var comp = CreateCompilationWithAsyncIterator(src, options: TestOptions.DebugExe); + + var v = CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "hello world"); v.VerifyIL("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { // Code size 268 (0x10c) @@ -319,17 +332,19 @@ .locals init (int V_0, IL_010a: ret IL_010b: ret }"); + + comp = CreateRuntimeAsyncCompilation(src, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] [WorkItem(38961, "https://github.com/dotnet/roslyn/issues/38961")] public void FinallyInsideFinally() { - var comp = CreateCompilationWithAsyncIterator(@" + var src = @" using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; public class C @@ -358,9 +373,14 @@ public static async Task Main() { await foreach (var i in GetSplits()) { } } -}", options: TestOptions.DebugExe); +}"; + var expectedOutput = "hello world!"; + + var comp = CreateCompilationWithAsyncIterator(src, options: TestOptions.DebugExe); + + var v = CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "hello world!"); v.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { // Code size 195 (0xc3) @@ -473,6 +493,105 @@ .locals init (int V_0, IL_00c1: ret IL_00c2: ret }"); + + comp = CreateRuntimeAsyncCompilation(src, options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 145 (0x91) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -3 + IL_000a: beq.s IL_000e + IL_000c: br.s IL_0010 + IL_000e: br.s IL_0010 + IL_0010: ldarg.0 + IL_0011: ldfld "bool C.d__0.<>w__disposeMode" + IL_0016: brfalse.s IL_001a + IL_0018: leave.s IL_0080 + IL_001a: ldarg.0 + IL_001b: ldc.i4.m1 + IL_001c: dup + IL_001d: stloc.0 + IL_001e: stfld "int C.d__0.<>1__state" + IL_0023: nop + .try + { + IL_0024: nop + IL_0025: nop + IL_0026: leave.s IL_005b + } + finally + { + IL_0028: ldloc.0 + IL_0029: ldc.i4.m1 + IL_002a: bne.un.s IL_005a + IL_002c: nop + .try + { + IL_002d: nop + IL_002e: ldstr "hello " + IL_0033: call "void System.Console.Write(string)" + IL_0038: nop + IL_0039: nop + IL_003a: leave.s IL_004e + } + finally + { + IL_003c: ldloc.0 + IL_003d: ldc.i4.m1 + IL_003e: bne.un.s IL_004d + IL_0040: nop + IL_0041: ldstr "world" + IL_0046: call "void System.Console.Write(string)" + IL_004b: nop + IL_004c: nop + IL_004d: endfinally + } + IL_004e: ldstr "!" + IL_0053: call "void System.Console.Write(string)" + IL_0058: nop + IL_0059: nop + IL_005a: endfinally + } + IL_005b: ldarg.0 + IL_005c: ldfld "bool C.d__0.<>w__disposeMode" + IL_0061: brfalse.s IL_0065 + IL_0063: leave.s IL_0080 + IL_0065: ldarg.0 + IL_0066: ldc.i4.1 + IL_0067: stfld "bool C.d__0.<>w__disposeMode" + IL_006c: leave.s IL_0080 + } + catch System.Exception + { + IL_006e: pop + IL_006f: ldarg.0 + IL_0070: ldc.i4.s -2 + IL_0072: stfld "int C.d__0.<>1__state" + IL_0077: ldarg.0 + IL_0078: ldnull + IL_0079: stfld "string C.d__0.<>2__current" + IL_007e: rethrow + } + IL_0080: ldarg.0 + IL_0081: ldc.i4.s -2 + IL_0083: stfld "int C.d__0.<>1__state" + IL_0088: ldarg.0 + IL_0089: ldnull + IL_008a: stfld "string C.d__0.<>2__current" + IL_008f: ldc.i4.0 + IL_0090: ret +} +"""); } [Fact] @@ -504,7 +623,7 @@ public async System.Threading.Tasks.Task GetTemperatureAsync() [WorkItem(30566, "https://github.com/dotnet/roslyn/issues/30566")] public void YieldReturnAwait1() { - var comp = CreateCompilationWithAsyncIterator(@" + var src = @" using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -523,17 +642,24 @@ public static async Task Main(string[] args) Console.WriteLine(i); } } -}", TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: @" +}"; + var expectedOutput = @" 2 -8"); +8"; + var comp = CreateCompilationWithAsyncIterator(src, TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src, TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] [WorkItem(30566, "https://github.com/dotnet/roslyn/issues/30566")] public void YieldReturnAwait2() { - var comp = CreateCompilationWithAsyncIterator(@" + var src = @" using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -557,10 +683,17 @@ public static async Task Main(string[] args) Console.WriteLine(i); } } -}", TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: @" +}"; + var expectedOutput = @" 2 -8"); +8"; + var comp = CreateCompilationWithAsyncIterator(src, TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -585,8 +718,17 @@ public static async Task Main(string[] args) } } }"; + var expectedOutput = "42"; var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, references: new[] { CSharpRef }, TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: @"42"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + // PROTOTYPE Tracked by https://github.com/dotnet/roslyn/issues/79762 + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics( + // (10,22): error CS9328: Method 'C.d__0.IAsyncEnumerator.MoveNextAsync()' uses a feature that is not supported by runtime async currently. Opt the method out of runtime async by attributing it with 'System.Runtime.CompilerServices.RuntimeAsyncMethodGenerationAttribute(false)'. + // yield return await d; + Diagnostic(ErrorCode.ERR_UnsupportedFeatureInRuntimeAsync, "await d").WithArguments("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()").WithLocation(10, 22)); } [Fact] @@ -717,8 +859,9 @@ ref struct S comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); comp.VerifyEmitDiagnostics(); + var expectedOutput = "123"; comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - var verifier = CompileAndVerify(comp, expectedOutput: "123", verify: Verification.FailsILVerify); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.
d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", """ { @@ -831,6 +974,10 @@ .locals init (int V_0, IL_00ed: ret } """); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -876,9 +1023,14 @@ ref struct S comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular13); comp.VerifyEmitDiagnostics(); + var expectedOutput = "123"; comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - var verifier = CompileAndVerify(comp, expectedOutput: "123", verify: Verification.FailsILVerify); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); verifier.VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1140,7 +1292,7 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } }"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugDll); - comp.VerifyDiagnostics(); + CompileAndVerify(comp, symbolValidator: module => { var method = module.GlobalNamespace.GetMember("C.M"); @@ -1151,7 +1303,15 @@ public static async System.Collections.Generic.IAsyncEnumerable M() var argument = attribute.ConstructorArguments.Single(); Assert.Equal("System.Type", argument.Type.ToTestDisplayString()); Assert.Equal("C.d__0", ((ITypeSymbol)argument.Value).ToTestDisplayString()); - }); + }).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + + CompileAndVerify(comp, symbolValidator: module => + { + var method = module.GlobalNamespace.GetMember("C.M"); + AssertEx.SetEqual(["AsyncIteratorStateMachineAttribute"], GetAttributeNames(method.GetAttributes())); // PROTOTYPE confirm what attribute to use, if any + }, verify: Verification.Skipped).VerifyDiagnostics(); } [Fact] @@ -1848,40 +2008,147 @@ static async System.Threading.Tasks.Task Main() } } }"; - var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Value:0 1 2 Value:3 4 Value:5 Done", symbolValidator: verifyMembersAndInterfaces); + var expectedOutput = "Value:0 1 2 Value:3 4 Value:5 Done"; + + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + CompileAndVerify(comp, expectedOutput: expectedOutput, symbolValidator: verifyAsync1MembersAndInterfaces) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + symbolValidator: verifyAsync2MembersAndInterfaces, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + // Note: for enumerator case, the initial state is different than for enumerable case, + // and we preserve the parameters directly into the final fields (no extra backup). + verifier.VerifyIL("C.M(int)", """ +{ + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldc.i4.s -3 + IL_0002: newobj "C.d__0..ctor(int)" + IL_0007: dup + IL_0008: ldarg.0 + IL_0009: stfld "int C.d__0.value" + IL_000e: ret +} +"""); + + // Enumerable case for contrast + source = @" +class C +{ + static async System.Collections.Generic.IAsyncEnumerable M(int value) + { + yield return value; + await System.Threading.Tasks.Task.CompletedTask; + } +}"; + comp = CreateRuntimeAsyncCompilation(source); + verifier = CompileAndVerify(comp, verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.M(int)", """ +{ + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldc.i4.s -2 + IL_0002: newobj "C.d__0..ctor(int)" + IL_0007: dup + IL_0008: ldarg.0 + IL_0009: stfld "int C.d__0.<>3__value" + IL_000e: ret +} +"""); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", """ +{ + // Code size 64 (0x40) + .maxstack 2 + .locals init (C.d__0 V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_002a + IL_000a: ldarg.0 + IL_000b: ldfld "int C.d__0.<>l__initialThreadId" + IL_0010: call "int System.Environment.CurrentManagedThreadId.get" + IL_0015: bne.un.s IL_002a + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld "int C.d__0.<>1__state" + IL_001f: ldarg.0 + IL_0020: ldc.i4.0 + IL_0021: stfld "bool C.d__0.<>w__disposeMode" + IL_0026: ldarg.0 + IL_0027: stloc.0 + IL_0028: br.s IL_0032 + IL_002a: ldc.i4.s -3 + IL_002c: newobj "C.d__0..ctor(int)" + IL_0031: stloc.0 + IL_0032: ldloc.0 + IL_0033: ldarg.0 + IL_0034: ldfld "int C.d__0.<>3__value" + IL_0039: stfld "int C.d__0.value" + IL_003e: ldloc.0 + IL_003f: ret +} +"""); - void verifyMembersAndInterfaces(ModuleSymbol module) + static void verifyAsync1MembersAndInterfaces(ModuleSymbol module) { var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__0"); - AssertEx.SetEqual(new[] { + AssertEx.SetEqual([ + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder", "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd", + "System.Int32 C.d__0.<>2__current", + "System.Boolean C.d__0.<>w__disposeMode", "System.Int32 C.d__0.value", + "System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1", "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)", - "void 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)", - "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)", + "void 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)", "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())); + "System.Int32 C.d__0.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); - AssertEx.SetEqual(new[] { + AssertEx.SetEqual([ "System.Runtime.CompilerServices.IAsyncStateMachine", "System.IAsyncDisposable", "System.Threading.Tasks.Sources.IValueTaskSource", "System.Threading.Tasks.Sources.IValueTaskSource", - "System.Collections.Generic.IAsyncEnumerator" }, - type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.Select(m => m.ToTestDisplayString())); + "System.Collections.Generic.IAsyncEnumerator" + ], type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToTestDisplayStrings()); + } + + static void verifyAsync2MembersAndInterfaces(ModuleSymbol module) + { + var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__0"); + AssertEx.SetEqual([ + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__0.<>2__current", + "System.Boolean C.d__0.<>w__disposeMode", + "System.Int32 C.d__0.value", + "C.d__0..ctor(System.Int32 <>1__state)", + "System.Threading.Tasks.ValueTask C.d__0.MoveNextAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Threading.Tasks.ValueTask C.d__0.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__0.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); + + AssertEx.SetEqual([ + "System.IAsyncDisposable", + "System.Collections.Generic.IAsyncEnumerator" + ], type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToTestDisplayStrings()); } } @@ -2038,17 +2305,23 @@ public static async System.Collections.Generic.IAsyncEnumerable M() throw new System.NotImplementedException(); } }"; - var comp = CreateCompilationWithAsyncIterator(source); - comp.VerifyDiagnostics(); - comp.VerifyEmitDiagnostics( + DiagnosticDescription[] expectedDiagnostics = [ // (4,74): error CS8420: The body of an async-iterator method must contain a 'yield' statement. Consider removing 'async' from the method declaration or adding a 'yield' statement. // public static async System.Collections.Generic.IAsyncEnumerable M() Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, "M").WithLocation(4, 74) - ); + ]; + + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); var m = comp.SourceModule.GlobalNamespace.GetMember("C.M"); Assert.False(m.IsIterator); Assert.True(m.IsAsync); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); } [Fact] @@ -2063,13 +2336,19 @@ public static async System.Collections.Generic.IAsyncEnumerator M() throw new System.NotImplementedException(); } }"; - var comp = CreateCompilationWithAsyncIterator(source); - comp.VerifyDiagnostics(); - comp.VerifyEmitDiagnostics( + DiagnosticDescription[] expectedDiagnostics = [ // (4,74): error CS8420: The body of an async-iterator method must contain a 'yield' statement. Consider removing `async` from the method declaration. // public static async System.Collections.Generic.IAsyncEnumerator M() Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, "M").WithLocation(4, 74) - ); + ]; + + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); } [Fact] @@ -2085,13 +2364,48 @@ async System.Collections.Generic.IAsyncEnumerator M() throw new System.NotImplementedException(); } }"; - var comp = CreateCompilationWithAsyncIterator(source); - comp.VerifyDiagnostics(); - comp.VerifyEmitDiagnostics( + + DiagnosticDescription[] expectedDiagnostics = [ // (4,60): error CS8419: The body of an async-iterator method must contain a 'yield' statement. // async System.Collections.Generic.IAsyncEnumerator M() Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYield, "M").WithLocation(4, 60) - ); + ]; + + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + } + + [Fact] + public void AsyncIteratorReturningEnumerable_WithAwaitAndThrow_LocalFunction() + { + string source = """ +await foreach (var i in local()) { } + +async System.Collections.Generic.IAsyncEnumerable local() +{ + await System.Threading.Tasks.Task.CompletedTask; + throw new System.NotImplementedException(); +} +"""; + + DiagnosticDescription[] expectedDiagnostics = [ + // source(3,56): error CS8419: The body of an async-iterator method must contain a 'yield' statement. + // async System.Collections.Generic.IAsyncEnumerable local() + Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYield, "local").WithLocation(3, 56) + ]; + + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); } [Fact] @@ -2107,13 +2421,48 @@ async System.Collections.Generic.IAsyncEnumerator M() throw new System.NotImplementedException(); } }"; + DiagnosticDescription[] expectedDiagnostics = [ + // source(4,60): error CS8420: The body of an async-iterator method must contain a 'yield' statement. Consider removing 'async' from the method declaration or adding a 'yield' statement. + // async System.Collections.Generic.IAsyncEnumerator M() + Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, "M").WithLocation(4, 60) + ]; + var comp = CreateCompilationWithAsyncIterator(source); comp.VerifyDiagnostics(); - comp.VerifyEmitDiagnostics( - // (4,60): error CS8420: The body of an async-iterator method must contain a 'yield' statement. Consider removing `async` from the method declaration. + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + } + + [Fact] + public void AsyncIteratorReturningEnumerator_WithThrow_WithAwaitInLocalFunction() + { + string source = """ +class C +{ + async System.Collections.Generic.IAsyncEnumerator M() + { + async System.Threading.Tasks.Task local() { await System.Threading.Tasks.Task.CompletedTask; }; + throw new System.NotImplementedException(); + } +} +"""; + DiagnosticDescription[] expectedDiagnostics = [ + // source(3,60): error CS8420: The body of an async-iterator method must contain a 'yield' statement. Consider removing 'async' from the method declaration or adding a 'yield' statement. // async System.Collections.Generic.IAsyncEnumerator M() - Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, "M").WithLocation(4, 60) - ); + Diagnostic(ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait, "M").WithLocation(3, 60), + // source(5,43): warning CS8321: The local function 'local' is declared but never used + // async System.Threading.Tasks.Task local() { await System.Threading.Tasks.Task.CompletedTask; }; + Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "local").WithArguments("local").WithLocation(5, 43) + ]; + + var comp = CreateCompilationWithAsyncIterator(source); + comp.VerifyEmitDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyEmitDiagnostics(expectedDiagnostics); } [Fact] @@ -2147,9 +2496,15 @@ public static async System.Collections.Generic.IAsyncEnumerable M() yield return 1; } }"; - var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations: 2), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "1 END DISPOSAL DONE"); + var expectedOutput = "1 END DISPOSAL DONE"; + + var comp = CreateCompilationWithAsyncIterator([Run(iterations: 2), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations: 2), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput)) + .VerifyDiagnostics(); } [Fact] @@ -2277,44 +2632,52 @@ static async System.Collections.Generic.IAsyncEnumerator M(int value) [Fact] public void CallingMoveNextAsyncTwice() { - string source = @" + string source = """ using static System.Console; class C { static async System.Collections.Generic.IAsyncEnumerable M() { - Write(""1 ""); + Write("1 "); await System.Threading.Tasks.Task.CompletedTask; - Write(""2 ""); + Write("2 "); yield return 3; - Write(""4 ""); + Write("4 "); } static async System.Threading.Tasks.Task Main() { - Write(""0 ""); + Write("0 "); await using (var enumerator = M().GetAsyncEnumerator()) { var found = await enumerator.MoveNextAsync(); if (!found) throw null; var value = enumerator.Current; - Write($""{value} ""); + Write($"{value} "); found = await enumerator.MoveNextAsync(); if (found) throw null; found = await enumerator.MoveNextAsync(); if (found) throw null; - Write(""5""); + Write("5"); } } -}"; +} +"""; + var expectedOutput = "0 1 2 3 4 5"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] [WorkItem(30275, "https://github.com/dotnet/roslyn/issues/30275")] public void CallingGetEnumeratorTwice() { + // Shows that disposeMode and parameter values are reset upon second enumeration string source = @" using static System.Console; class C @@ -2347,18 +2710,34 @@ 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", symbolValidator: verifyMembersAndInterfaces); + var expectedOutput = "1 2 Stream1:3 1 2 Stream2:3 4 2 4 2 Done"; + + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + + CompileAndVerify(comp, expectedOutput: expectedOutput, symbolValidator: verifyAsync1MembersAndInterfaces) + .VerifyDiagnostics(); // Illustrates that parameters are proxied (we save the original in the enumerable, then copy them into working fields when making an enumerator) - void verifyMembersAndInterfaces(ModuleSymbol module) + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + verify: Verification.Skipped, symbolValidator: verifyAsync2MembersAndInterfaces); + verifier.VerifyDiagnostics(); + + static void verifyAsync1MembersAndInterfaces(ModuleSymbol module) { var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__0"); - AssertEx.SetEqual(new[] { + AssertEx.SetEqual([ + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder", "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd", + "System.Int32 C.d__0.<>2__current", + "System.Boolean C.d__0.<>w__disposeMode", + "System.Int32 C.d__0.<>l__initialThreadId", + "System.Int32 C.d__0.value", "System.Int32 C.d__0.<>3__value", + "System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1", "C.d__0..ctor(System.Int32 <>1__state)", "void C.d__0.MoveNext()", "void C.d__0.SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine stateMachine)", @@ -2366,24 +2745,48 @@ void verifyMembersAndInterfaces(ModuleSymbol module) "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)", - "void 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)", - "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)", + "void 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)", "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())); + "System.Int32 C.d__0.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); - AssertEx.SetEqual(new[] { + AssertEx.SetEqual([ "System.Runtime.CompilerServices.IAsyncStateMachine", "System.IAsyncDisposable", "System.Threading.Tasks.Sources.IValueTaskSource", "System.Threading.Tasks.Sources.IValueTaskSource", "System.Collections.Generic.IAsyncEnumerable", - "System.Collections.Generic.IAsyncEnumerator" }, - type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.Select(m => m.ToTestDisplayString())); + "System.Collections.Generic.IAsyncEnumerator" + ], type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToTestDisplayStrings()); + } + + static void verifyAsync2MembersAndInterfaces(ModuleSymbol module) + { + var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__0"); + AssertEx.SetEqual([ + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__0.<>2__current", + "System.Boolean C.d__0.<>w__disposeMode", + "System.Int32 C.d__0.<>l__initialThreadId", + "System.Int32 C.d__0.value", + "System.Int32 C.d__0.<>3__value", + "C.d__0..ctor(System.Int32 <>1__state)", + "System.Collections.Generic.IAsyncEnumerator C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator([System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)])", + "System.Threading.Tasks.ValueTask C.d__0.MoveNextAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Threading.Tasks.ValueTask C.d__0.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__0.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); + + AssertEx.SetEqual([ + "System.Collections.Generic.IAsyncEnumerable", + "System.Collections.Generic.IAsyncEnumerator", + "System.IAsyncDisposable" + ], type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToTestDisplayStrings()); } } @@ -2425,9 +2828,14 @@ static async System.Threading.Tasks.Task Main() } } }"; + var expectedOutput = "1 2 Stream1:3 4 2 1 2 Stream2:3 4 2 Done"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "1 2 Stream1:3 4 2 1 2 Stream2:3 4 2 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -2474,9 +2882,14 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "Stream1:0 Stream2:0 1 2 Stream1:3 4 2 1 2 Stream2:3 4 2 Done"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Stream1:0 Stream2:0 1 2 Stream1:3 4 2 1 2 Stream2:3 4 2 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -2519,9 +2932,14 @@ static async Task Main() Write(""Done""); } }"; + var expectedOutput = "Stream1:0 1 2 Stream1:3 4 42 Await Stream2:0 1 2 Stream2:3 4 42 Done"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Stream1:0 1 2 Stream1:3 4 42 Await Stream2:0 1 2 Stream2:3 4 42 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -2562,9 +2980,49 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "Stream1:1 Finally Stream2:1 Stream2:2 Finally Done"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Stream1:1 Finally Stream2:1 Stream2:2 Finally Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + // Note: the dispose mode is set back to false during reset + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", """ +{ + // Code size 64 (0x40) + .maxstack 2 + .locals init (C.d__0 V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_002a + IL_000a: ldarg.0 + IL_000b: ldfld "int C.d__0.<>l__initialThreadId" + IL_0010: call "int System.Environment.CurrentManagedThreadId.get" + IL_0015: bne.un.s IL_002a + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld "int C.d__0.<>1__state" + IL_001f: ldarg.0 + IL_0020: ldc.i4.0 + IL_0021: stfld "bool C.d__0.<>w__disposeMode" + IL_0026: ldarg.0 + IL_0027: stloc.0 + IL_0028: br.s IL_0032 + IL_002a: ldc.i4.s -3 + IL_002c: newobj "C.d__0..ctor(int)" + IL_0031: stloc.0 + IL_0032: ldloc.0 + IL_0033: ldarg.0 + IL_0034: ldfld "int C.d__0.<>3__value" + IL_0039: stfld "int C.d__0.value" + IL_003e: ldloc.0 + IL_003f: ret +} +"""); } [Fact] @@ -2594,9 +3052,10 @@ static async System.Threading.Tasks.Task Main() }"; foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) { + var expectedOutput = "0 1 2 3 4 5"; + var comp = CreateCompilationWithAsyncIterator(source, options: options); - comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5"); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); var expectedFields = new[] { "FieldDefinition:Int32 <>1__state", @@ -3132,6 +3591,10 @@ .locals init (int V_0, IL_0134: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" IL_0139: ret }", sequencePointDisplay: SequencePointDisplayMode.Enhanced); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } } } @@ -4192,9 +4655,14 @@ static async System.Threading.Tasks.Task Main() Write(""5""); } }"; + var expectedOutput = "0 1 2 3 4 5"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5"); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4225,9 +4693,14 @@ static async System.Threading.Tasks.Task Main() Write(""5""); } }"; + var expectedOutput = "0 1 2 3 4 5"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4257,9 +4730,14 @@ static async System.Threading.Tasks.Task Main() Write(""End""); } }"; + var expectedOutput = "Start p:10 p:11 Value p:12 End"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Start p:10 p:11 Value p:12 End"); + CompileAndVerify(comp, expectedOutput: expectedOutput). + VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4290,9 +4768,14 @@ static async System.Threading.Tasks.Task Main() Write(""End""); } }"; + var expectedOutput = "Start f:10 f:11 Value f:12 End"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Start f:10 f:11 Value f:12 End"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4341,9 +4824,14 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "0 1 2 3 4 Done"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4372,9 +4860,14 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "0 1 2 3 4 5 Done"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4401,9 +4894,14 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "0 1 2 3 Done"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -4425,8 +4923,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } }"; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4458,9 +4960,15 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + var expectedOutput = "0 1 2 3 Done"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -4503,8 +5011,12 @@ static async System.Threading.Tasks.Task Main() }} }}"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectation); + CompileAndVerify(comp, expectedOutput: expectation) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectation), verify: Verification.Skipped) + .VerifyDiagnostics(); } void verifyLocalFunction(Instruction[] spec) @@ -4531,8 +5043,12 @@ async System.Collections.Generic.IAsyncEnumerable local() }} }}"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectation); + CompileAndVerify(comp, expectedOutput: expectation) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectation), verify: Verification.Skipped) + .VerifyDiagnostics(); } (string code, string expectation) generateCode(Instruction[] spec) @@ -4603,9 +5119,16 @@ static async System.Threading.Tasks.Task Main() Write(""Done""); } }"; + + var expectedOutput = "0 1 2 3 4 Done"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 Done"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -4639,8 +5162,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } }"; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -4687,8 +5214,12 @@ public async System.Collections.Generic.IAsyncEnumerator GetAsyncEnumerator } }"; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/79124")] @@ -4933,6 +5464,156 @@ .locals init (int V_0, IL_019b: ret } """); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 250 (0xfa) + .maxstack 3 + .locals init (int V_0, + bool V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0015 + IL_000c: br.s IL_000e + IL_000e: ldloc.0 + IL_000f: ldc.i4.s -3 + IL_0011: beq.s IL_0017 + IL_0013: br.s IL_0019 + IL_0015: br.s IL_0048 + IL_0017: br.s IL_0019 + IL_0019: ldarg.0 + IL_001a: ldfld "bool C.d__0.<>w__disposeMode" + IL_001f: brfalse.s IL_0026 + IL_0021: leave IL_00e7 + IL_0026: ldarg.0 + IL_0027: ldc.i4.m1 + IL_0028: dup + IL_0029: stloc.0 + IL_002a: stfld "int C.d__0.<>1__state" + IL_002f: nop + IL_0030: ldarg.0 + IL_0031: ldc.i4.1 + IL_0032: stfld "int C.d__0.<>2__current" + IL_0037: ldarg.0 + IL_0038: ldc.i4.s -4 + IL_003a: dup + IL_003b: stloc.0 + IL_003c: stfld "int C.d__0.<>1__state" + IL_0041: ldc.i4.1 + IL_0042: stloc.1 + IL_0043: leave IL_00f8 + IL_0048: ldarg.0 + IL_0049: ldc.i4.m1 + IL_004a: dup + IL_004b: stloc.0 + IL_004c: stfld "int C.d__0.<>1__state" + IL_0051: ldarg.0 + IL_0052: ldfld "bool C.d__0.<>w__disposeMode" + IL_0057: brfalse.s IL_005e + IL_0059: leave IL_00e7 + IL_005e: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0063: call "void System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_0068: nop + .try + { + .try + { + IL_0069: nop + .try + { + IL_006a: nop + IL_006b: ldstr "Break " + IL_0070: call "void System.Console.Write(string)" + IL_0075: nop + IL_0076: ldarg.0 + IL_0077: ldc.i4.1 + IL_0078: stfld "bool C.d__0.<>w__disposeMode" + IL_007d: leave.s IL_0092 + } + finally + { + IL_007f: ldloc.0 + IL_0080: ldc.i4.m1 + IL_0081: bne.un.s IL_0091 + IL_0083: nop + IL_0084: ldstr "Throw " + IL_0089: call "void System.Console.Write(string)" + IL_008e: nop + IL_008f: ldnull + IL_0090: throw + IL_0091: endfinally + } + IL_0092: ldarg.0 + IL_0093: ldfld "bool C.d__0.<>w__disposeMode" + IL_0098: brfalse.s IL_009c + IL_009a: leave.s IL_00c9 + IL_009c: nop + IL_009d: leave.s IL_00b5 + } + catch object + { + IL_009f: pop + IL_00a0: nop + IL_00a1: ldstr "Caught " + IL_00a6: call "void System.Console.Write(string)" + IL_00ab: nop + IL_00ac: ldarg.0 + IL_00ad: ldc.i4.1 + IL_00ae: stfld "bool C.d__0.<>w__disposeMode" + IL_00b3: leave.s IL_00c9 + } + IL_00b5: leave.s IL_00c9 + } + finally + { + IL_00b7: ldloc.0 + IL_00b8: ldc.i4.m1 + IL_00b9: bne.un.s IL_00c8 + IL_00bb: nop + IL_00bc: ldstr "Finally " + IL_00c1: call "void System.Console.Write(string)" + IL_00c6: nop + IL_00c7: nop + IL_00c8: endfinally + } + IL_00c9: ldarg.0 + IL_00ca: ldfld "bool C.d__0.<>w__disposeMode" + IL_00cf: brfalse.s IL_00d3 + IL_00d1: leave.s IL_00e7 + IL_00d3: leave.s IL_00e7 + } + catch System.Exception + { + IL_00d5: pop + IL_00d6: ldarg.0 + IL_00d7: ldc.i4.s -2 + IL_00d9: stfld "int C.d__0.<>1__state" + IL_00de: ldarg.0 + IL_00df: ldc.i4.0 + IL_00e0: stfld "int C.d__0.<>2__current" + IL_00e5: rethrow + } + IL_00e7: ldarg.0 + IL_00e8: ldc.i4.s -2 + IL_00ea: stfld "int C.d__0.<>1__state" + IL_00ef: ldarg.0 + IL_00f0: ldc.i4.0 + IL_00f1: stfld "int C.d__0.<>2__current" + IL_00f6: ldc.i4.0 + IL_00f7: stloc.1 + IL_00f8: ldloc.1 + IL_00f9: ret +} +"""); } [Theory] @@ -4977,8 +5658,12 @@ public async System.Threading.Tasks.ValueTask DisposeAsync() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5026,8 +5711,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M2() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5070,8 +5759,12 @@ public async System.Threading.Tasks.ValueTask DisposeAsync() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5124,8 +5817,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M2() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5168,8 +5865,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5214,8 +5915,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5267,8 +5972,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5300,8 +6009,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5331,8 +6044,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5396,8 +6113,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5430,8 +6151,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5461,8 +6186,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5493,8 +6222,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5528,8 +6261,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5562,8 +6299,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5601,8 +6342,12 @@ static async System.Threading.Tasks.Task SlowThrowAsync() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5641,8 +6386,12 @@ static async System.Threading.Tasks.Task FastThrowAsync() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5719,8 +6468,12 @@ static async System.Threading.Tasks.Task Main() "; var source = template.Replace("POSITION", position.ToString()); var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5812,8 +6565,12 @@ static async System.Threading.Tasks.Task Main() "; var source = template.Replace("POSITION", position.ToString()); var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5847,8 +6604,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5877,8 +6638,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5908,8 +6673,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -5941,8 +6710,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -6333,8 +7106,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -6367,8 +7144,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -6408,8 +7189,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -6447,8 +7232,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -6483,8 +7272,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Theory] @@ -6517,8 +7310,12 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } "; var comp = CreateCompilationWithAsyncIterator(new[] { Run(iterations), source }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: expectedOutput); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([Run(iterations), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -6552,8 +7349,9 @@ public static async System.Collections.Generic.IAsyncEnumerable M() } } """; + var expectedOutput = "1 Throw Caught Finally END DISPOSAL DONE"; var comp = CreateCompilationWithAsyncIterator(new[] { Run(2), source }, options: TestOptions.DebugExe); - var verifier = CompileAndVerify(comp, expectedOutput: "1 Throw Caught Finally END DISPOSAL DONE").VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ { @@ -6844,6 +7642,9 @@ .locals init (int V_0, IL_0286: ret } """); + comp = CreateRuntimeAsyncCompilation([Run(2), source], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -6883,9 +7684,14 @@ public static async System.Threading.Tasks.Task Main() } } }"; + var expectedOutput = "1"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "1"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -6907,9 +7713,14 @@ public static async System.Threading.Tasks.Task Main() System.Console.Write(""none""); } }"; + var expectedOutput = "none"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "none"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7093,6 +7904,7 @@ public static async Task Main() try { await enumerator.DisposeAsync(); + throw null; } catch (System.NotSupportedException) { @@ -7110,9 +7922,14 @@ public static async Task Main() } } }"; + var expectedOutput = "DisposeAsync threw. Already cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "DisposeAsync threw. Already cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7133,8 +7950,14 @@ public static async System.Threading.Tasks.Task Main() System.Console.Write(""done""); } }"; + var expectedOutput = "done"; + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "done"); + CompileAndVerify(comp, expectedOutput: expectedOutput); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7163,8 +7986,13 @@ public static async System.Threading.Tasks.Task Main() System.Console.Write(""done""); } }"; + var expectedOutput = "done"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - CompileAndVerify(comp, expectedOutput: "done"); + CompileAndVerify(comp, expectedOutput: expectedOutput); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7209,9 +8037,14 @@ public async IAsyncEnumerable Iter() yield return this.Func(); } }"; + var expectedOutput = "Base.Func;Derived.Func;"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "Base.Func;Derived.Func;"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7286,9 +8119,14 @@ static async Task Main() await (new D()).Test(); } }"; + var expectedOutput = "B1::F;D::F;B1::F;"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "B1::F;D::F;B1::F;"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -7302,11 +8140,11 @@ public class D { static async Task Main() { - var enumerable = new MyEnumerable(42); + var c = new C(42); using (CancellationTokenSource source = new CancellationTokenSource()) { CancellationToken token = source.Token; - await using (var enumerator = enumerable.GetAsyncEnumerator(token)) + await using (var enumerator = c.M(token)) { if (!await enumerator.MoveNextAsync()) throw null; System.Console.Write($""{enumerator.Current} ""); // 42 @@ -7329,14 +8167,14 @@ static async Task Main() } } } -public class MyEnumerable +public class C { private int value; - public MyEnumerable(int value) + public C(int value) { this.value = value; } - public async System.Collections.Generic.IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) + public async System.Collections.Generic.IAsyncEnumerator M(CancellationToken token) { yield return value++; yield return value; @@ -7350,9 +8188,14 @@ public async System.Collections.Generic.IAsyncEnumerator GetAsyncEnumerator System.Console.Write($""SKIPPED""); } }"; + var expectedOutput = "42 43 Long Cancelled"; var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Long Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7399,11 +8242,15 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E yield return value++; } }"; + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); // IL for GetAsyncEnumerator is verified by AsyncIteratorWithAwaitCompletedAndYield already + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7451,11 +8298,15 @@ static async System.Collections.Generic.IAsyncEnumerable localIter(int valu } } }"; + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular9); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); // IL for GetAsyncEnumerator is verified by AsyncIteratorWithAwaitCompletedAndYield_LocalFunction already + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7502,9 +8353,14 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E yield return value++; } }"; + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7550,9 +8406,14 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E yield return value++; } }"; + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7601,9 +8462,9 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E }"; foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) { + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options); - comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); // GetAsyncEnumerator's token parameter is used directly, since the argument token is default verifier.VerifyIL("C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" @@ -7685,6 +8546,9 @@ .locals init (C.d__1 V_0, IL_00c7: ret } "); + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } } @@ -7734,9 +8598,9 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E }"; foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) { + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options, parseOptions: TestOptions.Regular9); - comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); // GetAsyncEnumerator's token parameter is used directly, since the argument token is default verifier.VerifyIL("C.<
g__Iter|0_0>d.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" @@ -7818,6 +8682,9 @@ .locals init (C.<
g__Iter|0_0>d V_0, IL_00c7: ret } "); + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } } @@ -7867,9 +8734,14 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) { // field for token1 gets overridden with value from GetAsyncEnumerator's token parameter + var expectedOutput = "42 43 Cancelled"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } } @@ -7909,13 +8781,20 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, Ca yield return value++; } }"; - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics( - // (24,67): error CS8425: Async-iterator 'C.Iter(int, CancellationToken)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed + var expectedOutput = "42 43 REACHED 44"; + DiagnosticDescription[] expectedDiagnostics = [ + // 0.cs(24,67): warning CS8425: Async-iterator 'C.Iter(int, CancellationToken)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed // static async System.Collections.Generic.IAsyncEnumerable Iter(int value, CancellationToken token1) // no attribute set Diagnostic(ErrorCode.WRN_UndecoratedCancellationTokenParameter, "Iter").WithArguments("C.Iter(int, System.Threading.CancellationToken)").WithLocation(24, 67) - ); - CompileAndVerify(comp, expectedOutput: "42 43 REACHED 44"); + ]; + + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -7955,18 +8834,28 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, Ca } } }"; - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics( + var expectedOutput = "42 43 REACHED 44"; + DiagnosticDescription[] expectedDiagnostics = [ // (24,71): warning CS8425: Async-iterator 'Iter(int, CancellationToken)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed // static async System.Collections.Generic.IAsyncEnumerable Iter(int value, CancellationToken token1) // no attribute set Diagnostic(ErrorCode.WRN_UndecoratedCancellationTokenParameter, "Iter").WithArguments("Iter(int, System.Threading.CancellationToken)").WithLocation(24, 71) - ); - CompileAndVerify(comp, expectedOutput: "42 43 REACHED 44"); + ]; + + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } - [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] - public void CancellationTokenParameter_SomeOtherTokenPassedInGetAsyncEnumerator() + [Theory, CombinatorialData, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + public void CancellationTokenParameter_SomeOtherTokenPassedInGetAsyncEnumerator(bool useDebug, bool cancelFirst) { + var options = useDebug ? TestOptions.DebugExe : TestOptions.ReleaseExe; + var sourceToCancel = cancelFirst ? "source1" : "source2"; + string source = @" using static System.Console; using System.Runtime.CompilerServices; @@ -8011,22 +8900,136 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(CancellationT Write(""SKIPPED""); yield return value++; } -}"; +}".Replace("SOURCETOCANCEL", sourceToCancel); + + var expectedOutput = "42 43 Cancelled"; + // cancelling either the token given as argument or the one given to GetAsyncEnumerator results in cancelling the combined token3 - foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) - { - foreach (var sourceToCancel in new[] { "source1", "source2" }) - { - var comp = CreateCompilationWithAsyncIterator(new[] { source.Replace("SOURCETOCANCEL", sourceToCancel), EnumeratorCancellationAttributeType }, options: options); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); - } - } - } - [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] - public void CancellationTokenParameter_SomeOtherTokenPassedInGetAsyncEnumerator_LocalFunction() + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: options); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + symbolValidator: verifyAsync2MembersAndInterfaces, verify: Verification.Skipped); + + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", """ +{ + // Code size 201 (0xc9) + .maxstack 3 + .locals init (C.d__1 V_0, + System.Threading.CancellationToken V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__1.<>1__state" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_002a + IL_000a: ldarg.0 + IL_000b: ldfld "int C.d__1.<>l__initialThreadId" + IL_0010: call "int System.Environment.CurrentManagedThreadId.get" + IL_0015: bne.un.s IL_002a + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld "int C.d__1.<>1__state" + IL_001f: ldarg.0 + IL_0020: ldc.i4.0 + IL_0021: stfld "bool C.d__1.<>w__disposeMode" + IL_0026: ldarg.0 + IL_0027: stloc.0 + IL_0028: br.s IL_0032 + IL_002a: ldc.i4.s -3 + IL_002c: newobj "C.d__1..ctor(int)" + IL_0031: stloc.0 + IL_0032: ldloc.0 + IL_0033: ldarg.0 + IL_0034: ldfld "System.Threading.CancellationToken C.d__1.<>3__token1" + IL_0039: stfld "System.Threading.CancellationToken C.d__1.token1" + IL_003e: ldloc.0 + IL_003f: ldarg.0 + IL_0040: ldfld "System.Threading.CancellationToken C.d__1.<>3__token2" + IL_0045: stfld "System.Threading.CancellationToken C.d__1.token2" + IL_004a: ldarg.0 + IL_004b: ldflda "System.Threading.CancellationToken C.d__1.<>3__token3" + IL_0050: ldloca.s V_1 + IL_0052: initobj "System.Threading.CancellationToken" + IL_0058: ldloc.1 + IL_0059: call "bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)" + IL_005e: brfalse.s IL_0069 + IL_0060: ldloc.0 + IL_0061: ldarg.1 + IL_0062: stfld "System.Threading.CancellationToken C.d__1.token3" + IL_0067: br.s IL_00bb + IL_0069: ldarga.s V_1 + IL_006b: ldarg.0 + IL_006c: ldfld "System.Threading.CancellationToken C.d__1.<>3__token3" + IL_0071: call "bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)" + IL_0076: brtrue.s IL_008a + IL_0078: ldarga.s V_1 + IL_007a: ldloca.s V_1 + IL_007c: initobj "System.Threading.CancellationToken" + IL_0082: ldloc.1 + IL_0083: call "bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)" + IL_0088: brfalse.s IL_0098 + IL_008a: ldloc.0 + IL_008b: ldarg.0 + IL_008c: ldfld "System.Threading.CancellationToken C.d__1.<>3__token3" + IL_0091: stfld "System.Threading.CancellationToken C.d__1.token3" + IL_0096: br.s IL_00bb + IL_0098: ldarg.0 + IL_0099: ldarg.0 + IL_009a: ldfld "System.Threading.CancellationToken C.d__1.<>3__token3" + IL_009f: ldarg.1 + IL_00a0: call "System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)" + IL_00a5: stfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" + IL_00aa: ldloc.0 + IL_00ab: ldarg.0 + IL_00ac: ldfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" + IL_00b1: callvirt "System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get" + IL_00b6: stfld "System.Threading.CancellationToken C.d__1.token3" + IL_00bb: ldloc.0 + IL_00bc: ldarg.0 + IL_00bd: ldfld "int C.d__1.<>3__value" + IL_00c2: stfld "int C.d__1.value" + IL_00c7: ldloc.0 + IL_00c8: ret +} +"""); + + static void verifyAsync2MembersAndInterfaces(ModuleSymbol module) + { + var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__1"); + AssertEx.SetEqual([ + "System.Int32 C.d__1.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__1.<>2__current", + "System.Boolean C.d__1.<>w__disposeMode", + "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens", + "System.Int32 C.d__1.<>l__initialThreadId", + "System.Threading.CancellationToken C.d__1.token1", + "System.Threading.CancellationToken C.d__1.<>3__token1", + "System.Threading.CancellationToken C.d__1.token2", + "System.Threading.CancellationToken C.d__1.<>3__token2", + "System.Threading.CancellationToken C.d__1.token3", + "System.Threading.CancellationToken C.d__1.<>3__token3", + "System.Int32 C.d__1.value", + "System.Int32 C.d__1.<>3__value", + "C.d__1..ctor(System.Int32 <>1__state)", + "System.Collections.Generic.IAsyncEnumerator C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator([System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)])", + "System.Threading.Tasks.ValueTask C.d__1.MoveNextAsync()", + "System.Int32 C.d__1.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Threading.Tasks.ValueTask C.d__1.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__1.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); + } + } + + [Theory, CombinatorialData, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + public void CancellationTokenParameter_SomeOtherTokenPassedInGetAsyncEnumerator_LocalFunction(bool useDebug, bool cancelFirst) { + var options = useDebug ? TestOptions.DebugExe : TestOptions.ReleaseExe; + var sourceToCancel = cancelFirst ? "source1" : "source2"; + string source = @" using static System.Console; using System.Runtime.CompilerServices; @@ -8071,17 +9074,18 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(CancellationT yield return value++; } } -}"; +}".Replace("SOURCETOCANCEL", sourceToCancel); + + var expectedOutput = "42 43 Cancelled"; + // cancelling either the token given as argument or the one given to GetAsyncEnumerator results in cancelling the combined token3 - foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) - { - foreach (var sourceToCancel in new[] { "source1", "source2" }) - { - var comp = CreateCompilationWithAsyncIterator(new[] { source.Replace("SOURCETOCANCEL", sourceToCancel), EnumeratorCancellationAttributeType }, options: options, parseOptions: TestOptions.Regular9); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); - } - } + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options, parseOptions: TestOptions.Regular9); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8112,9 +9116,14 @@ static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCa await Task.Yield(); } }"; + var expectedOutput = "1"; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "1"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8139,13 +9148,20 @@ static async Task Main() } } }"; - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, TestOptions.DebugExe); - comp.VerifyDiagnostics( + var expectedOutput = "42"; + DiagnosticDescription[] expectedDiagnostics = [ // (6,73): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'value' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-enumerable method // static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCancellation] int value) Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("value").WithLocation(6, 73) - ); - CompileAndVerify(comp, expectedOutput: "42"); + ]; + + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8170,13 +9186,20 @@ static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCa } } }"; - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, TestOptions.DebugExe, TestOptions.Regular9); - comp.VerifyDiagnostics( + var expectedOutput = "42"; + DiagnosticDescription[] expectedDiagnostics = [ // (12,77): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'value' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable // static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCancellation] int value) // 1 Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("value").WithLocation(12, 77) - ); - CompileAndVerify(comp, expectedOutput: "42"); + ]; + + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, TestOptions.DebugExe, TestOptions.Regular9); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8394,11 +9417,16 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E yield return value++; } }"; - // The parameter proxy is left untouched by our copying the token parameter of GetAsyncEnumerator + var expectedOutput = "42 43 Cancelled 42 43 Reached 44"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "42 43 Cancelled 42 43 Reached 44"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8439,14 +9467,21 @@ public override async System.Collections.Generic.IAsyncEnumerable Iter(Canc yield return value; } }"; - // The overridden method lacks the EnumeratorCancellation attribute - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics( - // (28,76): error CS8425: Async-iterator 'C.Iter(CancellationToken, int)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed + var expectedOutput = "Reached 42"; + DiagnosticDescription[] expectedDiagnostics = [ + // 0.cs(28,76): warning CS8425: Async-iterator 'C.Iter(CancellationToken, int)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed // public override async System.Collections.Generic.IAsyncEnumerable Iter(CancellationToken token1, int value) // 1 Diagnostic(ErrorCode.WRN_UndecoratedCancellationTokenParameter, "Iter").WithArguments("C.Iter(System.Threading.CancellationToken, int)").WithLocation(28, 76) - ); - CompileAndVerify(comp, expectedOutput: "Reached 42"); + ]; + + // The overridden method lacks the EnumeratorCancellation attribute + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -8497,14 +9532,22 @@ public override async System.Collections.Generic.IAsyncEnumerable Iter([Enu yield return value; } }"; - // The overridden method has the EnumeratorCancellation attribute - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics( - // (8,75): error CS8425: Async-iterator 'Base.Iter(CancellationToken, int)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed + var expectedOutput = "42 Cancelled"; + + DiagnosticDescription[] expectedDiagnostics = [ + // 0.cs(8,75): warning CS8425: Async-iterator 'Base.Iter(CancellationToken, int)' has one or more parameters of type 'CancellationToken' but none of them is decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed // public virtual async System.Collections.Generic.IAsyncEnumerable Iter(CancellationToken token1, int value) // 1 Diagnostic(ErrorCode.WRN_UndecoratedCancellationTokenParameter, "Iter").WithArguments("Base.Iter(System.Threading.CancellationToken, int)").WithLocation(8, 75) - ); - CompileAndVerify(comp, expectedOutput: "42 Cancelled"); + ]; + + // The overridden method has the EnumeratorCancellation attribute + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(expectedDiagnostics); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(expectedDiagnostics); } [Fact, WorkItem(35165, "https://github.com/dotnet/roslyn/issues/35165")] @@ -8534,19 +9577,19 @@ public partial class C2 // (6,76): error CS1994: The 'async' modifier can only be used in methods that have a body. // public abstract async System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token); // 1 Diagnostic(ErrorCode.ERR_BadAsyncLacksBody, "M").WithLocation(6, 76), - // (7,74): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable + // (7,74): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable // public abstract System.Collections.Generic.IAsyncEnumerable M2([EnumeratorCancellation] CancellationToken token); // 2 Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("token").WithLocation(7, 74), - // (11,57): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable + // (11,57): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable // System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token); // 3 Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("token").WithLocation(11, 57), - // (15,80): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable + // (15,80): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable // public delegate System.Collections.Generic.IAsyncEnumerable Delegate([EnumeratorCancellation] CancellationToken token); // 4 Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("token").WithLocation(15, 80), // (16,62): error CS8794: Partial method 'C2.M(CancellationToken)' must have accessibility modifiers because it has a non-void return type. // partial System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token); // 5 Diagnostic(ErrorCode.ERR_PartialMethodWithNonVoidReturnMustHaveAccessMods, "M").WithArguments("C2.M(System.Threading.CancellationToken)").WithLocation(16, 62), - // (16,65): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable + // (16,65): warning CS8424: The EnumeratorCancellationAttribute applied to parameter 'token' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable // partial System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token); // 5 Diagnostic(ErrorCode.WRN_UnconsumedEnumeratorCancellationAttributeUsage, "EnumeratorCancellation").WithArguments("token").WithLocation(16, 65), // (17,68): error CS8794: Partial method 'C2.M2(CancellationToken)' must have accessibility modifiers because it has a non-void return type. @@ -8652,7 +9695,7 @@ public partial class C3 [WorkItem(43936, "https://github.com/dotnet/roslyn/issues/43936")] public void TryFinallyNestedInsideFinally() { - var comp = CreateCompilationWithAsyncIterator(@" + var source = @" using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -8691,17 +9734,24 @@ public static async Task Main() break; } } -}", options: TestOptions.DebugExe); +}"; - var v = CompileAndVerify(comp, expectedOutput: "BEFORE INSIDE INSIDE2 AFTER"); - v.VerifyDiagnostics(); + var expectedOutput = "BEFORE INSIDE INSIDE2 AFTER"; + + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] [WorkItem(43936, "https://github.com/dotnet/roslyn/issues/43936")] public void TryFinallyNestedInsideFinally_WithAwaitInFinally() { - var comp = CreateCompilationWithAsyncIterator(@" + var source = @" using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -8740,17 +9790,23 @@ public static async Task Main() break; } } -}", options: TestOptions.DebugExe); +}"; + var expectedOutput = "BEFORE INSIDE INSIDE2 AFTER"; + + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "BEFORE INSIDE INSIDE2 AFTER"); - v.VerifyDiagnostics(); + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] [WorkItem(43936, "https://github.com/dotnet/roslyn/issues/43936")] public void TryFinallyNestedInsideFinally_WithAwaitInNestedFinally() { - var comp = CreateCompilationWithAsyncIterator(@" + var source = @" using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -8789,18 +9845,22 @@ public static async Task Main() break; } } -}", options: TestOptions.DebugExe); +}"; + var expectedOutput = "BEFORE INSIDE INSIDE2 AFTER"; + + var comp = CreateCompilationWithAsyncIterator(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); - var v = CompileAndVerify(comp, expectedOutput: "BEFORE INSIDE INSIDE2 AFTER"); - v.VerifyDiagnostics(); + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(58444, "https://github.com/dotnet/roslyn/issues/58444")] public void ClearCurrentOnRegularExit() { var source = @" -using System; -using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8837,8 +9897,15 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cance } } "; + var expectedOutput = "RAN RAN RAN CLEARED"; + var comp = CreateCompilationWithAsyncIterator(source); - CompileAndVerify(comp, expectedOutput: "RAN RAN RAN CLEARED"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(58444, "https://github.com/dotnet/roslyn/issues/58444")] @@ -8846,7 +9913,6 @@ public void ClearCurrentOnException() { var source = @" using System; -using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8890,16 +9956,21 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cance } } "; + var expectedOutput = "RAN RAN EXCEPTION CLEARED"; + var comp = CreateCompilationWithAsyncIterator(source); - CompileAndVerify(comp, expectedOutput: "RAN RAN EXCEPTION CLEARED"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem(58444, "https://github.com/dotnet/roslyn/issues/58444")] public void ClearCurrentOnRegularExit_Generic() { var source = @" -using System; -using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8936,8 +10007,15 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati } } "; + var expectedOutput = "RAN RAN RAN CLEARED"; + var comp = CreateCompilationWithAsyncIterator(source); - CompileAndVerify(comp, expectedOutput: "RAN RAN RAN CLEARED"); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74362")] @@ -8981,9 +10059,15 @@ async IAsyncEnumerable Do() } } """; + var expectedOutput = "42"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: ExpectedOutput("42"), verify: Verification.FailsPEVerify); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74362")] @@ -9016,9 +10100,15 @@ public static async IAsyncEnumerable Do() } } """; + var expectedOutput = "42"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: ExpectedOutput("42"), verify: Verification.FailsPEVerify); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74362")] @@ -9057,9 +10147,15 @@ async Task Do() } } """; + var expectedOutput = "42"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.DebugExe); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: ExpectedOutput("42"), verify: Verification.FailsPEVerify); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73563")] @@ -9312,9 +10408,10 @@ public static async IAsyncEnumerable M(Task t) } } """; + var expectedOutput = "first True second"; var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "first True second" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """" @@ -9467,6 +10564,10 @@ .locals init (int V_0, IL_015c: ret } """"); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9510,10 +10611,16 @@ public static async IAsyncEnumerable M(Task t) } } """; + var expectedOutput = "first True second"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "first True second" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9551,10 +10658,16 @@ public static async IAsyncEnumerable M(Task t) } } """; + var expectedOutput = "424243"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "424243" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9596,10 +10709,15 @@ public static async IAsyncEnumerable M(Task t) } } """; + var expectedOutput = "424243"; var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "424243" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9637,9 +10755,11 @@ public static async IAsyncEnumerable M(Task t, T t1, T t2) } } """; + var expectedOutput = "first True second"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "first True second" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """" @@ -9791,6 +10911,57 @@ .locals init (int V_0, IL_0169: ret } """"); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 99 (0x63) + .maxstack 2 + .locals init (C.d__0 V_0, + short V_1, + System.Threading.Tasks.ValueTask V_2) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_0014 + IL_000a: ldloca.s V_2 + IL_000c: initobj "System.Threading.Tasks.ValueTask" + IL_0012: ldloc.2 + IL_0013: ret + IL_0014: ldarg.0 + IL_0015: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_001a: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.Reset()" + IL_001f: ldarg.0 + IL_0020: stloc.0 + IL_0021: ldarg.0 + IL_0022: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_0027: ldloca.s V_0 + IL_0029: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.MoveNextd__0>(ref C.d__0)" + IL_002e: ldarg.0 + IL_002f: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_0034: call "short System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.Version.get" + IL_0039: stloc.1 + IL_003a: ldarg.0 + IL_003b: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_0040: ldloc.1 + IL_0041: call "System.Threading.Tasks.Sources.ValueTaskSourceStatus System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.GetStatus(short)" + IL_0046: ldc.i4.1 + IL_0047: bne.un.s IL_005b + IL_0049: ldarg.0 + IL_004a: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_004f: ldloc.1 + IL_0050: call "bool System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.GetResult(short)" + IL_0055: newobj "System.Threading.Tasks.ValueTask..ctor(bool)" + IL_005a: ret + IL_005b: ldarg.0 + IL_005c: ldloc.1 + IL_005d: newobj "System.Threading.Tasks.ValueTask..ctor(System.Threading.Tasks.Sources.IValueTaskSource, short)" + IL_0062: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9833,10 +11004,16 @@ public static async IAsyncEnumerable> M(Task t, T t1, T t2) } } """; + var expectedOutput = "first True second"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "first True second" : null, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74013")] @@ -9879,10 +11056,16 @@ public static async IAsyncEnumerable> M(Task t, T t1, T t2) where T : un } } """; + var expectedOutput = "42False43"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); - var verifier = CompileAndVerify(comp, - expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? "42False43" : null, + CompileAndVerify(comp, + expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -9905,7 +11088,95 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce() } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("42"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "42"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 145 (0x91) + .maxstack 3 + .locals init (int V_0, + bool V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0050 + IL_000c: ldloc.0 + IL_000d: ldc.i4.s -3 + IL_000f: pop + IL_0010: pop + IL_0011: ldarg.0 + IL_0012: ldfld "bool C.d__0.<>w__disposeMode" + IL_0017: brfalse.s IL_001b + IL_0019: leave.s IL_007e + IL_001b: ldarg.0 + IL_001c: ldc.i4.m1 + IL_001d: dup + IL_001e: stloc.0 + IL_001f: stfld "int C.d__0.<>1__state" + IL_0024: ldarg.0 + IL_0025: ldc.i4.s 42 + IL_0027: stfld "int C.d__0.5__2" + IL_002c: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0031: call "void System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_0036: ldarg.0 + IL_0037: ldarg.0 + IL_0038: ldfld "int C.d__0.5__2" + IL_003d: stfld "int C.d__0.<>2__current" + IL_0042: ldarg.0 + IL_0043: ldc.i4.s -4 + IL_0045: dup + IL_0046: stloc.0 + IL_0047: stfld "int C.d__0.<>1__state" + IL_004c: ldc.i4.1 + IL_004d: stloc.1 + IL_004e: leave.s IL_008f + IL_0050: ldarg.0 + IL_0051: ldc.i4.m1 + IL_0052: dup + IL_0053: stloc.0 + IL_0054: stfld "int C.d__0.<>1__state" + IL_0059: ldarg.0 + IL_005a: ldfld "bool C.d__0.<>w__disposeMode" + IL_005f: brfalse.s IL_0063 + IL_0061: leave.s IL_007e + IL_0063: ldarg.0 + IL_0064: ldc.i4.1 + IL_0065: stfld "bool C.d__0.<>w__disposeMode" + IL_006a: leave.s IL_007e + } + catch System.Exception + { + IL_006c: pop + IL_006d: ldarg.0 + IL_006e: ldc.i4.s -2 + IL_0070: stfld "int C.d__0.<>1__state" + IL_0075: ldarg.0 + IL_0076: ldc.i4.0 + IL_0077: stfld "int C.d__0.<>2__current" + IL_007c: rethrow + } + IL_007e: ldarg.0 + IL_007f: ldc.i4.s -2 + IL_0081: stfld "int C.d__0.<>1__state" + IL_0086: ldarg.0 + IL_0087: ldc.i4.0 + IL_0088: stfld "int C.d__0.<>2__current" + IL_008d: ldc.i4.0 + IL_008e: stloc.1 + IL_008f: ldloc.1 + IL_0090: ret +} +"""); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -9932,9 +11203,12 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce() } } """; - var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value True"; + + var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ -{ + { // Code size 320 (0x140) .maxstack 3 .locals init (int V_0, @@ -10069,6 +11343,99 @@ .locals init (int V_0, IL_013a: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" IL_013f: ret } +"""); + var comp = CreateRuntimeAsyncCompilation(src); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 168 (0xa8) + .maxstack 3 + .locals init (int V_0, + bool V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_004e + IL_000c: ldloc.0 + IL_000d: ldc.i4.s -3 + IL_000f: pop + IL_0010: pop + IL_0011: ldarg.0 + IL_0012: ldfld "bool C.d__0.<>w__disposeMode" + IL_0017: brfalse.s IL_001b + IL_0019: leave.s IL_008e + IL_001b: ldarg.0 + IL_001c: ldc.i4.m1 + IL_001d: dup + IL_001e: stloc.0 + IL_001f: stfld "int C.d__0.<>1__state" + IL_0024: ldarg.0 + IL_0025: ldstr "value " + IL_002a: stfld "string C.d__0.5__2" + IL_002f: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0034: call "void System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_0039: ldarg.0 + IL_003a: ldc.i4.1 + IL_003b: stfld "int C.d__0.<>2__current" + IL_0040: ldarg.0 + IL_0041: ldc.i4.s -4 + IL_0043: dup + IL_0044: stloc.0 + IL_0045: stfld "int C.d__0.<>1__state" + IL_004a: ldc.i4.1 + IL_004b: stloc.1 + IL_004c: leave.s IL_00a6 + IL_004e: ldarg.0 + IL_004f: ldc.i4.m1 + IL_0050: dup + IL_0051: stloc.0 + IL_0052: stfld "int C.d__0.<>1__state" + IL_0057: ldarg.0 + IL_0058: ldfld "bool C.d__0.<>w__disposeMode" + IL_005d: brfalse.s IL_0061 + IL_005f: leave.s IL_008e + IL_0061: ldarg.0 + IL_0062: ldfld "string C.d__0.5__2" + IL_0067: call "void System.Console.Write(string)" + IL_006c: ldarg.0 + IL_006d: ldc.i4.1 + IL_006e: stfld "bool C.d__0.<>w__disposeMode" + IL_0073: leave.s IL_008e + } + catch System.Exception + { + IL_0075: pop + IL_0076: ldarg.0 + IL_0077: ldc.i4.s -2 + IL_0079: stfld "int C.d__0.<>1__state" + IL_007e: ldarg.0 + IL_007f: ldnull + IL_0080: stfld "string C.d__0.5__2" + IL_0085: ldarg.0 + IL_0086: ldc.i4.0 + IL_0087: stfld "int C.d__0.<>2__current" + IL_008c: rethrow + } + IL_008e: ldarg.0 + IL_008f: ldc.i4.s -2 + IL_0091: stfld "int C.d__0.<>1__state" + IL_0096: ldarg.0 + IL_0097: ldnull + IL_0098: stfld "string C.d__0.5__2" + IL_009d: ldarg.0 + IL_009e: ldc.i4.0 + IL_009f: stfld "int C.d__0.<>2__current" + IL_00a4: ldc.i4.0 + IL_00a5: stloc.1 + IL_00a6: ldloc.1 + IL_00a7: ret +} """); } @@ -10098,7 +11465,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10131,7 +11505,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value exception True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10159,7 +11540,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10203,7 +11591,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10242,7 +11637,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10288,7 +11690,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value exception True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10321,7 +11730,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10371,7 +11787,9 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput("4242"), references: [libComp.EmitToImageReference()], + var expectedOutput = "4242"; + + var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), references: [libComp.EmitToImageReference()], verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ @@ -10557,6 +11975,10 @@ .locals init (int V_0, IL_01b4: ret } """); + libComp = CreateCompilation(libSrc, targetFramework: TargetFramework.Net100); + var comp = CreateRuntimeAsyncCompilation(src, references: [libComp.EmitToImageReference()]); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10596,7 +12018,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce(boo } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("exception True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "exception True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10627,7 +12056,14 @@ public static async System.Collections.Generic.IAsyncEnumerator Produce() } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10668,7 +12104,14 @@ public static async System.Collections.Generic.IAsyncEnumerable Produce() } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("value value outer True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "value value outer True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10710,7 +12153,14 @@ public struct Buffer2 private T _element0; } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("False 0 False 1 True"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "False 0 False 1 True"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75666")] @@ -10752,7 +12202,9 @@ public struct Buffer2 private T _element0; } """; - var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput("False 0 False 1 True"), + var expectedOutput = "False 0 False 1 True"; + + var verifier = CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80, options: TestOptions.DebugExe); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ @@ -10947,6 +12399,9 @@ .locals init (int V_0, IL_01c0: ret } """); + var comp = CreateRuntimeAsyncCompilation(src, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -11016,7 +12471,14 @@ public static async System.Collections.Generic.IAsyncEnumerator Produce( } } """; - CompileAndVerify(src, expectedOutput: ExpectedOutput("True one False null"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics(); + var expectedOutput = "True one False null"; + + CompileAndVerify(src, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped, targetFramework: TargetFramework.Net80) + .VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -11915,5 +13377,2470 @@ .locals init (int V_0, } """); } + + [Fact] + public void RuntimeAsync_01() + { + // simplest scenario + string source = """ +using static System.Console; + +await using var enumerator = C.M().GetAsyncEnumerator(); +var found = await enumerator.MoveNextAsync(); +if (!found) throw new System.Exception("A"); +var value = enumerator.Current; +Write($"{value} "); +found = await enumerator.MoveNextAsync(); +if (found) throw new System.Exception("B"); +found = await enumerator.MoveNextAsync(); +if (found) throw new System.Exception("C"); +Write("5"); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + Write("1 "); + await System.Threading.Tasks.Task.CompletedTask; + Write("2 "); + yield return 3; + Write("4 "); + } +} +"""; + var comp = CreateRuntimeAsyncCompilation(source); + + Verification expectedVerification = Verification.FailsILVerify with + { + ILVerifyMessage = """ + [
$]: Return value missing on the stack. { Offset = 0xce } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0xa1, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } + """ + }; + + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput("1 2 3 4 5"), + symbolValidator: verify, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? expectedVerification : Verification.Skipped); + // Note: for runtime-async codegen, it is expected that we place bool instead of ValueTask on the stack, so IL verification is expected to fail. + + // PROTOTYPE confirm what attribute the kickoff method should have (if any) + verifier.VerifyTypeIL("C", """ +.class private auto ansi beforefieldinit C + extends [System.Runtime]System.Object +{ + // Nested Types + .class nested private auto ansi sealed beforefieldinit 'd__0' + extends [System.Runtime]System.Object + implements class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1, + class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1, + [System.Runtime]System.IAsyncDisposable + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public int32 '<>1__state' + .field private int32 '<>2__current' + .field private bool '<>w__disposeMode' + .field private int32 '<>l__initialThreadId' + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor ( + int32 '<>1__state' + ) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + // Code size 25 (0x19) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld int32 C/'d__0'::'<>1__state' + IL_000d: ldarg.0 + IL_000e: call int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId() + IL_0013: stfld int32 C/'d__0'::'<>l__initialThreadId' + IL_0018: ret + } // end of method 'd__0'::.ctor + .method private final hidebysig newslot virtual + instance class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1 'System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator' ( + [opt] valuetype [System.Runtime]System.Threading.CancellationToken cancellationToken + ) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1::GetAsyncEnumerator(valuetype [System.Runtime]System.Threading.CancellationToken) + .param [0] + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .param [1] = nullref + // Code size 52 (0x34) + .maxstack 2 + .locals init ( + [0] class C/'d__0' + ) + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_002a + IL_000a: ldarg.0 + IL_000b: ldfld int32 C/'d__0'::'<>l__initialThreadId' + IL_0010: call int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId() + IL_0015: bne.un.s IL_002a + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld int32 C/'d__0'::'<>1__state' + IL_001f: ldarg.0 + IL_0020: ldc.i4.0 + IL_0021: stfld bool C/'d__0'::'<>w__disposeMode' + IL_0026: ldarg.0 + IL_0027: stloc.0 + IL_0028: br.s IL_0032 + IL_002a: ldc.i4.s -3 + IL_002c: newobj instance void C/'d__0'::.ctor(int32) + IL_0031: stloc.0 + IL_0032: ldloc.0 + IL_0033: ret + } // end of method 'd__0'::'System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator' + .method private final hidebysig newslot virtual + instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 MoveNextAsync () cil managed flag(2000) + { + .override method instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::MoveNextAsync() + // Code size 162 (0xa2) + .maxstack 3 + .locals init ( + [0] int32, + [1] bool + ) + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0057 + IL_000c: ldloc.0 + IL_000d: ldc.i4.s -3 + IL_000f: pop + IL_0010: pop + IL_0011: ldarg.0 + IL_0012: ldfld bool C/'d__0'::'<>w__disposeMode' + IL_0017: brfalse.s IL_001b + IL_0019: leave.s IL_008f + IL_001b: ldarg.0 + IL_001c: ldc.i4.m1 + IL_001d: dup + IL_001e: stloc.0 + IL_001f: stfld int32 C/'d__0'::'<>1__state' + IL_0024: ldstr "1 " + IL_0029: call void [System.Console]System.Console::Write(string) + IL_002e: call class [System.Runtime]System.Threading.Tasks.Task [System.Runtime]System.Threading.Tasks.Task::get_CompletedTask() + IL_0033: call void [System.Runtime]System.Runtime.CompilerServices.AsyncHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0038: ldstr "2 " + IL_003d: call void [System.Console]System.Console::Write(string) + IL_0042: ldarg.0 + IL_0043: ldc.i4.3 + IL_0044: stfld int32 C/'d__0'::'<>2__current' + IL_0049: ldarg.0 + IL_004a: ldc.i4.s -4 + IL_004c: dup + IL_004d: stloc.0 + IL_004e: stfld int32 C/'d__0'::'<>1__state' + IL_0053: ldc.i4.1 + IL_0054: stloc.1 + IL_0055: leave.s IL_00a0 + IL_0057: ldarg.0 + IL_0058: ldc.i4.m1 + IL_0059: dup + IL_005a: stloc.0 + IL_005b: stfld int32 C/'d__0'::'<>1__state' + IL_0060: ldarg.0 + IL_0061: ldfld bool C/'d__0'::'<>w__disposeMode' + IL_0066: brfalse.s IL_006a + IL_0068: leave.s IL_008f + IL_006a: ldstr "4 " + IL_006f: call void [System.Console]System.Console::Write(string) + IL_0074: ldarg.0 + IL_0075: ldc.i4.1 + IL_0076: stfld bool C/'d__0'::'<>w__disposeMode' + IL_007b: leave.s IL_008f + } // end .try + catch [System.Runtime]System.Exception + { + IL_007d: pop + IL_007e: ldarg.0 + IL_007f: ldc.i4.s -2 + IL_0081: stfld int32 C/'d__0'::'<>1__state' + IL_0086: ldarg.0 + IL_0087: ldc.i4.0 + IL_0088: stfld int32 C/'d__0'::'<>2__current' + IL_008d: rethrow + } // end handler + IL_008f: ldarg.0 + IL_0090: ldc.i4.s -2 + IL_0092: stfld int32 C/'d__0'::'<>1__state' + IL_0097: ldarg.0 + IL_0098: ldc.i4.0 + IL_0099: stfld int32 C/'d__0'::'<>2__current' + IL_009e: ldc.i4.0 + IL_009f: stloc.1 + IL_00a0: ldloc.1 + IL_00a1: ret + } // end of method 'd__0'::MoveNextAsync + .method private final hidebysig specialname newslot virtual + instance int32 'System.Collections.Generic.IAsyncEnumerator.get_Current' () cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance !0 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::get_Current() + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>2__current' + IL_0006: ret + } // end of method 'd__0'::'System.Collections.Generic.IAsyncEnumerator.get_Current' + .method private final hidebysig newslot virtual + instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync () cil managed flag(2000) + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask [System.Runtime]System.IAsyncDisposable::DisposeAsync() + // Code size 46 (0x2e) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: ldc.i4.m1 + IL_0007: blt.s IL_000f + IL_0009: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() + IL_000e: throw + IL_000f: ldarg.0 + IL_0010: ldfld int32 C/'d__0'::'<>1__state' + IL_0015: ldc.i4.s -2 + IL_0017: bne.un.s IL_001a + IL_0019: ret + IL_001a: ldarg.0 + IL_001b: ldc.i4.1 + IL_001c: stfld bool C/'d__0'::'<>w__disposeMode' + IL_0021: ldarg.0 + IL_0022: callvirt instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::MoveNextAsync() + IL_0027: call !!0 [System.Runtime]System.Runtime.CompilerServices.AsyncHelpers::Await(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1) + IL_002c: pop + IL_002d: ret + } // end of method 'd__0'::System.IAsyncDisposable.DisposeAsync + // Properties + .property instance int32 'System.Collections.Generic.IAsyncEnumerator.Current'() + { + .get instance int32 C/'d__0'::'System.Collections.Generic.IAsyncEnumerator.get_Current'() + } + } // end of class d__0 + // Methods + .method public hidebysig static + class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1 M () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( + 01 00 09 43 2b 3c 4d 3e 64 5f 5f 30 00 00 + ) + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldc.i4.s -2 + IL_0002: newobj instance void C/'d__0'::.ctor(int32) + IL_0007: ret + } // end of method C::M + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ret + } // end of method C::.ctor +} // end of class C +"""); + // PROTOTYPE should this be debugger hidden? + verifier.VerifyIL("C.M()", """ +{ + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldc.i4.s -2 + IL_0002: newobj "C.d__0..ctor(int)" + IL_0007: ret +} +""", sequencePointDisplay: SequencePointDisplayMode.Enhanced); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 162 (0xa2) + .maxstack 3 + .locals init (int V_0, + bool V_1) + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0057 + IL_000c: ldloc.0 + IL_000d: ldc.i4.s -3 + IL_000f: pop + IL_0010: pop + IL_0011: ldarg.0 + IL_0012: ldfld "bool C.d__0.<>w__disposeMode" + IL_0017: brfalse.s IL_001b + IL_0019: leave.s IL_008f + IL_001b: ldarg.0 + IL_001c: ldc.i4.m1 + IL_001d: dup + IL_001e: stloc.0 + IL_001f: stfld "int C.d__0.<>1__state" + // sequence point: Write("1 "); + IL_0024: ldstr "1 " + IL_0029: call "void System.Console.Write(string)" + // sequence point: await System.Threading.Tasks.Task.CompletedTask; + IL_002e: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_0033: call "void System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + // sequence point: Write("2 "); + IL_0038: ldstr "2 " + IL_003d: call "void System.Console.Write(string)" + // sequence point: yield return 3; + IL_0042: ldarg.0 + IL_0043: ldc.i4.3 + IL_0044: stfld "int C.d__0.<>2__current" + IL_0049: ldarg.0 + IL_004a: ldc.i4.s -4 + IL_004c: dup + IL_004d: stloc.0 + IL_004e: stfld "int C.d__0.<>1__state" + IL_0053: ldc.i4.1 + IL_0054: stloc.1 + IL_0055: leave.s IL_00a0 + // sequence point: + IL_0057: ldarg.0 + IL_0058: ldc.i4.m1 + IL_0059: dup + IL_005a: stloc.0 + IL_005b: stfld "int C.d__0.<>1__state" + IL_0060: ldarg.0 + IL_0061: ldfld "bool C.d__0.<>w__disposeMode" + IL_0066: brfalse.s IL_006a + IL_0068: leave.s IL_008f + // sequence point: Write("4 "); + IL_006a: ldstr "4 " + IL_006f: call "void System.Console.Write(string)" + IL_0074: ldarg.0 + IL_0075: ldc.i4.1 + IL_0076: stfld "bool C.d__0.<>w__disposeMode" + IL_007b: leave.s IL_008f + } + catch System.Exception + { + // sequence point: + IL_007d: pop + IL_007e: ldarg.0 + IL_007f: ldc.i4.s -2 + IL_0081: stfld "int C.d__0.<>1__state" + IL_0086: ldarg.0 + IL_0087: ldc.i4.0 + IL_0088: stfld "int C.d__0.<>2__current" + IL_008d: rethrow + } + // sequence point: } + IL_008f: ldarg.0 + IL_0090: ldc.i4.s -2 + IL_0092: stfld "int C.d__0.<>1__state" + // sequence point: + IL_0097: ldarg.0 + IL_0098: ldc.i4.0 + IL_0099: stfld "int C.d__0.<>2__current" + IL_009e: ldc.i4.0 + IL_009f: stloc.1 + IL_00a0: ldloc.1 + IL_00a1: ret +} +""", sequencePointDisplay: SequencePointDisplayMode.Enhanced); + + void verify(ModuleSymbol module) + { + var test = module.ContainingAssembly.GetTypeByMetadataName("C").GetTypeMember("d__0"); + var f = test.GetMethod("MoveNextAsync"); + Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes); + } + } + + [Fact] + public void RuntimeAsync_02() + { + // scenario with parameter + string source = """ +using static System.Console; + +await using var enumerator = C.M(42).GetAsyncEnumerator(); +var found = await enumerator.MoveNextAsync(); +if (!found) throw null; +var value = enumerator.Current; +Write($"{value} "); +found = await enumerator.MoveNextAsync(); +if (found) throw null; +found = await enumerator.MoveNextAsync(); +if (found) throw null; +Write("5"); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(int value) + { + Write("1 "); + await System.Threading.Tasks.Task.CompletedTask; + Write("2 "); + yield return value; + Write("4 "); + } +} +"""; + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All)); + + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput("1 2 42 4 5"), + symbolValidator: verifyAsyncMembersAndInterfaces, verify: Verification.Skipped); + + verifier.VerifyTypeIL("C", """ +.class private auto ansi beforefieldinit C + extends [System.Runtime]System.Object +{ + // Nested Types + .class nested private auto ansi sealed beforefieldinit 'd__0' + extends [System.Runtime]System.Object + implements class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1, + class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1, + [System.Runtime]System.IAsyncDisposable + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public int32 '<>1__state' + .field private int32 '<>2__current' + .field private bool '<>w__disposeMode' + .field private int32 '<>l__initialThreadId' + .field private int32 'value' + .field public int32 '<>3__value' + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor ( + int32 '<>1__state' + ) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + // Code size 26 (0x1a) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: stfld int32 C/'d__0'::'<>1__state' + IL_000e: ldarg.0 + IL_000f: call int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId() + IL_0014: stfld int32 C/'d__0'::'<>l__initialThreadId' + IL_0019: ret + } // end of method 'd__0'::.ctor + .method private final hidebysig newslot virtual + instance class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1 'System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator' ( + [opt] valuetype [System.Runtime]System.Threading.CancellationToken cancellationToken + ) cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1::GetAsyncEnumerator(valuetype [System.Runtime]System.Threading.CancellationToken) + .param [0] + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .param [1] = nullref + // Code size 64 (0x40) + .maxstack 2 + .locals init ( + [0] class C/'d__0' + ) + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_002a + IL_000a: ldarg.0 + IL_000b: ldfld int32 C/'d__0'::'<>l__initialThreadId' + IL_0010: call int32 [System.Runtime]System.Environment::get_CurrentManagedThreadId() + IL_0015: bne.un.s IL_002a + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld int32 C/'d__0'::'<>1__state' + IL_001f: ldarg.0 + IL_0020: ldc.i4.0 + IL_0021: stfld bool C/'d__0'::'<>w__disposeMode' + IL_0026: ldarg.0 + IL_0027: stloc.0 + IL_0028: br.s IL_0032 + IL_002a: ldc.i4.s -3 + IL_002c: newobj instance void C/'d__0'::.ctor(int32) + IL_0031: stloc.0 + IL_0032: ldloc.0 + IL_0033: ldarg.0 + IL_0034: ldfld int32 C/'d__0'::'<>3__value' + IL_0039: stfld int32 C/'d__0'::'value' + IL_003e: ldloc.0 + IL_003f: ret + } // end of method 'd__0'::'System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator' + .method private final hidebysig newslot virtual + instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 MoveNextAsync () cil managed flag(2000) + { + .override method instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::MoveNextAsync() + // Code size 180 (0xb4) + .maxstack 3 + .locals init ( + [0] int32, + [1] bool + ) + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0015 + IL_000c: br.s IL_000e + IL_000e: ldloc.0 + IL_000f: ldc.i4.s -3 + IL_0011: beq.s IL_0017 + IL_0013: br.s IL_0019 + IL_0015: br.s IL_0068 + IL_0017: br.s IL_0019 + IL_0019: ldarg.0 + IL_001a: ldfld bool C/'d__0'::'<>w__disposeMode' + IL_001f: brfalse.s IL_0023 + IL_0021: leave.s IL_00a1 + IL_0023: ldarg.0 + IL_0024: ldc.i4.m1 + IL_0025: dup + IL_0026: stloc.0 + IL_0027: stfld int32 C/'d__0'::'<>1__state' + IL_002c: nop + IL_002d: ldstr "1 " + IL_0032: call void [System.Console]System.Console::Write(string) + IL_0037: nop + IL_0038: call class [System.Runtime]System.Threading.Tasks.Task [System.Runtime]System.Threading.Tasks.Task::get_CompletedTask() + IL_003d: call void [System.Runtime]System.Runtime.CompilerServices.AsyncHelpers::Await(class [System.Runtime]System.Threading.Tasks.Task) + IL_0042: nop + IL_0043: ldstr "2 " + IL_0048: call void [System.Console]System.Console::Write(string) + IL_004d: nop + IL_004e: ldarg.0 + IL_004f: ldarg.0 + IL_0050: ldfld int32 C/'d__0'::'value' + IL_0055: stfld int32 C/'d__0'::'<>2__current' + IL_005a: ldarg.0 + IL_005b: ldc.i4.s -4 + IL_005d: dup + IL_005e: stloc.0 + IL_005f: stfld int32 C/'d__0'::'<>1__state' + IL_0064: ldc.i4.1 + IL_0065: stloc.1 + IL_0066: leave.s IL_00b2 + IL_0068: ldarg.0 + IL_0069: ldc.i4.m1 + IL_006a: dup + IL_006b: stloc.0 + IL_006c: stfld int32 C/'d__0'::'<>1__state' + IL_0071: ldarg.0 + IL_0072: ldfld bool C/'d__0'::'<>w__disposeMode' + IL_0077: brfalse.s IL_007b + IL_0079: leave.s IL_00a1 + IL_007b: ldstr "4 " + IL_0080: call void [System.Console]System.Console::Write(string) + IL_0085: nop + IL_0086: ldarg.0 + IL_0087: ldc.i4.1 + IL_0088: stfld bool C/'d__0'::'<>w__disposeMode' + IL_008d: leave.s IL_00a1 + } // end .try + catch [System.Runtime]System.Exception + { + IL_008f: pop + IL_0090: ldarg.0 + IL_0091: ldc.i4.s -2 + IL_0093: stfld int32 C/'d__0'::'<>1__state' + IL_0098: ldarg.0 + IL_0099: ldc.i4.0 + IL_009a: stfld int32 C/'d__0'::'<>2__current' + IL_009f: rethrow + } // end handler + IL_00a1: ldarg.0 + IL_00a2: ldc.i4.s -2 + IL_00a4: stfld int32 C/'d__0'::'<>1__state' + IL_00a9: ldarg.0 + IL_00aa: ldc.i4.0 + IL_00ab: stfld int32 C/'d__0'::'<>2__current' + IL_00b0: ldc.i4.0 + IL_00b1: stloc.1 + IL_00b2: ldloc.1 + IL_00b3: ret + } // end of method 'd__0'::MoveNextAsync + .method private final hidebysig specialname newslot virtual + instance int32 'System.Collections.Generic.IAsyncEnumerator.get_Current' () cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance !0 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::get_Current() + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>2__current' + IL_0006: ret + } // end of method 'd__0'::'System.Collections.Generic.IAsyncEnumerator.get_Current' + .method private final hidebysig newslot virtual + instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync () cil managed flag(2000) + { + .custom instance void [System.Runtime]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask [System.Runtime]System.IAsyncDisposable::DisposeAsync() + // Code size 46 (0x2e) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 C/'d__0'::'<>1__state' + IL_0006: ldc.i4.m1 + IL_0007: blt.s IL_000f + IL_0009: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() + IL_000e: throw + IL_000f: ldarg.0 + IL_0010: ldfld int32 C/'d__0'::'<>1__state' + IL_0015: ldc.i4.s -2 + IL_0017: bne.un.s IL_001a + IL_0019: ret + IL_001a: ldarg.0 + IL_001b: ldc.i4.1 + IL_001c: stfld bool C/'d__0'::'<>w__disposeMode' + IL_0021: ldarg.0 + IL_0022: callvirt instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1 class [System.Runtime]System.Collections.Generic.IAsyncEnumerator`1::MoveNextAsync() + IL_0027: call !!0 [System.Runtime]System.Runtime.CompilerServices.AsyncHelpers::Await(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1) + IL_002c: pop + IL_002d: ret + } // end of method 'd__0'::System.IAsyncDisposable.DisposeAsync + // Properties + .property instance int32 'System.Collections.Generic.IAsyncEnumerator.Current'() + { + .get instance int32 C/'d__0'::'System.Collections.Generic.IAsyncEnumerator.get_Current'() + } + } // end of class d__0 + // Methods + .method public hidebysig static + class [System.Runtime]System.Collections.Generic.IAsyncEnumerable`1 M ( + int32 'value' + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( + 01 00 09 43 2b 3c 4d 3e 64 5f 5f 30 00 00 + ) + // Code size 15 (0xf) + .maxstack 8 + IL_0000: ldc.i4.s -2 + IL_0002: newobj instance void C/'d__0'::.ctor(int32) + IL_0007: dup + IL_0008: ldarg.0 + IL_0009: stfld int32 C/'d__0'::'<>3__value' + IL_000e: ret + } // end of method C::M + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method C::.ctor +} // end of class C +"""); + + static void verifyAsyncMembersAndInterfaces(ModuleSymbol module) + { + var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember("C.d__0"); + AssertEx.SetEqual([ + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current { get; }", + "System.Int32 C.d__0.<>2__current", + "System.Boolean C.d__0.<>w__disposeMode", + "System.Int32 C.d__0.<>l__initialThreadId", + "System.Int32 C.d__0.value", + "System.Int32 C.d__0.<>3__value", + "C.d__0..ctor(System.Int32 <>1__state)", + "System.Collections.Generic.IAsyncEnumerator C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator([System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)])", + "System.Threading.Tasks.ValueTask C.d__0.MoveNextAsync()", + "System.Int32 C.d__0.System.Collections.Generic.IAsyncEnumerator.Current.get", + "System.Threading.Tasks.ValueTask C.d__0.System.IAsyncDisposable.DisposeAsync()", + "System.Int32 C.d__0.<>1__state" + ], type.GetMembersUnordered().ToTestDisplayStrings()); + + AssertEx.SetEqual([ + "System.Collections.Generic.IAsyncEnumerable", + "System.Collections.Generic.IAsyncEnumerator", + "System.IAsyncDisposable" + ], type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToTestDisplayStrings()); + } + } + + [Fact] + public void RuntimeAsync_03() + { + // scenario with yield break + string source = """ +using static System.Console; + +await using var enumerator = C.M().GetAsyncEnumerator(); +var found = await enumerator.MoveNextAsync(); +if (found) throw null; +Write("5"); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + Write("1 "); + await System.Threading.Tasks.Task.CompletedTask; + + bool b = true; + if (b) + yield break; + + throw null; + } +} +"""; + var comp = CreateRuntimeAsyncCompilation(source); + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput("1 5")); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 100 (0x64) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -3 + IL_000a: pop + IL_000b: pop + IL_000c: ldarg.0 + IL_000d: ldfld "bool C.d__0.<>w__disposeMode" + IL_0012: brfalse.s IL_0016 + IL_0014: leave.s IL_0053 + IL_0016: ldarg.0 + IL_0017: ldc.i4.m1 + IL_0018: dup + IL_0019: stloc.0 + IL_001a: stfld "int C.d__0.<>1__state" + IL_001f: ldstr "1 " + IL_0024: call "void System.Console.Write(string)" + IL_0029: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get" + IL_002e: call "void System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_0033: ldc.i4.1 + IL_0034: brfalse.s IL_003f + IL_0036: ldarg.0 + IL_0037: ldc.i4.1 + IL_0038: stfld "bool C.d__0.<>w__disposeMode" + IL_003d: leave.s IL_0053 + IL_003f: ldnull + IL_0040: throw + } + catch System.Exception + { + IL_0041: pop + IL_0042: ldarg.0 + IL_0043: ldc.i4.s -2 + IL_0045: stfld "int C.d__0.<>1__state" + IL_004a: ldarg.0 + IL_004b: ldc.i4.0 + IL_004c: stfld "int C.d__0.<>2__current" + IL_0051: rethrow + } + IL_0053: ldarg.0 + IL_0054: ldc.i4.s -2 + IL_0056: stfld "int C.d__0.<>1__state" + IL_005b: ldarg.0 + IL_005c: ldc.i4.0 + IL_005d: stfld "int C.d__0.<>2__current" + IL_0062: ldc.i4.0 + IL_0063: ret +} +"""); + } + + [Fact] + public void RuntimeAsync_04() + { + var src = """ +var enumerator = local(); +var found = await enumerator.MoveNextAsync(); +if (!found) throw null; +System.Console.Write(enumerator.Current); + +found = await enumerator.MoveNextAsync(); +if (found) throw null; + +async System.Collections.Generic.IAsyncEnumerator local() +{ + yield return 42; + await System.Threading.Tasks.Task.Yield(); +} +"""; + var expectedOutput = "42"; + + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(src); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void DisposeCombinedTokens_01() + { + // CombinedTokens field is disposed/cleared when exiting normally + string source = """ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using CancellationTokenSource source1 = new CancellationTokenSource(); +CancellationToken token1 = source1.Token; +using CancellationTokenSource source2 = new CancellationTokenSource(); +CancellationToken token2 = source2.Token; + +var enumerable = C.M(token1, token2, token1); + +var enumerator = enumerable.GetAsyncEnumerator(token2); // some other token passed + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkCombinedTokens(enumerator); + +if (await enumerator.MoveNextAsync()) throw null; +System.Console.Write("end "); +checkCombinedTokens(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkCombinedTokens(enumerator); + +void checkCombinedTokens(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>x__combinedTokens", BindingFlags.Instance | BindingFlags.NonPublic); + if (field is null) throw null; + + var combinedToken = (CancellationTokenSource)field.GetValue(enumerator); + System.Console.Write(combinedToken is null ? "null " : "set "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(CancellationToken token1, CancellationToken token2, [EnumeratorCancellation] CancellationToken token3) + { + if (token3.Equals(token1) || token3.Equals(token2)) throw null; + yield return 1; + await Task.Yield(); + } +} +"""; + var expectedOutput = "1 set end null disposed null"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + var verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 275 (0x113) + .maxstack 3 + .locals init (int V_0, + bool V_1, + bool V_2, + System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter V_3, + System.Runtime.CompilerServices.YieldAwaitable V_4) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_0015 + IL_000c: br.s IL_000e + IL_000e: ldloc.0 + IL_000f: ldc.i4.s -3 + IL_0011: beq.s IL_0017 + IL_0013: br.s IL_0019 + IL_0015: br.s IL_0075 + IL_0017: br.s IL_0019 + IL_0019: ldarg.0 + IL_001a: ldfld "bool C.d__0.<>w__disposeMode" + IL_001f: brfalse.s IL_0026 + IL_0021: leave IL_00e5 + IL_0026: ldarg.0 + IL_0027: ldc.i4.m1 + IL_0028: dup + IL_0029: stloc.0 + IL_002a: stfld "int C.d__0.<>1__state" + IL_002f: nop + IL_0030: ldarg.0 + IL_0031: ldflda "System.Threading.CancellationToken C.d__0.token3" + IL_0036: ldarg.0 + IL_0037: ldfld "System.Threading.CancellationToken C.d__0.token1" + IL_003c: call "bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)" + IL_0041: brtrue.s IL_0056 + IL_0043: ldarg.0 + IL_0044: ldflda "System.Threading.CancellationToken C.d__0.token3" + IL_0049: ldarg.0 + IL_004a: ldfld "System.Threading.CancellationToken C.d__0.token2" + IL_004f: call "bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)" + IL_0054: br.s IL_0057 + IL_0056: ldc.i4.1 + IL_0057: stloc.1 + IL_0058: ldloc.1 + IL_0059: brfalse.s IL_005d + IL_005b: ldnull + IL_005c: throw + IL_005d: ldarg.0 + IL_005e: ldc.i4.1 + IL_005f: stfld "int C.d__0.<>2__current" + IL_0064: ldarg.0 + IL_0065: ldc.i4.s -4 + IL_0067: dup + IL_0068: stloc.0 + IL_0069: stfld "int C.d__0.<>1__state" + IL_006e: ldc.i4.1 + IL_006f: stloc.2 + IL_0070: leave IL_0111 + IL_0075: ldarg.0 + IL_0076: ldc.i4.m1 + IL_0077: dup + IL_0078: stloc.0 + IL_0079: stfld "int C.d__0.<>1__state" + IL_007e: ldarg.0 + IL_007f: ldfld "bool C.d__0.<>w__disposeMode" + IL_0084: brfalse.s IL_0088 + IL_0086: leave.s IL_00e5 + IL_0088: call "System.Runtime.CompilerServices.YieldAwaitable System.Threading.Tasks.Task.Yield()" + IL_008d: stloc.s V_4 + IL_008f: ldloca.s V_4 + IL_0091: call "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter System.Runtime.CompilerServices.YieldAwaitable.GetAwaiter()" + IL_0096: stloc.3 + IL_0097: ldloca.s V_3 + IL_0099: call "bool System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.IsCompleted.get" + IL_009e: brtrue.s IL_00a7 + IL_00a0: ldloc.3 + IL_00a1: call "void System.Runtime.CompilerServices.AsyncHelpers.UnsafeAwaitAwaiter(System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter)" + IL_00a6: nop + IL_00a7: ldloca.s V_3 + IL_00a9: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()" + IL_00ae: nop + IL_00af: ldarg.0 + IL_00b0: ldc.i4.1 + IL_00b1: stfld "bool C.d__0.<>w__disposeMode" + IL_00b6: leave.s IL_00e5 + } + catch System.Exception + { + IL_00b8: pop + IL_00b9: ldarg.0 + IL_00ba: ldc.i4.s -2 + IL_00bc: stfld "int C.d__0.<>1__state" + IL_00c1: ldarg.0 + IL_00c2: ldfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_00c7: brfalse.s IL_00dc + IL_00c9: ldarg.0 + IL_00ca: ldfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_00cf: callvirt "void System.Threading.CancellationTokenSource.Dispose()" + IL_00d4: nop + IL_00d5: ldarg.0 + IL_00d6: ldnull + IL_00d7: stfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_00dc: ldarg.0 + IL_00dd: ldc.i4.0 + IL_00de: stfld "int C.d__0.<>2__current" + IL_00e3: rethrow + } + IL_00e5: ldarg.0 + IL_00e6: ldc.i4.s -2 + IL_00e8: stfld "int C.d__0.<>1__state" + IL_00ed: ldarg.0 + IL_00ee: ldfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_00f3: brfalse.s IL_0108 + IL_00f5: ldarg.0 + IL_00f6: ldfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_00fb: callvirt "void System.Threading.CancellationTokenSource.Dispose()" + IL_0100: nop + IL_0101: ldarg.0 + IL_0102: ldnull + IL_0103: stfld "System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens" + IL_0108: ldarg.0 + IL_0109: ldc.i4.0 + IL_010a: stfld "int C.d__0.<>2__current" + IL_010f: ldc.i4.0 + IL_0110: stloc.2 + IL_0111: ldloc.2 + IL_0112: ret +} +"""); + } + + [Fact] + public void DisposeCombinedTokens_02() + { + // CombinedTokens field is disposed/cleared when exiting with exception + string source = """ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using CancellationTokenSource source1 = new CancellationTokenSource(); +CancellationToken token1 = source1.Token; +using CancellationTokenSource source2 = new CancellationTokenSource(); +CancellationToken token2 = source2.Token; + +var enumerable = C.M(token1, token2, token1); + +var enumerator = enumerable.GetAsyncEnumerator(token2); // some other token passed + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkCombinedTokens(enumerator); + +try +{ + await enumerator.MoveNextAsync(); + throw null; +} +catch (System.Exception) +{ + System.Console.Write("caught "); + checkCombinedTokens(enumerator); +} + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkCombinedTokens(enumerator); + +void checkCombinedTokens(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>x__combinedTokens", BindingFlags.Instance | BindingFlags.NonPublic); + if (field is null) throw null; + + var combinedToken = (CancellationTokenSource)field.GetValue(enumerator); + System.Console.Write(combinedToken is null ? "null " : "set "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(CancellationToken token1, CancellationToken token2, [EnumeratorCancellation] CancellationToken token3) + { + if (token3.Equals(token1) || token3.Equals(token2)) throw null; + yield return 1; + await Task.Yield(); + throw new System.Exception("exception "); + } +} +"""; + var expectedOutput = "1 set caught null disposed null"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void DisposeCombinedTokens_03() + { + // CombinedTokens field is disposed/cleared when exiting with yield break + string source = """ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using CancellationTokenSource source1 = new CancellationTokenSource(); +CancellationToken token1 = source1.Token; +using CancellationTokenSource source2 = new CancellationTokenSource(); +CancellationToken token2 = source2.Token; + +var enumerable = C.M(token1, token2, token1); + +var enumerator = enumerable.GetAsyncEnumerator(token2); // some other token passed + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkCombinedTokens(enumerator); + +if (await enumerator.MoveNextAsync()) throw null; +System.Console.Write("end "); +checkCombinedTokens(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkCombinedTokens(enumerator); + +void checkCombinedTokens(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>x__combinedTokens", BindingFlags.Instance | BindingFlags.NonPublic); + if (field is null) throw null; + + var combinedToken = (CancellationTokenSource)field.GetValue(enumerator); + System.Console.Write(combinedToken is null ? "null " : "set "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(CancellationToken token1, CancellationToken token2, [EnumeratorCancellation] CancellationToken token3) + { + if (token3.Equals(token1) || token3.Equals(token2)) throw null; + yield return 1; + await Task.Yield(); + bool b = true; + if (b) yield break; + + throw null; + } +} +"""; + var expectedOutput = "1 set end null disposed null"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_01() + { + // Observe state when enumeration terminates normally + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var enumerable = C.M(); + +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +if (await enumerator.MoveNextAsync()) throw null; +System.Console.Write("end "); +checkState(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + await Task.Yield(); + } +} +"""; + var expectedOutput = "-3 1 -4 end -2 disposed -2"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_02() + { + // Observe state when enumeration terminates with exception + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var enumerable = C.M(); +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +try +{ + await enumerator.MoveNextAsync(); + throw null; +} +catch (System.Exception) +{ + System.Console.Write("caught "); + checkState(enumerator); +} + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + await Task.Yield(); + throw new System.Exception("exception "); + } +} +"""; + var expectedOutput = "-3 1 -4 caught -2 disposed -2"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_03() + { + // Observe state when enumeration terminates with yield break + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var enumerable = C.M(); +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +if (await enumerator.MoveNextAsync()) throw null; +System.Console.Write("end "); +checkState(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + await Task.Yield(); + bool b = true; + if (b) yield break; + + throw null; + } +} +"""; + var expectedOutput = "-3 1 -4 end -2 disposed -2"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_04() + { + // Observe state when enumeration terminates with early disposal + string source = """ +using System.Reflection; + +var enumerable = C.M(); +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + throw null; + } +} +"""; + var expectedOutput = "-3 1 -4 disposed -2"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_05() + { + // Observe state when running + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var tcs = new System.Threading.Tasks.TaskCompletionSource(); +var enumerable = C.M(tcs.Task); +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +var promise = enumerator.MoveNextAsync(); +System.Console.Write("waiting "); +checkState(enumerator); + +tcs.SetResult(); +if (await promise) throw null; +System.Console.Write("done "); +checkState(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(Task task) + { + yield return 1; + await task; + } +} +"""; + + // Note: states for await suspensions are increasing from 0 + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("-3 1 -4 waiting 0 done -2 disposed -2"), verify: Verification.Skipped) + .VerifyDiagnostics(); + + // Note: the state machine is in running state when in an await + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput("-3 1 -4 waiting -1 done -2 disposed -2"), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_06() + { + // Observe state when running during early disposal + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var tcs = new TaskCompletionSource(); +var enumerable = C.M(tcs.Task); +var enumerator = enumerable.GetAsyncEnumerator(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +var promise = enumerator.DisposeAsync(); +System.Console.Write("waiting "); +checkState(enumerator); + +tcs.SetResult(); +await promise; +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(Task task) + { + try + { + yield return 1; + throw null; + } + finally + { + await task; + } + + throw null; + } +} +"""; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: ExpectedOutput("-3 1 -4 waiting 0 disposed -2"), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput("-3 1 -4 waiting -1 disposed -2"), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void ObserveState_07() + { + // Observe state when enumeration terminates normally, enumerator + string source = """ +using System.Reflection; +using System.Threading.Tasks; + +var enumerator = C.M(); +checkState(enumerator); + +if (!await enumerator.MoveNextAsync()) throw null; +System.Console.Write($"{enumerator.Current} "); // 1 +checkState(enumerator); + +if (await enumerator.MoveNextAsync()) throw null; +System.Console.Write("end "); +checkState(enumerator); + +await enumerator.DisposeAsync(); +System.Console.Write("disposed "); +checkState(enumerator); + +void checkState(System.Collections.Generic.IAsyncEnumerator enumerator) +{ + var type = enumerator.GetType(); + var field = type.GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public); + if (field is null) throw null; + + System.Console.Write($"{field.GetValue(enumerator)} "); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerator M() + { + yield return 1; + await Task.Yield(); + } +} +"""; + var expectedOutput = "-3 1 -4 end -2 disposed -2"; + + var comp = CreateCompilationWithAsyncIterator([source, EnumeratorCancellationAttributeType], options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: expectedOutput) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void RefHoisting_01() + { + // an await in the middle of a compound assignment involving a ref temp + string source = """ +using System.Threading.Tasks; + +await foreach (var i in C.M()) +{ + System.Console.Write(i); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M() + { + var array = new int[] { 42 }; + GetArray(array)[await GetIndexAsync()] += await GetIncrementAsync(); + yield return array[0]; + } + + static int[] GetArray(int[] array) + { + System.Console.Write("GetArray() "); + return array; + } + + static async Task GetIndexAsync() + { + System.Console.Write("GetIndexAsync() "); + await Task.Yield(); + return 0; + } + + static async Task GetIncrementAsync() + { + System.Console.Write("GetIncrementAsync() "); + await Task.Yield(); + return 1; + } +} +"""; + var expectedOutput = "GetArray() GetIndexAsync() GetIncrementAsync() 43"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Theory] + [InlineData("DebuggerHiddenAttribute")] + [InlineData("DebuggerNonUserCodeAttribute")] + [InlineData("DebuggerStepperBoundaryAttribute")] + [InlineData("DebuggerStepThroughAttribute")] + public void KickoffMethodAttributes_01(string attribute) + { + string source = $$""" +class C +{ + [System.Diagnostics.{{attribute}}] + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + await System.Threading.Tasks.Task.Yield(); + } +} +"""; + + var comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, verify: Verification.Skipped, symbolValidator: module => + { + var method = module.GlobalNamespace.GetTypeMember("C").GetTypeMember("d__0").GetMethod("MoveNextAsync"); + AssertEx.SetEqual([attribute], GetAttributeNames(method.GetAttributes())); + }); + } + + [Fact] + public void KickoffMethodAttributes_02() + { + string source = """ +class C +{ + [My] + public static async System.Collections.Generic.IAsyncEnumerable M() + { + yield return 1; + await System.Threading.Tasks.Task.Yield(); + } +} +class MyAttribute : System.Attribute { } +"""; + + var comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, verify: Verification.Skipped, symbolValidator: module => + { + var moveNextMethod = module.GlobalNamespace.GetTypeMember("C").GetTypeMember("d__0").GetMethod("MoveNextAsync"); + Assert.Empty(moveNextMethod.GetAttributes()); + + var mMethod = module.GlobalNamespace.GetTypeMember("C").GetMethod("M"); + AssertEx.SetEqual(["System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute(typeof(C.d__0))", "MyAttribute"], mMethod.GetAttributes().ToStrings()); + }); + } + + [Fact] + public void Lock_01() + { + // yield return in lock body, with Lock type + string source = """ +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.Threading.Lock l) + { + lock (l) + { + System.Console.Write(1); + yield return 2; + System.Console.Write(3); + } + + await System.Threading.Tasks.Task.Yield(); + } +} +"""; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + comp.VerifyEmitDiagnostics( + // (5,15): error CS4007: Instance of type 'System.Threading.Lock.Scope' cannot be preserved across 'await' or 'yield' boundary. + // lock (l) + Diagnostic(ErrorCode.ERR_ByRefTypeAndAwait, "l").WithArguments("System.Threading.Lock.Scope").WithLocation(5, 15)); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyEmitDiagnostics( + // (5,15): error CS4007: Instance of type 'System.Threading.Lock.Scope' cannot be preserved across 'await' or 'yield' boundary. + // lock (l) + Diagnostic(ErrorCode.ERR_ByRefTypeAndAwait, "l").WithArguments("System.Threading.Lock.Scope").WithLocation(5, 15)); + } + + [Fact] + public void Lock_02() + { + // yield return in lock body, with object type, async-enumerable + string source = """ +object o = new object(); +await foreach (var i in C.M(o)) +{ + System.Console.Write(i); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(object o) + { + lock (o) + { + System.Console.Write(1); + yield return 2; + System.Console.Write(3); + } + + await System.Threading.Tasks.Task.Yield(); + } +} + +namespace System.Threading +{ + public class Monitor + { + public static void Enter(object obj, ref bool lockTaken) + { + System.Console.Write("Enter "); + lockTaken = true; + } + + public static void Exit(object obj) + { + System.Console.Write(" Exit"); + } + } +} +"""; + + var expectedOutput = "Enter 123 Exit"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + var verifier = CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ +{ + // Code size 406 (0x196) + .maxstack 3 + .locals init (int V_0, + System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter V_1, + System.Runtime.CompilerServices.YieldAwaitable V_2, + C.d__0 V_3, + System.Exception V_4) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: switch ( + IL_004d, + IL_0024, + IL_0024, + IL_0024, + IL_0104) + IL_0024: ldarg.0 + IL_0025: ldfld "bool C.d__0.<>w__disposeMode" + IL_002a: brfalse.s IL_0031 + IL_002c: leave IL_015b + IL_0031: ldarg.0 + IL_0032: ldc.i4.m1 + IL_0033: dup + IL_0034: stloc.0 + IL_0035: stfld "int C.d__0.<>1__state" + IL_003a: ldarg.0 + IL_003b: ldarg.0 + IL_003c: ldfld "object C.d__0.o" + IL_0041: stfld "object C.d__0.<>7__wrap1" + IL_0046: ldarg.0 + IL_0047: ldc.i4.0 + IL_0048: stfld "bool C.d__0.<>7__wrap2" + IL_004d: nop + .try + { + IL_004e: ldloc.0 + IL_004f: ldc.i4.s -4 + IL_0051: beq.s IL_0080 + IL_0053: ldarg.0 + IL_0054: ldfld "object C.d__0.<>7__wrap1" + IL_0059: ldarg.0 + IL_005a: ldflda "bool C.d__0.<>7__wrap2" + IL_005f: call "void System.Threading.Monitor.Enter(object, ref bool)" + IL_0064: ldc.i4.1 + IL_0065: call "void System.Console.Write(int)" + IL_006a: ldarg.0 + IL_006b: ldc.i4.2 + IL_006c: stfld "int C.d__0.<>2__current" + IL_0071: ldarg.0 + IL_0072: ldc.i4.s -4 + IL_0074: dup + IL_0075: stloc.0 + IL_0076: stfld "int C.d__0.<>1__state" + IL_007b: leave IL_0189 + IL_0080: ldarg.0 + IL_0081: ldc.i4.m1 + IL_0082: dup + IL_0083: stloc.0 + IL_0084: stfld "int C.d__0.<>1__state" + IL_0089: ldarg.0 + IL_008a: ldfld "bool C.d__0.<>w__disposeMode" + IL_008f: brfalse.s IL_0093 + IL_0091: leave.s IL_00b3 + IL_0093: ldc.i4.3 + IL_0094: call "void System.Console.Write(int)" + IL_0099: leave.s IL_00b3 + } + finally + { + IL_009b: ldloc.0 + IL_009c: ldc.i4.m1 + IL_009d: bne.un.s IL_00b2 + IL_009f: ldarg.0 + IL_00a0: ldfld "bool C.d__0.<>7__wrap2" + IL_00a5: brfalse.s IL_00b2 + IL_00a7: ldarg.0 + IL_00a8: ldfld "object C.d__0.<>7__wrap1" + IL_00ad: call "void System.Threading.Monitor.Exit(object)" + IL_00b2: endfinally + } + IL_00b3: ldarg.0 + IL_00b4: ldfld "bool C.d__0.<>w__disposeMode" + IL_00b9: brfalse.s IL_00c0 + IL_00bb: leave IL_015b + IL_00c0: ldarg.0 + IL_00c1: ldnull + IL_00c2: stfld "object C.d__0.<>7__wrap1" + IL_00c7: call "System.Runtime.CompilerServices.YieldAwaitable System.Threading.Tasks.Task.Yield()" + IL_00cc: stloc.2 + IL_00cd: ldloca.s V_2 + IL_00cf: call "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter System.Runtime.CompilerServices.YieldAwaitable.GetAwaiter()" + IL_00d4: stloc.1 + IL_00d5: ldloca.s V_1 + IL_00d7: call "bool System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.IsCompleted.get" + IL_00dc: brtrue.s IL_0120 + IL_00de: ldarg.0 + IL_00df: ldc.i4.0 + IL_00e0: dup + IL_00e1: stloc.0 + IL_00e2: stfld "int C.d__0.<>1__state" + IL_00e7: ldarg.0 + IL_00e8: ldloc.1 + IL_00e9: stfld "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter C.d__0.<>u__1" + IL_00ee: ldarg.0 + IL_00ef: stloc.3 + IL_00f0: ldarg.0 + IL_00f1: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_00f6: ldloca.s V_1 + IL_00f8: ldloca.s V_3 + IL_00fa: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter, ref C.d__0)" + IL_00ff: leave IL_0195 + IL_0104: ldarg.0 + IL_0105: ldfld "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter C.d__0.<>u__1" + IL_010a: stloc.1 + IL_010b: ldarg.0 + IL_010c: ldflda "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter C.d__0.<>u__1" + IL_0111: initobj "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter" + IL_0117: ldarg.0 + IL_0118: ldc.i4.m1 + IL_0119: dup + IL_011a: stloc.0 + IL_011b: stfld "int C.d__0.<>1__state" + IL_0120: ldloca.s V_1 + IL_0122: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()" + IL_0127: leave.s IL_015b + } + catch System.Exception + { + IL_0129: stloc.s V_4 + IL_012b: ldarg.0 + IL_012c: ldc.i4.s -2 + IL_012e: stfld "int C.d__0.<>1__state" + IL_0133: ldarg.0 + IL_0134: ldnull + IL_0135: stfld "object C.d__0.<>7__wrap1" + IL_013a: ldarg.0 + IL_013b: ldc.i4.0 + IL_013c: stfld "int C.d__0.<>2__current" + IL_0141: ldarg.0 + IL_0142: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_0147: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_014c: ldarg.0 + IL_014d: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_0152: ldloc.s V_4 + IL_0154: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)" + IL_0159: leave.s IL_0195 + } + IL_015b: ldarg.0 + IL_015c: ldc.i4.s -2 + IL_015e: stfld "int C.d__0.<>1__state" + IL_0163: ldarg.0 + IL_0164: ldnull + IL_0165: stfld "object C.d__0.<>7__wrap1" + IL_016a: ldarg.0 + IL_016b: ldc.i4.0 + IL_016c: stfld "int C.d__0.<>2__current" + IL_0171: ldarg.0 + IL_0172: ldflda "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder" + IL_0177: call "void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Complete()" + IL_017c: ldarg.0 + IL_017d: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_0182: ldc.i4.0 + IL_0183: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_0188: ret + IL_0189: ldarg.0 + IL_018a: ldflda "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd" + IL_018f: ldc.i4.1 + IL_0190: call "void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)" + IL_0195: ret +} +"""); + + comp = CreateRuntimeAsyncCompilation(source); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 275 (0x113) + .maxstack 3 + .locals init (int V_0, + bool V_1, + System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter V_2, + System.Runtime.CompilerServices.YieldAwaitable V_3) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: beq.s IL_003a + IL_000c: ldloc.0 + IL_000d: ldc.i4.s -3 + IL_000f: pop + IL_0010: pop + IL_0011: ldarg.0 + IL_0012: ldfld "bool C.d__0.<>w__disposeMode" + IL_0017: brfalse.s IL_001e + IL_0019: leave IL_00f9 + IL_001e: ldarg.0 + IL_001f: ldc.i4.m1 + IL_0020: dup + IL_0021: stloc.0 + IL_0022: stfld "int C.d__0.<>1__state" + IL_0027: ldarg.0 + IL_0028: ldarg.0 + IL_0029: ldfld "object C.d__0.o" + IL_002e: stfld "object C.d__0.<>7__wrap1" + IL_0033: ldarg.0 + IL_0034: ldc.i4.0 + IL_0035: stfld "bool C.d__0.<>7__wrap2" + IL_003a: nop + .try + { + IL_003b: ldloc.0 + IL_003c: ldc.i4.s -4 + IL_003e: beq.s IL_006f + IL_0040: ldarg.0 + IL_0041: ldfld "object C.d__0.<>7__wrap1" + IL_0046: ldarg.0 + IL_0047: ldflda "bool C.d__0.<>7__wrap2" + IL_004c: call "void System.Threading.Monitor.Enter(object, ref bool)" + IL_0051: ldc.i4.1 + IL_0052: call "void System.Console.Write(int)" + IL_0057: ldarg.0 + IL_0058: ldc.i4.2 + IL_0059: stfld "int C.d__0.<>2__current" + IL_005e: ldarg.0 + IL_005f: ldc.i4.s -4 + IL_0061: dup + IL_0062: stloc.0 + IL_0063: stfld "int C.d__0.<>1__state" + IL_0068: ldc.i4.1 + IL_0069: stloc.1 + IL_006a: leave IL_0111 + IL_006f: ldarg.0 + IL_0070: ldc.i4.m1 + IL_0071: dup + IL_0072: stloc.0 + IL_0073: stfld "int C.d__0.<>1__state" + IL_0078: ldarg.0 + IL_0079: ldfld "bool C.d__0.<>w__disposeMode" + IL_007e: brfalse.s IL_0082 + IL_0080: leave.s IL_00a2 + IL_0082: ldc.i4.3 + IL_0083: call "void System.Console.Write(int)" + IL_0088: leave.s IL_00a2 + } + finally + { + IL_008a: ldloc.0 + IL_008b: ldc.i4.m1 + IL_008c: bne.un.s IL_00a1 + IL_008e: ldarg.0 + IL_008f: ldfld "bool C.d__0.<>7__wrap2" + IL_0094: brfalse.s IL_00a1 + IL_0096: ldarg.0 + IL_0097: ldfld "object C.d__0.<>7__wrap1" + IL_009c: call "void System.Threading.Monitor.Exit(object)" + IL_00a1: endfinally + } + IL_00a2: ldarg.0 + IL_00a3: ldfld "bool C.d__0.<>w__disposeMode" + IL_00a8: brfalse.s IL_00ac + IL_00aa: leave.s IL_00f9 + IL_00ac: ldarg.0 + IL_00ad: ldnull + IL_00ae: stfld "object C.d__0.<>7__wrap1" + IL_00b3: call "System.Runtime.CompilerServices.YieldAwaitable System.Threading.Tasks.Task.Yield()" + IL_00b8: stloc.3 + IL_00b9: ldloca.s V_3 + IL_00bb: call "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter System.Runtime.CompilerServices.YieldAwaitable.GetAwaiter()" + IL_00c0: stloc.2 + IL_00c1: ldloca.s V_2 + IL_00c3: call "bool System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.IsCompleted.get" + IL_00c8: brtrue.s IL_00d0 + IL_00ca: ldloc.2 + IL_00cb: call "void System.Runtime.CompilerServices.AsyncHelpers.UnsafeAwaitAwaiter(System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter)" + IL_00d0: ldloca.s V_2 + IL_00d2: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()" + IL_00d7: ldarg.0 + IL_00d8: ldc.i4.1 + IL_00d9: stfld "bool C.d__0.<>w__disposeMode" + IL_00de: leave.s IL_00f9 + } + catch System.Exception + { + IL_00e0: pop + IL_00e1: ldarg.0 + IL_00e2: ldc.i4.s -2 + IL_00e4: stfld "int C.d__0.<>1__state" + IL_00e9: ldarg.0 + IL_00ea: ldnull + IL_00eb: stfld "object C.d__0.<>7__wrap1" + IL_00f0: ldarg.0 + IL_00f1: ldc.i4.0 + IL_00f2: stfld "int C.d__0.<>2__current" + IL_00f7: rethrow + } + IL_00f9: ldarg.0 + IL_00fa: ldc.i4.s -2 + IL_00fc: stfld "int C.d__0.<>1__state" + IL_0101: ldarg.0 + IL_0102: ldnull + IL_0103: stfld "object C.d__0.<>7__wrap1" + IL_0108: ldarg.0 + IL_0109: ldc.i4.0 + IL_010a: stfld "int C.d__0.<>2__current" + IL_010f: ldc.i4.0 + IL_0110: stloc.1 + IL_0111: ldloc.1 + IL_0112: ret +} +"""); + } + + [Fact, CompilerTrait(CompilerFeature.Iterator)] + public void Lock_03() + { + // yield return in lock body, with object type, enumerable + string source = """ +object o = new object(); +foreach (var i in C.M(o)) +{ + System.Console.Write(i); +} + +class C +{ + public static System.Collections.Generic.IEnumerable M(object o) + { + lock (o) + { + System.Console.Write(1); + yield return 2; + System.Console.Write(3); + } + } +} + +namespace System.Threading +{ + public class Monitor + { + public static void Enter(object obj, ref bool lockTaken) + { + System.Console.Write("Enter "); + lockTaken = true; + } + + public static void Exit(object obj) + { + System.Console.Write(" Exit"); + } + } +} +"""; + + var expectedOutput = "Enter 123 Exit"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Lock_04() + { + // yield break in lock body + string source = """ +object o = new object(); +await foreach (var i in C.M(o)) +{ + throw null; +} + +System.Console.Write(2); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(object o) + { + await System.Threading.Tasks.Task.Yield(); + + lock (o) + { + bool b = true; + if (b) + { + System.Console.Write(1); + yield break; + } + + throw null; + } + } +} +"""; + + var expected = "12"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Lock_05() + { + // await in lock body + string source = """ +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(object o) + { + lock (o) + { + await System.Threading.Tasks.Task.Yield(); + } + + yield return 0; + } +} +"""; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + comp.VerifyEmitDiagnostics( + // (7,13): error CS1996: Cannot await in the body of a lock statement + // await System.Threading.Tasks.Task.Yield(); + Diagnostic(ErrorCode.ERR_BadAwaitInLock, "await System.Threading.Tasks.Task.Yield()").WithLocation(7, 13)); + + comp = CreateRuntimeAsyncCompilation(source); + comp.VerifyEmitDiagnostics( + // (7,13): error CS1996: Cannot await in the body of a lock statement + // await System.Threading.Tasks.Task.Yield(); + Diagnostic(ErrorCode.ERR_BadAwaitInLock, "await System.Threading.Tasks.Task.Yield()").WithLocation(7, 13)); + } + + [Fact] + public void Using_01() + { + // yield return in using, normal exit + string source = """ +D d = new D(); +await foreach (var i in C.M(d)) +{ + System.Console.Write(i); +} + +System.Console.Write(5); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.IDisposable d) + { + using (d) + { + System.Console.Write(1); + yield return 2; + System.Console.Write(3); + } + + await System.Threading.Tasks.Task.Yield(); + } +} + +class D : System.IDisposable +{ + public void Dispose() + { + System.Console.Write(4); + } +} +"""; + + var expected = "12345"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Using_02() + { + // yield return in using, break + string source = """ +D d = new D(); +await foreach (var i in C.M(d)) +{ + System.Console.Write(i); + break; +} + +System.Console.Write(4); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.IDisposable d) + { + await System.Threading.Tasks.Task.Yield(); + + using (d) + { + System.Console.Write(1); + yield return 2; + throw null; + } + } +} + +class D : System.IDisposable +{ + public void Dispose() + { + System.Console.Write(3); + } +} +"""; + + var expected = "1234"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Using_03() + { + // yield return in using, throw + string source = """ +D d = new D(); +try +{ + await foreach (var i in C.M(d)) + { + break; + } +} +catch (System.Exception) +{ + System.Console.Write(3); + return; +} + +throw null; + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.IDisposable d) + { + using (d) + { + bool b = true; + if (b) + { + System.Console.Write(1); + throw new System.Exception(); + } + + yield return 2; + } + + await System.Threading.Tasks.Task.Yield(); + } +} + +class D : System.IDisposable +{ + public void Dispose() + { + System.Console.Write(2); + } +} +"""; + + var expected = "123"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Using_04() + { + // yield break in using + string source = """ +System.IDisposable d = new D(); +await foreach (var i in C.M(d)) +{ + throw null; +} + +System.Console.Write(3); + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.IDisposable d) + { + await System.Threading.Tasks.Task.Yield(); + + using (d) + { + bool b = true; + if (b) + { + System.Console.Write(1); + yield break; + } + + throw null; + } + + throw null; + } +} + +class D : System.IDisposable +{ + public void Dispose() + { + System.Console.Write(2); + } +} +"""; + + var expected = "123"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } + + [Fact] + public void Using_05() + { + // await in using + string source = """ +System.IDisposable d = new D(); +await foreach (var i in C.M(d)) +{ + System.Console.Write(i); +} + +class C +{ + public static async System.Collections.Generic.IAsyncEnumerable M(System.IDisposable d) + { + using (d) + { + System.Console.Write(1); + await System.Threading.Tasks.Task.Yield(); + System.Console.Write(2); + } + + yield return 4; + } +} + +class D : System.IDisposable +{ + public void Dispose() + { + System.Console.Write(3); + } +} +"""; + + var expected = "1234"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net100); + CompileAndVerify(comp, expectedOutput: ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expected), verify: Verification.Skipped) + .VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs index 706a4e2b4476e..45efe35200579 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs @@ -2591,6 +2591,9 @@ public Task MoveNextAsync() { ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0x5b } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0xcb, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -3278,6 +3281,9 @@ public async Task MoveNextAsync() ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0x8a } [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x3b, Found = Int32, Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x12f, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -3599,6 +3605,9 @@ public S(int i) [get_Current]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xb } [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x3b, Found = Int32, Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' } [DisposeAsync]: Return value missing on the stack. { Offset = 0x24 } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x19a, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -3977,6 +3986,9 @@ public S(int i) ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0x76 } [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x4f, Found = Int32, Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1' } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x118, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -11382,6 +11394,9 @@ public static async IAsyncEnumerator GetAsyncEnumerator(this Range range) { ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0x5e } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0xf0, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -11472,6 +11487,9 @@ public static async IAsyncEnumerator GetAsyncEnumerator(this (T first, T s { ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0x55 } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x139, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ @@ -11568,6 +11586,9 @@ public static class Extensions { ILVerifyMessage = """ [Main]: Return value missing on the stack. { Offset = 0xd4 } + [MoveNextAsync]: Unexpected type on the stack. { Offset = 0x15e, Found = Int32, Expected = value '[System.Runtime]System.Threading.Tasks.ValueTask`1' } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x19 } + [System.IAsyncDisposable.DisposeAsync]: Return value missing on the stack. { Offset = 0x2d } """ }); verifier.VerifyIL("C.Main()", """ diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index b5a75239170f5..2d4e1d1fbbbd3 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -5701,6 +5701,7 @@ .locals init (bool V_0, }"); } + // PROTOTYPE figure out EnC impact [Fact] public void UpdateAsyncEnumerable_AwaitAndYield_AddAndRemove() { diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs index c9973dc4bfc37..d4d31525b96de 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs @@ -4105,7 +4105,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, "); } - [Fact] + [Fact, CompilerTrait(CompilerFeature.Async)] public void StateMachine_Async() { var source = WithHelpers(@" @@ -4126,7 +4126,7 @@ static async Task M(int p) } "); - var verifier = CompileAndVerify(source, expectedOutput: @" + var expectedOutput = @" Main: Entered state machine #1 M: Entered state machine #2 M: P'p'[0] = 2 @@ -4138,7 +4138,9 @@ static async Task M(int p) M: L'c' = 1 M: Returned Main: Returned -"); +"; + + var verifier = CompileAndVerify(source, expectedOutput: expectedOutput); verifier.VerifyMethodBody("C.M", @" { @@ -4343,6 +4345,89 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, IL_012f: ret } "); + + expectedOutput = """ +Main: Entered +M: Entered +M: P'p'[0] = 2 +M: L1 = 2 +F: Entered +F: P'a'[0] = 1 +F: Returned +M: L2 = 1 +M: L3 = 1 +M: Returned +Main: Returned +"""; + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.UnsafeDebugExe); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + emitOptions: s_emitOptions, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyMethodBody("C.M", """ +{ + // Code size 87 (0x57) + .maxstack 3 + .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, + int V_1, //a + int V_2, //b + int V_3) //c + // sequence point: + IL_0000: ldtoken "System.Threading.Tasks.Task C.M(int)" + IL_0005: call "Microsoft.CodeAnalysis.Runtime.LocalStoreTracker Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogMethodEntry(int)" + IL_000a: stloc.0 + .try + { + // sequence point: { + IL_000b: ldloca.s V_0 + IL_000d: ldarg.0 + IL_000e: ldc.i4.0 + IL_000f: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogParameterStore(uint, int)" + IL_0014: nop + // sequence point: int a = p; + IL_0015: ldloca.s V_0 + IL_0017: ldarg.0 + IL_0018: dup + IL_0019: stloc.1 + IL_001a: ldc.i4.1 + IL_001b: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_0020: nop + // sequence point: F(out var b); + IL_0021: ldloca.s V_2 + IL_0023: call "int C.F(out int)" + IL_0028: pop + IL_0029: ldloca.s V_0 + IL_002b: ldloc.2 + IL_002c: ldc.i4.2 + IL_002d: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_0032: nop + // sequence point: await Task.FromResult(1); + IL_0033: ldc.i4.1 + IL_0034: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.FromResult(int)" + IL_0039: call "int System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_003e: pop + // sequence point: int c = b; + IL_003f: ldloca.s V_0 + IL_0041: ldloc.2 + IL_0042: dup + IL_0043: stloc.3 + IL_0044: ldc.i4.3 + IL_0045: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_004a: nop + IL_004b: leave.s IL_0056 + } + finally + { + // sequence point: + IL_004d: ldloca.s V_0 + IL_004f: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogReturn()" + IL_0054: nop + IL_0055: endfinally + } + // sequence point: + IL_0056: ret +} +"""); } [Fact] @@ -4537,7 +4622,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, }"); } - [Fact] + [Fact, CompilerTrait(CompilerFeature.AsyncStreams)] public void StateMachine_AsyncIterator() { var source = WithHelpers(@" @@ -4564,7 +4649,7 @@ static async Task Main() } "); - var verifier = CompileAndVerify(source, expectedOutput: @" + var expectedOutput = @" Main: Entered state machine #1 M: Entered state machine #2 M: P'p'[0] = 2 @@ -4579,7 +4664,8 @@ static async Task Main() M: L'c' = 1 M: Returned Main: Returned -"); +"; + var verifier = CompileAndVerify(source, expectedOutput: expectedOutput); verifier.VerifyMethodBody("C.M", @" { @@ -4815,9 +4901,187 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: IL_01c8: ret }"); + expectedOutput = """ +Main: Entered +M: Entered state machine #1 +M: P'p'[0] = 2 +M: L'a' = 2 +F: Entered +F: P'a'[0] = 1 +F: Returned +M: L'b' = 1 +M: Returned +Main: L5 = 1 +M: Entered state machine #1 +M: L'c' = 1 +M: Returned +Main: Returned +"""; + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.UnsafeDebugExe); + verifier = CompileAndVerify(comp, emitOptions: s_emitOptions, + expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyMethodBody("C.d__0.System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()", """ +{ + // Code size 292 (0x124) + .maxstack 4 + .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, + int V_1, + int V_2, + bool V_3) + // sequence point: + IL_0000: ldtoken "System.Collections.Generic.IAsyncEnumerable C.M(int)" + IL_0005: ldarg.0 + IL_0006: ldfld "ulong C.d__0.<>I" + IL_000b: call "Microsoft.CodeAnalysis.Runtime.LocalStoreTracker Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogStateMachineMethodEntry(int, ulong)" + IL_0010: stloc.0 + .try + { + IL_0011: ldarg.0 + IL_0012: ldfld "int C.d__0.<>1__state" + IL_0017: stloc.1 + .try + { + IL_0018: ldloc.1 + IL_0019: ldc.i4.s -4 + IL_001b: beq.s IL_0026 + IL_001d: br.s IL_001f + IL_001f: ldloc.1 + IL_0020: ldc.i4.s -3 + IL_0022: beq.s IL_002b + IL_0024: br.s IL_002d + IL_0026: br IL_00b5 + IL_002b: br.s IL_002d + IL_002d: ldarg.0 + IL_002e: ldfld "bool C.d__0.<>w__disposeMode" + IL_0033: brfalse.s IL_003a + IL_0035: leave IL_0106 + IL_003a: ldarg.0 + IL_003b: ldc.i4.m1 + IL_003c: dup + IL_003d: stloc.1 + IL_003e: stfld "int C.d__0.<>1__state" + // sequence point: { + IL_0043: ldloca.s V_0 + IL_0045: ldarg.0 + IL_0046: ldfld "int C.d__0.p" + IL_004b: ldc.i4.0 + IL_004c: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogParameterStore(uint, int)" + IL_0051: nop + // sequence point: await Task.FromResult(1); + IL_0052: ldc.i4.1 + IL_0053: call "System.Threading.Tasks.Task System.Threading.Tasks.Task.FromResult(int)" + IL_0058: call "int System.Runtime.CompilerServices.AsyncHelpers.Await(System.Threading.Tasks.Task)" + IL_005d: pop + // sequence point: int a = p; + IL_005e: ldloca.s V_0 + IL_0060: ldarg.0 + IL_0061: ldarg.0 + IL_0062: ldfld "int C.d__0.p" + IL_0067: dup + IL_0068: stloc.2 + IL_0069: stfld "int C.d__0.5__1" + IL_006e: ldloc.2 + IL_006f: ldtoken "int C.d__0.5__1" + IL_0074: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_0079: nop + IL_007a: ldarg.0 + IL_007b: ldfld "int C.d__0.5__1" + IL_0080: pop + // sequence point: F(out var b); + IL_0081: ldarg.0 + IL_0082: ldflda "int C.d__0.5__2" + IL_0087: call "int C.F(out int)" + IL_008c: pop + IL_008d: ldloca.s V_0 + IL_008f: ldarg.0 + IL_0090: ldfld "int C.d__0.5__2" + IL_0095: ldtoken "int C.d__0.5__2" + IL_009a: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_009f: nop + // sequence point: yield return 1; + IL_00a0: ldarg.0 + IL_00a1: ldc.i4.1 + IL_00a2: stfld "int C.d__0.<>2__current" + IL_00a7: ldarg.0 + IL_00a8: ldc.i4.s -4 + IL_00aa: dup + IL_00ab: stloc.1 + IL_00ac: stfld "int C.d__0.<>1__state" + IL_00b1: ldc.i4.1 + IL_00b2: stloc.3 + IL_00b3: leave.s IL_0122 + // sequence point: + IL_00b5: ldarg.0 + IL_00b6: ldc.i4.m1 + IL_00b7: dup + IL_00b8: stloc.1 + IL_00b9: stfld "int C.d__0.<>1__state" + IL_00be: ldarg.0 + IL_00bf: ldfld "bool C.d__0.<>w__disposeMode" + IL_00c4: brfalse.s IL_00c8 + IL_00c6: leave.s IL_0106 + // sequence point: int c = b; + IL_00c8: ldloca.s V_0 + IL_00ca: ldarg.0 + IL_00cb: ldarg.0 + IL_00cc: ldfld "int C.d__0.5__2" + IL_00d1: dup + IL_00d2: stloc.2 + IL_00d3: stfld "int C.d__0.5__3" + IL_00d8: ldloc.2 + IL_00d9: ldtoken "int C.d__0.5__3" + IL_00de: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)" + IL_00e3: nop + IL_00e4: ldarg.0 + IL_00e5: ldfld "int C.d__0.5__3" + IL_00ea: pop + IL_00eb: ldarg.0 + IL_00ec: ldc.i4.1 + IL_00ed: stfld "bool C.d__0.<>w__disposeMode" + IL_00f2: leave.s IL_0106 + } + catch System.Exception + { + // sequence point: + IL_00f4: pop + IL_00f5: ldarg.0 + IL_00f6: ldc.i4.s -2 + IL_00f8: stfld "int C.d__0.<>1__state" + IL_00fd: ldarg.0 + IL_00fe: ldc.i4.0 + IL_00ff: stfld "int C.d__0.<>2__current" + IL_0104: rethrow + } + // sequence point: } + IL_0106: ldarg.0 + IL_0107: ldc.i4.s -2 + IL_0109: stfld "int C.d__0.<>1__state" + // sequence point: + IL_010e: ldarg.0 + IL_010f: ldc.i4.0 + IL_0110: stfld "int C.d__0.<>2__current" + IL_0115: ldc.i4.0 + IL_0116: stloc.3 + IL_0117: leave.s IL_0122 + } + finally + { + // sequence point: + IL_0119: ldloca.s V_0 + IL_011b: call "void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogReturn()" + IL_0120: nop + IL_0121: endfinally + } + // sequence point: + IL_0122: ldloc.3 + IL_0123: ret +} +"""); } - [Fact] + [Fact, CompilerTrait(CompilerFeature.Async)] public void StateMachine_Lambda_Async() { var source = WithHelpers(@" @@ -5123,7 +5387,7 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, }"); } - [Fact] + [Fact, CompilerTrait(CompilerFeature.AsyncStreams)] public void StateMachine_LocalFunction_AsyncIterator() { var source = WithHelpers(@" @@ -5152,7 +5416,7 @@ static IAsyncEnumerable M(Func> p) } "); - var verifier = CompileAndVerify(source, expectedOutput: @" + var expectedOutput = @" Main: Entered state machine #1 M: Entered M: P'p'[0] = System.Func`2[System.Int32,System.Collections.Generic.IAsyncEnumerable`1[System.Int32]] @@ -5165,7 +5429,8 @@ static IAsyncEnumerable M(Func> p) Main: Entered lambda '
g__f|0_0' state machine #2
g__f|0_0: Returned Main: Returned -"); +"; + var verifier = CompileAndVerify(source, expectedOutput: expectedOutput); verifier.VerifyMethodBody("C.<
g__f|0_0>d.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { @@ -5359,6 +5624,25 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0, // sequence point: IL_0186: ret }"); + + expectedOutput = """ +Main: Entered +M: Entered +M: P'p'[0] = System.Func`2[System.Int32,System.Collections.Generic.IAsyncEnumerable`1[System.Int32]] +M: Returned +Main: Entered lambda '
g__f|0_0' state machine #1 +
g__f|0_0: P'p'[0] = 2 +
g__f|0_0: L'a' = 2 +
g__f|0_0: Returned +Main: L5 = 1 +Main: Entered lambda '
g__f|0_0' state machine #1 +
g__f|0_0: Returned +Main: Returned +"""; + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.UnsafeDebugExe); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), + emitOptions: s_emitOptions, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/RefUnsafeInIteratorAndAsyncTests.cs b/src/Compilers/CSharp/Test/Emit3/RefUnsafeInIteratorAndAsyncTests.cs index 78b6ffa96f757..aeef440cc074b 100644 --- a/src/Compilers/CSharp/Test/Emit3/RefUnsafeInIteratorAndAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/RefUnsafeInIteratorAndAsyncTests.cs @@ -737,9 +737,16 @@ static async IAsyncEnumerable M(int x, int z) Console.Write(y); } } - """ + AsyncStreamsTypes; - var comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "-1 456").VerifyDiagnostics(); + """; + + var expectedOutput = "-1 456"; + + var comp = CreateCompilationWithTasksExtensions([source, AsyncStreamsTypes], options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -791,9 +798,15 @@ static async IAsyncEnumerable M(int x) yield return -1; await Task.Yield(); } } - """ + AsyncStreamsTypes; - var comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "123-1").VerifyDiagnostics(); + """; + + var expectedOutput = "123-1"; + var comp = CreateCompilationWithTasksExtensions([source, AsyncStreamsTypes], options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1003,7 +1016,13 @@ static async IAsyncEnumerable M(int x, int z) } } """; - CompileAndVerify(source, expectedOutput: IfSpans("-1 456"), verify: Verification.FailsPEVerify, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + + var expectedOutput = IfSpans("-1 456"); + CompileAndVerify(source, expectedOutput: expectedOutput, verify: Verification.FailsPEVerify, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1128,9 +1147,14 @@ static async IAsyncEnumerable M() Console.Write(y.GetHashCode()); } } - """ + AsyncStreamsTypes; - var comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "-1 0").VerifyDiagnostics(); + """; + var expectedOutput = "-1 0"; + var comp = CreateCompilationWithTasksExtensions([source, AsyncStreamsTypes], options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1202,9 +1226,14 @@ static async IAsyncEnumerable M(int x) System.Console.Write(y); } } - """ + AsyncStreamsTypes; - var comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "123").VerifyDiagnostics(); + """; + var expectedOutput = "123"; + var comp = CreateCompilationWithTasksExtensions([source, AsyncStreamsTypes], options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1253,7 +1282,12 @@ static async IAsyncEnumerable M(int x) } } """; - CompileAndVerify(source, expectedOutput: IfSpans("123"), verify: Verification.FailsPEVerify, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + var expectedOutput = IfSpans("123"); + CompileAndVerify(source, expectedOutput: expectedOutput, verify: Verification.FailsPEVerify, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + + var comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } [Fact] @@ -1301,8 +1335,13 @@ static async IAsyncEnumerable M(int x) Console.Write(x + t.GetHashCode()); } } - """ + AsyncStreamsTypes; - var comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); - CompileAndVerify(comp, expectedOutput: "123").VerifyDiagnostics(); + """; + var expectedOutput = "123"; + var comp = CreateCompilationWithTasksExtensions([source, AsyncStreamsTypes], options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped) + .VerifyDiagnostics(); } } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/LockTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/LockTests.cs index 570e26e36d8f1..6fd8aaed16235 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/LockTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/LockTests.cs @@ -3655,6 +3655,13 @@ async static IAsyncEnumerable M() comp = CreateCompilationWithTasksExtensions([source, LockTypeDefinition, AsyncStreamsTypes], options: TestOptions.DebugExe); verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); + + comp = CreateRuntimeAsyncCompilation([source, LockTypeDefinition], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped); + verifier.VerifyDiagnostics( + // (21,19): warning CS0436: The type 'Lock' in '' conflicts with the imported type 'Lock' in 'System.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using th e type defined in ''. + // lock (new Lock()) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Lock").WithArguments("", "System.Threading.Lock", "System.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Threading.Lock").WithLocation(21, 19)); } [Fact] @@ -4052,6 +4059,13 @@ .locals init (int V_0, IL_017e: ret } """); + + comp = CreateRuntimeAsyncCompilation([source, LockTypeDefinition], options: TestOptions.DebugExe); + verifier = CompileAndVerify(comp, expectedOutput: RuntimeAsyncTestHelpers.ExpectedOutput(expectedOutput), verify: Verification.Skipped); + verifier.VerifyDiagnostics( + // (19,19): warning CS0436: The type 'Lock' in '' conflicts with the imported type 'Lock' in 'System.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Using the type defined in ''. + // lock (new Lock()) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Lock").WithArguments("", "System.Threading.Lock", "System.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Threading.Lock").WithLocation(19, 19)); } [Theory, CombinatorialData] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 1d22d8f560fdf..a0a9b7ebb58f3 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -572,6 +572,7 @@ public void AllSpecialTypeMembers() || special == SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__UnsafeAwaitAwaiter_TAwaiter || special == SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task || special == SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task_Int32 + || special == SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT ) { Assert.Null(symbol); // Not available diff --git a/src/Compilers/Core/Portable/SpecialMember.cs b/src/Compilers/Core/Portable/SpecialMember.cs index 132d804290128..e5c0937987b81 100644 --- a/src/Compilers/Core/Portable/SpecialMember.cs +++ b/src/Compilers/Core/Portable/SpecialMember.cs @@ -199,6 +199,8 @@ internal enum SpecialMember System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task, System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task_Int32, + System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT, + Count } } diff --git a/src/Compilers/Core/Portable/SpecialMembers.cs b/src/Compilers/Core/Portable/SpecialMembers.cs index 26342d09acb17..cea2002e5ae68 100644 --- a/src/Compilers/Core/Portable/SpecialMembers.cs +++ b/src/Compilers/Core/Portable/SpecialMembers.cs @@ -1348,7 +1348,17 @@ static SpecialMembers() (byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Threading_Tasks_Task_T, 1, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, - }; + + // System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers, // DeclaringTypeId + 1, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.GenericMethodParameter, 0, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Threading_Tasks_ValueTask_T, + 1, + (byte)SignatureTypeCode.GenericMethodParameter, 0, + }; string[] allNames = new string[(int)SpecialMember.Count] { @@ -1513,6 +1523,7 @@ static SpecialMembers() "UnsafeAwaitAwaiter", // System_Runtime_CompilerServices_AsyncHelpers__UnsafeAwaitAwaiter_TAwaiter "HandleAsyncEntryPoint", // System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task "HandleAsyncEntryPoint", // System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task_Int32 + "Await", // System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Test/Core/Assert/AssertEx.cs b/src/Compilers/Test/Core/Assert/AssertEx.cs index cbe7bc403bd09..d410fd5712763 100644 --- a/src/Compilers/Test/Core/Assert/AssertEx.cs +++ b/src/Compilers/Test/Core/Assert/AssertEx.cs @@ -706,9 +706,9 @@ public static string GetAssertMessage( message.AppendLine("Expected:"); message.AppendLine(expectedString); - if (expected.Count() > 10) + if (expected.Count() is > 10 and var count) { - message.AppendLine("... truncated ..."); + message.AppendLine($"... truncated {count - 10} lines ..."); } message.AppendLine("Actual:"); diff --git a/src/Compilers/Test/Core/CompilationVerifier.cs b/src/Compilers/Test/Core/CompilationVerifier.cs index e943cfbdb74d9..b701fd387dd52 100644 --- a/src/Compilers/Test/Core/CompilationVerifier.cs +++ b/src/Compilers/Test/Core/CompilationVerifier.cs @@ -283,20 +283,35 @@ public void VerifyTypeIL(string typeName, string expected) expected = RemoveHeaderComments(expected); output = RemoveHeaderComments(output); + if (!s_rvaCommentsRegex.IsMatch(expected)) + { + // RVA comments are noisy (vary due to surrounding codegen) and not important to validate + // But if the expected IL includes them already, we honor/keep them in the output + output = s_rvaCommentsRegex.Replace(output, ""); + } + output = FixupCodeSizeComments(output); + output = RemoveTrailingWhitespaces(output); AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, output, escapeQuotes: false); }); } - private static readonly Regex s_headerCommentsRegex = new("""^\s*// Header size: [0-9]+\s*$""", RegexOptions.Multiline); - private static readonly Regex s_codeSizeCommentsRegex = new("""^\s*// Code size(:) [0-9]+\s*""", RegexOptions.Multiline); + private static readonly Regex s_headerCommentsRegex = new Regex("""^\s*// Header size: [0-9]+\s*$""", RegexOptions.Multiline); + private static readonly Regex s_codeSizeCommentsRegex = new Regex("""^\s*// Code size(:) [0-9]+\s*""", RegexOptions.Multiline); + private static readonly Regex s_rvaCommentsRegex = new Regex("""^\s*// Method begins at RVA 0x[0-9a-f]+\s*$""", RegexOptions.Multiline); + private static readonly Regex s_trailingWhitespacesRegex = new Regex("""\s*$""", RegexOptions.Multiline); private static string RemoveHeaderComments(string value) { return s_headerCommentsRegex.Replace(value, ""); } + private static string RemoveTrailingWhitespaces(string value) + { + return s_trailingWhitespacesRegex.Replace(value, ""); + } + private static string FixupCodeSizeComments(string output) { // We use the form `// Code size 7 (0x7)` while ilspy moved to the form `// Code size: 7 (0x7)` (with an diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 9325403e12680..0f476e9670833 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -3206,7 +3206,12 @@ public static IEnumerable FileScopedOrBracedNamespace internal static CSharpParseOptions WithRuntimeAsync(CSharpParseOptions options) => options.WithFeature("runtime-async", "on"); - internal static CSharpCompilation CreateRuntimeAsyncCompilation(CSharpTestSource source, CSharpCompilationOptions? options = null, CSharpParseOptions? parseOptions = null, bool includeSuppression = true) + internal static CSharpCompilation CreateRuntimeAsyncCompilation( + CSharpTestSource source, + CSharpCompilationOptions? options = null, + CSharpParseOptions? parseOptions = null, + bool includeSuppression = true, + IEnumerable? references = null) { parseOptions ??= WithRuntimeAsync(TestOptions.RegularPreview); var syntaxTrees = source.GetSyntaxTrees(parseOptions, sourceFileName: ""); @@ -3220,7 +3225,7 @@ internal static CSharpCompilation CreateRuntimeAsyncCompilation(CSharpTestSource options = options.WithSpecificDiagnosticOptions("SYSLIB5007", ReportDiagnostic.Suppress); } - return CreateCompilation(source, options: options, parseOptions: parseOptions, targetFramework: TargetFramework.Net100); + return CreateCompilation(source, references: references, options: options, parseOptions: parseOptions, targetFramework: TargetFramework.Net100); } #endregion diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index e74a13641320a..bf78f18baf2a6 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -501,7 +501,8 @@ End Namespace special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__AwaitAwaiter_TAwaiter OrElse special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__UnsafeAwaitAwaiter_TAwaiter OrElse special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task OrElse - special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task_Int32 Then + special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__HandleAsyncEntryPoint_Task_Int32 OrElse + special = SpecialMember.System_Runtime_CompilerServices_AsyncHelpers__Await_T_FromValueTaskT Then Assert.Null(symbol) ' Not available Else Assert.NotNull(symbol)