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 @@ -1912,6 +1912,22 @@ namespace ICSharpCode.Decompiler.CSharp
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)
{
if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType())

7
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4437,9 +4437,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4437,9 +4437,12 @@ namespace ICSharpCode.Decompiler.CSharp
// we can deference the managed reference by stripping away the 'ref'
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)
.WithILInstruction(inst)

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

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

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

@ -162,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -162,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
foreach (var inst in pauseBlock.Instructions)
awaitInst.AddILRange(inst);
awaitInst.GetAwaiterMethod = getAwaiterCall.Method;
awaitInst.GetAwaiterCallOpCode = getAwaiterCall.OpCode;
awaitInst.GetResultMethod = getResultCall.Method;
// 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 @@ -25,5 +25,8 @@ namespace ICSharpCode.Decompiler.IL
{
public IMethod? GetAwaiterMethod;
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