From 036d931af74248540a51c491e7ed0709168e7c92 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 23 Aug 2019 14:32:22 -0700 Subject: [PATCH] Fix IOperation for asynchronous using and foreach statements (#37963) --- .../Portable/Binder/ForEachEnumeratorInfo.cs | 9 +- .../Portable/Binder/ForEachLoopBinder.cs | 8 +- .../Compilation/ForEachStatementInfo.cs | 17 +- .../Compilation/MemberSemanticModel.cs | 1 + .../Operations/CSharpOperationFactory.cs | 3 +- .../Operations/CSharpOperationNodes.cs | 4 +- .../CSharp/Portable/PublicAPI.Unshipped.txt | 1 + .../Symbols/Compilation_WellKnownMembers.cs | 5 + .../Emit/CodeGen/CodeGenAwaitForeachTests.cs | 1 + .../IOperationTests_IForEachLoopStatement.cs | 219 +++++++++++++----- .../IOperationTests_IUsingStatement.cs | 27 +-- .../Test/Semantic/Semantics/ForEachTests.cs | 1 + .../Compilation/ForEachStatementInfoTests.cs | 15 +- .../Core/Portable/Compilation/Compilation.cs | 5 + .../Generated/Operations.Generated.cs | 38 ++- .../Operations/ControlFlowGraphBuilder.cs | 39 +++- .../Loops/ForEachLoopOperationInfo.cs | 3 + .../Portable/Operations/OperationCloner.cs | 4 +- .../Operations/OperationInterfaces.xml | 16 ++ .../Portable/Operations/OperationNodes.cs | 4 +- .../Core/Portable/PublicAPI.Unshipped.txt | 2 + .../Operations/VisualBasicOperationFactory.vb | 1 + .../Operations/VisualBasicOperationNodes.vb | 4 +- .../Portable/Symbols/WellKnownMembers.vb | 4 + .../Compilation/OperationTreeVerifier.cs | 8 + 25 files changed, 315 insertions(+), 124 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs b/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs index 73d2af7a725fb..58f694b39b45d 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs @@ -27,6 +27,8 @@ internal sealed class ForEachEnumeratorInfo // Computed during initial binding so that we can expose it in the semantic model. public readonly bool NeedsDisposal; + public readonly bool IsAsync; + // When async and needs disposal, this stores the information to await the DisposeAsync() invocation public AwaitableInfo DisposeAwaitableInfo; @@ -41,15 +43,13 @@ internal sealed class ForEachEnumeratorInfo public readonly BinderFlags Location; - internal bool IsAsync - => DisposeAwaitableInfo != null; - private ForEachEnumeratorInfo( TypeSymbol collectionType, TypeWithAnnotations elementType, MethodSymbol getEnumeratorMethod, MethodSymbol currentPropertyGetter, MethodSymbol moveNextMethod, + bool isAsync, bool needsDisposal, AwaitableInfo disposeAwaitableInfo, MethodSymbol disposeMethod, @@ -69,6 +69,7 @@ private ForEachEnumeratorInfo( this.GetEnumeratorMethod = getEnumeratorMethod; this.CurrentPropertyGetter = currentPropertyGetter; this.MoveNextMethod = moveNextMethod; + this.IsAsync = isAsync; this.NeedsDisposal = needsDisposal; this.DisposeAwaitableInfo = disposeAwaitableInfo; this.DisposeMethod = disposeMethod; @@ -89,6 +90,7 @@ internal struct Builder public MethodSymbol CurrentPropertyGetter; public MethodSymbol MoveNextMethod; + public bool IsAsync; public bool NeedsDisposal; public AwaitableInfo DisposeAwaitableInfo; public MethodSymbol DisposeMethod; @@ -112,6 +114,7 @@ public ForEachEnumeratorInfo Build(BinderFlags location) GetEnumeratorMethod, CurrentPropertyGetter, MoveNextMethod, + IsAsync, NeedsDisposal, DisposeAwaitableInfo, DisposeMethod, diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 9ff495547cf36..3f9c6cecdeb72 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -170,7 +170,7 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos // Use the right binder to avoid seeing iteration variable BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics); - ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); + var builder = new ForEachEnumeratorInfo.Builder(); TypeWithAnnotations inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); @@ -198,7 +198,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, // Use the right binder to avoid seeing iteration variable BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics); - ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); + var builder = new ForEachEnumeratorInfo.Builder(); TypeWithAnnotations inferredType; bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); @@ -533,7 +533,7 @@ internal TypeWithAnnotations InferCollectionElementType(DiagnosticBag diagnostic // Use the right binder to avoid seeing iteration variable BoundExpression collectionExpr = this.GetBinder(collectionSyntax).BindValue(collectionSyntax, diagnostics, BindValueKind.RValue); - ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder(); + var builder = new ForEachEnumeratorInfo.Builder(); GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out TypeWithAnnotations inferredType); return inferredType; } @@ -626,6 +626,8 @@ private void UnwrapCollectionExpressionIfNullable(ref BoundExpression collection private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, DiagnosticBag diagnostics) { bool isAsync = IsAsync; + builder.IsAsync = isAsync; + EnumeratorResult found = GetEnumeratorInfo(ref builder, collectionExpr, isAsync, diagnostics); switch (found) { diff --git a/src/Compilers/CSharp/Portable/Compilation/ForEachStatementInfo.cs b/src/Compilers/CSharp/Portable/Compilation/ForEachStatementInfo.cs index a884c962b7bd7..390b82abb2715 100644 --- a/src/Compilers/CSharp/Portable/Compilation/ForEachStatementInfo.cs +++ b/src/Compilers/CSharp/Portable/Compilation/ForEachStatementInfo.cs @@ -10,6 +10,11 @@ namespace Microsoft.CodeAnalysis.CSharp /// public struct ForEachStatementInfo : IEquatable { + /// + /// Whether this is an asynchronous foreach. + /// + public bool IsAsynchronous { get; } + /// /// Gets the "GetEnumerator" method. /// @@ -55,7 +60,8 @@ public struct ForEachStatementInfo : IEquatable /// /// Initializes a new instance of the structure. /// - internal ForEachStatementInfo(IMethodSymbol getEnumeratorMethod, + internal ForEachStatementInfo(bool isAsync, + IMethodSymbol getEnumeratorMethod, IMethodSymbol moveNextMethod, IPropertySymbol currentProperty, IMethodSymbol disposeMethod, @@ -63,6 +69,7 @@ internal ForEachStatementInfo(IMethodSymbol getEnumeratorMethod, Conversion elementConversion, Conversion currentConversion) { + this.IsAsynchronous = isAsync; this.GetEnumeratorMethod = getEnumeratorMethod; this.MoveNextMethod = moveNextMethod; this.CurrentProperty = currentProperty; @@ -79,7 +86,8 @@ public override bool Equals(object obj) public bool Equals(ForEachStatementInfo other) { - return object.Equals(this.GetEnumeratorMethod, other.GetEnumeratorMethod) + return this.IsAsynchronous == other.IsAsynchronous + && object.Equals(this.GetEnumeratorMethod, other.GetEnumeratorMethod) && object.Equals(this.MoveNextMethod, other.MoveNextMethod) && object.Equals(this.CurrentProperty, other.CurrentProperty) && object.Equals(this.DisposeMethod, other.DisposeMethod) @@ -90,13 +98,14 @@ public bool Equals(ForEachStatementInfo other) public override int GetHashCode() { - return Hash.Combine(GetEnumeratorMethod, + return Hash.Combine(IsAsynchronous, + Hash.Combine(GetEnumeratorMethod, Hash.Combine(MoveNextMethod, Hash.Combine(CurrentProperty, Hash.Combine(DisposeMethod, Hash.Combine(ElementType, Hash.Combine(ElementConversion.GetHashCode(), - CurrentConversion.GetHashCode())))))); + CurrentConversion.GetHashCode()))))))); } } } diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index c37ea2992cd26..18ca0c1f3ec34 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -953,6 +953,7 @@ public override ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatem } return new ForEachStatementInfo( + enumeratorInfoOpt.IsAsync, enumeratorInfoOpt.GetEnumeratorMethod, enumeratorInfoOpt.MoveNextMethod, currentProperty: (PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter?.AssociatedSymbol, diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 53c8b441234b3..19a2b055c0ffd 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -1533,7 +1533,8 @@ internal ForEachLoopOperationInfo GetForEachLoopOperatorInfo(BoundForEachStateme enumeratorInfoOpt.GetEnumeratorMethod, (PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter.AssociatedSymbol, enumeratorInfoOpt.MoveNextMethod, - enumeratorInfoOpt.NeedsDisposal, + isAsynchronous: enumeratorInfoOpt.IsAsync, + needsDispose: enumeratorInfoOpt.NeedsDisposal, knownToImplementIDisposable: enumeratorInfoOpt.NeedsDisposal && (object)enumeratorInfoOpt.GetEnumeratorMethod != null ? compilation.Conversions. ClassifyImplicitConversionFromType(enumeratorInfoOpt.GetEnumeratorMethod.ReturnType, diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs index 4bb7274efae6a..c9e5778230e21 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs @@ -608,7 +608,7 @@ internal sealed class CSharpLazyForEachLoopOperation : LazyForEachLoopOperation private readonly BoundForEachStatement _forEachStatement; internal CSharpLazyForEachLoopOperation(CSharpOperationFactory operationFactory, BoundForEachStatement forEachStatement, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) : - base(LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) + base(forEachStatement.AwaitOpt != null, LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) { _operationFactory = operationFactory; _forEachStatement = forEachStatement; @@ -1368,7 +1368,7 @@ internal sealed class CSharpLazyUsingOperation : LazyUsingOperation private readonly BoundUsingStatement _usingStatement; internal CSharpLazyUsingOperation(CSharpOperationFactory operationFactory, BoundUsingStatement usingStatement, ImmutableArray locals, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) : - base(locals, semanticModel, syntax, type, constantValue, isImplicit) + base(locals, usingStatement.AwaitOpt != null, semanticModel, syntax, type, constantValue, isImplicit) { _operationFactory = operationFactory; _usingStatement = usingStatement; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 96913a47f5ac0..debf6f61a93d7 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -24,6 +24,7 @@ *REMOVED*override Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax.Body.get -> Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind outputKind, bool reportSuppressedDiagnostics = false, string moduleName = null, string mainTypeName = null, string scriptClassName = null, System.Collections.Generic.IEnumerable usings = null, Microsoft.CodeAnalysis.OptimizationLevel optimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel.Debug, bool checkOverflow = false, bool allowUnsafe = false, string cryptoKeyContainer = null, string cryptoKeyFile = null, System.Collections.Immutable.ImmutableArray cryptoPublicKey = default(System.Collections.Immutable.ImmutableArray), bool? delaySign = null, Microsoft.CodeAnalysis.Platform platform = Microsoft.CodeAnalysis.Platform.AnyCpu, Microsoft.CodeAnalysis.ReportDiagnostic generalDiagnosticOption = Microsoft.CodeAnalysis.ReportDiagnostic.Default, int warningLevel = 4, System.Collections.Generic.IEnumerable> specificDiagnosticOptions = null, bool concurrentBuild = true, bool deterministic = false, Microsoft.CodeAnalysis.XmlReferenceResolver xmlReferenceResolver = null, Microsoft.CodeAnalysis.SourceReferenceResolver sourceReferenceResolver = null, Microsoft.CodeAnalysis.MetadataReferenceResolver metadataReferenceResolver = null, Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer = null, Microsoft.CodeAnalysis.StrongNameProvider strongNameProvider = null, bool publicSign = false, Microsoft.CodeAnalysis.MetadataImportOptions metadataImportOptions = Microsoft.CodeAnalysis.MetadataImportOptions.Public, Microsoft.CodeAnalysis.NullableContextOptions nullableContextOptions = Microsoft.CodeAnalysis.NullableContextOptions.Disable) -> void Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.WithNullableContextOptions(Microsoft.CodeAnalysis.NullableContextOptions options) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions +Microsoft.CodeAnalysis.CSharp.ForEachStatementInfo.IsAsynchronous.get -> bool Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.AddBlockStatements(params Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.Body.get -> Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.WithBlock(Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs index eb05172626788..fec7458123f8b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs @@ -231,6 +231,11 @@ internal override ISymbol CommonGetWellKnownTypeMember(WellKnownMember member) return GetWellKnownTypeMember(member); } + internal override ITypeSymbol CommonGetWellKnownType(WellKnownType wellknownType) + { + return GetWellKnownType(wellknownType); + } + internal static Symbol GetRuntimeMember(NamedTypeSymbol declaringType, ref MemberDescriptor descriptor, SignatureComparer comparer, AssemblySymbol accessWithinOpt) { Symbol result = null; diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs index 9cc47071fa531..a29d30de010d9 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs @@ -717,6 +717,7 @@ public int Current var foreachSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); var info = model.GetForEachStatementInfo(foreachSyntax); + Assert.True(info.IsAsynchronous); Assert.Equal("C.Enumerator C.GetAsyncEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString()); Assert.Equal("System.Threading.Tasks.Task C.Enumerator.MoveNextAsync()", info.MoveNextMethod.ToTestDisplayString()); Assert.Equal("System.Int32 C.Enumerator.Current { get; }", info.CurrentProperty.ToTestDisplayString()); diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs index 61f4390d373f7..2c440503eb931 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IForEachLoopStatement.cs @@ -3791,6 +3791,7 @@ void M(bool result, object a, object b) } [Fact, CompilerTrait(CompilerFeature.IOperation, CompilerFeature.AsyncStreams)] + [WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] public void IForEachLoopStatement_SimpleAwaitForEachLoop() { string source = @" @@ -3805,16 +3806,18 @@ static async System.Threading.Tasks.Task Main(System.Collections.Generic.IAsyncE } } "; - // https://github.com/dotnet/roslyn/issues/30362 how do we flag `await`? string expectedOperationTree = @" -IForEachLoopOperation (LoopKind.ForEach, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'await forea ... }') +IForEachLoopOperation (LoopKind.ForEach, IsAsynchronous, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'await forea ... }') Locals: Local_1: System.String value LoopControlVariable: IVariableDeclaratorOperation (Symbol: System.String value) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'string') Initializer: null Collection: - IParameterReferenceOperation: pets (OperationKind.ParameterReference, Type: System.Collections.Generic.IAsyncEnumerable) (Syntax: 'pets') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Collections.Generic.IAsyncEnumerable, IsImplicit) (Syntax: 'pets') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IParameterReferenceOperation: pets (OperationKind.ParameterReference, Type: System.Collections.Generic.IAsyncEnumerable) (Syntax: 'pets') Body: IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Cons ... ine(value);') @@ -3829,11 +3832,12 @@ static async System.Threading.Tasks.Task Main(System.Collections.Generic.IAsyncE OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) NextVariables(0) "; - VerifyOperationTreeForTest(source + s_IAsyncEnumerable, expectedOperationTree); + + VerifyOperationTreeForTest(source + s_IAsyncEnumerable + s_ValueTask, expectedOperationTree); } [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] - [Fact] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] public void ForEachAwaitFlow_SimpleAwaitForEachLoop() { string source = @" @@ -3853,15 +3857,11 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) var expectedDiagnostics = DiagnosticDescription.None; - // https://github.com/dotnet/roslyn/issues/30362 should signal `await`. - // https://github.com/dotnet/roslyn/issues/30362 missing await on `MoveNextAsync()` and `DisposeAsync()` calls - // https://github.com/dotnet/roslyn/issues/30362 showing `Dispose()` instead of `DisposeAsync()` string expectedFlowGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] Entering: {R1} - .locals {R1} { CaptureIds: [0] @@ -3878,26 +3878,24 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) Operand: IParameterReferenceOperation: pets (OperationKind.ParameterReference, Type: System.Collections.Generic.IAsyncEnumerable) (Syntax: 'pets') Arguments(0) - Next (Regular) Block[B2] Entering: {R2} {R3} - .try {R2, R3} { Block[B2] - Block Predecessors: [B1] [B3] Statements (0) Jump if False (Regular) to Block[B7] - IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'pets') - Instance Receiver: - IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.Generic.IAsyncEnumerator, IsImplicit) (Syntax: 'pets') - Arguments(0) + IAwaitOperation (OperationKind.Await, Type: System.Boolean, IsImplicit) (Syntax: 'await forea ... }') + Expression: + IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'pets') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.Generic.IAsyncEnumerator, IsImplicit) (Syntax: 'pets') + Arguments(0) Finalizing: {R5} Leaving: {R3} {R2} {R1} - Next (Regular) Block[B3] Entering: {R4} - .locals {R4} { Locals: [System.String value] @@ -3911,7 +3909,6 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) IPropertyReferenceOperation: System.String System.Collections.Generic.IAsyncEnumerator.Current { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'string') Instance Receiver: IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.Generic.IAsyncEnumerator, IsImplicit) (Syntax: 'pets') - IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Cons ... ine(value);') Expression: IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') @@ -3922,7 +3919,6 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) ILocalReferenceOperation: value (OperationKind.LocalReference, Type: System.String) (Syntax: 'value') InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Next (Regular) Block[B2] Leaving: {R4} } @@ -3935,26 +3931,25 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) Statements (1) IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'pets') Value: - IConversionOperation (TryCast: True, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsImplicit) (Syntax: 'pets') + IConversionOperation (TryCast: True, Unchecked) (OperationKind.Conversion, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'pets') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - (ExplicitReference) + (ImplicitReference) Operand: IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.Generic.IAsyncEnumerator, IsImplicit) (Syntax: 'pets') - Jump if True (Regular) to Block[B6] IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'pets') Operand: - IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IDisposable, IsImplicit) (Syntax: 'pets') - + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'pets') Next (Regular) Block[B5] Block[B5] - Block Predecessors: [B4] Statements (1) - IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'pets') - Instance Receiver: - IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IDisposable, IsImplicit) (Syntax: 'pets') - Arguments(0) - + IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'pets') + Expression: + IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'pets') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'pets') + Arguments(0) Next (Regular) Block[B6] Block[B6] - Block Predecessors: [B4] [B5] @@ -3962,7 +3957,6 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) Next (StructuredExceptionHandling) Block[null] } } - Block[B7] - Exit Predecessors: [B2] Statements (0) @@ -3970,62 +3964,167 @@ static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + s_ValueTask, expectedFlowGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] + public void ForEachAwaitFlow_SimpleAwaitForEachLoop_MissingIAsyncEnumerableType() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System; +class Program +{ + static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) + /**/{ + await foreach (string value in pets) + { + System.Console.WriteLine(value); + } + }/**/ +}"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(7,55): error CS0234: The type or namespace name 'IAsyncEnumerable<>' does not exist in the namespace 'System.Collections.Generic' (are you missing an assembly reference?) + // static async Task Main(System.Collections.Generic.IAsyncEnumerable pets) + Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInNS, "IAsyncEnumerable").WithArguments("IAsyncEnumerable<>", "System.Collections.Generic").WithLocation(7, 55) + }; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + IInvalidOperation (OperationKind.Invalid, Type: null, IsImplicit) (Syntax: 'pets') + Children(1): + IParameterReferenceOperation: pets (OperationKind.ParameterReference, Type: System.Collections.Generic.IAsyncEnumerable) (Syntax: 'pets') + Next (Regular) Block[B2] +Block[B2] - Block + Predecessors: [B1] [B3] + Statements (0) + Jump if False (Regular) to Block[B4] + IInvalidOperation (OperationKind.Invalid, Type: System.Boolean, IsImplicit) (Syntax: 'pets') + Children(1): + IInvalidOperation (OperationKind.Invalid, Type: null, IsImplicit) (Syntax: 'pets') + Children(0) + Next (Regular) Block[B3] + Entering: {R1} +.locals {R1} +{ + Locals: [System.String value] + Block[B3] - Block + Predecessors: [B2] + Statements (2) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: null, IsImplicit) (Syntax: 'string') + Left: + ILocalReferenceOperation: value (IsDeclaration: True) (OperationKind.LocalReference, Type: System.String, IsImplicit) (Syntax: 'string') + Right: + IInvalidOperation (OperationKind.Invalid, Type: null, IsImplicit) (Syntax: 'pets') + Children(1): + IInvalidOperation (OperationKind.Invalid, Type: null, IsImplicit) (Syntax: 'pets') + Children(0) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Cons ... ine(value);') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'value') + ILocalReferenceOperation: value (OperationKind.LocalReference, Type: System.String) (Syntax: 'value') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B2] + Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + + var expectedOperationTree = @" +IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IForEachLoopOperation (LoopKind.ForEach, IsAsynchronous, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'await forea ... }') + Locals: Local_1: System.String value + LoopControlVariable: + IVariableDeclaratorOperation (Symbol: System.String value) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'string') + Initializer: + null + Collection: + IParameterReferenceOperation: pets (OperationKind.ParameterReference, Type: System.Collections.Generic.IAsyncEnumerable) (Syntax: 'pets') + Body: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Cons ... ine(value);') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Cons ... Line(value)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'value') + ILocalReferenceOperation: value (OperationKind.LocalReference, Type: System.String) (Syntax: 'value') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + NextVariables(0)"; + VerifyOperationTreeForTest(source, expectedOperationTree); + } + private static string s_ValueTask = @" namespace System.Threading.Tasks { - [AsyncMethodBuilder(typeof(ValueTaskMethodBuilder))] + [System.Runtime.CompilerServices.AsyncMethodBuilder(typeof(System.Runtime.CompilerServices.ValueTaskMethodBuilder))] public struct ValueTask { public Awaiter GetAwaiter() => null; - public class Awaiter : INotifyCompletion + public class Awaiter : System.Runtime.CompilerServices.INotifyCompletion { public void OnCompleted(Action a) { } public bool IsCompleted => true; public void GetResult() { } } } - [AsyncMethodBuilder(typeof(ValueTaskMethodBuilder<>))] + [System.Runtime.CompilerServices.AsyncMethodBuilder(typeof(System.Runtime.CompilerServices.ValueTaskMethodBuilder<>))] public struct ValueTask { public Awaiter GetAwaiter() => null; - public class Awaiter : INotifyCompletion + public class Awaiter : System.Runtime.CompilerServices.INotifyCompletion { public void OnCompleted(Action a) { } public bool IsCompleted => true; - public T GetResult() => default(T); + public T GetResult() => default; } } } namespace System.Runtime.CompilerServices { - class AsyncMethodBuilderAttribute : Attribute + public class AsyncMethodBuilderAttribute : Attribute { public AsyncMethodBuilderAttribute(Type t) { } } -} -class ValueTaskMethodBuilder -{ - public static ValueTaskMethodBuilder Create() => null; - internal ValueTaskMethodBuilder(ValueTask task) { } - public void SetStateMachine(IAsyncStateMachine stateMachine) { } - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { } - public void SetException(Exception e) { } - public void SetResult() { } - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { } - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { } - public ValueTask Task => default(ValueTask); -} -class ValueTaskMethodBuilder -{ - public static ValueTaskMethodBuilder Create() => null; - internal ValueTaskMethodBuilder(ValueTask task) { } - public void SetStateMachine(IAsyncStateMachine stateMachine) { } - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { } - public void SetException(Exception e) { } - public void SetResult(T t) { } - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { } - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { } - public ValueTask Task => default(ValueTask); + public class ValueTaskMethodBuilder + { + public static ValueTaskMethodBuilder Create() => null; + internal ValueTaskMethodBuilder(System.Threading.Tasks.ValueTask task) { } + public void SetStateMachine(IAsyncStateMachine stateMachine) { } + public void Start(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public void SetException(Exception e) { } + public void SetResult() { } + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public System.Threading.Tasks.ValueTask Task => default; + } + public class ValueTaskMethodBuilder + { + public static ValueTaskMethodBuilder Create() => null; + internal ValueTaskMethodBuilder(System.Threading.Tasks.ValueTask task) { } + public void SetStateMachine(IAsyncStateMachine stateMachine) { } + public void Start(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public void SetException(Exception e) { } + public void SetResult(T t) { } + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { } + public System.Threading.Tasks.ValueTask Task => default; + } }"; } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs index f3fcd8b9fe52c..c9239fb1a57f9 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs @@ -70,7 +70,7 @@ public static void M1() } [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.AsyncStreams)] - [Fact] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] public void IUsingAwaitStatement_SimpleAwaitUsing() { string source = @" @@ -89,9 +89,8 @@ public static async Task M1(IAsyncDisposable disposable) } } "; - // https://github.com/dotnet/roslyn/issues/30362 should show `await` string expectedOperationTree = @" -IUsingOperation (OperationKind.Using, Type: null) (Syntax: 'await using ... }') +IUsingOperation (IsAsynchronous) (OperationKind.Using, Type: null) (Syntax: 'await using ... }') Locals: Local_1: System.IAsyncDisposable c Resources: IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsImplicit) (Syntax: 'var c = disposable') @@ -125,7 +124,7 @@ public static async Task M1(IAsyncDisposable disposable) } [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] - [Fact] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] public void UsingFlow_SimpleAwaitUsing() { string source = @" @@ -145,13 +144,11 @@ public static async Task M1(IAsyncDisposable disposable) } "; - // https://github.com/dotnet/roslyn/issues/30362 should be `await DisposeAsync()` string expectedGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] Entering: {R1} - .locals {R1} { Locals: [System.IAsyncDisposable c] @@ -163,10 +160,8 @@ public static async Task M1(IAsyncDisposable disposable) ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') Right: IParameterReferenceOperation: disposable (OperationKind.ParameterReference, Type: System.IAsyncDisposable) (Syntax: 'disposable') - Next (Regular) Block[B2] Entering: {R2} {R3} - .try {R2, R3} { Block[B2] - Block @@ -185,7 +180,6 @@ public static async Task M1(IAsyncDisposable disposable) Arguments(0) InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) - Next (Regular) Block[B6] Finalizing: {R4} Leaving: {R3} {R2} {R1} @@ -199,20 +193,16 @@ public static async Task M1(IAsyncDisposable disposable) IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'c = disposable') Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') - Next (Regular) Block[B4] Block[B4] - Block Predecessors: [B3] Statements (1) - IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'c = disposable') - Instance Receiver: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsImplicit) (Syntax: 'c = disposable') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - (ExplicitReference) - Operand: + IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'c = disposable') + Expression: + IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'c = disposable') + Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') - Arguments(0) - + Arguments(0) Next (Regular) Block[B5] Block[B5] - Block Predecessors: [B3] [B4] @@ -220,7 +210,6 @@ public static async Task M1(IAsyncDisposable disposable) Next (StructuredExceptionHandling) Block[null] } } - Block[B6] - Exit Predecessors: [B2] Statements (0) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs index 4a0976eb40193..94f74eab34bad 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ForEachTests.cs @@ -2099,6 +2099,7 @@ void Test(string s) var loopSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); var loopInfo = model.GetForEachStatementInfo(loopSyntax); + Assert.False(loopInfo.IsAsynchronous); Assert.Equal(comp.GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerable__GetEnumerator), loopInfo.GetEnumeratorMethod); Assert.Equal(comp.GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__Current), loopInfo.CurrentProperty); Assert.Equal(comp.GetSpecialTypeMember(SpecialMember.System_Collections_IEnumerator__MoveNext), loopInfo.MoveNextMethod); diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/ForEachStatementInfoTests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/ForEachStatementInfoTests.cs index f9ba6257ab656..2a85e30cd6944 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/ForEachStatementInfoTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/ForEachStatementInfoTests.cs @@ -45,13 +45,14 @@ public void Dispose() { } var conv2 = Conversion.NoConversion; EqualityTesting.AssertEqual(default(ForEachStatementInfo), default(ForEachStatementInfo)); - EqualityTesting.AssertEqual(new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge2, mn1, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge1, mn2, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge1, mn1, cur2, disp1, e1, conv1, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge1, mn1, cur1, disp2, e1, conv1, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv2, conv1), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); - EqualityTesting.AssertNotEqual(new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv2), new ForEachStatementInfo(ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge2, mn1, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn2, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur2, disp1, e1, conv1, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp2, e1, conv1, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv2, conv1), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv2), new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1)); + EqualityTesting.AssertNotEqual(new ForEachStatementInfo(isAsync: true, ge1, mn1, cur1, disp1, e1, conv1, conv1), new ForEachStatementInfo(isAsync: false, ge1, mn1, cur1, disp1, e1, conv1, conv1)); } } } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index a5cfdf601eaef..6e49054c93979 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -823,6 +823,11 @@ public INamedTypeSymbol GetSpecialType(SpecialType specialType) /// internal abstract ISymbol CommonGetWellKnownTypeMember(WellKnownMember member); + /// + /// Lookup well-known type used by this Compilation. + /// + internal abstract ITypeSymbol CommonGetWellKnownType(WellKnownType wellknownType); + /// /// Returns true if the specified type is equal to or derives from System.Attribute well-known type. /// diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index baee72579bd01..61813a6438b5f 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -165,6 +165,11 @@ public interface IForEachLoopOperation : ILoopOperation /// This list is always empty for C#. /// ImmutableArray NextVariables { get; } + /// + /// Whether this for each loop is asynchronous. + /// Always false for VB. + /// + bool IsAsynchronous { get; } } /// /// Represents a for loop. @@ -431,6 +436,11 @@ public interface IUsingOperation : IOperation /// Locals declared within the with scope spanning across this entire . /// ImmutableArray Locals { get; } + /// + /// Whether this using is asynchronous. + /// Always false for VB. + /// + bool IsAsynchronous { get; } } /// /// Represents an operation that drops the resulting value and the type of the underlying wrapped . @@ -2948,11 +2958,15 @@ protected BaseLoopOperation(LoopKind loopKind, ImmutableArray loca } internal abstract partial class BaseForEachLoopOperation : BaseLoopOperation, IForEachLoopOperation { - internal BaseForEachLoopOperation(LoopKind loopKind, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) - : base(loopKind, locals, continueLabel, exitLabel, OperationKind.Loop, semanticModel, syntax, type, constantValue, isImplicit) { } + internal BaseForEachLoopOperation(bool isAsynchronous, LoopKind loopKind, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(loopKind, locals, continueLabel, exitLabel, OperationKind.Loop, semanticModel, syntax, type, constantValue, isImplicit) + { + IsAsynchronous = isAsynchronous; + } public abstract IOperation LoopControlVariable { get; } public abstract IOperation Collection { get; } public abstract ImmutableArray NextVariables { get; } + public bool IsAsynchronous { get; } public override IEnumerable Children { get @@ -2971,8 +2985,8 @@ public override IEnumerable Children } internal sealed partial class ForEachLoopOperation : BaseForEachLoopOperation, IForEachLoopOperation { - internal ForEachLoopOperation(IOperation loopControlVariable, IOperation collection, ImmutableArray nextVariables, LoopKind loopKind, IOperation body, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) - : base(loopKind, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) + internal ForEachLoopOperation(IOperation loopControlVariable, IOperation collection, ImmutableArray nextVariables, bool isAsynchronous, LoopKind loopKind, IOperation body, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(isAsynchronous, loopKind, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) { LoopControlVariable = SetParentOperation(loopControlVariable, this); Collection = SetParentOperation(collection, this); @@ -2990,8 +3004,8 @@ internal abstract partial class LazyForEachLoopOperation : BaseForEachLoopOperat private IOperation _lazyCollection = s_unset; private ImmutableArray _lazyNextVariables; private IOperation _lazyBody = s_unset; - internal LazyForEachLoopOperation(LoopKind loopKind, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) - : base(loopKind, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit){ } + internal LazyForEachLoopOperation(bool isAsynchronous, LoopKind loopKind, ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(isAsynchronous, loopKind, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit){ } protected abstract IOperation CreateLoopControlVariable(); public override IOperation LoopControlVariable { @@ -3656,14 +3670,16 @@ public override IBlockOperation Finally } internal abstract partial class BaseUsingOperation : Operation, IUsingOperation { - internal BaseUsingOperation(ImmutableArray locals, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + internal BaseUsingOperation(ImmutableArray locals, bool isAsynchronous, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) : base(OperationKind.Using, semanticModel, syntax, type, constantValue, isImplicit) { Locals = locals; + IsAsynchronous = isAsynchronous; } public abstract IOperation Resources { get; } public abstract IOperation Body { get; } public ImmutableArray Locals { get; } + public bool IsAsynchronous { get; } public override IEnumerable Children { get @@ -3677,8 +3693,8 @@ public override IEnumerable Children } internal sealed partial class UsingOperation : BaseUsingOperation, IUsingOperation { - internal UsingOperation(IOperation resources, IOperation body, ImmutableArray locals, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) - : base(locals, semanticModel, syntax, type, constantValue, isImplicit) + internal UsingOperation(IOperation resources, IOperation body, ImmutableArray locals, bool isAsynchronous, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(locals, isAsynchronous, semanticModel, syntax, type, constantValue, isImplicit) { Resources = SetParentOperation(resources, this); Body = SetParentOperation(body, this); @@ -3690,8 +3706,8 @@ internal abstract partial class LazyUsingOperation : BaseUsingOperation, IUsingO { private IOperation _lazyResources = s_unset; private IOperation _lazyBody = s_unset; - internal LazyUsingOperation(ImmutableArray locals, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) - : base(locals, semanticModel, syntax, type, constantValue, isImplicit){ } + internal LazyUsingOperation(ImmutableArray locals, bool isAsynchronous, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(locals, isAsynchronous, semanticModel, syntax, type, constantValue, isImplicit){ } protected abstract IOperation CreateResources(); public override IOperation Resources { diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 839ca6a703276..c1406d3aab1f9 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -3641,7 +3641,9 @@ public override IOperation VisitUsing(IUsingOperation operation, int? captureIdF { StartVisitingStatement(operation); - ITypeSymbol iDisposable = _compilation.GetSpecialType(SpecialType.System_IDisposable); + ITypeSymbol iDisposable = operation.IsAsynchronous + ? _compilation.CommonGetWellKnownType(WellKnownType.System_IAsyncDisposable) + : _compilation.GetSpecialType(SpecialType.System_IDisposable); var usingRegion = new RegionBuilder(ControlFlowRegionKind.LocalLifetime, locals: operation.Locals); EnterRegion(usingRegion); @@ -3762,7 +3764,7 @@ void processResource(IOperation resource, ArrayBuilder<(IVariableDeclarationOper LeaveRegion(); - AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable); + AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable, operation.IsAsynchronous); Debug.Assert(_currentRegion.Kind == ControlFlowRegionKind.TryAndFinally); LeaveRegion(); @@ -3777,7 +3779,7 @@ void processResource(IOperation resource, ArrayBuilder<(IVariableDeclarationOper } } - private void AddDisposingFinally(IOperation resource, bool knownToImplementIDisposable, ITypeSymbol iDisposable) + private void AddDisposingFinally(IOperation resource, bool knownToImplementIDisposable, ITypeSymbol iDisposable, bool isAsynchronous) { Debug.Assert(_currentRegion.Kind == ControlFlowRegionKind.TryAndFinally); @@ -3822,12 +3824,21 @@ IOperation tryDispose(IOperation value) { Debug.Assert(value.Type == iDisposable); - var method = (IMethodSymbol)_compilation.CommonGetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose); + var method = isAsynchronous + ? (IMethodSymbol)_compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_IAsyncDisposable__DisposeAsync) + : (IMethodSymbol)_compilation.CommonGetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose); if (method != null) { - return new InvocationOperation(method, value, isVirtual: true, + var invocation = new InvocationOperation(method, value, isVirtual: true, ImmutableArray.Empty, semanticModel: null, value.Syntax, method.ReturnType, constantValue: default, isImplicit: true); + + if (isAsynchronous) + { + return new AwaitOperation(invocation, semanticModel: null, value.Syntax, _compilation.GetSpecialType(SpecialType.System_Void), constantValue: default, isImplicit: true); + } + + return invocation; } return null; @@ -3841,7 +3852,8 @@ bool isNotNullableValueType(ITypeSymbol type) private IOperation ConvertToIDisposable(IOperation operand, ITypeSymbol iDisposable, bool isTryCast = false) { - Debug.Assert(iDisposable.SpecialType == SpecialType.System_IDisposable); + Debug.Assert(iDisposable.SpecialType == SpecialType.System_IDisposable || + iDisposable.Equals(_compilation.CommonGetWellKnownType(WellKnownType.System_IAsyncDisposable))); return new ConversionOperation(operand, _compilation.ClassifyConvertibleConversion(operand, iDisposable, out var constantValue), isTryCast, isChecked: false, semanticModel: null, operand.Syntax, iDisposable, constantValue, isImplicit: true); } @@ -4110,9 +4122,15 @@ public override IOperation VisitForEachLoop(IForEachLoopOperation operation, int LeaveRegion(); + bool isAsynchronous = info.IsAsynchronous; + var iDisposable = isAsynchronous + ? _compilation.CommonGetWellKnownType(WellKnownType.System_IAsyncDisposable) + : _compilation.GetSpecialType(SpecialType.System_IDisposable); + AddDisposingFinally(OperationCloner.CloneOperation(enumerator), info.KnownToImplementIDisposable, - _compilation.GetSpecialType(SpecialType.System_IDisposable)); + iDisposable, + isAsynchronous); Debug.Assert(_currentRegion.Kind == ControlFlowRegionKind.TryAndFinally); LeaveRegion(); @@ -4169,7 +4187,12 @@ IOperation getCondition(IOperation enumeratorRef) { if (info?.MoveNextMethod != null) { - return makeInvocationDroppingInstanceForStaticMethods(info.MoveNextMethod, enumeratorRef, info.MoveNextArguments); + var moveNext = makeInvocationDroppingInstanceForStaticMethods(info.MoveNextMethod, enumeratorRef, info.MoveNextArguments); + if (operation.IsAsynchronous) + { + return new AwaitOperation(moveNext, semanticModel: null, operation.Syntax, _compilation.GetSpecialType(SpecialType.System_Boolean), constantValue: default, isImplicit: true); + } + return moveNext; } else { diff --git a/src/Compilers/Core/Portable/Operations/Loops/ForEachLoopOperationInfo.cs b/src/Compilers/Core/Portable/Operations/Loops/ForEachLoopOperationInfo.cs index 4417d8855390e..b1b663ef53f1d 100644 --- a/src/Compilers/Core/Portable/Operations/Loops/ForEachLoopOperationInfo.cs +++ b/src/Compilers/Core/Portable/Operations/Loops/ForEachLoopOperationInfo.cs @@ -16,6 +16,7 @@ internal class ForEachLoopOperationInfo public readonly IPropertySymbol CurrentProperty; public readonly IMethodSymbol MoveNextMethod; + public readonly bool IsAsynchronous; public readonly bool NeedsDispose; public readonly bool KnownToImplementIDisposable; @@ -38,6 +39,7 @@ public ForEachLoopOperationInfo( IMethodSymbol getEnumeratorMethod, IPropertySymbol currentProperty, IMethodSymbol moveNextMethod, + bool isAsynchronous, bool needsDispose, bool knownToImplementIDisposable, IConvertibleConversion currentConversion, @@ -50,6 +52,7 @@ public ForEachLoopOperationInfo( GetEnumeratorMethod = getEnumeratorMethod; CurrentProperty = currentProperty; MoveNextMethod = moveNextMethod; + IsAsynchronous = isAsynchronous; NeedsDispose = needsDispose; KnownToImplementIDisposable = knownToImplementIDisposable; CurrentConversion = currentConversion; diff --git a/src/Compilers/Core/Portable/Operations/OperationCloner.cs b/src/Compilers/Core/Portable/Operations/OperationCloner.cs index cd079825564f7..2b51ee84ff98e 100644 --- a/src/Compilers/Core/Portable/Operations/OperationCloner.cs +++ b/src/Compilers/Core/Portable/Operations/OperationCloner.cs @@ -133,7 +133,7 @@ public override IOperation VisitForToLoop(IForToLoopOperation operation, object public override IOperation VisitForEachLoop(IForEachLoopOperation operation, object argument) { return new ForEachLoopOperation(operation.Locals, operation.ContinueLabel, operation.ExitLabel, Visit(operation.LoopControlVariable), - Visit(operation.Collection), VisitArray(operation.NextVariables), Visit(operation.Body), ((BaseForEachLoopOperation)operation).Info, + Visit(operation.Collection), VisitArray(operation.NextVariables), operation.IsAsynchronous, Visit(operation.Body), ((BaseForEachLoopOperation)operation).Info, ((Operation)operation).OwningSemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); } @@ -175,7 +175,7 @@ public override IOperation VisitCatchClause(ICatchClauseOperation operation, obj public override IOperation VisitUsing(IUsingOperation operation, object argument) { - return new UsingOperation(Visit(operation.Resources), Visit(operation.Body), operation.Locals, ((Operation)operation).OwningSemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); + return new UsingOperation(Visit(operation.Resources), Visit(operation.Body), operation.Locals, operation.IsAsynchronous, ((Operation)operation).OwningSemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); } // https://github.com/dotnet/roslyn/issues/21281 diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index e2c661f17a98b..4f1428254b881 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -180,6 +180,14 @@ + + + + Whether this for each loop is asynchronous. + Always false for VB. + + + @@ -465,6 +473,14 @@ + + + + Whether this using is asynchronous. + Always false for VB. + + + diff --git a/src/Compilers/Core/Portable/Operations/OperationNodes.cs b/src/Compilers/Core/Portable/Operations/OperationNodes.cs index 61e649909c73b..83c462926be8c 100644 --- a/src/Compilers/Core/Portable/Operations/OperationNodes.cs +++ b/src/Compilers/Core/Portable/Operations/OperationNodes.cs @@ -144,9 +144,9 @@ internal abstract partial class BaseForEachLoopOperation : BaseLoopOperation, IF internal sealed partial class ForEachLoopOperation : BaseForEachLoopOperation, IForEachLoopOperation { public ForEachLoopOperation(ImmutableArray locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, IOperation loopControlVariable, - IOperation collection, ImmutableArray nextVariables, IOperation body, ForEachLoopOperationInfo info, + IOperation collection, ImmutableArray nextVariables, bool isAsynchronous, IOperation body, ForEachLoopOperationInfo info, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) : - this(loopControlVariable, collection, nextVariables, LoopKind.ForEach, body, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) + this(loopControlVariable, collection, nextVariables, isAsynchronous, LoopKind.ForEach, body, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) { Info = info; } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 1fe1bea5ea81d..d4fbb02fc5774 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -79,6 +79,8 @@ Microsoft.CodeAnalysis.NullableFlowState Microsoft.CodeAnalysis.NullableFlowState.MaybeNull = 2 -> Microsoft.CodeAnalysis.NullableFlowState Microsoft.CodeAnalysis.NullableFlowState.None = 0 -> Microsoft.CodeAnalysis.NullableFlowState Microsoft.CodeAnalysis.NullableFlowState.NotNull = 1 -> Microsoft.CodeAnalysis.NullableFlowState +Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.IsAsynchronous.get -> bool +Microsoft.CodeAnalysis.Operations.IUsingOperation.IsAsynchronous.get -> bool Microsoft.CodeAnalysis.SuppressionDescriptor Microsoft.CodeAnalysis.SuppressionDescriptor.Equals(Microsoft.CodeAnalysis.SuppressionDescriptor other) -> bool Microsoft.CodeAnalysis.SuppressionDescriptor.Id.get -> string diff --git a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb index 6f4b97248e475..369485a6325ac 100644 --- a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb +++ b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationFactory.vb @@ -1167,6 +1167,7 @@ Namespace Microsoft.CodeAnalysis.Operations statementInfo.GetEnumeratorMethod, statementInfo.CurrentProperty, statementInfo.MoveNextMethod, + isAsynchronous:=False, boundForEachStatement.EnumeratorInfo.NeedToDispose, knownToImplementIDisposable:=boundForEachStatement.EnumeratorInfo.NeedToDispose AndAlso boundForEachStatement.EnumeratorInfo.IsOrInheritsFromOrImplementsIDisposable, diff --git a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationNodes.vb b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationNodes.vb index fbd48aaf12ea0..5bcc42ef65900 100644 --- a/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationNodes.vb +++ b/src/Compilers/VisualBasic/Portable/Operations/VisualBasicOperationNodes.vb @@ -413,7 +413,7 @@ Namespace Microsoft.CodeAnalysis.Operations Private ReadOnly _forEachLoop As BoundForEachStatement Friend Sub New(operationFactory As VisualBasicOperationFactory, forEachLoop As BoundForEachStatement, locals As ImmutableArray(Of ILocalSymbol), continueLabel As ILabelSymbol, exitLabel As ILabelSymbol, semanticModel As SemanticModel, syntax As SyntaxNode, type As ITypeSymbol, constantValue As [Optional](Of Object), isImplicit As Boolean) - MyBase.New(LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) + MyBase.New(isAsynchronous:=False, LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit) _operationFactory = operationFactory _forEachLoop = forEachLoop End Sub @@ -1095,7 +1095,7 @@ _operationFactory.CreateFromArray(Of BoundExpression, IOperation)(_boundForToLoo Private ReadOnly _boundUsingStatement As BoundUsingStatement Friend Sub New(operationFactory As VisualBasicOperationFactory, boundUsingStatement As BoundUsingStatement, locals As ImmutableArray(Of ILocalSymbol), semanticModel As SemanticModel, syntax As SyntaxNode, type As ITypeSymbol, constantValue As [Optional](Of Object), isImplicit As Boolean) - MyBase.New(locals, semanticModel, syntax, type, constantValue, isImplicit) + MyBase.New(locals, isAsynchronous:=False, semanticModel, syntax, type, constantValue, isImplicit) _operationFactory = operationFactory _boundUsingStatement = boundUsingStatement End Sub diff --git a/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb b/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb index 20b1920c9cdac..b8b56874a9dea 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb @@ -338,6 +338,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return GetWellKnownTypeMember(member) End Function + Friend Overrides Function CommonGetWellKnownType(wellknownType As WellKnownType) As ITypeSymbol + Return GetWellKnownType(wellknownType) + End Function + Friend Overrides Function IsAttributeType(type As ITypeSymbol) As Boolean If type.Kind <> SymbolKind.NamedType Then Return False diff --git a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs index 9d87867c3c07f..b00b56cb9f2cb 100644 --- a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs +++ b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs @@ -600,6 +600,10 @@ private void LogLoopStatementHeader(ILoopOperation operation, bool? isChecked = var propertyStringBuilder = new StringBuilder(); propertyStringBuilder.Append(" ("); propertyStringBuilder.Append($"{nameof(LoopKind)}.{operation.LoopKind}"); + if (operation is IForEachLoopOperation { IsAsynchronous: true }) + { + propertyStringBuilder.Append($", IsAsynchronous"); + } propertyStringBuilder.Append($", Continue Label Id: {GetLabelId(operation.ContinueLabel)}"); propertyStringBuilder.Append($", Exit Label Id: {GetLabelId(operation.ExitLabel)}"); if (isChecked.GetValueOrDefault()) @@ -709,6 +713,10 @@ public override void VisitCatchClause(ICatchClauseOperation operation) public override void VisitUsing(IUsingOperation operation) { LogString(nameof(IUsingOperation)); + if (operation.IsAsynchronous) + { + LogString($" (IsAsynchronous)"); + } LogCommonPropertiesAndNewLine(operation); LogLocals(operation.Locals);