diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index e68b10f72..e3fd7f88e 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -196,6 +196,9 @@ + + + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 281b12ee4..c1c6070b8 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -333,6 +333,12 @@ namespace ICSharpCode.Decompiler.Tests await Run(); } + [Test] + public async Task InstanceOperatorCall() + { + await Run(); + } + [Test] public async Task FSharpLoops_Debug() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.cs new file mode 100644 index 000000000..96b356467 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.cs @@ -0,0 +1,24 @@ +public class CallSite +{ + public static bool CompareVirtual(Source s, T a, T b) + { + return a < b; + } + + public static bool CompareDirect(Source s, int a, int b) + { + return a > b; + } +} +public class Source +{ + public virtual bool operator <(T a, T b) + { + return false; + } + + public bool operator >(T a, T b) + { + return false; + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.il new file mode 100644 index 000000000..edc89891e --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/InstanceOperatorCall.il @@ -0,0 +1,89 @@ +// Metadata version: v4.0.30319 +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 11:0:0:0 +} +.assembly InstanceOperatorCall +{ + .ver 1:0:0:0 +} +.module InstanceOperatorCall.dll +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + +// F# emits comparison operators as instance members, so operator metadata names appear on +// methods C# would never bind as operators: the operands are the arguments, and the operator +// lives on the receiver type rather than on either operand type. +.class public auto ansi beforefieldinit Source`1 + extends [System.Runtime]System.Object +{ + .method public hidebysig specialname newslot virtual + instance bool op_LessThan(!T a, + !T b) cil managed + { + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: ret + } + + .method public hidebysig specialname + instance bool op_GreaterThan(!T a, + !T b) cil managed + { + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ret + } +} + +.class public auto ansi beforefieldinit CallSite + extends [System.Runtime]System.Object +{ + .method public hidebysig static bool CompareVirtual(class Source`1 s, + !!T a, + !!T b) cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.2 + IL_0003: callvirt instance bool class Source`1::op_LessThan(!0, + !0) + IL_0008: ret + } + + .method public hidebysig static bool CompareDirect(class Source`1 s, + int32 a, + int32 b) cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.2 + IL_0003: call instance bool class Source`1::op_GreaterThan(!0, + !0) + IL_0008: ret + } + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ret + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 1efe31af6..62e7d9f66 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1643,6 +1643,13 @@ namespace ICSharpCode.Decompiler.CSharp if (or.IsAmbiguous) return OverloadResolutionErrors.AmbiguousMatch; foundMember = or.GetBestCandidateWithSubstitutedTypeArguments(); + if (foundMember == null) + { + // Overload resolution reports no error for an empty candidate set - there is no + // best candidate to carry one - so a call that matched nothing has to be reported + // as unresolvable here. + return OverloadResolutionErrors.AmbiguousMatch; + } if (!IsAppropriateCallTarget(expectedTargetDetails, method, foundMember)) return OverloadResolutionErrors.AmbiguousMatch; var map = or.GetArgumentToParameterMap();