Browse Source

Avoid `allowImplicitConversion: true` for `await` expressions -- there's no target type that the C# compiler could convert to.

Instead, use `IsAppropriateCallTarget` to detect whether an explicit cast is necessary for calling the correct `GetAwaiter` method.
pull/4021/head
Daniel Grunwald 1 month ago
parent
commit
e530b1c1b6
  1. 16
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  2. 7
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 4
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  4. 1
      ICSharpCode.Decompiler/IL/ControlFlow/RuntimeAsyncManualAwaitTransform.cs
  5. 3
      ICSharpCode.Decompiler/IL/Instructions/Await.cs

16
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -1912,6 +1912,22 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
} }
/// <summary>
/// Checks whether calling `target.methodName()` will use `expected` as the method to invoke.
/// </summary>
public bool CheckSimpleCall(ResolveResult target, IMethod expected, OpCode expectedCallOpCode = OpCode.Call)
{
var details = new ExpectedTargetDetails { CallOpCode = expectedCallOpCode, NeedsBoxingConversion = false };
if (resolver.ResolveMemberAccess(target, expected.Name, [], NameLookupMode.InvocationTarget)
is not MethodGroupResolveResult mgrr)
return false;
var or = mgrr.PerformOverloadResolution(typeSystem, []);
if (or.BestCandidateErrors != OverloadResolutionErrors.None || or.IsAmbiguous)
return false;
return IsAppropriateCallTarget(details, expected, or.GetBestCandidateWithSubstitutedTypeArguments()!);
}
ExpressionWithResolveResult HandleConstructorCall(ExpectedTargetDetails expectedTargetDetails, ResolveResult? target, IMethod method, ArgumentList argumentList) ExpressionWithResolveResult HandleConstructorCall(ExpectedTargetDetails expectedTargetDetails, ResolveResult? target, IMethod method, ArgumentList argumentList)
{ {
if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType())

7
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4437,9 +4437,12 @@ namespace ICSharpCode.Decompiler.CSharp
// we can deference the managed reference by stripping away the 'ref' // we can deference the managed reference by stripping away the 'ref'
value = value.UnwrapChild(((DirectionExpression)value.Expression).Expression); value = value.UnwrapChild(((DirectionExpression)value.Expression).Expression);
} }
if (expectedType != null) var callBuilder = new CallBuilder(this, typeSystem, settings);
if (expectedType != null
&& inst.GetAwaiterMethod != null
&& !callBuilder.CheckSimpleCall(value.ResolveResult, inst.GetAwaiterMethod, inst.GetAwaiterCallOpCode))
{ {
value = value.ConvertTo(expectedType, this, allowImplicitConversion: true); value = value.ConvertTo(expectedType, this);
} }
return new UnaryOperatorExpression(UnaryOperatorType.Await, value.Expression) return new UnaryOperatorExpression(UnaryOperatorType.Await, value.Expression)
.WithILInstruction(inst) .WithILInstruction(inst)

4
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -1820,6 +1820,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ILVariable awaiterVar = stLocAwaiter.Variable; ILVariable awaiterVar = stLocAwaiter.Variable;
ILInstruction awaitedValue; ILInstruction awaitedValue;
IMethod getAwaiterMethod; IMethod getAwaiterMethod;
OpCode getAwaiterCallOpCode;
bool isDynamicAwait = false; bool isDynamicAwait = false;
if (stLocAwaiter.Value is CallInstruction getAwaiterCall if (stLocAwaiter.Value is CallInstruction getAwaiterCall
&& getAwaiterCall.Method.Name == "GetAwaiter" && getAwaiterCall.Method.Name == "GetAwaiter"
@ -1828,6 +1829,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
awaitedValue = getAwaiterCall.Arguments[0]; awaitedValue = getAwaiterCall.Arguments[0];
getAwaiterMethod = getAwaiterCall.Method; getAwaiterMethod = getAwaiterCall.Method;
getAwaiterCallOpCode = getAwaiterCall.OpCode;
} }
else if (stLocAwaiter.Value is DynamicInvokeMemberInstruction dynGetAwaiter else if (stLocAwaiter.Value is DynamicInvokeMemberInstruction dynGetAwaiter
&& dynGetAwaiter.Name == "GetAwaiter" && dynGetAwaiter.Arguments.Count == 1) && dynGetAwaiter.Name == "GetAwaiter" && dynGetAwaiter.Arguments.Count == 1)
@ -1836,6 +1838,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
awaitedValue = dynGetAwaiter.Arguments[0]; awaitedValue = dynGetAwaiter.Arguments[0];
getAwaiterMethod = CreateDynamicAwaiterMethod(context, "GetAwaiter"); getAwaiterMethod = CreateDynamicAwaiterMethod(context, "GetAwaiter");
isDynamicAwait = true; isDynamicAwait = true;
getAwaiterCallOpCode = OpCode.CallVirt;
} }
else else
{ {
@ -1917,6 +1920,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Await awaitInst = new Await(UnwrapConvUnknown(awaitedValue)); Await awaitInst = new Await(UnwrapConvUnknown(awaitedValue));
awaitInst.GetResultMethod = getResultMethod; awaitInst.GetResultMethod = getResultMethod;
awaitInst.GetAwaiterMethod = getAwaiterMethod; awaitInst.GetAwaiterMethod = getAwaiterMethod;
awaitInst.GetAwaiterCallOpCode = getAwaiterCallOpCode;
getResultInst.ReplaceWith(awaitInst); getResultInst.ReplaceWith(awaitInst);
// Remove useless reset of awaiterVar. // Remove useless reset of awaiterVar.

1
ICSharpCode.Decompiler/IL/ControlFlow/RuntimeAsyncManualAwaitTransform.cs

@ -162,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
foreach (var inst in pauseBlock.Instructions) foreach (var inst in pauseBlock.Instructions)
awaitInst.AddILRange(inst); awaitInst.AddILRange(inst);
awaitInst.GetAwaiterMethod = getAwaiterCall.Method; awaitInst.GetAwaiterMethod = getAwaiterCall.Method;
awaitInst.GetAwaiterCallOpCode = getAwaiterCall.OpCode;
awaitInst.GetResultMethod = getResultCall.Method; awaitInst.GetResultMethod = getResultCall.Method;
// Remove the trailing 3 (or 4) instructions of the head block; replace with `br completedBlock`. // Remove the trailing 3 (or 4) instructions of the head block; replace with `br completedBlock`.

3
ICSharpCode.Decompiler/IL/Instructions/Await.cs

@ -25,5 +25,8 @@ namespace ICSharpCode.Decompiler.IL
{ {
public IMethod? GetAwaiterMethod; public IMethod? GetAwaiterMethod;
public IMethod? GetResultMethod; public IMethod? GetResultMethod;
// Whether the original GetAwaiter call was `call` or `callvirt`, so ExpressionBuilder can tell
// whether re-emitting it as a plain `await` expression would change which method gets invoked.
public OpCode GetAwaiterCallOpCode = OpCode.Call;
} }
} }

Loading…
Cancel
Save