Browse Source

Look through the box when checking an await's GetAwaiter

An operand boxed for the GetAwaiter call is typed 'object', so the member
lookup that decides whether the await needs a cast finds nothing and a
redundant cast to the receiver type reaches the output. C# inserts that boxing
conversion implicitly, so the box may be dropped -- but only after the lookup
confirms the unboxed operand still binds the same GetAwaiter, and only via the
resolve result: UnwrapChild detaches the operand from the AST, so running it
speculatively leaves a cast with no child behind and decompilation of the whole
method falls back to the raw state machine.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/4021/head
Christoph Wille 1 month ago committed by Daniel Grunwald
parent
commit
f2b80df148
  1. 26
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

26
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4438,11 +4438,29 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4438,11 +4438,29 @@ namespace ICSharpCode.Decompiler.CSharp
value = value.UnwrapChild(((DirectionExpression)value.Expression).Expression);
}
var callBuilder = new CallBuilder(this, typeSystem, settings);
if (expectedType != null
&& inst.GetAwaiterMethod != null
&& !callBuilder.CheckSimpleCall(value.ResolveResult, inst.GetAwaiterMethod, inst.GetAwaiterCallOpCode))
if (expectedType != null && inst.GetAwaiterMethod != null)
{
value = value.ConvertTo(expectedType, this);
// An operand boxed for the GetAwaiter call is typed 'object', which hides the receiver
// from member lookup. C# boxes the operand of an `await` implicitly, so the box need
// not appear in the output as long as the unboxed operand still binds the same
// GetAwaiter. Look through the box for that question only; UnwrapChild detaches the
// operand from the AST, so it must not run before the answer is known.
Expression? boxedOperand = null;
var lookupTarget = value.ResolveResult;
if (value.ResolveResult is ConversionResolveResult { Conversion.IsBoxingConversion: true } boxing
&& value.Expression is CastExpression boxCast)
{
boxedOperand = boxCast.Expression;
lookupTarget = boxing.Input;
}
if (!callBuilder.CheckSimpleCall(lookupTarget, inst.GetAwaiterMethod, inst.GetAwaiterCallOpCode))
{
value = value.ConvertTo(expectedType, this);
}
else if (boxedOperand != null)
{
value = value.UnwrapChild(boxedOperand);
}
}
return new UnaryOperatorExpression(UnaryOperatorType.Await, value.Expression)
.WithILInstruction(inst)

Loading…
Cancel
Save