Fix #1603: recognize foreach over enumerators that cannot be disposable - #3916
Merged
Conversation
The foreach pattern was only matched against using instructions, but the compiler emits no using/try-finally at all when the enumerator's static type can never require disposal: a struct or a sealed class that does not implement IDisposable (SerializationInfoEnumerator in the issue's example). Such loops stayed while loops. Recognize the bare 'enumerator = x.GetEnumerator(); while (enumerator.MoveNext())' shape during statement building and reuse the existing foreach transformation core for it. The transformation is restricted to exactly the cases where recompilation would produce the same IL: the enumerator type rules above (ref structs are excluded because of pattern-based disposal), a single-store enumerator variable unused outside the loop, and synchronous enumeration only, since async enumerators are always IAsyncDisposable. Assisted-by: Claude:claude-fable-5:Claude Code
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 #1603.
The compiler emits no using/try-finally at all when the enumerator's static type can never require disposal: a struct or a sealed class that does not implement
IDisposable(SerializationInfoEnumeratorin the issue's example). Since the decompiler's foreach pattern was only matched against using instructions, such loops stayedwhile (enumerator.MoveNext())loops.This PR recognizes the bare
enumerator = x.GetEnumerator(); while (enumerator.MoveNext()) { ... }shape during statement building and reuses the existing foreach transformation core for it (TransformToForeachis split into theUsingInstructionwrapper and a core taking the containers/enumerator variable explicitly).The transformation is restricted to exactly the cases where recompilation produces the same IL:
IDisposable(a non-sealed class would be enumerated with afinally { (enumerator as IDisposable)?.Dispose(); }block; ref structs are excluded because of pattern-based disposal);IAsyncDisposable.Tests: the two commented-out TODO cases in
Pretty/Loops.cs(ForEachOnCustomStructEnumeratorand its generic variant) are revived, plus a new sealed-class enumerator case andIssue1603using the actualSerializationInfo/SerializationEntrytypes from the issue. All were verified to decompile to while loops before the fix. Full decompiler suite on Linux: 2544 total, 0 failed, 38 skipped. Note the legacy-csc and mcs configurations of the Loops test do not run on Linux, so the Windows CI matrix is the first place they get exercised.🤖 This PR was prepared by an AI agent (Claude Code) operated by @siegfriedpammer.