未验证 提交 2af97d27 编写于 作者: C Chris Sienkiewicz 提交者: GitHub

Merge pull request #32640 from chsienki/foreach_pattern_disposal

Dispose pattern based enumerators in foreach
......@@ -2383,6 +2383,11 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin
// just say it does not escape anywhere, so that we do not get false errors.
return scopeOfTheContainingExpression;
case BoundKind.DisposableValuePlaceholder:
// Disposable value placeholder is only ever used to lookup a pattern dispose method
// then immediately discarded. The actual expression will be generated during lowering
return scopeOfTheContainingExpression;
case BoundKind.PointerElementAccess:
case BoundKind.PointerIndirectionOperator:
// Unsafe code will always be allowed to escape.
......
......@@ -21,13 +21,17 @@ internal sealed class ForEachEnumeratorInfo
public readonly MethodSymbol CurrentPropertyGetter;
public readonly MethodSymbol MoveNextMethod;
// Dispose method to be called on the enumerator (may be null).
// True if the enumerator needs disposal once used.
// Will be either IDisposable/IAsyncDisposable, or use DisposeMethod below if set
// Computed during initial binding so that we can expose it in the semantic model.
public readonly bool NeedsDisposeMethod;
public readonly bool NeedsDisposal;
// When async and needs disposal, this stores the information to await the DisposeAsync() invocation
public AwaitableInfo DisposeAwaitableInfo;
// When using pattern-based Dispose, this stores the method to invoke to Dispose
public readonly MethodSymbol DisposeMethod;
// Conversions that will be required when the foreach is lowered.
public readonly Conversion CollectionConversion; //collection expression to collection type
public readonly Conversion CurrentConversion; // current to element type
......@@ -47,6 +51,7 @@ internal bool IsAsync
MethodSymbol moveNextMethod,
bool needsDisposeMethod,
AwaitableInfo disposeAwaitableInfo,
MethodSymbol disposeMethod,
Conversion collectionConversion,
Conversion currentConversion,
Conversion enumeratorConversion,
......@@ -63,8 +68,9 @@ internal bool IsAsync
this.GetEnumeratorMethod = getEnumeratorMethod;
this.CurrentPropertyGetter = currentPropertyGetter;
this.MoveNextMethod = moveNextMethod;
this.NeedsDisposeMethod = needsDisposeMethod;
this.NeedsDisposal = needsDisposeMethod;
this.DisposeAwaitableInfo = disposeAwaitableInfo;
this.DisposeMethod = disposeMethod;
this.CollectionConversion = collectionConversion;
this.CurrentConversion = currentConversion;
this.EnumeratorConversion = enumeratorConversion;
......@@ -81,8 +87,9 @@ internal struct Builder
public MethodSymbol CurrentPropertyGetter;
public MethodSymbol MoveNextMethod;
public bool NeedsDisposeMethod;
public bool NeedsDisposal;
public AwaitableInfo DisposeAwaitableInfo;
public MethodSymbol DisposeMethod;
public Conversion CollectionConversion;
public Conversion CurrentConversion;
......@@ -103,8 +110,9 @@ public ForEachEnumeratorInfo Build(BinderFlags location)
GetEnumeratorMethod,
CurrentPropertyGetter,
MoveNextMethod,
NeedsDisposeMethod,
NeedsDisposal,
DisposeAwaitableInfo,
DisposeMethod,
CollectionConversion,
CurrentConversion,
EnumeratorConversion,
......
......@@ -479,7 +479,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
ConstantValue.NotAvailable,
builder.CollectionType);
if (builder.NeedsDisposeMethod && IsAsync)
if (builder.NeedsDisposal && IsAsync)
{
hasErrors |= GetAwaitDisposeAsyncInfo(ref builder, diagnostics);
}
......@@ -712,26 +712,8 @@ private EnumeratorResult GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder bui
{
builder.ElementType = ((PropertySymbol)builder.CurrentPropertyGetter.AssociatedSymbol).Type;
// NOTE: if IDisposable is not available at all, no diagnostics will be reported - we will just assume that
// the enumerator is not disposable. If it has IDisposable in its interface list, there will be a diagnostic there.
// If IDisposable is available but its Dispose method is not, then diagnostics will be reported only if the enumerator
// is potentially disposable.
GetDisposalInfoForEnumerator(ref builder, collectionExpr, isAsync, diagnostics);
var useSiteDiagnosticBag = DiagnosticBag.GetInstance();
TypeSymbol enumeratorType = builder.GetEnumeratorMethod.ReturnType.TypeSymbol;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
if (!enumeratorType.IsSealed ||
this.Conversions.ClassifyImplicitConversionFromType(enumeratorType,
isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable),
ref useSiteDiagnostics).IsImplicit)
{
builder.NeedsDisposeMethod = true;
diagnostics.AddRange(useSiteDiagnosticBag);
}
useSiteDiagnosticBag.Free();
diagnostics.Add(_syntax, useSiteDiagnostics);
return EnumeratorResult.Succeeded;
}
......@@ -826,7 +808,7 @@ private EnumeratorResult GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder bui
}
// We don't know the runtime type, so we will have to insert a runtime check for IDisposable (with a conditional call to IDisposable.Dispose).
builder.NeedsDisposeMethod = true;
builder.NeedsDisposal = true;
return EnumeratorResult.Succeeded;
}
......@@ -844,6 +826,41 @@ private EnumeratorResult GetEnumeratorInfo(ref ForEachEnumeratorInfo.Builder bui
return EnumeratorResult.FailedNotReported;
}
private void GetDisposalInfoForEnumerator(ref ForEachEnumeratorInfo.Builder builder, BoundExpression expr, bool isAsync, DiagnosticBag diagnostics)
{
// NOTE: if IDisposable is not available at all, no diagnostics will be reported - we will just assume that
// the enumerator is not disposable. If it has IDisposable in its interface list, there will be a diagnostic there.
// If IDisposable is available but its Dispose method is not, then diagnostics will be reported only if the enumerator
// is potentially disposable.
TypeSymbol enumeratorType = builder.GetEnumeratorMethod.ReturnType.TypeSymbol;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
if (!enumeratorType.IsSealed ||
this.Conversions.ClassifyImplicitConversionFromType(enumeratorType,
isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable),
ref useSiteDiagnostics).IsImplicit)
{
builder.NeedsDisposal = true;
}
else if (enumeratorType.IsRefLikeType)
{
// if it wasn't directly convertable to IDisposable, see if it is pattern disposable
// again, we throw away any binding diagnostics, and assume it's not disposable if we encounter errors
var patternDisposeDiags = new DiagnosticBag();
var receiver = new BoundDisposableValuePlaceholder(_syntax, enumeratorType);
MethodSymbol disposeMethod = TryFindDisposePatternMethod(receiver, _syntax, isAsync, patternDisposeDiags);
if (!(disposeMethod is null))
{
builder.NeedsDisposal = true;
builder.DisposeMethod = disposeMethod;
}
patternDisposeDiags.Free();
}
diagnostics.Add(_syntax, useSiteDiagnostics);
}
private MethodSymbol GetGetEnumeratorMethod(NamedTypeSymbol collectionType, DiagnosticBag diagnostics, bool isAsync, CSharpSyntaxNode errorLocationSyntax)
{
MethodSymbol getEnumeratorMethod;
......@@ -893,7 +910,7 @@ private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumerator
TypeSymbol.Equals(builder.GetEnumeratorMethod.ReturnType.TypeSymbol, this.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerator), TypeCompareKind.ConsiderEverything2));
// We don't know the runtime type, so we will have to insert a runtime check for IDisposable (with a conditional call to IDisposable.Dispose).
builder.NeedsDisposeMethod = true;
builder.NeedsDisposal = true;
return builder;
}
......
......@@ -104,6 +104,13 @@
<Node Name="BoundAwaitableValuePlaceholder" Base="BoundValuePlaceholderBase">
</Node>
<!--
This node is used to represent an expression of a certain type, when attempting to bind its pattern dispose method
It does not survive past initial binding.
-->
<Node Name="BoundDisposableValuePlaceholder" Base="BoundValuePlaceholderBase">
</Node>
<!-- only used by codegen -->
<Node Name="BoundDup" Base="BoundExpression">
<!-- when duplicating a local or parameter, must remember original ref kind -->
......
......@@ -862,11 +862,20 @@ public override ForEachStatementInfo GetForEachStatementInfo(CommonForEachStatem
// NOTE: we're going to list GetEnumerator, etc for array and string
// collections, even though we know that's not how the implementation
// actually enumerates them.
MethodSymbol disposeMethod = enumeratorInfoOpt.NeedsDisposeMethod
? enumeratorInfoOpt.IsAsync
MethodSymbol disposeMethod = null;
if (enumeratorInfoOpt.NeedsDisposal)
{
if (!(enumeratorInfoOpt.DisposeMethod is null))
{
disposeMethod = enumeratorInfoOpt.DisposeMethod;
}
else
{
disposeMethod = enumeratorInfoOpt.IsAsync
? (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_IAsyncDisposable__DisposeAsync)
: (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose)
: null;
: (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose);
}
}
return new ForEachStatementInfo(
enumeratorInfoOpt.GetEnumeratorMethod,
......
......@@ -27,6 +27,7 @@ internal enum BoundKind: byte
DeconstructValuePlaceholder,
TupleOperandPlaceholder,
AwaitableValuePlaceholder,
DisposableValuePlaceholder,
Dup,
PassByCopy,
BadExpression,
......@@ -527,6 +528,42 @@ public BoundAwaitableValuePlaceholder Update(TypeSymbol type)
}
}
internal sealed partial class BoundDisposableValuePlaceholder : BoundValuePlaceholderBase
{
public BoundDisposableValuePlaceholder(SyntaxNode syntax, TypeSymbol type, bool hasErrors)
: base(BoundKind.DisposableValuePlaceholder, syntax, type, hasErrors)
{
Debug.Assert((object)type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
}
public BoundDisposableValuePlaceholder(SyntaxNode syntax, TypeSymbol type)
: base(BoundKind.DisposableValuePlaceholder, syntax, type)
{
Debug.Assert((object)type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
}
public override BoundNode Accept(BoundTreeVisitor visitor)
{
return visitor.VisitDisposableValuePlaceholder(this);
}
public BoundDisposableValuePlaceholder Update(TypeSymbol type)
{
if (!TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything))
{
var result = new BoundDisposableValuePlaceholder(this.Syntax, type, this.HasErrors);
result.WasCompilerGenerated = this.WasCompilerGenerated;
return result;
}
return this;
}
}
internal sealed partial class BoundDup : BoundExpression
{
public BoundDup(SyntaxNode syntax, RefKind refKind, TypeSymbol type, bool hasErrors)
......@@ -7493,6 +7530,8 @@ internal R VisitInternal(BoundNode node, A arg)
return VisitTupleOperandPlaceholder(node as BoundTupleOperandPlaceholder, arg);
case BoundKind.AwaitableValuePlaceholder:
return VisitAwaitableValuePlaceholder(node as BoundAwaitableValuePlaceholder, arg);
case BoundKind.DisposableValuePlaceholder:
return VisitDisposableValuePlaceholder(node as BoundDisposableValuePlaceholder, arg);
case BoundKind.Dup:
return VisitDup(node as BoundDup, arg);
case BoundKind.PassByCopy:
......@@ -7877,6 +7916,10 @@ public virtual R VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder n
{
return this.DefaultVisit(node, arg);
}
public virtual R VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node, A arg)
{
return this.DefaultVisit(node, arg);
}
public virtual R VisitDup(BoundDup node, A arg)
{
return this.DefaultVisit(node, arg);
......@@ -8605,6 +8648,10 @@ public virtual BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlace
{
return this.DefaultVisit(node);
}
public virtual BoundNode VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node)
{
return this.DefaultVisit(node);
}
public virtual BoundNode VisitDup(BoundDup node)
{
return this.DefaultVisit(node);
......@@ -9337,6 +9384,10 @@ public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlac
{
return null;
}
public override BoundNode VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node)
{
return null;
}
public override BoundNode VisitDup(BoundDup node)
{
return null;
......@@ -10289,6 +10340,11 @@ public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlac
TypeSymbol type = this.VisitType(node.Type);
return node.Update(type);
}
public override BoundNode VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node)
{
TypeSymbol type = this.VisitType(node.Type);
return node.Update(type);
}
public override BoundNode VisitDup(BoundDup node)
{
TypeSymbol type = this.VisitType(node.Type);
......@@ -11407,6 +11463,14 @@ public override TreeDumperNode VisitAwaitableValuePlaceholder(BoundAwaitableValu
}
);
}
public override TreeDumperNode VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node, object arg)
{
return new TreeDumperNode("disposableValuePlaceholder", null, new TreeDumperNode[]
{
new TreeDumperNode("type", node.Type, null)
}
);
}
public override TreeDumperNode VisitDup(BoundDup node, object arg)
{
return new TreeDumperNode("dup", null, new TreeDumperNode[]
......
......@@ -927,6 +927,12 @@ public override BoundNode VisitDeconstructValuePlaceholder(BoundDeconstructValue
return null;
}
public override BoundNode VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node)
{
Fail(node);
return null;
}
private void Fail(BoundNode node)
{
Debug.Assert(false, $"Bound nodes of kind {node.Kind} should not survive past local rewriting");
......
......@@ -182,8 +182,9 @@ private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement n
hasErrors: false);
BoundStatement result;
MethodSymbol disposeMethod = enumeratorInfo.DisposeMethod;
if (enumeratorInfo.NeedsDisposeMethod && TryGetDisposeMethod(forEachSyntax, enumeratorInfo, out MethodSymbol disposeMethod))
if (enumeratorInfo.NeedsDisposal && (!(disposeMethod is null) || TryGetDisposeMethod(forEachSyntax, enumeratorInfo, out disposeMethod)))
{
BoundStatement tryFinally = WrapWithTryFinallyDispose(forEachSyntax, enumeratorInfo, enumeratorType, boundEnumeratorVar, whileLoop, disposeMethod);
......@@ -241,22 +242,26 @@ private bool TryGetDisposeMethod(CommonForEachStatementSyntax forEachSyntax, For
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
var isImplicit = conversions.ClassifyImplicitConversionFromType(enumeratorType, idisposableTypeSymbol, ref useSiteDiagnostics).IsImplicit;
_diagnostics.Add(forEachSyntax, useSiteDiagnostics);
var isExtension = disposeMethod?.IsExtensionMethod == true;
if (isImplicit)
if (isImplicit || isExtension)
{
Debug.Assert(enumeratorInfo.NeedsDisposeMethod);
Debug.Assert(enumeratorInfo.NeedsDisposal);
Conversion receiverConversion = enumeratorType.IsStructType() ?
Conversion.Boxing :
Conversion.ImplicitReference;
BoundExpression receiver = isExtension || idisposableTypeSymbol.IsRefLikeType ?
boundEnumeratorVar :
ConvertReceiverForInvocation(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol);
// ((IDisposable)e).Dispose() or e.Dispose() or await ((IAsyncDisposable)e).DisposeAsync() or await e.DisposeAsync()
// Note: pattern-based async disposal is not allowed (cannot use ref structs in async methods), so the arguments are known to be empty even for async case
BoundExpression disposeCall = BoundCall.Synthesized(
BoundExpression disposeCall = MakeCallWithNoExplicitArgument(
forEachSyntax,
ConvertReceiverForInvocation(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol),
disposeMethod,
ImmutableArray<BoundExpression>.Empty);
receiver,
disposeMethod);
BoundStatement disposeCallStatement;
if (disposeAwaitableInfoOpt != null)
{
......
......@@ -1504,8 +1504,8 @@ internal ForEachLoopOperationInfo GetForEachLoopOperatorInfo(BoundForEachStateme
enumeratorInfoOpt.GetEnumeratorMethod,
(PropertySymbol)enumeratorInfoOpt.CurrentPropertyGetter.AssociatedSymbol,
enumeratorInfoOpt.MoveNextMethod,
enumeratorInfoOpt.NeedsDisposeMethod,
knownToImplementIDisposable: enumeratorInfoOpt.NeedsDisposeMethod && (object)enumeratorInfoOpt.GetEnumeratorMethod != null ?
enumeratorInfoOpt.NeedsDisposal,
knownToImplementIDisposable: enumeratorInfoOpt.NeedsDisposal && (object)enumeratorInfoOpt.GetEnumeratorMethod != null ?
compilation.Conversions.
ClassifyImplicitConversionFromType(enumeratorInfoOpt.GetEnumeratorMethod.ReturnType.TypeSymbol,
compilation.GetSpecialType(SpecialType.System_IDisposable),
......
......@@ -1592,7 +1592,7 @@ public int Current
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.False(internalInfo.NeedsDisposeMethod);
Assert.False(internalInfo.NeedsDisposal);
}
[Fact]
......@@ -2046,7 +2046,7 @@ public async ValueTask DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[ConditionalFact(typeof(WindowsDesktopOnly))]
......@@ -2365,7 +2365,7 @@ public async ValueTask DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
CompileAndVerify(comp,
expectedOutput: "NextAsync(0) Current(1) Got(1) NextAsync(1) Current(2) Got(2) NextAsync(2) Current(3) Got(3) NextAsync(3)");
......@@ -2428,7 +2428,7 @@ public async ValueTask DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
var verifier = CompileAndVerify(comp,
expectedOutput: "NextAsync(0) Current(1) Got(1) NextAsync(1) Current(2) Got(2) NextAsync(2) Current(3) Got(3) NextAsync(3) Dispose(4)");
......@@ -2725,7 +2725,7 @@ public async ValueTask DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
CompileAndVerify(comp, expectedOutput: "NextAsync(0) Current(1) Got(1) NextAsync(1) Current(2) Got(2) NextAsync(2) Current(3) Got(3) NextAsync(3) Dispose(4)");
}
......@@ -2773,7 +2773,7 @@ await foreach (var i in new C())
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.False(internalInfo.NeedsDisposeMethod);
Assert.False(internalInfo.NeedsDisposal);
}
[Fact]
......@@ -2906,7 +2906,7 @@ async ValueTask IAsyncDisposable.DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[ConditionalFact(typeof(WindowsDesktopOnly))]
......@@ -3651,7 +3651,7 @@ class Element
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[ConditionalFact(typeof(WindowsDesktopOnly))]
......@@ -3724,7 +3724,7 @@ public async ValueTask DisposeAsync()
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[ConditionalFact(typeof(WindowsDesktopOnly))]
......@@ -3834,7 +3834,7 @@ public static class Extensions
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[Fact]
......@@ -4160,7 +4160,7 @@ await foreach (var i in new Collection<int>())
var memberModel = model.GetMemberModel(foreachSyntax);
var boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
}
[ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.NativePdbRequiresDesktop)]
......@@ -4241,7 +4241,7 @@ await foreach (var i in c)
var memberModel = model.GetMemberModel(foreachSyntax);
BoundForEachStatement boundNode = (BoundForEachStatement)memberModel.GetUpperBoundNode(foreachSyntax);
ForEachEnumeratorInfo internalInfo = boundNode.EnumeratorInfoOpt;
Assert.True(internalInfo.NeedsDisposeMethod);
Assert.True(internalInfo.NeedsDisposal);
verifier.VerifyIL("C.<Main>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @"
{
......
......@@ -1352,6 +1352,329 @@ class NonDisposableEnumerator : AbstractEnumerator
-3");
}
[Fact]
public void TestForEachPatternDisposableRefStruct()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose() { System.Console.WriteLine(""Done with DisposableEnumerator""); }
}";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator");
// IL Should not contain any Box/unbox instructions as we're a ref struct
compilation.VerifyIL("C.Main", @"
{
// Code size 45 (0x2d)
.maxstack 1
.locals init (DisposableEnumerator V_0)
IL_0000: newobj ""Enumerable1..ctor()""
IL_0005: call ""DisposableEnumerator Enumerable1.GetEnumerator()""
IL_000a: stloc.0
.try
{
IL_000b: br.s IL_0019
IL_000d: ldloca.s V_0
IL_000f: call ""int DisposableEnumerator.Current.get""
IL_0014: call ""void System.Console.WriteLine(int)""
IL_0019: ldloca.s V_0
IL_001b: call ""bool DisposableEnumerator.MoveNext()""
IL_0020: brtrue.s IL_000d
IL_0022: leave.s IL_002c
}
finally
{
IL_0024: ldloca.s V_0
IL_0026: call ""void DisposableEnumerator.Dispose()""
IL_002b: endfinally
}
IL_002c: ret
}");
}
[Fact]
public void TestForEachPatternDisposableRefStructWithParams()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose(params object[] args) { System.Console.WriteLine($""Done with DisposableEnumerator. args was {args}, length {args.Length}""); }
}";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator. args was System.Object[], length 0");
}
[Fact]
public void TestForEachPatternDisposableRefStructWithDefaultArguments()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose(int arg = 1) { System.Console.WriteLine($""Done with DisposableEnumerator. arg was {arg}""); }
}";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator. arg was 1");
}
[Fact]
public void TestForEachPatternDisposableRefStructWithExtensionMethod()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
}
static class DisposeExtension
{
public static void Dispose(this DisposableEnumerator de) { System.Console.WriteLine(""Done with DisposableEnumerator""); }
}
";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator");
}
[Fact]
public void TestForEachPatternDisposableRefStructWithExtensionMethodAndDefaultArguments()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
}
static class DisposeExtension
{
public static void Dispose(this DisposableEnumerator de, int arg = 4) { System.Console.WriteLine($""Done with DisposableEnumerator. arg was {arg}""); }
}
";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator. arg was 4");
}
[Fact]
public void TestForEachPatternDisposableRefStructWithExtensionMethodAndParams()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
}
static class DisposeExtension
{
public static void Dispose(this DisposableEnumerator de, params object[] args) { System.Console.WriteLine($""Done with DisposableEnumerator. Args was {args}, length {args.Length}""); }
}
";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3
Done with DisposableEnumerator. Args was System.Object[], length 0");
}
[Fact]
public void TestForEachPatternDisposableIgnoredForNonRefStruct()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose() { System.Console.WriteLine(""Done with DisposableEnumerator""); }
}";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3");
}
[Fact]
public void TestForEachPatternDisposableIgnoredForClass()
{
var source = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
class DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose() { System.Console.WriteLine(""Done with DisposableEnumerator""); }
}";
var compilation = CompileAndVerify(source, expectedOutput: @"
1
2
3");
}
[Fact]
public void TestForEachNested()
{
......
......@@ -1201,7 +1201,7 @@ void Goo(int[] a)
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Unboxing, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1233,7 +1233,7 @@ void Goo(string s)
Assert.Equal("System.CharEnumerator System.String.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Char System.CharEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.CharEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1276,7 +1276,7 @@ class Enumerator
Assert.Equal("Enumerator Enumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Int32 Enumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean Enumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1319,7 +1319,7 @@ struct Enumerator
Assert.Equal("Enumerator Enumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Int32 Enumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean Enumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.False(info.NeedsDisposeMethod); // Definitely not disposable
Assert.False(info.NeedsDisposal); // Definitely not disposable
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.EnumeratorConversion.Kind);
......@@ -1351,7 +1351,7 @@ void Goo(System.Collections.IEnumerable e)
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1390,7 +1390,7 @@ class Enumerable : System.Collections.Generic.IEnumerable<int>
Assert.Equal("System.Collections.Generic.IEnumerator<System.Int32> System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Int32 System.Collections.Generic.IEnumerator<System.Int32>.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString()); //NB: not on generic interface
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1431,7 +1431,7 @@ private class Hidden { }
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1469,7 +1469,7 @@ class Enumerable : System.Collections.IEnumerable
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1501,7 +1501,7 @@ void Goo(int[] a)
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Unboxing, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1531,7 +1531,7 @@ void Goo(string s)
Assert.Equal("System.CharEnumerator System.String.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Char System.CharEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.CharEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1616,7 +1616,7 @@ class var { }
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitReference, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.ExplicitReference, info.CurrentConversion.Kind); //object to C.var
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1646,7 +1646,7 @@ void Goo(dynamic d)
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitDynamic, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1678,7 +1678,7 @@ void Goo(dynamic d)
Assert.Equal("System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.ImplicitDynamic, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -1718,7 +1718,7 @@ public class Enumerable<T>
Assert.Equal("T Enumerable<T>.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object System.Collections.IEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.Collections.IEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind);
......@@ -1801,7 +1801,7 @@ interface MyEnumerator
Assert.Equal("T Enumerable<T>.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Object MyEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean MyEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.Boxing, info.EnumeratorConversion.Kind);
......@@ -1847,7 +1847,7 @@ struct Enumerator
Assert.Equal("Enumerator Enumerable.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Int32 Enumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean Enumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.False(info.NeedsDisposeMethod); // Definitely not disposable
Assert.False(info.NeedsDisposal); // Definitely not disposable
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.EnumeratorConversion.Kind);
......@@ -3046,7 +3046,7 @@ .maxstack 2
Assert.Equal("System.CharEnumerator System.String.GetEnumerator()", info.GetEnumeratorMethod.ToTestDisplayString());
Assert.Equal("System.Char System.CharEnumerator.Current.get", info.CurrentPropertyGetter.ToTestDisplayString());
Assert.Equal("System.Boolean System.CharEnumerator.MoveNext()", info.MoveNextMethod.ToTestDisplayString());
Assert.True(info.NeedsDisposeMethod);
Assert.True(info.NeedsDisposal);
Assert.Equal(ConversionKind.Identity, info.CollectionConversion.Kind);
Assert.Equal(ConversionKind.Identity, info.CurrentConversion.Kind);
Assert.Equal(ConversionKind.ImplicitReference, info.EnumeratorConversion.Kind);
......@@ -3055,7 +3055,39 @@ .maxstack 2
Assert.Equal(SpecialType.System_Char, boundNode.IterationVariables.Single().Type.SpecialType);
}
[Fact]
public void TestPatternDisposeRefStruct()
{
var text = @"
class C
{
static void Main()
{
foreach (var x in new Enumerable1())
{
System.Console.WriteLine(x);
}
}
}
class Enumerable1
{
public DisposableEnumerator GetEnumerator() { return new DisposableEnumerator(); }
}
ref struct DisposableEnumerator
{
int x;
public int Current { get { return x; } }
public bool MoveNext() { return ++x < 4; }
public void Dispose() { }
}";
var boundNode = GetBoundForEachStatement(text);
var enumeratorInfo = boundNode.EnumeratorInfoOpt;
Assert.Equal("void DisposableEnumerator.Dispose()", enumeratorInfo.DisposeMethod.ToTestDisplayString());
}
private static BoundForEachStatement GetBoundForEachStatement(string text, params DiagnosticDescription[] diagnostics)
{
......@@ -3086,9 +3118,21 @@ private static BoundForEachStatement GetBoundForEachStatement(string text, param
Assert.Equal(enumeratorInfo.CurrentPropertyGetter, statementInfo.CurrentProperty.GetMethod);
Assert.Equal(enumeratorInfo.MoveNextMethod, statementInfo.MoveNextMethod);
if (enumeratorInfo.NeedsDisposeMethod)
if (enumeratorInfo.NeedsDisposal)
{
Assert.Equal("void System.IDisposable.Dispose()", statementInfo.DisposeMethod.ToTestDisplayString());
if (!(enumeratorInfo.DisposeMethod is null))
{
Assert.Equal(enumeratorInfo.DisposeMethod, statementInfo.DisposeMethod);
}
else if (enumeratorInfo.IsAsync)
{
Assert.Equal("System.ValueTask System.IAsyncDisposable.DisposeAsync()", statementInfo.DisposeMethod.ToTestDisplayString());
}
else
{
Assert.Equal("void System.IDisposable.Dispose()", statementInfo.DisposeMethod.ToTestDisplayString());
}
}
else
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册