From 3f1ef717b67fe59321941c2ff0e0ff94d81bf96a Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 10 Oct 2026 13:01:51 +0200 Subject: [PATCH] Fix #4017: Look through LdObjIfRef on the operand of await A constrained GetAwaiter call on a type parameter that is not known to be a value type wraps its receiver in LdObjIfRef so the reference is read before any further arguments. ExpressionBuilder has no translation for LdObjIfRef, so the operand was dropped. GetAwaiter takes no further arguments, so the operand can be translated from the address, as CallBuilder already does for call receivers. --- .../TestCases/Pretty/AsyncAwaitPatterns.cs | 15 ++++++++++++--- .../CSharp/ExpressionBuilder.cs | 7 ++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs index 858f8e6..d547959 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatterns.cs @@ -16,9 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -// Await shapes that still decompile to code that does not compile are tracked as #4017 (type -// parameter with an interface constraint), #4018 (static dynamic call) and #4019 (with -// expression); they are absent here because they have no correct output to pin yet. +// An await shape that still decompiles to code that does not compile is tracked as #4019 (with +// expression); it is absent here because it has no correct output to pin yet. #pragma warning disable 1998 using System; @@ -539,6 +538,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwait await awaitable; } + public async Task TypeParameterWithInterfaceConstraint(T awaitable) where T : IAwaitable + { + await awaitable; + } + + public async Task TypeParameterArrayElementWithInterfaceConstraint(T[] awaitables) where T : IAwaitable + { + await awaitables[0]; + } + public async Task ConfiguredTaskAwaitable(Task task) { #if ROSLYN2 diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 5655ce3..10cd9e4 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4557,7 +4557,12 @@ namespace ICSharpCode.Decompiler.CSharp } } - var value = Translate(inst.Value, typeHint: expectedType); + var operand = inst.Value; + if (operand is LdObjIfRef ldObjIfRef) + { + operand = ldObjIfRef.Target; + } + var value = Translate(operand, typeHint: expectedType); if (value.Expression is DirectionExpression) { // we can deference the managed reference by stripping away the 'ref'