Correctly record the dispose method to be invoked in operations#49481
Correctly record the dispose method to be invoked in operations#49481chsienki merged 26 commits intodotnet:features/ioperationfrom
Conversation
|
@dotnet/roslyn-compiler for review Note: this is targeting @333fred's IOperation refactoring branch rather than master to reduce merge issues |
| </summary> | ||
| </Comments> | ||
| </Property> | ||
| <Property Name="DisposeMethod" Type="IMethodSymbol?"> |
There was a problem hiding this comment.
It doesn't feel like making this only be populated if it's not IDisposable.Dispose is particularly useful, it feels more like an implementation detail bleeding through. I think we should just always populate this, and if it's IDiposable then we populate it with IDisposable.Dispose. #Closed
| LeaveRegion(); | ||
|
|
||
| AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable, isAsynchronous); | ||
| AddDisposingFinally(resource, knownToImplementIDisposable: true, iDisposable, disposeMethod, isAsynchronous); |
There was a problem hiding this comment.
Well, it's certainly not know to implement IDisposable if it's a ref struct. Please update the comments above and the parameter name. #Closed
- Always populate the dispose method in binding, even if its for I{Async}Disposable
- Remove knownToImplementIDisposable
- Just use the precalcualted dispose method in IOperation rather than calculating it
- Use the presence of the DisposeMethod to calculate the expected output in CFG
- Correctly report invalid operations in CFG
- Update tests
|
@333fred / @AlekseyTs assuming tests are good, this should be good to review now. By always populating the dispose method in binding I was able to remove a bunch of logic in the operation and lowering to just use what we found, instead of calculating it again. |
|
Note to self: run VB tests locally too. 🤦♂️ |
|
Note on the determinism test: I haven't merged Aleksey's fix from master into this branch yet, so it's flaky. |
| AppendNewBlock(endOfFinally); | ||
| var isPatternDispose = !disposeMethod.ContainingType.Equals(iDisposable, SymbolEqualityComparer.Default); | ||
| var isNull = resource.GetConstantValue() == ConstantValue.Null; | ||
| var requiresConversion = !isPatternDispose && !isAsynchronous && !isNull && !_compilation.HasImplicitConversion(resource.Type, iDisposable) && !isNonNullableValueType(resource.Type); |
There was a problem hiding this comment.
!_compilation.HasImplicitConversion(resource.Type, iDisposable) [](start = 92, length = 63)
If it has an implicit conversion, we probably still need to represent that implicit conversion, right? Or am I misunderstanding the expansion?
There was a problem hiding this comment.
If it has an implicit conversion we'll represent that later on. This checks if we're doing a runtime conversion check. That only happens in foreach on enumerators. The simplest example is with a dynamic enumerator: we try and convert it to IDisposable first then check it for null. I'll give the var a more descriptive name.
|
Done review pass (commit 9) |
…cfg_pattern_dispose
| { | ||
| iDisposableConversion = Conversion.ImplicitDynamic; | ||
| disposeMethodOpt = null; | ||
| disposeMethodOpt = originalBinder.Compilation.GetWellKnownDisposeMethod(isAsync: hasAwait); |
There was a problem hiding this comment.
originalBinder.Compilation.GetWellKnownDisposeMethod(isAsync: hasAwait); [](start = 39, length = 72)
Same comment as for other call-sites #Closed
|
|
||
| if (iDisposableConversion.IsImplicit) | ||
| { | ||
| disposeMethodOpt = originalBinder.Compilation.GetWellKnownDisposeMethod(hasAwait); |
There was a problem hiding this comment.
originalBinder.Compilation.GetWellKnownDisposeMethod(hasAwait); [](start = 39, length = 63)
Same comment #Closed
| : compilation.GetSpecialType(SpecialType.System_IDisposable); | ||
|
|
||
| var enumeratorImplementsDisposable = enumeratorInfoOpt.NeedsDisposal | ||
| && compilation.Conversions.ClassifyImplicitConversionFromType(enumeratorInfoOpt.GetEnumeratorMethod.ReturnType, |
There was a problem hiding this comment.
enumeratorInfoOpt.GetEnumeratorMethod [](start = 115, length = 37)
It looks like we dropped a null check for this value. #Closed
There was a problem hiding this comment.
It was never supposed to be able to contain a null value. The enumeratorInfo has nullable enabled.
There was a problem hiding this comment.
It was never supposed to be able to contain a null value. The enumeratorInfo has nullable enabled.
It looks to me that definition of ForEachEnumeratorInfo is in nullable disabled region.
In reply to: 533648164 [](ancestors = 533648164)
| <CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | ||
| <Fact()> | ||
| Public Sub UsingFlow_14() | ||
| Public Sub UsingFlow_01() |
There was a problem hiding this comment.
UsingFlow_01 [](start = 19, length = 12)
Why was that renamed? Is it possible that it corresponded to C# test with the same name? #Closed
There was a problem hiding this comment.
Long ago there were UsingFlow_{1-13} in C# and then {14,15} in VB, but C# has since added significantly more tests with {14...} so it makes no sense to have a random set of numbered VB tests. We can move these to the end of the C# tests, but the likelihood is just that they'll clash again.
There was a problem hiding this comment.
Ideally we'd give them descriptive names tbh
There was a problem hiding this comment.
We can move these to the end of the C# tests, but the likelihood is just that they'll clash again.
There is no requirement for test methods to be sorted in any way. The test was not affected by the change as well. I find the rename unnecessary.
In reply to: 533652564 [](ancestors = 533652564)
| <CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | ||
| <Fact()> | ||
| Public Sub UsingFlow_15() | ||
| Public Sub UsingFlow_02() |
There was a problem hiding this comment.
UsingFlow_02 [](start = 19, length = 12)
Why was this renamed? #Closed
|
|
||
| <CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | ||
| <Fact()> | ||
| Public Sub UsingFlow_04() |
There was a problem hiding this comment.
UsingFlow_04 [](start = 19, length = 12)
Is there a similar C# test? Consider using the same name. #Closed
|
|
||
| <CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)> | ||
| <Fact()> | ||
| Public Sub UsingFlow_03() |
There was a problem hiding this comment.
UsingFlow_03 [](start = 19, length = 12)
Is there a similar C# test? Consider using the same name. #Closed
|
Done with review pass (commit 20). Overall, it feels to me that a lot of changes could be avoided. Let's discuss offline in more details. #Closed |
| return new AwaitOperation(invocation, semanticModel: null, value.Syntax, _compilation.GetSpecialType(SpecialType.System_Void), isImplicit: true); | ||
| } | ||
| // resource.Dispose(); | ||
| resource = new InvocationOperation(disposeMethod, resource, isVirtual: true, |
There was a problem hiding this comment.
new InvocationOperation(disposeMethod, resource, isVirtual: true, [](start = 27, length = 65)
Can pattern-based expose use an extension method? In that scenario, the resource should be the first parameter instead. Do we need to handle omitted optional and param array parameters? #Closed
There was a problem hiding this comment.
No, pattern disposal does not support extensions methods. We should report the params and optional arguments, however
There was a problem hiding this comment.
We should report the params and optional arguments, however
See how we deal with arguments for GetEnumerator and MoveNext in VisitForEachLoop
In reply to: 533774968 [](ancestors = 533774968)
There was a problem hiding this comment.
@AlekseyTs I have a fix for this, but its a little involved, as we now need to pass the binder down through the BoundUsingStatement like we do for Foreach. Given the mild complexity, do you want me to add it to this PR, or wait and open a separate one?
There was a problem hiding this comment.
Given the mild complexity, do you want me to add it to this PR, or wait and open a separate one?
I think a separate PR is fine.
In reply to: 534633638 [](ancestors = 534633638)
| // { | ||
| // ResourceType resource = expr; | ||
| // try { statement; } | ||
| // finally { if (resource != null) resource.Dispose(); } |
There was a problem hiding this comment.
resource [](start = 53, length = 8)
Is this accurate for a nullable value type? We are not unwrapping the Nullable<T>? #Closed
There was a problem hiding this comment.
Updated the comment to show what we do in lowering.
There was a problem hiding this comment.
Updated the comment to show what we do in lowering.
Why would you do this? Is this comment for lowering? Is this comment for lowering of a specific compiler?
In reply to: 534592569 [](ancestors = 534592569)
There was a problem hiding this comment.
I understood your comment to mean that we should be showing the unwrapping of the nullable in CFG, but clearly I misunderstood you.
There was a problem hiding this comment.
I understood your comment to mean that we should be showing the unwrapping of the nullable in CFG, but clearly I misunderstood you.
I asked you about behavior that you are describing in a new comment specifically for a pattern-based case. I couldn't find a test for it, hence the question. Clearly the comment looked strange to me, but you started looking at completely different scenario.
In reply to: 534620372 [](ancestors = 534620372)
There was a problem hiding this comment.
Apologies, as I said, I misunderstood what you were asking.
| } | ||
|
|
||
| private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics, TypeSymbol collectionExprType) | ||
| private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics, TypeSymbol collectionExprType, bool isAsync) |
There was a problem hiding this comment.
, bool isAsync [](start = 166, length = 14)
It looks like this parameter is not used. #Closed
|
Done with review pass (commit 24) #Closed |
| // } | ||
| // | ||
| // Otherwise, when ResourceType is a non-nullable value type that implements | ||
| // disposal via pattern, the expansion is: |
There was a problem hiding this comment.
Are we testing this scenario? #Closed
There was a problem hiding this comment.
Yes in UsingDeclaration_Flow_20 #Closed
| // finally { if (resource != null) ((IDisposable)resource).Dispose(); } | ||
| // } | ||
| // | ||
| // Otherwise, when Resource type is a nullable value type or |
There was a problem hiding this comment.
when Resource type is a nullable value type [](start = 30, length = 43)
Are we testing this scenario? #Closed
There was a problem hiding this comment.
Yes, in UsingFlow_05 but actually, it doesn't appear to be correctly reporting what we do. It matches the comments in the Builder, but that isnt how we lower the code, as you pointed out we unwrap the nullable, which we're not representing here. https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQBMQGoA+ABATAAgAUBYAKAG8z9r9sAWfAWQApGBPAEQEsoAHAeygRgAGzgB+fFwB2vAK4wk+YP34jlASjIB6AFQAeYDJQA+Xdsqka12gAZ8zAOJwY3PoOFjmG/OMkz5GC0rG2pLUNDgfABefBgEOTgAbioIgF9U6jS9fW0jaVNzMkymTh4BIVEJfGdXco8q7xiTOIALBH4Ad3xpORERFNIM0jJYBIBjGFK3Cs84fBBaAEYAOgBJGYaxMnCabABmWgZNqDgm6JaYdq6evoGyNKA=
There was a problem hiding this comment.
Oh, interesting. Actually we're representing in ControlFlowGraph what the spec says we should be doing:
http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs,277
Except in lowering we purposefully deviate from the spec to improve perf.
@AlekseyTs what do you think? Should the CFG represent the spirit of the code in the spec, or what we actually lower to? I guess I lean towards representing the spec? because like the async stuff we give a general representation of what it's lowered to, not the implementation specific details.
There was a problem hiding this comment.
It looks to me that UsingFlow_05 and the sharplab link is about non-pattern disposal and this change is not supposed to make any changes for these scenarios. I am fine with the current behavior of the builder for them. The question I have is about pattern-based disposal for nullable value types. This is what this comment is about. Are we testing the scenario?
In reply to: 534599322 [](ancestors = 534599322)
There was a problem hiding this comment.
It is not currently possible to perform pattern disposal on a nullable value types: see #34701
I will add a test to show the error recovery scenario, and update the comment in CFG to correctly reflect that.
| // { | ||
| // ResourceType resource = expr; | ||
| // try { statement; } | ||
| // finally { if (resource.HasValue()) ((IDisposable)resource.GetValueOrDefault()).Dispose(); } |
There was a problem hiding this comment.
finally { if (resource.HasValue()) ((IDisposable)resource.GetValueOrDefault()).Dispose(); } [](start = 21, length = 91)
Where do you see this in flow graph tests? #Closed
There was a problem hiding this comment.
Mentioned above, we don't. The test shows something different: do we want to make the CFG represent the actual lowered value or what the spec says?
There was a problem hiding this comment.
Mentioned above, we don't. The test shows something different: do we want to make the CFG represent the actual lowered value or what the spec says?
Does the comment accurately reflect CFG builder's behavior? If so, we don't want to make any changes for the comment. We also do not want to make any changes for scenarios that are not about pattern-based disposal, which is supposed to be the focus of this PR.
In reply to: 534616619 [](ancestors = 534616619)
There was a problem hiding this comment.
Yes apologies. I was confused by your comment, I thought you were saying that the comment, and thus the tests are incorrect for those scenarios.
AlekseyTs
left a comment
There was a problem hiding this comment.
LGTM (commit 26), assuming that we will deal with the arguments in a separate PR.
|
@dotnet/roslyn-compiler / @333fred for a second review please :) |
…c-foreach-binding * upstream/features/ioperation: Correctly record the dispose method to be invoked in operations (dotnet#49481)
Fixes #49267