Skip to content

Commit 036d931

Browse files
authored
Fix IOperation for asynchronous using and foreach statements (#37963)
1 parent 4b8c6a7 commit 036d931

25 files changed

+315
-124
lines changed

src/Compilers/CSharp/Portable/Binder/ForEachEnumeratorInfo.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ internal sealed class ForEachEnumeratorInfo
2727
// Computed during initial binding so that we can expose it in the semantic model.
2828
public readonly bool NeedsDisposal;
2929

30+
public readonly bool IsAsync;
31+
3032
// When async and needs disposal, this stores the information to await the DisposeAsync() invocation
3133
public AwaitableInfo DisposeAwaitableInfo;
3234

@@ -41,15 +43,13 @@ internal sealed class ForEachEnumeratorInfo
4143

4244
public readonly BinderFlags Location;
4345

44-
internal bool IsAsync
45-
=> DisposeAwaitableInfo != null;
46-
4746
private ForEachEnumeratorInfo(
4847
TypeSymbol collectionType,
4948
TypeWithAnnotations elementType,
5049
MethodSymbol getEnumeratorMethod,
5150
MethodSymbol currentPropertyGetter,
5251
MethodSymbol moveNextMethod,
52+
bool isAsync,
5353
bool needsDisposal,
5454
AwaitableInfo disposeAwaitableInfo,
5555
MethodSymbol disposeMethod,
@@ -69,6 +69,7 @@ private ForEachEnumeratorInfo(
6969
this.GetEnumeratorMethod = getEnumeratorMethod;
7070
this.CurrentPropertyGetter = currentPropertyGetter;
7171
this.MoveNextMethod = moveNextMethod;
72+
this.IsAsync = isAsync;
7273
this.NeedsDisposal = needsDisposal;
7374
this.DisposeAwaitableInfo = disposeAwaitableInfo;
7475
this.DisposeMethod = disposeMethod;
@@ -89,6 +90,7 @@ internal struct Builder
8990
public MethodSymbol CurrentPropertyGetter;
9091
public MethodSymbol MoveNextMethod;
9192

93+
public bool IsAsync;
9294
public bool NeedsDisposal;
9395
public AwaitableInfo DisposeAwaitableInfo;
9496
public MethodSymbol DisposeMethod;
@@ -112,6 +114,7 @@ public ForEachEnumeratorInfo Build(BinderFlags location)
112114
GetEnumeratorMethod,
113115
CurrentPropertyGetter,
114116
MoveNextMethod,
117+
IsAsync,
115118
NeedsDisposal,
116119
DisposeAwaitableInfo,
117120
DisposeMethod,

src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos
170170
// Use the right binder to avoid seeing iteration variable
171171
BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics);
172172

173-
ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder();
173+
var builder = new ForEachEnumeratorInfo.Builder();
174174
TypeWithAnnotations inferredType;
175175
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType);
176176

@@ -198,7 +198,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
198198
// Use the right binder to avoid seeing iteration variable
199199
BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics);
200200

201-
ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder();
201+
var builder = new ForEachEnumeratorInfo.Builder();
202202
TypeWithAnnotations inferredType;
203203
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType);
204204

@@ -533,7 +533,7 @@ internal TypeWithAnnotations InferCollectionElementType(DiagnosticBag diagnostic
533533
// Use the right binder to avoid seeing iteration variable
534534
BoundExpression collectionExpr = this.GetBinder(collectionSyntax).BindValue(collectionSyntax, diagnostics, BindValueKind.RValue);
535535

536-
ForEachEnumeratorInfo.Builder builder = new ForEachEnumeratorInfo.Builder();
536+
var builder = new ForEachEnumeratorInfo.Builder();
537537
GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out TypeWithAnnotations inferredType);
538538
return inferredType;
539539
}
@@ -626,6 +626,8 @@ private void UnwrapCollectionExpressionIfNullable(ref BoundExpression collection
626626
private bool GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder builder, BoundExpression collectionExpr, DiagnosticBag diagnostics)
627627
{
628628
bool isAsync = IsAsync;
629+
builder.IsAsync = isAsync;
630+
629631
EnumeratorResult found = GetEnumeratorInfo(ref builder, collectionExpr, isAsync, diagnostics);
630632
switch (found)
631633
{

src/Compilers/CSharp/Portable/Compilation/ForEachStatementInfo.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ namespace Microsoft.CodeAnalysis.CSharp
1010
/// </summary>
1111
public struct ForEachStatementInfo : IEquatable<ForEachStatementInfo>
1212
{
13+
/// <summary>
14+
/// Whether this is an asynchronous foreach.
15+
/// </summary>
16+
public bool IsAsynchronous { get; }
17+
1318
/// <summary>
1419
/// Gets the &quot;GetEnumerator&quot; method.
1520
/// </summary>
@@ -55,14 +60,16 @@ public struct ForEachStatementInfo : IEquatable<ForEachStatementInfo>
5560
/// <summary>
5661
/// Initializes a new instance of the <see cref="ForEachStatementInfo" /> structure.
5762
/// </summary>
58-
internal ForEachStatementInfo(IMethodSymbol getEnumeratorMethod,
63+
internal ForEachStatementInfo(bool isAsync,
64+
IMethodSymbol getEnumeratorMethod,
5965
IMethodSymbol moveNextMethod,
6066
IPropertySymbol currentProperty,
6167
IMethodSymbol disposeMethod,
6268
ITypeSymbol elementType,
6369
Conversion elementConversion,
6470
Conversion currentConversion)
6571
{
72+
this.IsAsynchronous = isAsync;
6673
this.GetEnumeratorMethod = getEnumeratorMethod;
6774
this.MoveNextMethod = moveNextMethod;
6875
this.CurrentProperty = currentProperty;
@@ -79,7 +86,8 @@ public override bool Equals(object obj)
7986

8087
public bool Equals(ForEachStatementInfo other)
8188
{
82-
return object.Equals(this.GetEnumeratorMethod, other.GetEnumeratorMethod)
89+
return this.IsAsynchronous == other.IsAsynchronous
90+
&& object.Equals(this.GetEnumeratorMethod, other.GetEnumeratorMethod)
8391
&& object.Equals(this.MoveNextMethod, other.MoveNextMethod)
8492
&& object.Equals(this.CurrentProperty, other.CurrentProperty)
8593
&& object.Equals(this.DisposeMethod, other.DisposeMethod)
@@ -90,13 +98,14 @@ public bool Equals(ForEachStatementInfo other)
9098

9199
public override int GetHashCode()
92100
{
93-
return Hash.Combine(GetEnumeratorMethod,
101+
return Hash.Combine(IsAsynchronous,
102+
Hash.Combine(GetEnumeratorMethod,
94103
Hash.Combine(MoveNextMethod,
95104
Hash.Combine(CurrentProperty,
96105
Hash.Combine(DisposeMethod,
97106
Hash.Combine(ElementType,
98107
Hash.Combine(ElementConversion.GetHashCode(),
99-
CurrentConversion.GetHashCode()))))));
108+
CurrentConversion.GetHashCode())))))));
100109
}
101110
}
102111
}

src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ public override ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatem
953953
}
954954

955955
return new ForEachStatementInfo(
956+
enumeratorInfoOpt.IsAsync,
956957
enumeratorInfoOpt.GetEnumeratorMethod,
957958
enumeratorInfoOpt.MoveNextMethod,
958959
currentProperty: (PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter?.AssociatedSymbol,

src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1533,7 +1533,8 @@ internal ForEachLoopOperationInfo GetForEachLoopOperatorInfo(BoundForEachStateme
15331533
enumeratorInfoOpt.GetEnumeratorMethod,
15341534
(PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter.AssociatedSymbol,
15351535
enumeratorInfoOpt.MoveNextMethod,
1536-
enumeratorInfoOpt.NeedsDisposal,
1536+
isAsynchronous: enumeratorInfoOpt.IsAsync,
1537+
needsDispose: enumeratorInfoOpt.NeedsDisposal,
15371538
knownToImplementIDisposable: enumeratorInfoOpt.NeedsDisposal && (object)enumeratorInfoOpt.GetEnumeratorMethod != null ?
15381539
compilation.Conversions.
15391540
ClassifyImplicitConversionFromType(enumeratorInfoOpt.GetEnumeratorMethod.ReturnType,

src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ internal sealed class CSharpLazyForEachLoopOperation : LazyForEachLoopOperation
608608
private readonly BoundForEachStatement _forEachStatement;
609609

610610
internal CSharpLazyForEachLoopOperation(CSharpOperationFactory operationFactory, BoundForEachStatement forEachStatement, ImmutableArray<ILocalSymbol> locals, ILabelSymbol continueLabel, ILabelSymbol exitLabel, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue, bool isImplicit) :
611-
base(LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit)
611+
base(forEachStatement.AwaitOpt != null, LoopKind.ForEach, locals, continueLabel, exitLabel, semanticModel, syntax, type, constantValue, isImplicit)
612612
{
613613
_operationFactory = operationFactory;
614614
_forEachStatement = forEachStatement;
@@ -1368,7 +1368,7 @@ internal sealed class CSharpLazyUsingOperation : LazyUsingOperation
13681368
private readonly BoundUsingStatement _usingStatement;
13691369

13701370
internal CSharpLazyUsingOperation(CSharpOperationFactory operationFactory, BoundUsingStatement usingStatement, ImmutableArray<ILocalSymbol> locals, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional<object> constantValue, bool isImplicit) :
1371-
base(locals, semanticModel, syntax, type, constantValue, isImplicit)
1371+
base(locals, usingStatement.AwaitOpt != null, semanticModel, syntax, type, constantValue, isImplicit)
13721372
{
13731373
_operationFactory = operationFactory;
13741374
_usingStatement = usingStatement;

src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*REMOVED*override Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax.Body.get -> Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode
2525
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<string> 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<byte> cryptoPublicKey = default(System.Collections.Immutable.ImmutableArray<byte>), 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<System.Collections.Generic.KeyValuePair<string, Microsoft.CodeAnalysis.ReportDiagnostic>> 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
2626
Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.WithNullableContextOptions(Microsoft.CodeAnalysis.NullableContextOptions options) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions
27+
Microsoft.CodeAnalysis.CSharp.ForEachStatementInfo.IsAsynchronous.get -> bool
2728
Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.AddBlockStatements(params Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax
2829
Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.Body.get -> Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode
2930
Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.WithBlock(Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax

src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ internal override ISymbol CommonGetWellKnownTypeMember(WellKnownMember member)
231231
return GetWellKnownTypeMember(member);
232232
}
233233

234+
internal override ITypeSymbol CommonGetWellKnownType(WellKnownType wellknownType)
235+
{
236+
return GetWellKnownType(wellknownType);
237+
}
238+
234239
internal static Symbol GetRuntimeMember(NamedTypeSymbol declaringType, ref MemberDescriptor descriptor, SignatureComparer<MethodSymbol, FieldSymbol, PropertySymbol, TypeSymbol, ParameterSymbol> comparer, AssemblySymbol accessWithinOpt)
235240
{
236241
Symbol result = null;

src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ public int Current
717717
var foreachSyntax = tree.GetRoot().DescendantNodes().OfType<ForEachStatementSyntax>().Single();
718718
var info = model.GetForEachStatementInfo(foreachSyntax);
719719

720+
Assert.True(info.IsAsynchronous);
720721
Assert.Equal("C.Enumerator C.GetAsyncEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
721722
Assert.Equal("System.Threading.Tasks.Task<System.Boolean> C.Enumerator.MoveNextAsync()", info.MoveNextMethod.ToTestDisplayString());
722723
Assert.Equal("System.Int32 C.Enumerator.Current { get; }", info.CurrentProperty.ToTestDisplayString());

0 commit comments

Comments
 (0)