Fix IDE0059 false positive for out arguments read in sibling arguments (#58564)#84309
Draft
jcouv wants to merge 2 commits into
Draft
Fix IDE0059 false positive for out arguments read in sibling arguments (#58564)#84309jcouv wants to merge 2 commits into
jcouv wants to merge 2 commits into
Conversation
added 2 commits
June 25, 2026 15:49
… sibling reads see the prior value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #58564 (also resolves the closed duplicate #61347).
Problem
IDE0059 ("Unnecessary assignment of a value") was a false positive when a local is initialized and then passed both as an
outargument and read in another argument of the same call:The initial assignment is genuinely live — its value flows into the second argument before the
outwrite takes effect — so removing it does not compile.Root cause
IDE0059 relies on the shared liveness pass in
SymbolUsageAnalysis.Walker, used by both the operation-tree and control-flow-graph paths. The walker visits invocation arguments in evaluation order, soout a(arg 1) is visited before the sibling reada(arg 2). Anoutargument was recorded as a definite write immediately, which cleared the prior reaching writea = 2. The sibling read then "read" the just-recorded out-write instead ofa = 2, leaving the real assignment unread → false IDE0059. (refargs were unaffected because they record a read first and use a maybe-written write that doesn't clear the prior write;inargs are reads only.)Fix
Extend the walker's existing pending-write deferral mechanism — previously used only for assignment / deconstruction targets — to also cover
outarguments. Theoutwrite is now recorded only after all sibling arguments of the same call have been visited, so sibling reads correctly see the prior value. The write keeps its original definite-write semantics, so a genuinely dead out-assignment (x = 1; M2(out x);with no sibling read) is still reported.MakePendingWritedefers writes whose parent isIArgumentOperation { Parameter.RefKind: RefKind.Out }, keyed on the containing call (map key widened fromIAssignmentOperationtoIOperation).ProcessPendingWritesForArgumentContainerflushes those deferred writes and returns the pooled set to the pool.base.Visit…inVisitInvocation,VisitObjectCreation(constructors:new C(out x, x)),VisitRaiseEvent(defensive for VB), andVisitFunctionPointerInvocation— the complete set of operations that ownIArgumentOperations, so no deferred write is ever lost. One change fixes both analysis paths.Tests
Added to
RemoveUnusedValueAssignmentTests:NonRedundantAssignment_BeforeUseAsOutAndReadArgument— the repro,M(out x, x)reports nothing.NonRedundantAssignment_BeforeUseAsConstructorOutAndReadArgument—new C(out x, x)(object-creation path).NonRedundantAssignments_BeforeUseAsOutAndReadArguments—M(out x, x, out y, y).NonRedundantAssignment_BeforeReadAndUseAsOutArgument—M(x, out x).NonRedundantAssignment_BeforeUseAsFunctionPointerOutAndReadArgument—delegate*<out int, int, void>path.Assignment_BeforeUseAsOutArgumentWithoutSiblingRead— control: a dead out-assignment is still flagged.Validation:
RemoveUnusedValueAssignmentTests+RemoveUnusedValueExpressionStatementTestspass 617/617; broader remove-unused filter 749/749. No regressions.Microsoft Reviewers: Open in CodeFlow