diff --git a/docs/features/async-streams.md b/docs/features/async-streams.md index edad6aa50b531f8ae276e682a3ca36585e2ce073..ae9e97062381fe8c9783fdb4e8965e26371645e1 100644 --- a/docs/features/async-streams.md +++ b/docs/features/async-streams.md @@ -3,7 +3,7 @@ async-streams (C# 8.0) Async-streams are asynchronous variants of enumerables, where getting the next element may involve an asynchronous operation. They are types that implement `IAsyncEnumerable`. -```C# +```csharp // Those interfaces will ship as part of .NET Core 3 namespace System.Collections.Generic { @@ -40,7 +40,7 @@ An async-iterator method is a method that: 3. uses both `await` syntax (`await` expression, `await foreach` or `await using` statements) and `yield` statements (`yield return`, `yield break`). For example: -```C# +```csharp async IAsyncEnumerable GetValuesFromServer() { while (true) @@ -86,7 +86,7 @@ thereby allowing consumers of async-streams to control cancellation. A producer of async-streams can make use of the cancellation token by writing an `IAsyncEnumerator GetAsyncEnumerator(CancellationToken)` async-iterator method in a custom type. -```C# +```csharp E e = ((C)(x)).GetAsyncEnumerator(default); try { @@ -117,7 +117,8 @@ But it contains additional state: - a promise of a value-or-end, - a current yielded value of type `T`, - an `int` capturing the id of the thread that created it, -- a `bool` flag indicating "dispose mode". +- a `bool` flag indicating "dispose mode", +- a `CancellationTokenSource` for combining tokens (in enumerables). The central method of the state machine is `MoveNext()`. It gets run by `MoveNextAsync()`, or as a background continuation initiated from these from an `await` in the method. @@ -133,8 +134,8 @@ Compared to the state machine for a regular async method, the `MoveNext()` for a - to support handling a `yield return` statement, which saves the current value and fulfills the promise with result `true`, - to support handling a `yield break` statement, which sets the dispose mode on and jumps to the enclosing `finally` or exit, - to dispatch execution to `finally` blocks (when disposing), -- to exit the method, which fulfills the promise with result `false`, -- to catch exceptions, which set the exception into the promise. +- to exit the method, which disposes the `CancellationTokenSource` (if any) and fulfills the promise with result `false`, +- to catch exceptions, which disposes the `CancellationTokenSource` (if any) and sets the exception into the promise. (The handling of an `await` is unchanged) This is reflected in the implementation, which extends the lowering machinery for async methods to: @@ -143,7 +144,7 @@ This is reflected in the implementation, which extends the lowering machinery fo 3. produce additional state and logic for the promise itself (see `AsyncIteratorRewriter`, which produces various other members: `MoveNextAsync`, `Current`, `DisposeAsync`, and some members supporting the resettable `ValueTask` behavior, namely `GetResult`, `SetStatus`, `OnCompleted`). -```C# +```csharp ValueTask MoveNextAsync() { if (state == StateMachineStates.FinishedStateMachine) @@ -162,13 +163,13 @@ ValueTask MoveNextAsync() } ``` -```C# +```csharp T Current => current; ``` The kick-off method and the initialization of the state machine for an async-iterator method follows those for regular iterator methods. In particular, the synthesized `GetAsyncEnumerator()` method is like `GetEnumerator()` except that it sets the initial state to to StateMachineStates.NotStartedStateMachine (-1): -```C# +```csharp IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) { {StateMachineType} result; @@ -182,13 +183,30 @@ IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) { result = new {StateMachineType}(InitialState); } - /* copy each parameter proxy, or copy the token parameter if it's not default for any parameter marked with [EnumeratorCancellation] attribute */ + /* copy each parameter proxy, or in the case of the parameter marked with [EnumeratorCancellation] combine it with `GetAsyncEnumerator`'s `token` parameter */ +} +``` + +For the parameter with `[EnumeratorCancellation]`, `GetAsyncEnumerator` initializes it by combining the two available tokens: +```csharp +if (this.parameterProxy.Equals(default)) +{ + result.parameter = token; +} +else if (token.Equals(this.parameterProxy) || token.Equals(default)) +{ + result.parameter = this.parameterProxy; +} +else +{ + result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); + result.parameter = combinedTokens.Token; } ``` For a discussion of the threadID check, see https://github.com/dotnet/corefx/issues/3481 Similarly, the kick-off method is much like those of regular iterator methods: -```C# +```csharp { {StateMachineType} result = new {StateMachineType}(StateMachineStates.FinishedStateMachine); // -2 /* save parameters into parameter proxies */ @@ -227,7 +245,7 @@ Looking at disposal from the perspective of a given `finally` block, the code in - in dispose mode, following a nested `finally`. A `yield return` is lowered as: -```C# +```csharp _current = expression; _state = ; goto ; // which does _valueOrEndPromise.SetResult(true); return; @@ -239,12 +257,12 @@ if (disposeMode) /* jump to enclosing finally or exit */ ``` A `yield break` is lowered as: -```C# +```csharp disposeMode = true; /* jump to enclosing finally or exit */ ``` -```C# +```csharp ValueTask IAsyncDisposable.DisposeAsync() { if (state >= StateMachineStates.NotStartedStateMachine /* -1 */) @@ -266,7 +284,7 @@ ValueTask IAsyncDisposable.DisposeAsync() ##### Regular versus extracted finally When the `finally` clause contains no `await` expressions, a `try/finally` is lowered as: -```C# +```csharp try { ... @@ -280,7 +298,7 @@ if (disposeMode) /* jump to enclosing finally or exit */ ``` When a `finally` contains `await` expressions, it is extracted before async rewriting (by AsyncExceptionHandlerRewriter). In those cases, we get: -```C# +```csharp try { ... @@ -325,7 +343,7 @@ The result of invoking `DisposeAsync` from states -1 or N is unspecified. This c ^ | | | | done and disposed | | yield return | +-----------------------------------+ +-----------------------> -N - | | | + | or exception thrown | | | | | | yield | | | break | DisposeAsync | diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 9161ea56dbbdce5faf460c4c6a25ddf4f434a5e3..d610ed01fc72ce67ebf38d3284900553a51bac78 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5901,6 +5901,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Async-iterator member 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 + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + Method '{0}' specifies a 'class' constraint for type parameter '{1}', but corresponding type parameter '{2}' of overridden or explicitly implemented method '{3}' is not a reference type. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 573ee9e9b5dbc0cd05abd5add55ca1869f617375..2878ba9e04dd30a7448a7c2d86c9ad3600a238b2 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1601,6 +1601,7 @@ internal enum ErrorCode ERR_AttributeNotOnEventAccessor = 8423, WRN_UnconsumedEnumeratorCancellationAttributeUsage = 8424, WRN_UndecoratedCancellationTokenParameter = 8425, + ERR_MultipleEnumeratorCancellationAttributes = 8426, // available range #region diagnostics introduced for recursive patterns diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorInfo.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorInfo.cs index bdf1660807950fe1b6d21e3c0e4805921f2ac4a5..c8aa69a76958a9917d8683ae101056102085d308 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorInfo.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorInfo.cs @@ -12,6 +12,9 @@ internal sealed class AsyncIteratorInfo // This `ManualResetValueTaskSourceCore` struct implements the `IValueTaskSource` logic internal FieldSymbol PromiseOfValueOrEndField { get; } + // This `CancellationTokenSource` field helps combine two cancellation tokens + internal FieldSymbol CombinedTokensField { get; } + // Stores the current/yielded value internal FieldSymbol CurrentField { get; } @@ -24,10 +27,11 @@ internal sealed class AsyncIteratorInfo // Method to fulfill the promise with an exception: `void ManualResetValueTaskSourceCore.SetException(Exception error)` internal MethodSymbol SetExceptionMethod { get; } - public AsyncIteratorInfo(FieldSymbol promiseOfValueOrEndField, FieldSymbol currentField, FieldSymbol disposeModeField, + public AsyncIteratorInfo(FieldSymbol promiseOfValueOrEndField, FieldSymbol combinedTokensField, FieldSymbol currentField, FieldSymbol disposeModeField, MethodSymbol setResultMethod, MethodSymbol setExceptionMethod) { PromiseOfValueOrEndField = promiseOfValueOrEndField; + CombinedTokensField = combinedTokensField; CurrentField = currentField; DisposeModeField = disposeModeField; SetResultMethod = setResultMethod; diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs index 5d9418406f5bd5d1f3f9d72087fcb2c15457c903..d15419fd8b2a97bb5001bbbeb11c0de19829f8f3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncIteratorMethodToStateMachineRewriter.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.CodeAnalysis.CodeGen; @@ -66,6 +67,7 @@ protected override BoundStatement GenerateSetResultCall() // ... _exprReturnLabel: ... // ... this.state = FinishedState; ... + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only // this.promiseOfValueOrEnd.SetResult(false); // return; // _exprReturnLabelTrue: @@ -74,7 +76,12 @@ protected override BoundStatement GenerateSetResultCall() // ... _exitLabel: ... // ... return; ... - return F.Block( + var builder = ArrayBuilder.GetInstance(); + + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only + AddDisposeCombinedTokensIfNeeded(builder); + + builder.AddRange( // this.promiseOfValueOrEnd.SetResult(false); generateSetResultOnPromise(false), F.Return(), @@ -82,6 +89,8 @@ protected override BoundStatement GenerateSetResultCall() // this.promiseOfValueOrEnd.SetResult(true); generateSetResultOnPromise(true)); + return F.Block(builder.ToImmutableAndFree()); + BoundExpressionStatement generateSetResultOnPromise(bool result) { // Produce: @@ -91,13 +100,36 @@ BoundExpressionStatement generateSetResultOnPromise(bool result) } } + private void AddDisposeCombinedTokensIfNeeded(ArrayBuilder builder) + { + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only + if (_asyncIteratorInfo.CombinedTokensField is object) + { + var combinedTokens = F.Field(F.This(), _asyncIteratorInfo.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))))); + } + } + protected override BoundStatement GenerateSetExceptionCall(LocalSymbol exceptionLocal) { + var builder = ArrayBuilder.GetInstance(); + + // if (this.combinedTokens != null) { this.combinedTokens.Dispose(); this.combinedTokens = null; } // for enumerables only + AddDisposeCombinedTokensIfNeeded(builder); + // _promiseOfValueOrEnd.SetException(ex); - return F.ExpressionStatement(F.Call( + builder.Add(F.ExpressionStatement(F.Call( F.InstanceField(_asyncIteratorInfo.PromiseOfValueOrEndField), _asyncIteratorInfo.SetExceptionMethod, - F.Local(exceptionLocal))); + F.Local(exceptionLocal)))); + + return F.Block(builder.ToImmutableAndFree()); } private BoundStatement GenerateJumpToCurrentFinallyOrExit() diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index df3722be7e7a43a990aa84a8d6c8d1a3fcae2d81..168a407d4025afafc36a4ddd9f540e31089cc1ef 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -208,7 +208,7 @@ protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal) // catch (Exception ex) // { // _state = finishedState; - // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ + // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ // return; // } @@ -216,7 +216,7 @@ protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal) BoundStatement assignFinishedState = F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineStates.FinishedStateMachine))); - // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); + // builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); return new BoundCatchBlock( @@ -234,6 +234,8 @@ protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal) protected virtual BoundStatement GenerateSetExceptionCall(LocalSymbol exceptionLocal) { + Debug.Assert(!CurrentMethod.IsIterator); // an override handles async-iterators + // builder.SetException(ex); return F.ExpressionStatement( F.Call( diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index 226a171296951a050d0173341eed2d04bea3a2b2..31080de662c09e60733901a869356fcc45559e39 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; @@ -18,6 +19,7 @@ private sealed class AsyncIteratorRewriter : AsyncRewriter private FieldSymbol _promiseOfValueOrEndField; // this struct implements the IValueTaskSource logic 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 // true if the iterator implements IAsyncEnumerable, // false if it implements IAsyncEnumerator @@ -46,7 +48,12 @@ protected override void VerifyPresenceOfRequiredAPIs(DiagnosticBag 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); @@ -124,6 +131,14 @@ protected override void GenerateControlFields() // Add a field: bool disposeMode _disposeModeField = F.StateMachineField(boolType, GeneratedNames.MakeDisposeModeFieldName()); + + if (_isEnumerable && this.method.Parameters.Any(p => p is SourceComplexParameterSymbol { HasEnumeratorCancellationAttribute: true })) + { + // Add a field: CancellationTokenSource combinedTokens + _combinedTokensField = F.StateMachineField( + F.WellKnownType(WellKnownType.System_Threading_CancellationTokenSource), + GeneratedNames.MakeAsyncIteratorCombinedTokensFieldName()); + } } protected override void GenerateConstructor() @@ -176,6 +191,57 @@ protected override void InitializeStateMachine(ArrayBuilder body F.New(stateMachineType.Constructor.AsMember(frameType), F.Literal(initialState)))); } + protected override BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) + { + BoundStatement result; + if (_combinedTokensField is object && + parameter is SourceComplexParameterSymbol { HasEnumeratorCancellationAttribute: true }) + { + // For the parameter with [EnumeratorCancellation] + // if (this.parameterProxy.Equals(default)) + // { + // result.parameter = token; + // } + // else if (token.Equals(this.parameterProxy) || token.Equals(default)) + // { + // result.parameter = this.parameterProxy; + // } + // else + // { + // result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); + // result.parameter = combinedTokens.Token; + // } + + 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)), + // result.parameter = token; + 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))), + // result.parameter = this.parameterProxy; + 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)), + // result.parameter = result.combinedTokens.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); + } + + return result; + } + protected override BoundStatement GenerateStateMachineCreation(LocalSymbol stateMachineVariable, NamedTypeSymbol frameType) { // return local; @@ -590,7 +656,7 @@ protected override void GenerateMoveNext(SynthesizedImplementationMethod moveNex method: method, methodOrdinal: _methodOrdinal, asyncMethodBuilderMemberCollection: _asyncMethodBuilderMemberCollection, - asyncIteratorInfo: new AsyncIteratorInfo(_promiseOfValueOrEndField, _currentField, _disposeModeField, setResultMethod, setExceptionMethod), + asyncIteratorInfo: new AsyncIteratorInfo(_promiseOfValueOrEndField, _combinedTokensField, _currentField, _disposeModeField, setResultMethod, setExceptionMethod), F: F, state: stateField, builder: _builderField, diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index b6c50e37f2da41a59922ec855740ca60d7536748..37ec9d6f2ed50db5531fc51548a5c1446ab61883 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -375,10 +375,7 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy // result = new {StateMachineType}({initialState}); // } // - // // Initialize each parameter fields - // result.parameter = this.parameterProxy; - // OR - // if (token.Equals(default)) { result.parameter = this.parameterProxy; } else { result.parameter = token; } // for async-enumerable parameters marked with [EnumeratorCancellation] + // result.parameter = this.parameterProxy; // OR more complex initialization for async-iterator parameter marked with [EnumeratorCancellation] // The implementation doesn't depend on the method body of the iterator method. // Only on its parameters and staticness. @@ -455,29 +452,11 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy CapturedSymbolReplacement proxy; if (copyDest.TryGetValue(parameter, out proxy)) { - // result.parameter = this.parameterProxy; - BoundExpression left = proxy.Replacement(F.Syntax, stateMachineType => F.Local(resultVariable)); - BoundStatement copy = F.Assignment( - left, - copySrc[parameter].Replacement(F.Syntax, stateMachineType => F.This())); - - if (this.method.IsAsync && parameter is SourceComplexParameterSymbol { HasEnumeratorCancellationAttribute: true }) - { - ParameterSymbol tokenParameter = getEnumeratorMethod.Parameters[0]; - // For any async-enumerable parameter marked with [EnumeratorCancellation] attribute, conditionally copy GetAsyncEnumerator's cancellation token parameter instead - // if (token.Equals(default)) - // result.parameter = this.parameterProxy; - // else - // result.parameter = token; - - copy = F.If( - // if (token.Equals(default)) - F.Call(F.Parameter(tokenParameter), WellKnownMember.System_Threading_CancellationToken__Equals, F.Default(tokenParameter.Type)), - // result.parameter = this.parameterProxy; - copy, - // result.parameter = token; - F.Assignment(left, F.Parameter(tokenParameter))); - } + // result.parameter + BoundExpression resultParameter = proxy.Replacement(F.Syntax, stateMachineType => F.Local(resultVariable)); + // this.parameterProxy + BoundExpression parameterProxy = copySrc[parameter].Replacement(F.Syntax, stateMachineType => F.This()); + BoundStatement copy = InitializeParameterField(getEnumeratorMethod, parameter, resultParameter, parameterProxy); bodyBuilder.Add(copy); } @@ -488,6 +467,14 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy return getEnumerator; } + protected virtual BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) + { + Debug.Assert(!method.IsIterator || !method.IsAsync); // an override handles async-iterators + + // result.parameter = this.parameterProxy; + return F.Assignment(resultParameter, parameterProxy); + } + /// /// Async-iterator methods use a GetAsyncEnumerator method just like the GetEnumerator of iterator methods. /// But they need to do a bit more work (to reset the dispose mode). diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs index e70070268ecb605a779f2fc77dd74c2a0040cfea..8137ae09d86715133dafe72be193fd1ce4ee4f41 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs @@ -184,16 +184,25 @@ private void MethodChecks(MethodDeclarationSyntax syntax, Binder withTypeParamsB var location = this.Locations[0]; if (IsAsync) { - // Warn for CancellationToken parameters in async-iterators with no parameter decorated with [EnumeratorCancellation] var cancellationTokenType = DeclaringCompilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken); var iAsyncEnumerableType = DeclaringCompilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T); + var enumeratorCancellationCount = Parameters.Count(p => p is SourceComplexParameterSymbol { HasEnumeratorCancellationAttribute: true }); if (ReturnType.OriginalDefinition.Equals(iAsyncEnumerableType) && - (Bodies.blockBody != null || Bodies.arrowBody != null) && - ParameterTypesWithAnnotations.Any(p => p.Type.Equals(cancellationTokenType)) && - !Parameters.Any(p => p is SourceComplexParameterSymbol { HasEnumeratorCancellationAttribute: true })) + (Bodies.blockBody != null || Bodies.arrowBody != null)) { - // There could be more than one parameter that could be decorated with [EnumeratorCancellation] so we warn on the method instead - diagnostics.Add(ErrorCode.WRN_UndecoratedCancellationTokenParameter, location, this); + if (enumeratorCancellationCount == 0 && + ParameterTypesWithAnnotations.Any(p => p.Type.Equals(cancellationTokenType))) + { + // Warn for CancellationToken parameters in async-iterators with no parameter decorated with [EnumeratorCancellation] + // There could be more than one parameter that could be decorated with [EnumeratorCancellation] so we warn on the method instead + diagnostics.Add(ErrorCode.WRN_UndecoratedCancellationTokenParameter, location, this); + } + + if (enumeratorCancellationCount > 1) + { + // The [EnumeratorCancellation] attribute can only be used on one parameter + diagnostics.Add(ErrorCode.ERR_MultipleEnumeratorCancellationAttributes, location); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs index 2e2030bd53fce7bf60f9f82596fa4c196d20a6ac..8d11ed1a4cc801d94e6142cb754ceff864826c03 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs @@ -39,7 +39,8 @@ internal enum GeneratedNameKind DynamicCallSiteContainerType = 'o', DynamicCallSiteField = 'p', AsyncIteratorPromiseOfValueOrEndBackingField = 'v', - DisposeModeField = 'w', // last + DisposeModeField = 'w', + CombinedTokensField = 'x', // last // Deprecated - emitted by Dev12, but not by Roslyn. // Don't reuse the values because the debugger might encounter them when consuming old binaries. diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs index 0979be4b3d803937b28c771926550901776aaab2..9504c30596bf4507e0d12c47f16c0d44b0eacfb5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs @@ -436,6 +436,12 @@ internal static string MakeAsyncIteratorPromiseOfValueOrEndFieldName() return "<>v__promiseOfValueOrEnd"; } + internal static string MakeAsyncIteratorCombinedTokensFieldName() + { + Debug.Assert((char)GeneratedNameKind.CombinedTokensField == 'x'); + return "<>x__combinedTokens"; + } + internal static string MakeIteratorCurrentFieldName() { Debug.Assert((char)GeneratedNameKind.IteratorCurrentBackingField == '2'); diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 9b3e7edda310701ad0ea1fedd03938a0c5580043..4042ec9cba16e8029bd5d82a71095fb4d9a0e82c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Omezení new() nejde používat s omezením unmanaged. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index a0841c8f8b1b379aec74435cf94809d2b8c6f966..fef77e97be587e8145d1a1200a111a7813d05348 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Die new()-Einschränkung kann nicht mit der unmanaged-Einschränkung verwendet werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index a0a2e5438bf557a421947b861a31a77134ace485..2a2d5787dfcaf635a637572dc1514c0f9a42c8aa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint La restricción "new()" no se puede utilizar con la restricción "unmanaged" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index b9ecb32721e1cfcaac3ac10bf7a78d3c008a97db..890fdc29bb7f682f57bdd075d4a5094650c17c00 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint La contrainte 'new()' ne peut pas être utilisée avec la contrainte 'unmanaged' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 9bc5d82bf83cffc61987da8cf5a7cf17cb41994f..e3d24bf83bed53e6cf0f359a64692d85ed057301 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Non è possibile usare il vincolo 'new()' con il vincolo 'unmanaged' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 052db3787bf47f131e59da9db5dfd670f65802b6..a976bf8f00f8be66194f9e6dea2fe2e070ba81df 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 制約は 'unmanaged' 制約と一緒には使用できません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index ec4b83d6925c1fce45944a72f8e6aa2df0d3db30..0ca6d91dea5ce72609c8c5def4532e9e25c66376 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 제약 조건은 'unmanaged' 제약 조건과 함께 사용할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 2e1dd4540c2167265327372450f152c1ff107b3a..98ff000b76be29cc5bf29946282166ba3864901b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Ograniczenie „new()” nie może być używane z ograniczeniem „unmanaged” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 0480178193f5041ea69d196070826feaaae0caf0..5def73734d2b6732bbc9b943b0bfefb863547cb2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint A restrição 'new()' não pode ser usada com a restrição 'unmanaged' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index dfe63f9e2fa223e5f9bff2700d4d1be0bf2c63a4..a0389c52a2c4dbd150fd50fa11f3c2bed9ba9eaf 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint Ограничение "new()" невозможно использовать вместе с ограничением "unmanaged" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index ce7651588ccda0555371e70c182f750b961fff30..a001b96112b2935f6d99086794e4346ba06b3c97 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint 'new()' kısıtlaması, 'unmanaged' kısıtlamasıyla kullanılamaz diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 2ca77b813255bfea0be953e3ac11b7560e0e5f42..950b73900e9fc5c8310964e91fceeee9fc70c038 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -282,6 +282,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint "new()" 约束不能与 "unmanaged" 约束一起使用 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 384ae0838d861b32d4747a417285145f692a8f0a..6d472473d645a4befbba544a2fd7f64a6b14c913 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -257,6 +257,11 @@ Multiple analyzer config files cannot be in the same directory ('{0}'). + + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + The attribute [EnumeratorCancellation] cannot be used on multiple parameters + + The 'new()' constraint cannot be used with the 'unmanaged' constraint new()' 條件約束不能和 'unmanaged' 條件約束一起使用 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index f41d74473929a76df5e92fc401a920153808a028..9ed075a2f3311237555dec7ca5ab0e84f7f591ef 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -1,7 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; @@ -114,7 +115,7 @@ private static void VerifyMissingType(WellKnownType type, params DiagnosticDescr private static void VerifyMissingType(string source, WellKnownType type, params DiagnosticDescription[] expected) { - var lib = CreateCompilationWithTasksExtensions(AsyncStreamsTypes); + var lib = CreateCompilationWithTasksExtensions(new[] { AsyncStreamsTypes }); var lib_ref = lib.EmitToImageReference(); var comp = CreateCompilationWithTasksExtensions(source, references: new[] { lib_ref }); comp.MakeTypeMissing(type); @@ -862,6 +863,74 @@ public void MissingTypeAndMembers_ValueTask() ); } + [Fact] + public void MissingTypeAndMembers_CancellationToken() + { + VerifyMissingMember(_enumerable, WellKnownMember.System_Threading_CancellationToken__Equals, + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationToken.Equals' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationToken", "Equals").WithLocation(5, 64) + ); + + VerifyMissingMember(_enumerator, WellKnownMember.System_Threading_CancellationToken__Equals); + + VerifyMissingType(_enumerable, WellKnownType.System_Threading_CancellationToken, + // (5,64): error CS0656: Missing compiler required member 'System.Collections.Generic.IAsyncEnumerable`1.GetAsyncEnumerator' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Collections.Generic.IAsyncEnumerable`1", "GetAsyncEnumerator").WithLocation(5, 64), + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationToken.Equals' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationToken", "Equals").WithLocation(5, 64), + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.CreateLinkedTokenSource' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "CreateLinkedTokenSource").WithLocation(5, 64), + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.Token' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "Token").WithLocation(5, 64) + ); + + VerifyMissingType(_enumerator, WellKnownType.System_Threading_CancellationToken); + } + + [Fact] + public void MissingTypeAndMembers_CancellationTokenSource() + { + VerifyMissingMember(_enumerable, WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.CreateLinkedTokenSource' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "CreateLinkedTokenSource").WithLocation(5, 64) + ); + + VerifyMissingMember(_enumerable, WellKnownMember.System_Threading_CancellationTokenSource__Token, + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.Token' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "Token").WithLocation(5, 64) + ); + + VerifyMissingMember(_enumerable, WellKnownMember.System_Threading_CancellationTokenSource__Dispose, + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.Dispose' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "Dispose").WithLocation(5, 64) + ); + + VerifyMissingType(_enumerable, WellKnownType.System_Threading_CancellationTokenSource, + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.CreateLinkedTokenSource' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "CreateLinkedTokenSource").WithLocation(5, 64), + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.Token' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "Token").WithLocation(5, 64), + // (5,64): error CS0656: Missing compiler required member 'System.Threading.CancellationTokenSource.Dispose' + // async System.Collections.Generic.IAsyncEnumerable M() { await Task.CompletedTask; yield return 3; } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "{ await Task.CompletedTask; yield return 3; }").WithArguments("System.Threading.CancellationTokenSource", "Dispose").WithLocation(5, 64) + ); + + VerifyMissingMember(_enumerator, WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource); + VerifyMissingMember(_enumerator, WellKnownMember.System_Threading_CancellationTokenSource__Token); + VerifyMissingMember(_enumerator, WellKnownMember.System_Threading_CancellationTokenSource__Dispose); + VerifyMissingType(_enumerator, WellKnownType.System_Threading_CancellationTokenSource); + } + [Fact] public void AsyncIteratorWithBreak() { @@ -1814,9 +1883,9 @@ public void AsyncIteratorWithAwaitCompletedAndYield() { string source = @" using static System.Console; -class C +public class C { - static async System.Collections.Generic.IAsyncEnumerable M() + public static async System.Collections.Generic.IAsyncEnumerable M() { Write(""1 ""); await System.Threading.Tasks.Task.CompletedTask; @@ -1840,6 +1909,17 @@ await foreach (var i in M()) comp.VerifyDiagnostics(); var verifier = CompileAndVerify(comp, expectedOutput: "0 1 2 3 4 5"); + var expectedFields = new[] { + "FieldDefinition:Int32 <>1__state", + "FieldDefinition:System.Runtime.CompilerServices.AsyncIteratorMethodBuilder <>t__builder", + "FieldDefinition:System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1{Boolean} <>v__promiseOfValueOrEnd", + "FieldDefinition:Int32 <>2__current", + "FieldDefinition:Boolean <>w__disposeMode", + "FieldDefinition:Int32 <>l__initialThreadId", + "FieldDefinition:System.Runtime.CompilerServices.TaskAwaiter <>u__1" + }; + VerifyStateMachineFields(comp, "d__0", expectedFields); + verifier.VerifyIL("C.M", @" { // Code size 8 (0x8) @@ -2338,6 +2418,484 @@ .maxstack 3 } } + [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void AsyncIteratorWithAwaitCompletedAndYield_WithEnumeratorCancellation() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Threading; +class C +{ + static async System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token) + { + _ = token; + await System.Threading.Tasks.Task.CompletedTask; + yield return 3; + } +}"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp); + + var expectedFields = new[] { + "FieldDefinition:Int32 <>1__state", + "FieldDefinition:System.Runtime.CompilerServices.AsyncIteratorMethodBuilder <>t__builder", + "FieldDefinition:System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1{Boolean} <>v__promiseOfValueOrEnd", + "FieldDefinition:Int32 <>2__current", + "FieldDefinition:Boolean <>w__disposeMode", + "FieldDefinition:System.Threading.CancellationTokenSource <>x__combinedTokens", + "FieldDefinition:Int32 <>l__initialThreadId", + "FieldDefinition:System.Threading.CancellationToken token", + "FieldDefinition:System.Threading.CancellationToken <>3__token", + "FieldDefinition:System.Runtime.CompilerServices.TaskAwaiter <>u__1" + }; + VerifyStateMachineFields(comp, "d__0", expectedFields); + + // we generate initialization logic for the token parameter + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" +{ + // Code size 165 (0xa5) + .maxstack 3 + .locals init (C.d__0 V_0, + System.Threading.CancellationToken V_1) + 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: stloc.0 + IL_0021: ldarg.0 + IL_0022: ldc.i4.0 + IL_0023: stfld ""bool C.d__0.<>w__disposeMode"" + 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: ldarg.0 + IL_0033: ldflda ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0038: ldloca.s V_1 + IL_003a: initobj ""System.Threading.CancellationToken"" + IL_0040: ldloc.1 + IL_0041: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0046: brfalse.s IL_0051 + IL_0048: ldloc.0 + IL_0049: ldarg.1 + IL_004a: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_004f: br.s IL_00a3 + IL_0051: ldarga.s V_1 + IL_0053: ldarg.0 + IL_0054: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0059: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_005e: brtrue.s IL_0072 + IL_0060: ldarga.s V_1 + IL_0062: ldloca.s V_1 + IL_0064: initobj ""System.Threading.CancellationToken"" + IL_006a: ldloc.1 + IL_006b: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0070: brfalse.s IL_0080 + IL_0072: ldloc.0 + IL_0073: ldarg.0 + IL_0074: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0079: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_007e: br.s IL_00a3 + IL_0080: ldarg.0 + IL_0081: ldarg.0 + IL_0082: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0087: ldarg.1 + IL_0088: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" + IL_008d: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_0092: ldloc.0 + IL_0093: ldarg.0 + IL_0094: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_0099: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" + IL_009e: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_00a3: ldloc.0 + IL_00a4: ret +}"); + + // we generate disposal logic for the combinedTokens field + verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" +{ + // Code size 304 (0x130) + .maxstack 3 + .locals init (int V_0, + System.Runtime.CompilerServices.TaskAwaiter V_1, + C.d__0 V_2, + System.Exception V_3) + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + // sequence point: + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: switch ( + IL_00b1, + IL_0024, + IL_0024, + IL_0024, + IL_007b) + IL_0024: ldarg.0 + IL_0025: ldfld ""bool C.d__0.<>w__disposeMode"" + IL_002a: brfalse.s IL_0031 + IL_002c: leave IL_00f4 + 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"" + // sequence point: _ = token; + IL_003a: ldarg.0 + IL_003b: ldfld ""System.Threading.CancellationToken C.d__0.token"" + IL_0040: pop + // sequence point: await System.Threading.Tasks.Task.CompletedTask; + IL_0041: call ""System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get"" + IL_0046: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" + IL_004b: stloc.1 + // sequence point: + IL_004c: ldloca.s V_1 + IL_004e: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" + IL_0053: brtrue.s IL_0097 + IL_0055: ldarg.0 + IL_0056: ldc.i4.0 + IL_0057: dup + IL_0058: stloc.0 + IL_0059: stfld ""int C.d__0.<>1__state"" + // async: yield + IL_005e: ldarg.0 + IL_005f: ldloc.1 + IL_0060: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_0065: ldarg.0 + IL_0066: stloc.2 + IL_0067: ldarg.0 + IL_0068: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder"" + IL_006d: ldloca.s V_1 + IL_006f: ldloca.s V_2 + IL_0071: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.d__0)"" + IL_0076: leave IL_012f + // async: resume + IL_007b: ldarg.0 + IL_007c: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_0081: stloc.1 + IL_0082: ldarg.0 + IL_0083: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_0088: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" + IL_008e: ldarg.0 + IL_008f: ldc.i4.m1 + IL_0090: dup + IL_0091: stloc.0 + IL_0092: stfld ""int C.d__0.<>1__state"" + IL_0097: ldloca.s V_1 + IL_0099: call ""void System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" + // sequence point: yield return 3; + IL_009e: ldarg.0 + IL_009f: ldc.i4.3 + IL_00a0: stfld ""int C.d__0.<>2__current"" + IL_00a5: ldarg.0 + IL_00a6: ldc.i4.s -4 + IL_00a8: dup + IL_00a9: stloc.0 + IL_00aa: stfld ""int C.d__0.<>1__state"" + IL_00af: leave.s IL_0123 + // sequence point: + IL_00b1: ldarg.0 + IL_00b2: ldc.i4.m1 + IL_00b3: dup + IL_00b4: stloc.0 + IL_00b5: stfld ""int C.d__0.<>1__state"" + IL_00ba: ldarg.0 + IL_00bb: ldfld ""bool C.d__0.<>w__disposeMode"" + IL_00c0: pop + // sequence point: + IL_00c1: leave.s IL_00f4 + } + catch System.Exception + { + // sequence point: + IL_00c3: stloc.3 + IL_00c4: ldarg.0 + IL_00c5: ldc.i4.s -2 + IL_00c7: stfld ""int C.d__0.<>1__state"" + IL_00cc: ldarg.0 + IL_00cd: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00d2: brfalse.s IL_00e6 + IL_00d4: ldarg.0 + IL_00d5: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00da: callvirt ""void System.Threading.CancellationTokenSource.Dispose()"" + IL_00df: ldarg.0 + IL_00e0: ldnull + IL_00e1: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00e6: ldarg.0 + IL_00e7: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_00ec: ldloc.3 + IL_00ed: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)"" + IL_00f2: leave.s IL_012f + } + // sequence point: } + IL_00f4: ldarg.0 + IL_00f5: ldc.i4.s -2 + IL_00f7: stfld ""int C.d__0.<>1__state"" + // sequence point: + IL_00fc: ldarg.0 + IL_00fd: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_0102: brfalse.s IL_0116 + IL_0104: ldarg.0 + IL_0105: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_010a: callvirt ""void System.Threading.CancellationTokenSource.Dispose()"" + IL_010f: ldarg.0 + IL_0110: ldnull + IL_0111: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_0116: ldarg.0 + IL_0117: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_011c: ldc.i4.0 + IL_011d: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" + IL_0122: ret + IL_0123: ldarg.0 + IL_0124: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_0129: ldc.i4.1 + IL_012a: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" + IL_012f: ret +}", sequencePoints: "C+d__0.MoveNext", source: source); + } + + [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)] + public void AsyncIteratorWithAwaitCompletedAndYield_WithEnumeratorCancellation_NoUsage() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Threading; +class C +{ + static async System.Collections.Generic.IAsyncEnumerable M([EnumeratorCancellation] CancellationToken token) + { + await System.Threading.Tasks.Task.CompletedTask; + yield return 3; + } +}"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.ReleaseDll); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp); + + var expectedFields = new[] { + "FieldDefinition:Int32 <>1__state", + "FieldDefinition:System.Runtime.CompilerServices.AsyncIteratorMethodBuilder <>t__builder", + "FieldDefinition:System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1{Boolean} <>v__promiseOfValueOrEnd", + "FieldDefinition:Int32 <>2__current", + "FieldDefinition:Boolean <>w__disposeMode", + "FieldDefinition:System.Threading.CancellationTokenSource <>x__combinedTokens", // we generated the field + "FieldDefinition:Int32 <>l__initialThreadId", + "FieldDefinition:System.Runtime.CompilerServices.TaskAwaiter <>u__1" + }; + VerifyStateMachineFields(comp, "d__0", expectedFields); + + // we don't generate initialization logic + verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" +{ + // Code size 52 (0x34) + .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: stloc.0 + IL_0021: ldarg.0 + IL_0022: ldc.i4.0 + IL_0023: stfld ""bool C.d__0.<>w__disposeMode"" + 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: ret +}"); + + // we generate disposal logic for the combinedTokens field + verifier.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" +{ + // Code size 297 (0x129) + .maxstack 3 + .locals init (int V_0, + System.Runtime.CompilerServices.TaskAwaiter V_1, + C.d__0 V_2, + System.Exception V_3) + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + // sequence point: + IL_0007: ldloc.0 + IL_0008: ldc.i4.s -4 + IL_000a: sub + IL_000b: switch ( + IL_00aa, + IL_0024, + IL_0024, + IL_0024, + IL_0074) + IL_0024: ldarg.0 + IL_0025: ldfld ""bool C.d__0.<>w__disposeMode"" + IL_002a: brfalse.s IL_0031 + IL_002c: leave IL_00ed + 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"" + // sequence point: await System.Threading.Tasks.Task.CompletedTask; + IL_003a: call ""System.Threading.Tasks.Task System.Threading.Tasks.Task.CompletedTask.get"" + IL_003f: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" + IL_0044: stloc.1 + // sequence point: + IL_0045: ldloca.s V_1 + IL_0047: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" + IL_004c: brtrue.s IL_0090 + IL_004e: ldarg.0 + IL_004f: ldc.i4.0 + IL_0050: dup + IL_0051: stloc.0 + IL_0052: stfld ""int C.d__0.<>1__state"" + // async: yield + IL_0057: ldarg.0 + IL_0058: ldloc.1 + IL_0059: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_005e: ldarg.0 + IL_005f: stloc.2 + IL_0060: ldarg.0 + IL_0061: ldflda ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder"" + IL_0066: ldloca.s V_1 + IL_0068: ldloca.s V_2 + IL_006a: call ""void System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.d__0)"" + IL_006f: leave IL_0128 + // async: resume + IL_0074: ldarg.0 + IL_0075: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_007a: stloc.1 + IL_007b: ldarg.0 + IL_007c: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.d__0.<>u__1"" + IL_0081: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" + IL_0087: ldarg.0 + IL_0088: ldc.i4.m1 + IL_0089: dup + IL_008a: stloc.0 + IL_008b: stfld ""int C.d__0.<>1__state"" + IL_0090: ldloca.s V_1 + IL_0092: call ""void System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" + // sequence point: yield return 3; + IL_0097: ldarg.0 + IL_0098: ldc.i4.3 + IL_0099: stfld ""int C.d__0.<>2__current"" + IL_009e: ldarg.0 + IL_009f: ldc.i4.s -4 + IL_00a1: dup + IL_00a2: stloc.0 + IL_00a3: stfld ""int C.d__0.<>1__state"" + IL_00a8: leave.s IL_011c + // sequence point: + IL_00aa: ldarg.0 + IL_00ab: ldc.i4.m1 + IL_00ac: dup + IL_00ad: stloc.0 + IL_00ae: stfld ""int C.d__0.<>1__state"" + IL_00b3: ldarg.0 + IL_00b4: ldfld ""bool C.d__0.<>w__disposeMode"" + IL_00b9: pop + // sequence point: + IL_00ba: leave.s IL_00ed + } + catch System.Exception + { + // sequence point: + IL_00bc: stloc.3 + IL_00bd: ldarg.0 + IL_00be: ldc.i4.s -2 + IL_00c0: stfld ""int C.d__0.<>1__state"" + IL_00c5: ldarg.0 + IL_00c6: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00cb: brfalse.s IL_00df + IL_00cd: ldarg.0 + IL_00ce: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00d3: callvirt ""void System.Threading.CancellationTokenSource.Dispose()"" + IL_00d8: ldarg.0 + IL_00d9: ldnull + IL_00da: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00df: ldarg.0 + IL_00e0: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_00e5: ldloc.3 + IL_00e6: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetException(System.Exception)"" + IL_00eb: leave.s IL_0128 + } + // sequence point: } + IL_00ed: ldarg.0 + IL_00ee: ldc.i4.s -2 + IL_00f0: stfld ""int C.d__0.<>1__state"" + // sequence point: + IL_00f5: ldarg.0 + IL_00f6: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00fb: brfalse.s IL_010f + IL_00fd: ldarg.0 + IL_00fe: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_0103: callvirt ""void System.Threading.CancellationTokenSource.Dispose()"" + IL_0108: ldarg.0 + IL_0109: ldnull + IL_010a: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_010f: ldarg.0 + IL_0110: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_0115: ldc.i4.0 + IL_0116: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" + IL_011b: ret + IL_011c: ldarg.0 + IL_011d: ldflda ""System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore C.d__0.<>v__promiseOfValueOrEnd"" + IL_0122: ldc.i4.1 + IL_0123: call ""void System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore.SetResult(bool)"" + IL_0128: ret +}", sequencePoints: "C+d__0.MoveNext", source: source); + } + + private static void VerifyStateMachineFields(CSharpCompilation comp, string methodName, string[] expectedFields) + { + var peReader = new PEReader(comp.EmitToArray()); + var metadataReader = peReader.GetMetadataReader(); + var types = metadataReader.TypeDefinitions.Select(t => metadataReader.GetString(metadataReader.GetTypeDefinition(t).Name)); + var type = metadataReader.TypeDefinitions.Single(t => metadataReader.GetString(metadataReader.GetTypeDefinition(t).Name) == methodName); + var fields = metadataReader.GetTypeDefinition(type).GetFields().Select(f => metadataReader.Dump(f)); + AssertEx.SetEqual(expectedFields, fields); + } + + private void ValidateStateMachineFields(ModuleSymbol module, string methodName, string[] expected) + { + var method = module.GlobalNamespace.GetMember(methodName); + AssertEx.SetEqual(new[] { "AsyncIteratorStateMachineAttribute" }, + GetAttributeNames(method.GetAttributes())); + + var attribute = method.GetAttributes().Single(); + var argument = attribute.ConstructorArguments.Single(); + var stateMachineType = (ITypeSymbol)argument.Value; + Assert.Equal("C.d__0", stateMachineType.ToTestDisplayString()); + + AssertEx.SetEqual(expected, stateMachineType.GetMembers().Where(m => m.Kind == SymbolKind.Field).Select(m => m.ToTestDisplayString())); + } + [Fact] public void AsyncIteratorWithGenericReturn() { @@ -5027,7 +5585,7 @@ static async Task Main() { using CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; - var enumerable = Iter(42, token); + var enumerable = Iter(42, token, token); await using var enumerator = enumerable.GetAsyncEnumerator(); // no token passed if (!await enumerator.MoveNextAsync()) throw null; @@ -5046,8 +5604,9 @@ static async Task Main() Write(""Cancelled""); } } - static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token) + static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token, CancellationToken origToken) { + if (!token.Equals(origToken)) throw null; // no need for a combined token yield return value++; await Task.Yield(); yield return value++; @@ -5063,6 +5622,55 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E // IL for GetAsyncEnumerator is verified by AsyncIteratorWithAwaitCompletedAndYield already } + [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + public void CancellationTokenParameter_SameTokenPassedInGetAsyncEnumerator() + { + string source = @" +using static System.Console; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +class C +{ + static async Task Main() + { + using CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken token = source.Token; + var enumerable = Iter(42, token, origToken: token); + await using var enumerator = enumerable.GetAsyncEnumerator(token); // same token passed + + if (!await enumerator.MoveNextAsync()) throw null; + System.Console.Write($""{enumerator.Current} ""); // 42 + + if (!await enumerator.MoveNextAsync()) throw null; + System.Console.Write($""{enumerator.Current} ""); // 43 + + source.Cancel(); + try + { + await enumerator.MoveNextAsync(); + } + catch (System.OperationCanceledException) + { + Write(""Cancelled""); + } + } + static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token, CancellationToken origToken) + { + if (!token.Equals(origToken)) throw null; // no need for a combined token + yield return value++; + await Task.Yield(); + yield return value++; + token.ThrowIfCancellationRequested(); + Write(""SKIPPED""); + yield return value++; + } +}"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + } + [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] public void CancellationTokenParameter_DefaultTokenPassedInGetAsyncEnumerator() { @@ -5125,7 +5733,7 @@ static async Task Main() { using CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; - var enumerable = Iter(42, default); + var enumerable = Iter(42, default, token); await using var enumerator = enumerable.GetAsyncEnumerator(token); // some token passed if (!await enumerator.MoveNextAsync()) throw null; @@ -5144,8 +5752,9 @@ static async Task Main() Write(""Cancelled""); } } - static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token1) + static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token1, CancellationToken origToken) { + if (!token1.Equals(origToken)) throw null; yield return value++; await Task.Yield(); yield return value++; @@ -5160,11 +5769,11 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E comp.VerifyDiagnostics(); var verifier = CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); - // field for token1 gets overridden with value from GetAsyncEnumerator's token parameter + // 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)", @" { - // Code size 103 (0x67) - .maxstack 2 + // Code size 189 (0xbd) + .maxstack 3 .locals init (C.d__1 V_0, System.Threading.CancellationToken V_1) IL_0000: ldarg.0 @@ -5191,22 +5800,50 @@ .maxstack 2 IL_0033: ldarg.0 IL_0034: ldfld ""int C.d__1.<>3__value"" IL_0039: stfld ""int C.d__1.value"" - IL_003e: ldarga.s V_1 - IL_0040: ldloca.s V_1 - IL_0042: initobj ""System.Threading.CancellationToken"" - IL_0048: ldloc.1 - IL_0049: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_004e: brfalse.s IL_005e - IL_0050: ldloc.0 - IL_0051: ldarg.0 - IL_0052: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" - IL_0057: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_005c: br.s IL_0065 - IL_005e: ldloc.0 - IL_005f: ldarg.1 - IL_0060: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_0065: ldloc.0 - IL_0066: ret + IL_003e: ldarg.0 + IL_003f: ldflda ""System.Threading.CancellationToken C.d__1.<>3__token1"" + IL_0044: ldloca.s V_1 + IL_0046: initobj ""System.Threading.CancellationToken"" + IL_004c: ldloc.1 + IL_004d: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0052: brfalse.s IL_005d + IL_0054: ldloc.0 + IL_0055: ldarg.1 + IL_0056: stfld ""System.Threading.CancellationToken C.d__1.token1"" + IL_005b: br.s IL_00af + IL_005d: ldarga.s V_1 + IL_005f: ldarg.0 + IL_0060: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" + IL_0065: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_006a: brtrue.s IL_007e + IL_006c: ldarga.s V_1 + IL_006e: ldloca.s V_1 + IL_0070: initobj ""System.Threading.CancellationToken"" + IL_0076: ldloc.1 + IL_0077: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_007c: brfalse.s IL_008c + IL_007e: ldloc.0 + IL_007f: ldarg.0 + IL_0080: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" + IL_0085: stfld ""System.Threading.CancellationToken C.d__1.token1"" + IL_008a: br.s IL_00af + IL_008c: ldarg.0 + IL_008d: ldarg.0 + IL_008e: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" + IL_0093: ldarg.1 + IL_0094: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" + IL_0099: stfld ""System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens"" + IL_009e: ldloc.0 + IL_009f: ldarg.0 + IL_00a0: ldfld ""System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens"" + IL_00a5: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" + IL_00aa: stfld ""System.Threading.CancellationToken C.d__1.token1"" + IL_00af: ldloc.0 + IL_00b0: ldarg.0 + IL_00b1: ldfld ""System.Threading.CancellationToken C.d__1.<>3__origToken"" + IL_00b6: stfld ""System.Threading.CancellationToken C.d__1.origToken"" + IL_00bb: ldloc.0 + IL_00bc: ret } "); } @@ -5257,59 +5894,10 @@ 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 comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options); comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); - - // field for token1 gets overridden with value from GetAsyncEnumerator's token parameter - verifier.VerifyIL("C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" -{ - // Code size 103 (0x67) - .maxstack 2 - .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: stloc.0 - IL_0021: ldarg.0 - IL_0022: ldc.i4.0 - IL_0023: stfld ""bool C.d__1.<>w__disposeMode"" - 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 ""int C.d__1.<>3__value"" - IL_0039: stfld ""int C.d__1.value"" - IL_003e: ldarga.s V_1 - IL_0040: ldloca.s V_1 - IL_0042: initobj ""System.Threading.CancellationToken"" - IL_0048: ldloc.1 - IL_0049: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_004e: brfalse.s IL_005e - IL_0050: ldloc.0 - IL_0051: ldarg.0 - IL_0052: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" - IL_0057: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_005c: br.s IL_0065 - IL_005e: ldloc.0 - IL_005f: ldarg.1 - IL_0060: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_0065: ldloc.0 - IL_0066: ret -} -"); + CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); } } @@ -5371,12 +5959,11 @@ class C static async Task Main() { using CancellationTokenSource source1 = new CancellationTokenSource(); - source1.Cancel(); CancellationToken token1 = source1.Token; - var enumerable = Iter(default, 42); - using CancellationTokenSource source2 = new CancellationTokenSource(); CancellationToken token2 = source2.Token; + var enumerable = Iter(token1, token2, token1, 42); + await using var enumerator = enumerable.GetAsyncEnumerator(token2); // some other token passed if (!await enumerator.MoveNextAsync()) throw null; @@ -5385,7 +5972,7 @@ static async Task Main() if (!await enumerator.MoveNextAsync()) throw null; System.Console.Write($""{enumerator.Current} ""); // 43 - source2.Cancel(); + SOURCETOCANCEL.Cancel(); try { await enumerator.MoveNextAsync(); @@ -5395,72 +5982,61 @@ static async Task Main() Write(""Cancelled""); } } - static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCancellation] CancellationToken token1, int value) // note: token is in first position + + static async System.Collections.Generic.IAsyncEnumerable Iter(CancellationToken token1, CancellationToken token2, [EnumeratorCancellation] CancellationToken token3, int value) // note: token is in first position { + if (token3.Equals(token1) || token3.Equals(token2)) throw null; yield return value++; await Task.Yield(); yield return value++; - token1.ThrowIfCancellationRequested(); + token3.ThrowIfCancellationRequested(); Write(""SKIPPED""); yield return value++; } }"; + // 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 }) { - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options); - comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "42 43 Cancelled"); + 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"); + } + } + } - // field for token1 gets overridden with value from GetAsyncEnumerator's token parameter - verifier.VerifyIL("C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" + [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + public void CancellationTokenParameter_TwoDefaultTokens() + { + string source = @" +using static System.Console; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +class C { - // Code size 103 (0x67) - .maxstack 2 - .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: stloc.0 - IL_0021: ldarg.0 - IL_0022: ldc.i4.0 - IL_0023: stfld ""bool C.d__1.<>w__disposeMode"" - 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: ldarga.s V_1 - IL_0034: ldloca.s V_1 - IL_0036: initobj ""System.Threading.CancellationToken"" - IL_003c: ldloc.1 - IL_003d: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0042: brfalse.s IL_0052 - IL_0044: ldloc.0 - IL_0045: ldarg.0 - IL_0046: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" - IL_004b: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_0050: br.s IL_0059 - IL_0052: ldloc.0 - IL_0053: ldarg.1 - IL_0054: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_0059: ldloc.0 - IL_005a: ldarg.0 - IL_005b: ldfld ""int C.d__1.<>3__value"" - IL_0060: stfld ""int C.d__1.value"" - IL_0065: ldloc.0 - IL_0066: ret -} -"); - } + static async Task Main() + { + var enumerable = Iter(default); + await using var enumerator = enumerable.GetAsyncEnumerator(default); + + if (!await enumerator.MoveNextAsync()) throw null; + System.Console.Write($""{enumerator.Current} ""); // 1 + + if (await enumerator.MoveNextAsync()) throw null; + } + + static async System.Collections.Generic.IAsyncEnumerable Iter([EnumeratorCancellation] CancellationToken token) + { + if (!token.Equals(default)) Write(""SKIPPED""); + yield return 1; + await Task.Yield(); + } +}"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "1"); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -5589,123 +6165,29 @@ class C ); } - [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + [Fact] + [WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] + [WorkItem(35159, "https://github.com/dotnet/roslyn/issues/35159")] public void CancellationTokenParameter_TwoParameterHaveAttribute() { string source = @" -using static System.Console; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; class C { - static async Task Main() - { - using CancellationTokenSource source = new CancellationTokenSource(); - CancellationToken token = source.Token; - var enumerable = Iter(42, default, default); - await using var enumerator = enumerable.GetAsyncEnumerator(token); // some token passed - - if (!await enumerator.MoveNextAsync()) throw null; - System.Console.Write($""{enumerator.Current} ""); // 42 - - if (!await enumerator.MoveNextAsync()) throw null; - System.Console.Write($""{enumerator.Current} ""); // 43 - - source.Cancel(); - try - { - await enumerator.MoveNextAsync(); - } - catch (System.OperationCanceledException) - { - Write(""Cancelled""); - } - } static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token1, [EnumeratorCancellation] CancellationToken token2) { yield return value++; await Task.Yield(); - yield return value++; - if (token1.IsCancellationRequested) Write(""token1 cancelled ""); - if (token2.IsCancellationRequested) Write(""token2 cancelled ""); - token2.ThrowIfCancellationRequested(); - Write(""SKIPPED""); - yield return value++; } }"; - // Should we produce a warning here (attribute specified on two parameters)? - // Tracked by https://github.com/dotnet/roslyn/issues/35159 - foreach (var options in new[] { TestOptions.DebugExe, TestOptions.ReleaseExe }) - { - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: options); - comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(comp, expectedOutput: "42 43 token1 cancelled token2 cancelled Cancelled"); - - // fields for token1 and token2 get overridden with value from token parameter - verifier.VerifyIL("C.d__1.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" -{ - // Code size 142 (0x8e) - .maxstack 2 - .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: stloc.0 - IL_0021: ldarg.0 - IL_0022: ldc.i4.0 - IL_0023: stfld ""bool C.d__1.<>w__disposeMode"" - 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 ""int C.d__1.<>3__value"" - IL_0039: stfld ""int C.d__1.value"" - IL_003e: ldarga.s V_1 - IL_0040: ldloca.s V_1 - IL_0042: initobj ""System.Threading.CancellationToken"" - IL_0048: ldloc.1 - IL_0049: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_004e: brfalse.s IL_005e - IL_0050: ldloc.0 - IL_0051: ldarg.0 - IL_0052: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" - IL_0057: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_005c: br.s IL_0065 - IL_005e: ldloc.0 - IL_005f: ldarg.1 - IL_0060: stfld ""System.Threading.CancellationToken C.d__1.token1"" - IL_0065: ldarga.s V_1 - IL_0067: ldloca.s V_1 - IL_0069: initobj ""System.Threading.CancellationToken"" - IL_006f: ldloc.1 - IL_0070: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0075: brfalse.s IL_0085 - IL_0077: ldloc.0 - IL_0078: ldarg.0 - IL_0079: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token2"" - IL_007e: stfld ""System.Threading.CancellationToken C.d__1.token2"" - IL_0083: br.s IL_008c - IL_0085: ldloc.0 - IL_0086: ldarg.1 - IL_0087: stfld ""System.Threading.CancellationToken C.d__1.token2"" - IL_008c: ldloc.0 - IL_008d: ret -} -"); - } + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }); + comp.VerifyDiagnostics( + // (7,67): error CS8426: The attribute [EnumeratorCancellation] cannot be used on multiple parameters + // static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [EnumeratorCancellation] CancellationToken token1, [EnumeratorCancellation] CancellationToken token2) + Diagnostic(ErrorCode.ERR_MultipleEnumeratorCancellationAttributes, "Iter").WithLocation(7, 67) + ); } [Fact, WorkItem(34407, "https://github.com/dotnet/roslyn/issues/34407")] @@ -5955,6 +6437,37 @@ public partial class C2 ); } + [Fact, WorkItem(35165, "https://github.com/dotnet/roslyn/issues/35165")] + public void CancellationTokenParameter_LocalFunction() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +public class C +{ + async Task M() + { + await foreach (var i in local(default)) + { + } + + async System.Collections.Generic.IAsyncEnumerable local([EnumeratorCancellation] CancellationToken token) + { + yield return 1; + await Task.Yield(); + } + } +} +"; + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }); + comp.VerifyDiagnostics( + // (13,70): error CS8205: Attributes are not allowed on local function parameters or type parameters + // async System.Collections.Generic.IAsyncEnumerable local([EnumeratorCancellation] CancellationToken token) + Diagnostic(ErrorCode.ERR_AttributesInLocalFuncDecl, "[EnumeratorCancellation]").WithLocation(13, 70) + ); + } + [Fact, WorkItem(35166, "https://github.com/dotnet/roslyn/issues/35166")] public void CancellationTokenParameter_ParameterWithoutAttribute() { diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index cb7ad08a23db4a4122f6bab10189d7990e68ebb1..5b23c2c7ed7b027747e8dd3ecb75fc38ac29be39 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -492,6 +492,9 @@ internal enum WellKnownMember System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, System_Threading_CancellationToken__Equals, + System_Threading_CancellationTokenSource__CreateLinkedTokenSource, + System_Threading_CancellationTokenSource__Token, + System_Threading_CancellationTokenSource__Dispose, Count diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 16a3d5108ffb6eeb90bd359ac173c6539876e411..f17d81ddfdcccca6df51a75250a7dcdf0fbefcdb 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3396,6 +3396,29 @@ static WellKnownMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationToken - WellKnownType.ExtSentinel), // Argument: CancellationToken + + // System_Threading_CancellationTokenSource__CreateLinkedTokenSource + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationTokenSource - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationTokenSource - WellKnownType.ExtSentinel), // Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationToken - WellKnownType.ExtSentinel), // Argument: CancellationToken + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationToken - WellKnownType.ExtSentinel), // Argument: CancellationToken + + // System_Threading_CancellationTokenSource__Token + (byte)MemberFlags.Property, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationTokenSource - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationToken - WellKnownType.ExtSentinel), // Return Type + + // System_Threading_CancellationTokenSource__Dispose + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Threading_CancellationTokenSource - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -3823,6 +3846,9 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_SwitchExpressionException__ctor ".ctor", // System_Runtime_CompilerServices_SwitchExpressionException__ctorObject "Equals", // System_Threading_CancellationToken__Equals + "CreateLinkedTokenSource", // System_Threading_CancellationTokenSource__CreateLinkedTokenSource + "Token", // System_Threading_CancellationTokenSource__Token + "Dispose", // System_Threading_CancellationTokenSource__Dispose }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index c12b95e763b2071842d8549c672d64e327c42116..aca3a83d6a3d1360abf7c620897327a4a761f3a6 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -297,6 +297,7 @@ internal enum WellKnownType System_Threading_Tasks_ValueTask, System_Runtime_CompilerServices_AsyncIteratorMethodBuilder, System_Threading_CancellationToken, + System_Threading_CancellationTokenSource, System_InvalidOperationException, System_Runtime_CompilerServices_SwitchExpressionException, @@ -594,6 +595,7 @@ internal static class WellKnownTypes "System.Threading.Tasks.ValueTask", "System.Runtime.CompilerServices.AsyncIteratorMethodBuilder", "System.Threading.CancellationToken", + "System.Threading.CancellationTokenSource", "System.InvalidOperationException", "System.Runtime.CompilerServices.SwitchExpressionException" diff --git a/src/Test/Utilities/Portable/Metadata/MetadataReaderUtils.cs b/src/Test/Utilities/Portable/Metadata/MetadataReaderUtils.cs index f638e6f345e8e28995fd0e0c9197f2b8115340ab..584a2464cae61f74bc531d140cc7bd33dd285f9e 100644 --- a/src/Test/Utilities/Portable/Metadata/MetadataReaderUtils.cs +++ b/src/Test/Utilities/Portable/Metadata/MetadataReaderUtils.cs @@ -350,6 +350,17 @@ private static string DumpRec(this MetadataReader reader, EntityHandle handle) TypeReference type = reader.GetTypeReference((TypeReferenceHandle)handle); return getQualifiedName(type.Namespace, type.Name); } + case HandleKind.FieldDefinition: + { + FieldDefinition field = reader.GetFieldDefinition((FieldDefinitionHandle)handle); + var name = reader.GetString(field.Name); + + var blob = reader.GetBlobReader(field.Signature); + var decoder = new SignatureDecoder(ConstantSignatureVisualizer.Instance, reader, genericContext: null); + var type = decoder.DecodeFieldSignature(ref blob); + + return $"{type} {name}"; + } default: return null; }