Decompile await on dynamic expressions#3872
Open
siegfriedpammer wants to merge 1 commit into
Open
Conversation
2b1e677 to
041bed3
Compare
Awaiting a dynamic value lowers GetAwaiter/IsCompleted/GetResult to dynamic callsites, which async decompilation could not recognize: await detection ran before DynamicCallSiteTransform, so the await was emitted as a raw state machine (or crashed in AnalyzeAwaitBlock, #1388). AnalyzeStateMachine now collapses the awaiter callsites per block, folds the runtime ICriticalNotifyCompletion branch the compiler emits for an awaiter not statically known to implement it into the canonical single call, and re-joins the branch chains the collapse leaves so each dynamic await sits in one block. DetectAwaitPattern matches the dynamic GetAwaiter/IsCompleted/GetResult shape and emits `await expr`; a synthesized dynamic GetResult method gives the await and its local the dynamic type. DynamicCallSiteTransform also follows callsite targets spilled into state-machine locals, so an awaited value flowing into a dynamic callsite (e.g. d.Result = await ..., #1928) decompiles too. Assisted-by: Claude:claude-fable-5:Claude Code
041bed3 to
781d378
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enhances ILSpy’s async/await decompiler so it can recognize and emit await when the awaited value/awaiter is dynamic, avoiding raw state-machine output and fixing crashes in await analysis for dynamic callsite-based awaiters.
Changes:
- Normalizes compiler-emitted dual-branch
AwaitOnCompleted/AwaitUnsafeOnCompletedpatterns so await analysis can proceed consistently even when the awaiter type isn’t statically known. - Extends await-pattern detection to match dynamic
GetAwaiter/IsCompleted/GetResultcallsite shapes and propagatedynamictyping through the await. - Enhances dynamic callsite transformation to follow callsite targets spilled across awaits and adds a new
DynamicAwaitpretty-test fixture + runner entry.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ICSharpCode.Decompiler/IL/Transforms/IntroduceDynamicTypeOnLocals.cs | Marks locals as dynamic when they participate in a dynamic await, improving type reconstruction. |
| ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs | Refactors callsite detection/transform; adds per-basic-block entrypoint used during async analysis. |
| ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs | Exposes block-combine helper for reuse by async decompiler post-processing. |
| ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs | Normalizes dynamic awaiter runtime branches, detects dynamic await patterns, synthesizes dynamic awaiter methods, and coalesces blocks post-transform. |
| ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs | Adds new pretty-test cases covering dynamic awaiters and dynamic operations around/through awaits. |
| ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs | Wires up the new DynamicAwait pretty-test suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+65
to
+70
| Dictionary<IField, CallSiteInfo> callsites = new Dictionary<IField, CallSiteInfo>(); | ||
| FindDynamicCallSitesInBlock(block, callsites, context); | ||
| // Deleting the now-unreachable callsite-init blocks is deferred to the SortBlocks call | ||
| // at the end of AsyncAwaitDecompiler.AnalyzeStateMachine: this runs while that method is | ||
| // iterating over the container's blocks, so removing blocks here would corrupt the loop. | ||
| TransformCallSites((BlockContainer)block.Parent, callsites, context); |
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.
Awaiting a dynamic value was not decompiled. Because the async decompiler runs before
DynamicCallSiteTransform,GetAwaiter/IsCompleted/GetResultwere still dynamic callsites that await detection could not recognize, so the method was emitted as a raw state machine (or crashed inAnalyzeAwaitBlock, #1388).What changed
AnalyzeStateMachinecollapses the awaiter callsites per block, folds the runtimeICriticalNotifyCompletionbranch the compiler emits for an awaiter not statically known to implement it into the canonical single call, and re-joins the branch chains the collapse leaves so each dynamic await sits in a single block.DetectAwaitPatternmatches the dynamicGetAwaiter/IsCompleted/GetResultshape and emitsawait expr. A synthesized dynamicGetResultmethod gives the await (and the awaited local) thedynamictype.DynamicCallSiteTransformfollows callsite targets spilled into state-machine locals across an await, so an awaited value flowing into a dynamic callsite (e.g.d.Result = await ..., dynamic JObject() not decompile correctly #1928) decompiles too.Tests
New
DynamicAwaitpretty-test fixture covering dynamic awaiters (await d,await d.RunAsync()), dynamic operations around an await, and awaited values feeding a dynamic set-member/invoke/index/binary-op/convert. FullAsync*+DynamicTests+DynamicAwaitpretty-test suites pass with no regression.Fixes #1388
Fixes #1928
🤖 Generated with Claude Code