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

CFG pattern dispose with args (#49842)

* Correctly handle default and params arguments in cfg for pattern based disposal
上级 d57cddba
......@@ -3479,6 +3479,12 @@ internal virtual ImmutableArray<LabelSymbol> Labels
return PatternLookupResult.NotAMethod;
}
// NOTE: Because we're calling this method with no arguments and we
// explicitly ignore default values for params parameters
// (see ParamterSymbol.IsOptional) we know that no ParameterArray
// containing method can be invoked in normal form which allows
// us to skip some work during the lookup.
var analyzedArguments = AnalyzedArguments.GetInstance();
var patternMethodCall = BindMethodGroupInvocation(
syntaxNode,
......
......@@ -27,7 +27,7 @@ internal sealed class ForEachEnumeratorInfo
public readonly MethodSymbol MoveNextMethod;
// True if the enumerator needs disposal once used.
// Will be either IDisposable/IAsyncDisposable, or use DisposeMethod below if set
// 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 NeedsDisposal;
......
......@@ -156,7 +156,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo
// In the future it might be better to have a separate shared type that we add the info to, and have the callers create the appropriate bound nodes from it
if (isUsingDeclaration)
{
return new BoundUsingLocalDeclarations(syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, declarationsOpt, hasErrors);
return new BoundUsingLocalDeclarations(syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, originalBinder, declarationsOpt, hasErrors);
}
else
{
......@@ -171,6 +171,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo
boundBody,
awaitOpt,
disposeMethodOpt,
usingBinderOpt,
hasErrors);
}
......
......@@ -933,6 +933,7 @@
<Field Name="DisposeMethodOpt" Type="MethodSymbol?" Null="Allow"/>
<Field Name="IDisposableConversion" Type="Conversion"/>
<Field Name="AwaitOpt" Type="BoundAwaitableInfo?"/>
<Field Name="Binder" Type="Binder" />
</Node>
<!--
......@@ -1101,6 +1102,7 @@
<Field Name="Body" Type="BoundStatement"/>
<Field Name="AwaitOpt" Type="BoundAwaitableInfo?"/>
<Field Name="DisposeMethodOpt" Type="MethodSymbol?"/>
<Field Name="Binder" Type="Binder" />
</Node>
<Node Name="BoundFixedStatement" Base="BoundStatement">
......
......@@ -3016,15 +3016,17 @@ public BoundMultipleLocalDeclarations Update(ImmutableArray<BoundLocalDeclaratio
internal sealed partial class BoundUsingLocalDeclarations : BoundMultipleLocalDeclarationsBase
{
public BoundUsingLocalDeclarations(SyntaxNode syntax, MethodSymbol? disposeMethodOpt, Conversion iDisposableConversion, BoundAwaitableInfo? awaitOpt, ImmutableArray<BoundLocalDeclaration> localDeclarations, bool hasErrors = false)
public BoundUsingLocalDeclarations(SyntaxNode syntax, MethodSymbol? disposeMethodOpt, Conversion iDisposableConversion, BoundAwaitableInfo? awaitOpt, Binder binder, ImmutableArray<BoundLocalDeclaration> localDeclarations, bool hasErrors = false)
: base(BoundKind.UsingLocalDeclarations, syntax, localDeclarations, hasErrors || awaitOpt.HasErrors() || localDeclarations.HasErrors())
{
RoslynDebug.Assert(binder is object, "Field 'binder' cannot be null (make the type nullable in BoundNodes.xml to remove this check)");
RoslynDebug.Assert(!localDeclarations.IsDefault, "Field 'localDeclarations' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.DisposeMethodOpt = disposeMethodOpt;
this.IDisposableConversion = iDisposableConversion;
this.AwaitOpt = awaitOpt;
this.Binder = binder;
}
......@@ -3033,14 +3035,16 @@ public BoundUsingLocalDeclarations(SyntaxNode syntax, MethodSymbol? disposeMetho
public Conversion IDisposableConversion { get; }
public BoundAwaitableInfo? AwaitOpt { get; }
public Binder Binder { get; }
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitUsingLocalDeclarations(this);
public BoundUsingLocalDeclarations Update(MethodSymbol? disposeMethodOpt, Conversion iDisposableConversion, BoundAwaitableInfo? awaitOpt, ImmutableArray<BoundLocalDeclaration> localDeclarations)
public BoundUsingLocalDeclarations Update(MethodSymbol? disposeMethodOpt, Conversion iDisposableConversion, BoundAwaitableInfo? awaitOpt, Binder binder, ImmutableArray<BoundLocalDeclaration> localDeclarations)
{
if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(disposeMethodOpt, this.DisposeMethodOpt) || iDisposableConversion != this.IDisposableConversion || awaitOpt != this.AwaitOpt || localDeclarations != this.LocalDeclarations)
if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(disposeMethodOpt, this.DisposeMethodOpt) || iDisposableConversion != this.IDisposableConversion || awaitOpt != this.AwaitOpt || binder != this.Binder || localDeclarations != this.LocalDeclarations)
{
var result = new BoundUsingLocalDeclarations(this.Syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, localDeclarations, this.HasErrors);
var result = new BoundUsingLocalDeclarations(this.Syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, binder, localDeclarations, this.HasErrors);
result.CopyAttributes(this);
return result;
}
......@@ -3691,12 +3695,13 @@ public BoundForEachDeconstructStep Update(BoundDeconstructionAssignmentOperator
internal sealed partial class BoundUsingStatement : BoundStatement
{
public BoundUsingStatement(SyntaxNode syntax, ImmutableArray<LocalSymbol> locals, BoundMultipleLocalDeclarations? declarationsOpt, BoundExpression? expressionOpt, Conversion iDisposableConversion, BoundStatement body, BoundAwaitableInfo? awaitOpt, MethodSymbol? disposeMethodOpt, bool hasErrors = false)
public BoundUsingStatement(SyntaxNode syntax, ImmutableArray<LocalSymbol> locals, BoundMultipleLocalDeclarations? declarationsOpt, BoundExpression? expressionOpt, Conversion iDisposableConversion, BoundStatement body, BoundAwaitableInfo? awaitOpt, MethodSymbol? disposeMethodOpt, Binder binder, bool hasErrors = false)
: base(BoundKind.UsingStatement, syntax, hasErrors || declarationsOpt.HasErrors() || expressionOpt.HasErrors() || body.HasErrors() || awaitOpt.HasErrors())
{
RoslynDebug.Assert(!locals.IsDefault, "Field 'locals' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
RoslynDebug.Assert(body is object, "Field 'body' cannot be null (make the type nullable in BoundNodes.xml to remove this check)");
RoslynDebug.Assert(binder is object, "Field 'binder' cannot be null (make the type nullable in BoundNodes.xml to remove this check)");
this.Locals = locals;
this.DeclarationsOpt = declarationsOpt;
......@@ -3705,6 +3710,7 @@ public BoundUsingStatement(SyntaxNode syntax, ImmutableArray<LocalSymbol> locals
this.Body = body;
this.AwaitOpt = awaitOpt;
this.DisposeMethodOpt = disposeMethodOpt;
this.Binder = binder;
}
......@@ -3721,14 +3727,16 @@ public BoundUsingStatement(SyntaxNode syntax, ImmutableArray<LocalSymbol> locals
public BoundAwaitableInfo? AwaitOpt { get; }
public MethodSymbol? DisposeMethodOpt { get; }
public Binder Binder { get; }
[DebuggerStepThrough]
public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitUsingStatement(this);
public BoundUsingStatement Update(ImmutableArray<LocalSymbol> locals, BoundMultipleLocalDeclarations? declarationsOpt, BoundExpression? expressionOpt, Conversion iDisposableConversion, BoundStatement body, BoundAwaitableInfo? awaitOpt, MethodSymbol? disposeMethodOpt)
public BoundUsingStatement Update(ImmutableArray<LocalSymbol> locals, BoundMultipleLocalDeclarations? declarationsOpt, BoundExpression? expressionOpt, Conversion iDisposableConversion, BoundStatement body, BoundAwaitableInfo? awaitOpt, MethodSymbol? disposeMethodOpt, Binder binder)
{
if (locals != this.Locals || declarationsOpt != this.DeclarationsOpt || expressionOpt != this.ExpressionOpt || iDisposableConversion != this.IDisposableConversion || body != this.Body || awaitOpt != this.AwaitOpt || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(disposeMethodOpt, this.DisposeMethodOpt))
if (locals != this.Locals || declarationsOpt != this.DeclarationsOpt || expressionOpt != this.ExpressionOpt || iDisposableConversion != this.IDisposableConversion || body != this.Body || awaitOpt != this.AwaitOpt || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(disposeMethodOpt, this.DisposeMethodOpt) || binder != this.Binder)
{
var result = new BoundUsingStatement(this.Syntax, locals, declarationsOpt, expressionOpt, iDisposableConversion, body, awaitOpt, disposeMethodOpt, this.HasErrors);
var result = new BoundUsingStatement(this.Syntax, locals, declarationsOpt, expressionOpt, iDisposableConversion, body, awaitOpt, disposeMethodOpt, binder, this.HasErrors);
result.CopyAttributes(this);
return result;
}
......@@ -10128,7 +10136,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor
{
BoundAwaitableInfo? awaitOpt = (BoundAwaitableInfo?)this.Visit(node.AwaitOpt);
ImmutableArray<BoundLocalDeclaration> localDeclarations = this.VisitList(node.LocalDeclarations);
return node.Update(node.DisposeMethodOpt, node.IDisposableConversion, awaitOpt, localDeclarations);
return node.Update(node.DisposeMethodOpt, node.IDisposableConversion, awaitOpt, node.Binder, localDeclarations);
}
public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
{
......@@ -10222,7 +10230,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor
BoundExpression? expressionOpt = (BoundExpression?)this.Visit(node.ExpressionOpt);
BoundStatement body = (BoundStatement)this.Visit(node.Body);
BoundAwaitableInfo? awaitOpt = (BoundAwaitableInfo?)this.Visit(node.AwaitOpt);
return node.Update(node.Locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body, awaitOpt, node.DisposeMethodOpt);
return node.Update(node.Locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body, awaitOpt, node.DisposeMethodOpt, node.Binder);
}
public override BoundNode? VisitFixedStatement(BoundFixedStatement node)
{
......@@ -11862,7 +11870,7 @@ public NullabilityRewriter(ImmutableDictionary<BoundExpression, (NullabilityInfo
MethodSymbol? disposeMethodOpt = GetUpdatedSymbol(node, node.DisposeMethodOpt);
BoundAwaitableInfo? awaitOpt = (BoundAwaitableInfo?)this.Visit(node.AwaitOpt);
ImmutableArray<BoundLocalDeclaration> localDeclarations = this.VisitList(node.LocalDeclarations);
return node.Update(disposeMethodOpt, node.IDisposableConversion, awaitOpt, localDeclarations);
return node.Update(disposeMethodOpt, node.IDisposableConversion, awaitOpt, node.Binder, localDeclarations);
}
public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
......@@ -11938,7 +11946,7 @@ public NullabilityRewriter(ImmutableDictionary<BoundExpression, (NullabilityInfo
BoundExpression? expressionOpt = (BoundExpression?)this.Visit(node.ExpressionOpt);
BoundStatement body = (BoundStatement)this.Visit(node.Body);
BoundAwaitableInfo? awaitOpt = (BoundAwaitableInfo?)this.Visit(node.AwaitOpt);
return node.Update(locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body, awaitOpt, disposeMethodOpt);
return node.Update(locals, declarationsOpt, expressionOpt, node.IDisposableConversion, body, awaitOpt, disposeMethodOpt, node.Binder);
}
public override BoundNode? VisitFixedStatement(BoundFixedStatement node)
......@@ -13941,6 +13949,7 @@ private BoundTreeDumperNodeProducer()
new TreeDumperNode("disposeMethodOpt", node.DisposeMethodOpt, null),
new TreeDumperNode("iDisposableConversion", node.IDisposableConversion, null),
new TreeDumperNode("awaitOpt", null, new TreeDumperNode[] { Visit(node.AwaitOpt, null) }),
new TreeDumperNode("binder", node.Binder, null),
new TreeDumperNode("localDeclarations", null, from x in node.LocalDeclarations select Visit(x, null)),
new TreeDumperNode("hasErrors", node.HasErrors, null)
}
......@@ -14093,6 +14102,7 @@ public override TreeDumperNode VisitYieldBreakStatement(BoundYieldBreakStatement
new TreeDumperNode("body", null, new TreeDumperNode[] { Visit(node.Body, null) }),
new TreeDumperNode("awaitOpt", null, new TreeDumperNode[] { Visit(node.AwaitOpt, null) }),
new TreeDumperNode("disposeMethodOpt", node.DisposeMethodOpt, null),
new TreeDumperNode("binder", node.Binder, null),
new TreeDumperNode("hasErrors", node.HasErrors, null)
}
);
......
......@@ -193,7 +193,7 @@ public override BoundNode VisitUsingStatement(BoundUsingStatement node)
BoundExpression expressionOpt = (BoundExpression)this.Visit(node.ExpressionOpt);
BoundStatement body = (BoundStatement)this.Visit(node.Body);
Conversion disposableConversion = RewriteConversion(node.IDisposableConversion);
return node.Update(newLocals, declarationsOpt, expressionOpt, disposableConversion, body, node.AwaitOpt, node.DisposeMethodOpt);
return node.Update(newLocals, declarationsOpt, expressionOpt, disposableConversion, body, node.AwaitOpt, node.DisposeMethodOpt, binder: null);
}
private Conversion RewriteConversion(Conversion conversion)
......
......@@ -1650,18 +1650,18 @@ private IForLoopOperation CreateBoundForStatementOperation(BoundForStatement bou
getEnumeratorArguments: enumeratorInfoOpt.GetEnumeratorMethod is { IsExtensionMethod: true } enumeratorMethod
? Operation.SetParentOperation(
DeriveArguments(
boundForEachStatement,
enumeratorInfoOpt.Binder,
enumeratorMethod,
ImmutableArray.Create(boundForEachStatement.Expression),
argumentNamesOpt: default,
argumentsToParametersOpt: default,
defaultArguments: default,
argumentRefKindsOpt: default,
expanded: false,
boundForEachStatement.Expression.Syntax,
invokedAsExtensionMethod: true),
null)
: default,
disposeArguments: enumeratorInfoOpt.DisposeMethod is object
? CreateDisposeArguments(enumeratorInfoOpt.DisposeMethod, boundForEachStatement.Syntax, enumeratorInfoOpt.Binder)
: default);
}
else
......@@ -1747,14 +1747,19 @@ private IFixedOperation CreateBoundFixedStatementOperation(BoundFixedStatement b
private IUsingOperation CreateBoundUsingStatementOperation(BoundUsingStatement boundUsingStatement)
{
Debug.Assert((boundUsingStatement.DeclarationsOpt == null) != (boundUsingStatement.ExpressionOpt == null));
Debug.Assert(boundUsingStatement.ExpressionOpt is object || boundUsingStatement.Locals.Length > 0);
IOperation resources = Create(boundUsingStatement.DeclarationsOpt ?? (BoundNode)boundUsingStatement.ExpressionOpt!);
IOperation body = Create(boundUsingStatement.Body);
ImmutableArray<ILocalSymbol> locals = boundUsingStatement.Locals.GetPublicSymbols();
bool isAsynchronous = boundUsingStatement.AwaitOpt != null;
IMethodSymbol? disposeMethod = boundUsingStatement.DisposeMethodOpt.GetPublicSymbol();
DisposeOperationInfo disposeOperationInfo = boundUsingStatement.DisposeMethodOpt is object
? new DisposeOperationInfo(
disposeMethod: boundUsingStatement.DisposeMethodOpt.GetPublicSymbol(),
disposeArguments: CreateDisposeArguments(boundUsingStatement.DisposeMethodOpt, boundUsingStatement.Syntax, boundUsingStatement.Binder))
: default;
SyntaxNode syntax = boundUsingStatement.Syntax;
bool isImplicit = boundUsingStatement.WasCompilerGenerated;
return new UsingOperation(resources, body, locals, isAsynchronous, disposeMethod, _semanticModel, syntax, isImplicit);
return new UsingOperation(resources, body, locals, isAsynchronous, disposeOperationInfo, _semanticModel, syntax, isImplicit);
}
private IThrowOperation CreateBoundThrowStatementOperation(BoundThrowStatement boundThrowStatement)
......@@ -1886,8 +1891,12 @@ private IOperation CreateBoundMultipleLocalDeclarationsBaseOperation(BoundMultip
return new UsingDeclarationOperation(
variableDeclaration,
isAsynchronous: usingDecl.AwaitOpt is object,
usingDecl.DisposeMethodOpt.GetPublicSymbol(),
_semanticModel,
disposeInfo: usingDecl.DisposeMethodOpt is object
? new DisposeOperationInfo(
disposeMethod: usingDecl.DisposeMethodOpt.GetPublicSymbol(),
disposeArguments: CreateDisposeArguments(usingDecl.DisposeMethodOpt, usingDecl.Syntax, usingDecl.Binder))
: default,
_semanticModel,
declarationGroupSyntax,
isImplicit: boundMultipleLocalDeclarations.WasCompilerGenerated);
}
......@@ -2318,5 +2327,45 @@ private IInstanceReferenceOperation CreateCollectionValuePlaceholderOperation(Bo
bool isImplicit = placeholder.WasCompilerGenerated;
return new InstanceReferenceOperation(referenceKind, _semanticModel, syntax, type, isImplicit);
}
private ImmutableArray<IArgumentOperation> CreateDisposeArguments(MethodSymbol disposeMethod, SyntaxNode syntax, Binder binder)
{
// can't be an extension method for dispose
Debug.Assert(!disposeMethod.IsStatic);
if (disposeMethod.ParameterCount == 0)
{
return ImmutableArray<IArgumentOperation>.Empty;
}
var argumentsBuilder = ArrayBuilder<BoundExpression>.GetInstance(disposeMethod.ParameterCount);
ImmutableArray<int> argsToParams = default;
var expanded = disposeMethod.HasParamsParameter();
Debug.Assert(!expanded || disposeMethod.GetParameters().Last().OriginalDefinition.Type.IsSZArray());
binder.BindDefaultArguments(
syntax,
disposeMethod.Parameters,
argumentsBuilder,
argumentRefKindsBuilder: null,
ref argsToParams,
out BitVector defaultArguments,
expanded,
enableCallerInfo: true,
new DiagnosticBag());
var args = DeriveArguments(
binder,
disposeMethod,
argumentsBuilder.ToImmutableAndFree(),
argsToParams,
defaultArguments,
expanded,
syntax,
invokedAsExtensionMethod: false);
return Operation.SetParentOperation(args, null);
}
}
}
......@@ -200,14 +200,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
var property = (PropertySymbol?)boundObjectInitializerMember.MemberSymbol;
Debug.Assert(property is not null);
return DeriveArguments(
boundObjectInitializerMember,
boundObjectInitializerMember.Binder,
property,
boundObjectInitializerMember.Arguments,
boundObjectInitializerMember.ArgumentNamesOpt,
boundObjectInitializerMember.ArgsToParamsOpt,
boundObjectInitializerMember.DefaultArguments,
boundObjectInitializerMember.ArgumentRefKindsOpt,
boundObjectInitializerMember.Expanded,
boundObjectInitializerMember.Syntax);
}
......@@ -225,14 +222,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
{
var boundIndexer = (BoundIndexerAccess)containingExpression;
Debug.Assert(boundIndexer.BinderOpt is not null);
return DeriveArguments(boundIndexer,
boundIndexer.BinderOpt,
return DeriveArguments(boundIndexer.BinderOpt,
boundIndexer.Indexer,
boundIndexer.Arguments,
boundIndexer.ArgumentNamesOpt,
boundIndexer.ArgsToParamsOpt,
boundIndexer.DefaultArguments,
boundIndexer.ArgumentRefKindsOpt,
boundIndexer.Expanded,
boundIndexer.Syntax);
}
......@@ -240,14 +234,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
{
var objectCreation = (BoundObjectCreationExpression)containingExpression;
Debug.Assert(objectCreation.BinderOpt is not null);
return DeriveArguments(objectCreation,
objectCreation.BinderOpt,
return DeriveArguments(objectCreation.BinderOpt,
objectCreation.Constructor,
objectCreation.Arguments,
objectCreation.ArgumentNamesOpt,
objectCreation.ArgsToParamsOpt,
objectCreation.DefaultArguments,
objectCreation.ArgumentRefKindsOpt,
objectCreation.Expanded,
objectCreation.Syntax);
}
......@@ -255,14 +246,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
{
var boundCall = (BoundCall)containingExpression;
Debug.Assert(boundCall.BinderOpt is not null);
return DeriveArguments(boundCall,
boundCall.BinderOpt,
return DeriveArguments(boundCall.BinderOpt,
boundCall.Method,
boundCall.Arguments,
boundCall.ArgumentNamesOpt,
boundCall.ArgsToParamsOpt,
boundCall.DefaultArguments,
boundCall.ArgumentRefKindsOpt,
boundCall.Expanded,
boundCall.Syntax,
boundCall.InvokedAsExtensionMethod);
......@@ -271,14 +259,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
{
var boundCollectionElementInitializer = (BoundCollectionElementInitializer)containingExpression;
Debug.Assert(boundCollectionElementInitializer.BinderOpt is not null);
return DeriveArguments(boundCollectionElementInitializer,
boundCollectionElementInitializer.BinderOpt,
return DeriveArguments(boundCollectionElementInitializer.BinderOpt,
boundCollectionElementInitializer.AddMethod,
boundCollectionElementInitializer.Arguments,
argumentNamesOpt: default,
boundCollectionElementInitializer.ArgsToParamsOpt,
boundCollectionElementInitializer.DefaultArguments,
argumentRefKindsOpt: default,
boundCollectionElementInitializer.Expanded,
boundCollectionElementInitializer.Syntax,
boundCollectionElementInitializer.InvokedAsExtensionMethod);
......@@ -290,14 +275,11 @@ internal ImmutableArray<IArgumentOperation> DeriveArguments(BoundNode containing
}
private ImmutableArray<IArgumentOperation> DeriveArguments(
BoundNode boundNode,
Binder binder,
Symbol methodOrIndexer,
ImmutableArray<BoundExpression> boundArguments,
ImmutableArray<string> argumentNamesOpt,
ImmutableArray<int> argumentsToParametersOpt,
BitVector defaultArguments,
ImmutableArray<RefKind> argumentRefKindsOpt,
bool expanded,
SyntaxNode invocationSyntax,
bool invokedAsExtensionMethod = false)
......
......@@ -5663,7 +5663,7 @@ public struct AsyncEnumerator
Statements (1)
IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'new C()')
Expression:
IInvocationOperation (virtual System.Threading.Tasks.ValueTask C.AsyncEnumerator.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'new C()')
IInvocationOperation ( System.Threading.Tasks.ValueTask C.AsyncEnumerator.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'new C()')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C.AsyncEnumerator, IsImplicit) (Syntax: 'new C()')
Arguments(0)
......@@ -5970,7 +5970,7 @@ static void Main()
NextVariables(0)", DiagnosticDescription.None);
VerifyFlowGraphForTest<BlockSyntax>(compilation, @"
Block[B0] - Entry
Block[B0] - Entry
Statements (0)
Next (Regular) Block[B1]
Entering: {R1}
......@@ -6031,7 +6031,7 @@ static void Main()
Block[B4] - Block
Predecessors (0)
Statements (1)
IInvocationOperation (virtual void C.Enumerator.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'new C()')
IInvocationOperation ( void C.Enumerator.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'new C()')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C.Enumerator, IsImplicit) (Syntax: 'new C()')
Arguments(0)
......@@ -6185,6 +6185,141 @@ await foreach (var i in (C?)null)
");
}
[CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)]
[Fact]
public void Foreach_RefStructEnumerator_DefaultDisposeArguments()
{
var compilation = CreateCompilation(@"
class C
{
static void Main()
/*<bind>*/{
foreach (var i in new C())
{
}
}/*</bind>*/
public Enumerator GetEnumerator() => throw null;
public ref struct Enumerator
{
public int Current => throw null;
public bool MoveNext() => throw null;
public void Dispose(int a = 1, bool b = true, params object[] extras) => throw null;
}
}", targetFramework: TargetFramework.NetCoreApp30);
VerifyOperationTreeAndDiagnosticsForTest<BlockSyntax>(compilation, @"
IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }')
IForEachLoopOperation (LoopKind.ForEach, Continue Label Id: 0, Exit Label Id: 1) (OperationKind.Loop, Type: null) (Syntax: 'foreach (va ... }')
Locals: Local_1: System.Int32 i
LoopControlVariable:
IVariableDeclaratorOperation (Symbol: System.Int32 i) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'var')
Initializer:
null
Collection:
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, IsImplicit) (Syntax: 'new C()')
Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
Operand:
IObjectCreationOperation (Constructor: C..ctor()) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C()')
Arguments(0)
Initializer:
null
Body:
IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }')
NextVariables(0)", DiagnosticDescription.None);
VerifyFlowGraphForTest<BlockSyntax>(compilation, @"
Block[B0] - Entry
Statements (0)
Next (Regular) Block[B1]
Entering: {R1}
.locals {R1}
{
CaptureIds: [0]
Block[B1] - Block
Predecessors: [B0]
Statements (1)
IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'new C()')
Value:
IInvocationOperation ( C.Enumerator C.GetEnumerator()) (OperationKind.Invocation, Type: C.Enumerator, IsImplicit) (Syntax: 'new C()')
Instance Receiver:
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, IsImplicit) (Syntax: 'new C()')
Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
(Identity)
Operand:
IObjectCreationOperation (Constructor: C..ctor()) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C()')
Arguments(0)
Initializer:
null
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[B5]
IInvocationOperation ( System.Boolean C.Enumerator.MoveNext()) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'new C()')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C.Enumerator, IsImplicit) (Syntax: 'new C()')
Arguments(0)
Finalizing: {R5}
Leaving: {R3} {R2} {R1}
Next (Regular) Block[B3]
Entering: {R4}
.locals {R4}
{
Locals: [System.Int32 i]
Block[B3] - Block
Predecessors: [B2]
Statements (1)
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: null, IsImplicit) (Syntax: 'var')
Left:
ILocalReferenceOperation: i (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32, IsImplicit) (Syntax: 'var')
Right:
IPropertyReferenceOperation: System.Int32 C.Enumerator.Current { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'var')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C.Enumerator, IsImplicit) (Syntax: 'new C()')
Next (Regular) Block[B2]
Leaving: {R4}
}
}
.finally {R5}
{
Block[B4] - Block
Predecessors (0)
Statements (1)
IInvocationOperation ( void C.Enumerator.Dispose([System.Int32 a = 1], [System.Boolean b = true], params System.Object[] extras)) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'new C()')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C.Enumerator, IsImplicit) (Syntax: 'new C()')
Arguments(3):
IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'foreach (va ... }')
ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: 'foreach (va ... }')
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)
IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: b) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'foreach (va ... }')
ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: True, IsImplicit) (Syntax: 'foreach (va ... }')
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)
IArgumentOperation (ArgumentKind.ParamArray, Matching Parameter: extras) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'foreach (va ... }')
IArrayCreationOperation (OperationKind.ArrayCreation, Type: System.Object[], IsImplicit) (Syntax: 'foreach (va ... }')
Dimension Sizes(1):
ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsImplicit) (Syntax: 'foreach (va ... }')
Initializer:
IArrayInitializerOperation (0 elements) (OperationKind.ArrayInitializer, Type: null, IsImplicit) (Syntax: 'foreach (va ... }')
Element Values(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 (StructuredExceptionHandling) Block[null]
}
}
Block[B5] - Exit
Predecessors: [B2]
Statements (0)
");
}
private static readonly string s_ValueTask = @"
namespace System.Threading.Tasks
{
......
......@@ -3875,20 +3875,20 @@ protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int prev
}
internal sealed partial class UsingOperation : Operation, IUsingOperation
{
internal UsingOperation(IOperation resources, IOperation body, ImmutableArray<ILocalSymbol> locals, bool isAsynchronous, IMethodSymbol? disposeMethod, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit)
internal UsingOperation(IOperation resources, IOperation body, ImmutableArray<ILocalSymbol> locals, bool isAsynchronous, DisposeOperationInfo disposeInfo, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit)
: base(semanticModel, syntax, isImplicit)
{
Resources = SetParentOperation(resources, this);
Body = SetParentOperation(body, this);
Locals = locals;
IsAsynchronous = isAsynchronous;
DisposeMethod = disposeMethod;
DisposeInfo = disposeInfo;
}
public IOperation Resources { get; }
public IOperation Body { get; }
public ImmutableArray<ILocalSymbol> Locals { get; }
public bool IsAsynchronous { get; }
public IMethodSymbol? DisposeMethod { get; }
public DisposeOperationInfo DisposeInfo { get; }
protected override IOperation GetCurrent(int slot, int index)
=> slot switch
{
......@@ -7382,16 +7382,16 @@ protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int prev
}
internal sealed partial class UsingDeclarationOperation : Operation, IUsingDeclarationOperation
{
internal UsingDeclarationOperation(IVariableDeclarationGroupOperation declarationGroup, bool isAsynchronous, IMethodSymbol? disposeMethod, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit)
internal UsingDeclarationOperation(IVariableDeclarationGroupOperation declarationGroup, bool isAsynchronous, DisposeOperationInfo disposeInfo, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit)
: base(semanticModel, syntax, isImplicit)
{
DeclarationGroup = SetParentOperation(declarationGroup, this);
IsAsynchronous = isAsynchronous;
DisposeMethod = disposeMethod;
DisposeInfo = disposeInfo;
}
public IVariableDeclarationGroupOperation DeclarationGroup { get; }
public bool IsAsynchronous { get; }
public IMethodSymbol? DisposeMethod { get; }
public DisposeOperationInfo DisposeInfo { get; }
protected override IOperation GetCurrent(int slot, int index)
=> slot switch
{
......@@ -7677,7 +7677,7 @@ public override IOperation VisitTry(ITryOperation operation, object? argument)
public override IOperation VisitUsing(IUsingOperation operation, object? argument)
{
var internalOperation = (UsingOperation)operation;
return new UsingOperation(Visit(internalOperation.Resources), Visit(internalOperation.Body), internalOperation.Locals, internalOperation.IsAsynchronous, internalOperation.DisposeMethod, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit);
return new UsingOperation(Visit(internalOperation.Resources), Visit(internalOperation.Body), internalOperation.Locals, internalOperation.IsAsynchronous, internalOperation.DisposeInfo, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit);
}
public override IOperation VisitExpressionStatement(IExpressionStatementOperation operation, object? argument)
{
......@@ -8127,7 +8127,7 @@ internal override IOperation VisitWithStatement(IWithStatementOperation operatio
public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation operation, object? argument)
{
var internalOperation = (UsingDeclarationOperation)operation;
return new UsingDeclarationOperation(Visit(internalOperation.DeclarationGroup), internalOperation.IsAsynchronous, internalOperation.DisposeMethod, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit);
return new UsingDeclarationOperation(Visit(internalOperation.DeclarationGroup), internalOperation.IsAsynchronous, internalOperation.DisposeInfo, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit);
}
public override IOperation VisitNegatedPattern(INegatedPatternOperation operation, object? argument)
{
......
......@@ -3735,11 +3735,12 @@ private void LinkThrowStatement(IOperation? exception)
public override IOperation? VisitUsing(IUsingOperation operation, int? captureIdForResult)
{
StartVisitingStatement(operation);
HandleUsingOperationParts(operation.Resources, operation.Body, ((UsingOperation)operation).DisposeMethod, operation.Locals, operation.IsAsynchronous);
DisposeOperationInfo disposeInfo = ((UsingOperation)operation).DisposeInfo;
HandleUsingOperationParts(operation.Resources, operation.Body, disposeInfo.DisposeMethod, disposeInfo.DisposeArguments, operation.Locals, operation.IsAsynchronous);
return FinishVisitingStatement(operation);
}
private void HandleUsingOperationParts(IOperation resources, IOperation body, IMethodSymbol? disposeMethod, ImmutableArray<ILocalSymbol> locals, bool isAsynchronous)
private void HandleUsingOperationParts(IOperation resources, IOperation body, IMethodSymbol? disposeMethod, ImmutableArray<IArgumentOperation> disposeArguments, ImmutableArray<ILocalSymbol> locals, bool isAsynchronous)
{
var usingRegion = new RegionBuilder(ControlFlowRegionKind.LocalLifetime, locals: locals);
EnterRegion(usingRegion);
......@@ -3878,7 +3879,7 @@ void processResource(IOperation resource, ArrayBuilder<(IVariableDeclarationOper
LeaveRegion();
AddDisposingFinally(resource, requiresRuntimeConversion: false, iDisposable, disposeMethod, isAsynchronous);
AddDisposingFinally(resource, requiresRuntimeConversion: false, iDisposable, disposeMethod, disposeArguments, isAsynchronous);
Debug.Assert(CurrentRegionRequired.Kind == ControlFlowRegionKind.TryAndFinally);
LeaveRegion();
......@@ -3893,7 +3894,7 @@ void processResource(IOperation resource, ArrayBuilder<(IVariableDeclarationOper
}
}
private void AddDisposingFinally(IOperation resource, bool requiresRuntimeConversion, ITypeSymbol iDisposable, IMethodSymbol? disposeMethod, bool isAsynchronous)
private void AddDisposingFinally(IOperation resource, bool requiresRuntimeConversion, ITypeSymbol iDisposable, IMethodSymbol? disposeMethod, ImmutableArray<IArgumentOperation> disposeArguments, bool isAsynchronous)
{
Debug.Assert(CurrentRegionRequired.Kind == ControlFlowRegionKind.TryAndFinally);
......@@ -3925,9 +3926,13 @@ private void AddDisposingFinally(IOperation resource, bool requiresRuntimeConver
resource = ConvertToIDisposable(resource, iDisposable);
}
EvalStackFrame disposeFrame = PushStackFrame();
AddStatement(tryDispose(resource) ??
MakeInvalidOperation(type: null, resource));
PopStackFrameAndLeaveRegion(disposeFrame);
AppendNewBlock(endOfFinally);
Debug.Assert(_currentRegion == finallyRegion);
......@@ -3936,15 +3941,17 @@ private void AddDisposingFinally(IOperation resource, bool requiresRuntimeConver
IOperation? tryDispose(IOperation value)
{
Debug.Assert(disposeMethod is object || value.Type!.Equals(iDisposable));
Debug.Assert((disposeMethod is object && !disposeArguments.IsDefault) || (value.Type!.Equals(iDisposable) && disposeArguments.IsDefaultOrEmpty));
var method = disposeMethod ?? (isAsynchronous
? (IMethodSymbol?)_compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_IAsyncDisposable__DisposeAsync)?.GetISymbol()
: (IMethodSymbol?)_compilation.CommonGetSpecialTypeMember(SpecialMember.System_IDisposable__Dispose)?.GetISymbol());
if (method != null)
{
var invocation = new InvocationOperation(method, value, isVirtual: true,
ImmutableArray<IArgumentOperation>.Empty, semanticModel: null, value.Syntax,
var args = disposeMethod is object ? VisitArguments(disposeArguments) : ImmutableArray<IArgumentOperation>.Empty;
var invocation = new InvocationOperation(method, value, isVirtual: disposeMethod?.IsVirtual ?? true,
args, semanticModel: null, value.Syntax,
method.ReturnType, isImplicit: true);
if (isAsynchronous)
......@@ -4248,6 +4255,7 @@ private IOperation ConvertToIDisposable(IOperation operand, ITypeSymbol iDisposa
requiresRuntimeConversion: !info.KnownToImplementIDisposable && !info.IsPatternDispose,
iDisposable,
info.DisposeMethod,
info.DisposeArguments,
isAsynchronous);
Debug.Assert(_currentRegion.Kind == ControlFlowRegionKind.TryAndFinally);
......@@ -7001,10 +7009,13 @@ private void VisitUsingVariableDeclarationOperation(IUsingDeclarationOperation o
// a using statement introduces a 'logical' block after declaration, we synthesize one here in order to analyze it like a regular using
BlockOperation logicalBlock = BlockOperation.CreateTemporaryBlock(statements, ((Operation)operation).OwningSemanticModel!, operation.Syntax);
DisposeOperationInfo disposeInfo = ((UsingDeclarationOperation)operation).DisposeInfo;
HandleUsingOperationParts(
resources: operation.DeclarationGroup,
body: logicalBlock,
((UsingDeclarationOperation)operation).DisposeMethod,
disposeInfo.DisposeMethod,
disposeInfo.DisposeArguments,
locals: ImmutableArray<ILocalSymbol>.Empty,
isAsynchronous: operation.IsAsynchronous);
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.Operations
{
internal struct DisposeOperationInfo
{
public readonly IMethodSymbol? DisposeMethod;
public readonly ImmutableArray<IArgumentOperation> DisposeArguments;
public DisposeOperationInfo(IMethodSymbol? disposeMethod, ImmutableArray<IArgumentOperation> disposeArguments)
{
DisposeMethod = disposeMethod;
DisposeArguments = disposeArguments;
}
}
}
......@@ -36,6 +36,7 @@ internal class ForEachLoopOperationInfo
public readonly ImmutableArray<IArgumentOperation> GetEnumeratorArguments;
public readonly ImmutableArray<IArgumentOperation> MoveNextArguments;
public readonly ImmutableArray<IArgumentOperation> CurrentArguments;
public readonly ImmutableArray<IArgumentOperation> DisposeArguments;
public ForEachLoopOperationInfo(
ITypeSymbol elementType,
......@@ -51,7 +52,8 @@ internal class ForEachLoopOperationInfo
IConvertibleConversion elementConversion,
ImmutableArray<IArgumentOperation> getEnumeratorArguments = default,
ImmutableArray<IArgumentOperation> moveNextArguments = default,
ImmutableArray<IArgumentOperation> currentArguments = default)
ImmutableArray<IArgumentOperation> currentArguments = default,
ImmutableArray<IArgumentOperation> disposeArguments = default)
{
ElementType = elementType;
GetEnumeratorMethod = getEnumeratorMethod;
......@@ -67,6 +69,7 @@ internal class ForEachLoopOperationInfo
GetEnumeratorArguments = getEnumeratorArguments;
MoveNextArguments = moveNextArguments;
CurrentArguments = currentArguments;
DisposeArguments = disposeArguments;
}
}
}
......@@ -485,9 +485,9 @@
</summary>
</Comments>
</Property>
<Property Name="DisposeMethod" Type="IMethodSymbol?" Internal="true">
<Property Name="DisposeInfo" Type="DisposeOperationInfo" Internal="true">
<Comments>
<summary>The method that will be invoked to dispose the <see cref="Resources" /> when pattern based disposal is used.</summary>
<summary>Information about the method that will be invoked to dispose the <see cref="Resources" /> when pattern based disposal is used.</summary>
</Comments>
</Property>
</Node>
......@@ -2939,9 +2939,9 @@
<summary>True if this is an asynchronous using declaration.</summary>
</Comments>
</Property>
<Property Name="DisposeMethod" Type="IMethodSymbol?" Internal="true">
<Property Name="DisposeInfo" Type="DisposeOperationInfo" Internal="true">
<Comments>
<summary>The method that will be invoked to dispose the declared instances when pattern based disposal is used.</summary>
<summary>Information about the method that will be invoked to dispose the declared instances when pattern based disposal is used.</summary>
</Comments>
</Property>
</Node>
......
......@@ -465,10 +465,22 @@ public override void VisitVariableDeclarationGroup(IVariableDeclarationGroupOper
public override void VisitUsingDeclaration(IUsingDeclarationOperation operation)
{
LogString($"{nameof(IUsingDeclarationOperation)}");
LogString($"(IsAsynchronous: {operation.IsAsynchronous})");
LogString($"(IsAsynchronous: {operation.IsAsynchronous}");
var disposeMethod = ((UsingDeclarationOperation)operation).DisposeInfo.DisposeMethod;
if (disposeMethod is object)
{
LogSymbol(disposeMethod, ", DisposeMethod");
}
LogString(")");
LogCommonPropertiesAndNewLine(operation);
Visit(operation.DeclarationGroup, "DeclarationGroup");
var disposeArgs = ((UsingDeclarationOperation)operation).DisposeInfo.DisposeArguments;
if (!disposeArgs.IsDefaultOrEmpty)
{
VisitArray(disposeArgs, "DisposeArguments", logElementCount: true);
}
}
public override void VisitVariableDeclarator(IVariableDeclaratorOperation operation)
......@@ -731,6 +743,12 @@ public override void VisitUsing(IUsingOperation operation)
{
LogString($" (IsAsynchronous)");
}
var disposeMethod = ((UsingOperation)operation).DisposeInfo.DisposeMethod;
if (disposeMethod is object)
{
LogSymbol(disposeMethod, " (DisposeMethod");
LogString(")");
}
LogCommonPropertiesAndNewLine(operation);
LogLocals(operation.Locals);
......@@ -739,6 +757,12 @@ public override void VisitUsing(IUsingOperation operation)
Assert.NotEqual(OperationKind.VariableDeclaration, operation.Resources.Kind);
Assert.NotEqual(OperationKind.VariableDeclarator, operation.Resources.Kind);
var disposeArgs = ((UsingOperation)operation).DisposeInfo.DisposeArguments;
if (!disposeArgs.IsDefaultOrEmpty)
{
VisitArray(disposeArgs, "DisposeArguments", logElementCount: true);
}
}
// https://github.com/dotnet/roslyn/issues/21281
......
......@@ -300,6 +300,7 @@ public override void VisitForEachLoop(IForEachLoopOperation operation)
visitArguments(info.GetEnumeratorArguments);
visitArguments(info.MoveNextArguments);
visitArguments(info.CurrentArguments);
visitArguments(info.DisposeArguments);
}
void visitArguments(ImmutableArray<IArgumentOperation> arguments)
......@@ -424,6 +425,16 @@ public override void VisitUsing(IUsingOperation operation)
AssertEx.Equal(new[] { operation.Resources, operation.Body }, operation.Children);
Assert.NotEqual(OperationKind.VariableDeclaration, operation.Resources.Kind);
Assert.NotEqual(OperationKind.VariableDeclarator, operation.Resources.Kind);
_ = ((UsingOperation)operation).DisposeInfo.DisposeMethod;
var disposeArgs = ((UsingOperation)operation).DisposeInfo.DisposeArguments;
if (!disposeArgs.IsDefaultOrEmpty)
{
foreach (var arg in disposeArgs)
{
VerifySubTree(arg);
}
}
}
// https://github.com/dotnet/roslyn/issues/21281
......@@ -1530,6 +1541,16 @@ public override void VisitUsingDeclaration(IUsingDeclarationOperation operation)
Assert.False(operation.ConstantValue.HasValue);
_ = operation.IsAsynchronous;
_ = operation.IsImplicit;
_ = ((UsingDeclarationOperation)operation).DisposeInfo.DisposeMethod;
var disposeArgs = ((UsingDeclarationOperation)operation).DisposeInfo.DisposeArguments;
if (!disposeArgs.IsDefaultOrEmpty)
{
foreach (var arg in disposeArgs)
{
VerifySubTree(arg);
}
}
}
public override void VisitWith(IWithOperation operation)
......
' Licensed to the .NET Foundation under one or more agreements.
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
......@@ -1421,7 +1421,7 @@ Namespace Microsoft.CodeAnalysis.Operations
Dim locals As ImmutableArray(Of ILocalSymbol) = ImmutableArray(Of ILocalSymbol).CastUp(boundUsingStatement.Locals)
Dim syntax As SyntaxNode = boundUsingStatement.Syntax
Dim isImplicit As Boolean = boundUsingStatement.WasCompilerGenerated
Return New UsingOperation(resources, body, locals, isAsynchronous:=False, disposeMethod:=Nothing, _semanticModel, syntax, isImplicit)
Return New UsingOperation(resources, body, locals, isAsynchronous:=False, disposeInfo:=Nothing, _semanticModel, syntax, isImplicit)
End Function
Private Function CreateBoundExpressionStatementOperation(boundExpressionStatement As BoundExpressionStatement) As IExpressionStatementOperation
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册