Browse Source

Merge 28faea9099 into c1abc29765

pull/4043/merge
Siegfried Pammer 3 days ago committed by GitHub
parent
commit
37ad6efeb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 24
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  3. 99
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs
  4. 20
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/IndexerAccessorParameterNames.cs
  5. 70
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/IndexerAccessorParameterNames.il
  6. 21
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertyInitializer.cs
  7. 60
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertyInitializer.il
  8. 19
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertySetterCall.cs
  9. 55
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertySetterCall.il
  10. 18
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParamsPropertySetter.cs
  11. 68
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParamsPropertySetter.il
  12. 91
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs
  13. 166
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs
  14. 16
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArgumentsDisabled.cs
  15. 355
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  16. 52
      ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs

8
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -224,6 +224,14 @@
<Compile Remove="TestCases\ILPretty\Issue3729.cs" /> <Compile Remove="TestCases\ILPretty\Issue3729.cs" />
<None Include="TestCases\ILPretty\Issue3729.cs" /> <None Include="TestCases\ILPretty\Issue3729.cs" />
<None Include="TestCases\ILPretty\Issue3729.il" /> <None Include="TestCases\ILPretty\Issue3729.il" />
<Compile Remove="TestCases\ILPretty\ParameterizedPropertyInitializer.cs" />
<None Include="TestCases\ILPretty\ParameterizedPropertyInitializer.cs" />
<None Include="TestCases\ILPretty\ParameterizedPropertyInitializer.il" />
<None Include="TestCases\ILPretty\ParamsPropertySetter.il" />
<None Include="TestCases\ILPretty\IndexerAccessorParameterNames.il" />
<Compile Remove="TestCases\ILPretty\ParameterizedPropertySetterCall.cs" />
<None Include="TestCases\ILPretty\ParameterizedPropertySetterCall.cs" />
<None Include="TestCases\ILPretty\ParameterizedPropertySetterCall.il" />
<Compile Remove="TestCases\ILPretty\SpanConversionOperatorMismatch.cs" /> <Compile Remove="TestCases\ILPretty\SpanConversionOperatorMismatch.cs" />
<None Include="TestCases\ILPretty\SpanConversionOperatorMismatch.cs" /> <None Include="TestCases\ILPretty\SpanConversionOperatorMismatch.cs" />
<Compile Remove="TestCases\ILPretty\FSharpLoops_Debug.cs" /> <Compile Remove="TestCases\ILPretty\FSharpLoops_Debug.cs" />

24
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -435,6 +435,30 @@ namespace ICSharpCode.Decompiler.Tests
await Run(); await Run();
} }
[Test]
public async Task ParamsPropertySetter()
{
await Run();
}
[Test]
public async Task ParameterizedPropertyInitializer()
{
await Run();
}
[Test]
public async Task IndexerAccessorParameterNames()
{
await Run();
}
[Test]
public async Task ParameterizedPropertySetterCall()
{
await Run();
}
async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null, async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null,
AssemblerOptions assemblerOptions = AssemblerOptions.Library) AssemblerOptions assemblerOptions = AssemblerOptions.Library)
{ {

99
ICSharpCode.Decompiler.Tests/TestCases/Correctness/OverloadResolution.cs

@ -32,6 +32,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Generics(); Generics();
ConstructorTest(); ConstructorTest();
TestIndexer(); TestIndexer();
TestIndexerWithNamedArguments();
TestRedeclaredDefaultValues();
#if !MCS2
TestNamedWithOmittedOptional();
#endif
Issue1281(); Issue1281();
Issue1747(); Issue1747();
CallAmbiguousOutParam(); CallAmbiguousOutParam();
@ -330,6 +335,87 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
} }
#endregion #endregion
#region Named arguments with omitted optional arguments
// mcs 2.6.4 crashes while emitting a call that names its arguments and leaves an optional
// one out.
#if !MCS2
static void TestNamedWithOmittedOptional()
{
var obj = new NamedOptionalTests();
obj.M(b: Trace(2), a: Trace(1));
obj.N(y: Trace(1), x: Trace(2));
obj.N(z: Trace(1), x: Trace(2));
}
class NamedOptionalTests
{
public void M(int a, int b, int c = 3)
{
Console.WriteLine("M(" + a + ", " + b + ", " + c + ")");
}
public void N(int x, int y = 10, int z = 20)
{
Console.WriteLine("N(" + x + ", " + y + ", " + z + ")");
}
}
#endif
#endregion
#region Redeclared default values
static void TestRedeclaredDefaultValues()
{
var derived = new DerivedDefaultValue();
Console.WriteLine(derived[1, 10]);
Console.WriteLine(derived.Method(1, 10));
}
class BaseDefaultValue
{
public virtual int this[int x, int y = 10] {
get {
return x + y;
}
}
public virtual int Method(int x, int y = 10)
{
return x + y;
}
}
class DerivedDefaultValue : BaseDefaultValue
{
public override int this[int x, int y = 20] {
get {
return x + y + 1;
}
}
public override int Method(int x, int y = 20)
{
return x + y + 1;
}
}
#endregion
#region Indexer with named arguments
static void TestIndexerWithNamedArguments()
{
var obj = new NamedArgumentIndexerTests();
Console.WriteLine(obj[y: Trace(1), x: Trace(2)]);
obj[y: Trace(3), x: Trace(4)] = Trace(5);
Console.WriteLine(obj[y: Trace(6), x: Trace(7)] = Trace(8));
obj[y: Trace(9), x: Trace(10)] += 5;
}
static int Trace(int i)
{
Console.WriteLine("Trace(" + i + ")");
return i;
}
#endregion
#region Out Parameter #region Out Parameter
static void AmbiguousOutParam(out string a) static void AmbiguousOutParam(out string a)
{ {
@ -602,6 +688,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
#endregion #endregion
} }
class NamedArgumentIndexerTests
{
public int this[int x, int y] {
get {
Console.WriteLine("get_Item(" + x + ", " + y + ")");
return x;
}
set {
Console.WriteLine("set_Item(" + x + ", " + y + ", " + value + ")");
}
}
}
class IndexerTests class IndexerTests
{ {
public object this[object key] { public object this[object key] {

20
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/IndexerAccessorParameterNames.cs

@ -0,0 +1,20 @@
public class IndexerAccessorParameterNames
{
public int this[int x, int y] {
get {
return x;
}
set {
}
}
private int Get(int i)
{
return i;
}
public void Use()
{
this[y: Get(1), x: Get(2)] = 3;
}
}

70
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/IndexerAccessorParameterNames.il

@ -0,0 +1,70 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.assembly IndexerAccessorParameterNames { }
.class public auto ansi beforefieldinit IndexerAccessorParameterNames
extends [CORE_ASSEMBLY]System.Object
{
.custom instance void [CORE_ASSEMBLY]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 )
// The indexer's parameter names are the getter's: x and y.
.method public hidebysig specialname instance int32 get_Item (int32 x, int32 y) cil managed
{
.maxstack 8
ldarg.1
ret
}
// The setter is free to name the same parameters differently, which C# cannot express.
.method public hidebysig specialname instance void set_Item (int32 a, int32 b, int32 'value') cil managed
{
.maxstack 8
ret
}
.property instance int32 Item(int32, int32)
{
.get instance int32 IndexerAccessorParameterNames::get_Item(int32, int32)
.set instance void IndexerAccessorParameterNames::set_Item(int32, int32, int32)
}
.method private hidebysig instance int32 Get (int32 i) cil managed
{
.maxstack 8
ldarg.1
ret
}
// this[y: Get(1), x: Get(2)] = 3;
.method public hidebysig instance void Use () cil managed
{
.maxstack 4
.locals init (int32 V_0)
ldarg.0
ldarg.0
ldc.i4.1
call instance int32 IndexerAccessorParameterNames::Get(int32)
stloc.0
ldarg.0
ldc.i4.2
call instance int32 IndexerAccessorParameterNames::Get(int32)
ldloc.0
ldc.i4.3
call instance void IndexerAccessorParameterNames::set_Item(int32, int32, int32)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
.maxstack 8
ldarg.0
call instance void [CORE_ASSEMBLY]System.Object::.ctor()
ret
}
}

21
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertyInitializer.cs

@ -0,0 +1,21 @@
public class ParameterizedPropertyInitializer
{
// C# has no syntax for parameterized property 'Foo'.
public int get_Foo(int x)
{
return x;
}
public void set_Foo(int x, int value)
{
}
public static void Consume(ParameterizedPropertyInitializer p)
{
}
public static void Use()
{
Consume(new ParameterizedPropertyInitializer { [7] = 5 });
}
}

60
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertyInitializer.il

@ -0,0 +1,60 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.assembly ParameterizedPropertyInitializer { }
// A parameterized property that is not an indexer: the type carries no DefaultMemberAttribute,
// which C# cannot express, but VB, C++/CLI and COM interop all produce it.
.class public auto ansi beforefieldinit ParameterizedPropertyInitializer
extends [CORE_ASSEMBLY]System.Object
{
.method public hidebysig specialname instance int32 get_Foo (int32 x) cil managed
{
.maxstack 8
ldarg.1
ret
}
.method public hidebysig specialname instance void set_Foo (int32 x, int32 'value') cil managed
{
.maxstack 8
ret
}
.property instance int32 Foo(int32)
{
.get instance int32 ParameterizedPropertyInitializer::get_Foo(int32)
.set instance void ParameterizedPropertyInitializer::set_Foo(int32, int32)
}
.method public hidebysig static void Consume (class ParameterizedPropertyInitializer p) cil managed
{
.maxstack 8
ret
}
.method public hidebysig static void Use () cil managed
{
.maxstack 8
newobj instance void ParameterizedPropertyInitializer::.ctor()
dup
ldc.i4.7
ldc.i4.5
call instance void ParameterizedPropertyInitializer::set_Foo(int32, int32)
call void ParameterizedPropertyInitializer::Consume(class ParameterizedPropertyInitializer)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
.maxstack 8
ldarg.0
call instance void [CORE_ASSEMBLY]System.Object::.ctor()
ret
}
}

19
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertySetterCall.cs

@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
public class ParameterizedPropertySetterCall
{
// C# has no syntax for parameterized property 'P'.
public int get_P(int i)
{
return i;
}
public void set_P([Optional][DefaultParameterValue(0)] int i, int value)
{
}
public void Use()
{
this.set_P(0, 5);
}
}

55
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParameterizedPropertySetterCall.il

@ -0,0 +1,55 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.assembly ParameterizedPropertySetterCall { }
// A parameterized property whose setter takes an index and the assigned value. It is not the
// type's default member, so there is no access syntax for it and the accessor is written as a
// call - which means the assigned value is an ordinary argument, and the optional index before
// it is not trailing.
.class public auto ansi beforefieldinit ParameterizedPropertySetterCall
extends [CORE_ASSEMBLY]System.Object
{
.method public hidebysig specialname instance int32 get_P (int32 i) cil managed
{
.maxstack 8
ldarg.1
ret
}
.method public hidebysig specialname instance void set_P ([opt] int32 i, int32 'value') cil managed
{
.param [1] = int32(0x00000000)
.maxstack 8
ret
}
.property instance int32 P(int32)
{
.get instance int32 ParameterizedPropertySetterCall::get_P(int32)
.set instance void ParameterizedPropertySetterCall::set_P(int32, int32)
}
.method public hidebysig instance void Use () cil managed
{
.maxstack 8
ldarg.0
ldc.i4.0
ldc.i4.5
call instance void ParameterizedPropertySetterCall::set_P(int32, int32)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
.maxstack 8
ldarg.0
call instance void [CORE_ASSEMBLY]System.Object::.ctor()
ret
}
}

18
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParamsPropertySetter.cs

@ -0,0 +1,18 @@
public class ParamsPropertySetter
{
private int[] values;
public int[] Values {
get {
return values;
}
set {
values = value;
}
}
public void Use()
{
Values = new int[2] { 1, 2 };
}
}

68
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ParamsPropertySetter.il

@ -0,0 +1,68 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.assembly ParamsPropertySetter { }
.class public auto ansi beforefieldinit ParamsPropertySetter
extends [CORE_ASSEMBLY]System.Object
{
.field private int32[] 'values'
.method public hidebysig specialname instance int32[] get_Values () cil managed
{
.maxstack 8
ldarg.0
ldfld int32[] ParamsPropertySetter::'values'
ret
}
// C# cannot declare a property whose value is a parameter array, and an assignment has no
// argument list to expand one into.
.method public hidebysig specialname instance void set_Values (int32[] 'value') cil managed
{
.param [1]
.custom instance void [CORE_ASSEMBLY]System.ParamArrayAttribute::.ctor() = ( 01 00 00 00 )
.maxstack 8
ldarg.0
ldarg.1
stfld int32[] ParamsPropertySetter::'values'
ret
}
.property instance int32[] Values()
{
.get instance int32[] ParamsPropertySetter::get_Values()
.set instance void ParamsPropertySetter::set_Values(int32[])
}
.method public hidebysig instance void Use () cil managed
{
.maxstack 4
ldarg.0
ldc.i4.2
newarr [CORE_ASSEMBLY]System.Int32
dup
ldc.i4.0
ldc.i4.1
stelem.i4
dup
ldc.i4.1
ldc.i4.2
stelem.i4
call instance void ParamsPropertySetter::set_Values(int32[])
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
{
.maxstack 8
ldarg.0
call instance void [CORE_ASSEMBLY]System.Object::.ctor()
ret
}
}

91
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs

@ -60,6 +60,68 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
public class BaseNames
{
public virtual int this[int x, int y] {
get {
return x;
}
set {
}
}
}
public class DerivedNames : BaseNames
{
public override int this[int a, int b] {
get {
return a;
}
set {
}
}
}
public int this[int x, int y] {
get {
return x;
}
set {
}
}
public int this[int i, object o] {
get {
return i;
}
set {
}
}
public int this[int i, string o] {
get {
return i;
}
set {
}
}
public int this[int a, int b, int c = 30] {
get {
return a;
}
set {
}
}
public void UseOptional(int a, int b, int c = 3)
{
}
public void UseTwoOptional(int x, int y = 10, int z = 20)
{
}
public void Use(int a, int b, int c) public void Use(int a, int b, int c)
{ {
} }
@ -81,5 +143,34 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
int b = Get(1); int b = Get(1);
Use(Get(2), b, Get(3)); Use(Get(2), b, Get(3));
} }
public void NamedArgsForIndexer()
{
Use(this[y: Get(1), x: Get(2)], 0, 0);
this[y: Get(1), x: Get(2)] = 3;
}
public void NamedArgsWithOmittedOptional()
{
UseOptional(b: Get(2), a: Get(1));
UseTwoOptional(y: Get(1), x: Get(2));
Use(this[b: Get(1), a: Get(2)], 0, 0);
this[b: Get(1), a: Get(2)] = 4;
}
public void NamedArgsWithOmittedMiddleOptional()
{
UseTwoOptional(z: Get(1), x: Get(2));
}
public void NamedArgsForIndexerNeedingCast()
{
Use(this[o: (object)((Get(1) == 1) ? "a" : "b"), i: Get(2)], 0, 0);
}
public void NamedArgsForOverriddenIndexer(DerivedNames derived)
{
// The names are the base indexer's, which is what the call instruction names.
Use(((BaseNames)derived)[y: Get(1), x: Get(2)], 0, 0);
}
} }
} }

166
ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArguments.cs

@ -55,6 +55,115 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
internal class Indexer
{
public int this[int x, int y = 10] {
get {
return x + y;
}
set {
}
}
}
internal class IndexerWithOverload
{
public int this[int x] {
get {
return x;
}
set {
}
}
public int this[int x, int y = 10] {
get {
return x + y;
}
set {
}
}
}
internal class AllOptionalIndexer
{
public int this[int x = 10, int y = 20] {
get {
return x + y;
}
set {
}
}
}
internal class BaseDefaultValue
{
public virtual int this[int x, int y = 10] {
get {
return x + y;
}
set {
}
}
public virtual int Method(int x, int y = 10)
{
return x + y;
}
}
internal class DerivedDefaultValue : BaseDefaultValue
{
public override int this[int x, int y = 20] {
get {
return x + y + 1;
}
set {
}
}
public override int Method(int x, int y = 20)
{
return x + y + 1;
}
}
internal class BaseIndexer
{
public virtual int this[bool flag] {
get {
return 1;
}
set {
}
}
}
internal class DerivedIndexer : BaseIndexer
{
public override int this[bool f] {
get {
return 2;
}
set {
}
}
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
internal struct StructIndexer
{
public int this[int x, int y = 10] {
get {
return x + y;
}
set {
}
}
}
private static StructIndexer structIndexer;
public OptionalArguments(string name, int a = 5) public OptionalArguments(string name, int a = 5)
{ {
@ -330,5 +439,62 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
d(42); d(42);
} }
#endif #endif
private void RedeclaredDefaultValues(DerivedDefaultValue derived)
{
// The calls go to the base declarations, whose defaults the override redeclares:
// leaving the argument out would pass the override's value instead.
Console.WriteLine(derived[1, 10]);
derived[1, 10] = 5;
Console.WriteLine(derived.Method(1, 10));
}
private void Indexers(Indexer indexer, IndexerWithOverload overloaded)
{
Console.WriteLine(indexer[1]);
Console.WriteLine(indexer[1, 20]);
indexer[1] = 5;
indexer[1] += 5;
indexer[1]++;
Console.WriteLine(structIndexer[1]);
structIndexer[1] = 5;
// Leaving the argument out would bind to the single-parameter indexer.
Console.WriteLine(overloaded[1, 10]);
Console.WriteLine(overloaded[1]);
}
private void AllOptionalIndexers(AllOptionalIndexer allOptional, BaseIndexer boolIndexer, DerivedIndexer derived)
{
// An indexer access keeps an argument even when every one of them is optional.
Console.WriteLine(allOptional[10]);
allOptional[10] = 5;
// Primitive values are not named in an access, unlike in a call. The second one binds
// to an override that names the parameter differently.
Console.WriteLine(boolIndexer[true]);
Console.WriteLine(derived[true]);
}
// Only the index initializers below need C# 6.
#if CS60
private Indexer IndexerInitializer()
{
return new Indexer {
[1] = 5,
[2, 20] = 6
};
}
private BaseIndexer BoolIndexerInitializer()
{
// An index initializer does not name its arguments either.
return new BaseIndexer { [true] = 5 };
}
private DerivedIndexer DerivedIndexerInitializer()
{
// The call goes to the base indexer, which names the parameter differently.
return new DerivedIndexer { [true] = 7 };
}
#endif
} }
} }

16
ICSharpCode.Decompiler.Tests/TestCases/Pretty/OptionalArgumentsDisabled.cs

@ -1,7 +1,17 @@
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
public class OptionalArgumentsDisabled public class OptionalArgumentsDisabled
{ {
public int this[int x, int y = 10] {
get {
return x + y;
}
set {
}
}
public void Test() public void Test()
{ {
MixedArguments("123", 0, 0); MixedArguments("123", 0, 0);
@ -15,5 +25,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void OnlyOptionalArguments(int a = 0, int b = 0) public void OnlyOptionalArguments(int a = 0, int b = 0)
{ {
} }
public void TestIndexer()
{
Console.WriteLine(this[1, 10]);
this[1, 10] = 5;
}
} }
} }

355
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -58,12 +58,15 @@ namespace ICSharpCode.Decompiler.CSharp
public bool AddNamesToPrimitiveValues; public bool AddNamesToPrimitiveValues;
public bool UseImplicitlyTypedOut; public bool UseImplicitlyTypedOut;
public bool IsExpandedForm; public bool IsExpandedForm;
public bool IsSetter;
public int Length => Arguments.Length; public int Length => Arguments.Length;
private int GetActualArgumentCount() public int GetActualArgumentCount()
{ {
int count = IsSetter ? Arguments.Length - 1 : Arguments.Length;
if (FirstOptionalArgumentIndex < 0) if (FirstOptionalArgumentIndex < 0)
return Arguments.Length; return count;
Debug.Assert(FirstOptionalArgumentIndex <= count);
return FirstOptionalArgumentIndex; return FirstOptionalArgumentIndex;
} }
@ -74,10 +77,11 @@ namespace ICSharpCode.Decompiler.CSharp
&& !ParameterNames.Any(string.IsNullOrEmpty)) && !ParameterNames.Any(string.IsNullOrEmpty))
{ {
Debug.Assert(skipCount == 0); Debug.Assert(skipCount == 0);
if (argumentNames == null) // On a copy: giving these names up again must leave the ones that order the
{ // arguments untouched.
argumentNames = new string[Arguments.Length]; argumentNames = argumentNames == null
} ? new string[Arguments.Length]
: (string[])argumentNames.Clone();
for (int i = 0; i < Arguments.Length; i++) for (int i = 0; i < Arguments.Length; i++)
{ {
@ -88,10 +92,19 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
// The names cover the full parameter list and have to stop where the arguments do.
int argumentCount = GetActualArgumentCount();
if (argumentNames != null && argumentNames.Length > argumentCount)
{
var writtenNames = new string[argumentCount];
Array.Copy(argumentNames, writtenNames, argumentCount);
argumentNames = writtenNames;
}
return argumentNames; return argumentNames;
} }
public IList<ResolveResult> GetArgumentResolveResults(int skipCount = 0) public ResolveResult[] GetArgumentResolveResults(int skipCount = 0)
{ {
var expectedParameters = ExpectedParameters; var expectedParameters = ExpectedParameters;
var useImplicitlyTypedOut = UseImplicitlyTypedOut; var useImplicitlyTypedOut = UseImplicitlyTypedOut;
@ -111,7 +124,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
public IList<ResolveResult> GetArgumentResolveResultsDirect(int skipCount = 0) public ResolveResult[] GetArgumentResolveResultsDirect(int skipCount = 0)
{ {
return Arguments return Arguments
.Skip(skipCount) .Skip(skipCount)
@ -132,7 +145,7 @@ namespace ICSharpCode.Decompiler.CSharp
else else
{ {
Debug.Assert(skipCount == 0); Debug.Assert(skipCount == 0);
return Arguments.Take(argumentCount).Zip(argumentNames.Take(argumentCount), return Arguments.Take(argumentCount).Zip(argumentNames,
(arg, name) => { (arg, name) => {
if (name == null) if (name == null)
return AddAnnotations(arg.Expression); return AddAnnotations(arg.Expression);
@ -500,11 +513,17 @@ namespace ICSharpCode.Decompiler.CSharp
return result; return result;
} }
int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0); // IsSetter carries the answer for an accessor that takes the assigned value, including
if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || argumentList.ExpectedParameters.Length == allowedParamCount)) // the argument order that keeps it last.
if (argumentList.IsSetter || (!TakesAssignedValueLast(method) && IsWrittenAsMemberAccess(method)))
{ {
argumentList.CheckNoNamedOrOptionalArguments(); // Only an indexer access has an argument list to carry names or leave arguments out of.
return HandleAccessorCall(expectedTargetDetails, method, target, argumentList.Arguments.ToList(), argumentList.ArgumentNames); if (method.AccessorOwner!.SymbolKind != SymbolKind.Indexer)
argumentList.CheckNoNamedOrOptionalArguments();
// An access spells its index out anyway, and the ladder answers a name the member
// does not have with a cast of the target rather than by giving the name up.
argumentList.AddNamesToPrimitiveValues = false;
return HandleAccessorCall(expectedTargetDetails, method, target, argumentList);
} }
if (IsDelegateEqualityComparison(method, argumentList.Arguments)) if (IsDelegateEqualityComparison(method, argumentList.Arguments))
@ -793,10 +812,15 @@ namespace ICSharpCode.Decompiler.CSharp
callArguments.Add(value ?? new Nop()); callArguments.Add(value ?? new Nop());
var argumentList = BuildArgumentList(expectedTargetDetails, target, method, 1, callArguments, null); var argumentList = BuildArgumentList(expectedTargetDetails, target, method, 1, callArguments, null);
// An index initializer is an assignment whatever the accessor looks like, even for a
// parameterized property, which has no access syntax of its own.
argumentList.IsSetter = true;
// The cast the ladder would answer an unresolvable name with is removed again below,
// together with the target.
argumentList.AddNamesToPrimitiveValues = false;
var unused = new IdentifierExpression("initializedObject").WithRR(target).WithoutILInstruction(); var unused = new IdentifierExpression("initializedObject").WithRR(target).WithoutILInstruction();
var assignment = HandleAccessorCall(expectedTargetDetails, method, unused, var assignment = HandleAccessorCall(expectedTargetDetails, method, unused, argumentList);
argumentList.Arguments.ToList(), argumentList.ArgumentNames);
if (((AssignmentExpression)assignment).Left is IndexerExpression indexer && indexer.Target is not null) if (((AssignmentExpression)assignment).Left is IndexerExpression indexer && indexer.Target is not null)
indexer.Target.Remove(); indexer.Target.Remove();
@ -1013,6 +1037,22 @@ namespace ICSharpCode.Decompiler.CSharp
// >= 0 - the index of the first argument that can be removed, because it is optional // >= 0 - the index of the first argument that can be removed, because it is optional
// and is the default value of the parameter. // and is the default value of the parameter.
int firstOptionalArgumentIndex = expressionBuilder.settings.OptionalArguments ? -2 : -1; int firstOptionalArgumentIndex = expressionBuilder.settings.OptionalArguments ? -2 : -1;
// Only an access takes the assigned value out of the argument list; an accessor
// written as a call passes it like any other argument.
bool writtenAsMemberAccess = IsWrittenAsMemberAccess(method);
if (writtenAsMemberAccess && TakesAssignedValueLast(method) && argumentToParameterMap != null
&& argumentToParameterMap[callArguments.Count - 1] != method.Parameters.Count - 1)
{
// An access writes the value from the last argument; in any other order there is
// no access syntax for it.
writtenAsMemberAccess = false;
}
bool isSetter = writtenAsMemberAccess && TakesAssignedValueLast(method);
// A name in an element access names a parameter of the indexer, which the type system
// takes from the getter; the accessor being called may name them differently.
IReadOnlyList<IParameter> namedParameters = method.AccessorOwner is IProperty { IsIndexer: true } indexer
? indexer.Parameters
: method.Parameters;
for (int i = firstParamIndex; i < callArguments.Count; i++) for (int i = firstParamIndex; i < callArguments.Count; i++)
{ {
IParameter parameter; IParameter parameter;
@ -1024,10 +1064,13 @@ namespace ICSharpCode.Decompiler.CSharp
// assign names to that argument and all following arguments: // assign names to that argument and all following arguments:
argumentNames = new string[method.Parameters.Count]; argumentNames = new string[method.Parameters.Count];
} }
parameter = method.Parameters[argumentToParameterMap[i]]; int parameterIndex = argumentToParameterMap[i];
if (argumentNames != null && AssignVariableNames.IsValidName(parameter.Name)) parameter = method.Parameters[parameterIndex];
// The assigned value is past the end of the indexer's parameters.
if (argumentNames != null && parameterIndex < namedParameters.Count
&& AssignVariableNames.IsValidName(namedParameters[parameterIndex].Name))
{ {
argumentNames[arguments.Count] = parameter.Name; argumentNames[arguments.Count] = namedParameters[parameterIndex].Name;
} }
} }
else else
@ -1039,17 +1082,24 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
isPrimitiveValue.Set(arguments.Count); isPrimitiveValue.Set(arguments.Count);
} }
if (IsOptionalArgument(parameter, arg)) // The assigned value of a setter is not part of the argument list, so it does not
// end the run of optional arguments either.
if (!(isSetter && i + 1 == callArguments.Count))
{ {
if (firstOptionalArgumentIndex == -2) if (IsOptionalArgument(parameter, arg))
firstOptionalArgumentIndex = i - firstParamIndex; {
} if (firstOptionalArgumentIndex == -2)
else firstOptionalArgumentIndex = i - firstParamIndex;
{ }
if (firstOptionalArgumentIndex != -1) else if (firstOptionalArgumentIndex != -1)
{
firstOptionalArgumentIndex = -2; firstOptionalArgumentIndex = -2;
}
} }
if (expressionBuilder.settings.ExpandParamsArguments && parameter.IsParams && i + 1 == callArguments.Count && argumentToParameterMap == null) // An assignment has no argument list to spread a parameter array over, and C#
// cannot declare a property whose value is one.
if (expressionBuilder.settings.ExpandParamsArguments && parameter.IsParams && !isSetter
&& i + 1 == callArguments.Count && argumentToParameterMap == null)
{ {
// Parameter is marked params // Parameter is marked params
// If the argument is an array creation, inline all elements into the call and add missing default values. // If the argument is an array creation, inline all elements into the call and add missing default values.
@ -1111,6 +1161,7 @@ namespace ICSharpCode.Decompiler.CSharp
list.IsExpandedForm = isExpandedForm; list.IsExpandedForm = isExpandedForm;
list.IsPrimitiveValue = isPrimitiveValue; list.IsPrimitiveValue = isPrimitiveValue;
list.FirstOptionalArgumentIndex = firstOptionalArgumentIndex; list.FirstOptionalArgumentIndex = firstOptionalArgumentIndex;
list.IsSetter = isSetter;
list.UseImplicitlyTypedOut = true; list.UseImplicitlyTypedOut = true;
list.AddNamesToPrimitiveValues = expressionBuilder.settings.NamedArguments && expressionBuilder.settings.NonTrailingNamedArguments; list.AddNamesToPrimitiveValues = expressionBuilder.settings.NamedArguments && expressionBuilder.settings.NonTrailingNamedArguments;
return list; return list;
@ -1133,8 +1184,7 @@ namespace ICSharpCode.Decompiler.CSharp
expandedParameters.InsertRange(0, expectedParameters); expandedParameters.InsertRange(0, expectedParameters);
expandedArguments.InsertRange(0, arguments); expandedArguments.InsertRange(0, arguments);
if (IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, Empty<IType>.Array, if (IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, Empty<IType>.Array,
expandedArguments.SelectArray(a => a.ResolveResult), argumentNames: null, expandedArguments.SelectArray(a => a.ResolveResult), argumentNames: null, out _,
firstOptionalArgumentIndex: -1, out _,
out var bestCandidateIsExpandedForm) == OverloadResolutionErrors.None && bestCandidateIsExpandedForm) out var bestCandidateIsExpandedForm) == OverloadResolutionErrors.None && bestCandidateIsExpandedForm)
{ {
expectedParameters = expandedParameters; expectedParameters = expandedParameters;
@ -1214,6 +1264,35 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
/// <summary>
/// Whether the omitted arguments are the defaults of the member the shortened call resolves
/// to. They were compared against the method the call instruction names, which for a virtual
/// call is the base declaration, and an override may redeclare a different default.
/// </summary>
bool OmittedArgumentsAreDefaultsOf(ArgumentList argumentList, IMember? foundMember)
{
int argumentCount = argumentList.IsSetter ? argumentList.Length - 1 : argumentList.Length;
int omittedFrom = argumentList.GetActualArgumentCount();
if (omittedFrom >= argumentCount)
return true;
if (foundMember is not IParameterizedMember foundParameterizedMember)
return false;
var parameters = foundParameterizedMember.Parameters;
// Names may leave out a parameter in the middle, so what was dropped is found through
// the map rather than by position. Its first entries are the target's.
var map = argumentList.ArgumentToParameterMap;
int firstParamIndex = map != null ? map.Count - argumentList.Length : 0;
for (int i = omittedFrom; i < argumentCount; i++)
{
int parameterIndex = map != null ? map[i + firstParamIndex] : i;
if (parameterIndex < 0 || parameterIndex >= parameters.Count)
return false;
if (!IsOptionalArgument(parameters[parameterIndex], argumentList.Arguments[i]))
return false;
}
return true;
}
bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg) bool IsOptionalArgument(IParameter parameter, TranslatedExpression arg)
{ {
if (!parameter.IsOptional) if (!parameter.IsOptional)
@ -1245,13 +1324,46 @@ namespace ICSharpCode.Decompiler.CSharp
private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
ref TranslatedExpression target, ref ArgumentList argumentList, CallTransformation allowedTransforms, out IParameterizedMember? foundMethod) ref TranslatedExpression target, ref ArgumentList argumentList, CallTransformation allowedTransforms, out IParameterizedMember? foundMethod)
{
var transform = GetRequiredTransformations(expectedTargetDetails, method, ref target, ref argumentList,
allowedTransforms, writtenAsMemberAccess: false, out var foundMember);
foundMethod = (IParameterizedMember?)foundMember;
return transform;
}
/// <summary>
/// Finds the transformations an expression needs to bind back to the member the IL names,
/// cheapest first. With <paramref name="writtenAsMemberAccess"/> the expression is a
/// property, indexer or event access, which resolves against the accessor's owner.
/// </summary>
private CallTransformation GetRequiredTransformations(ExpectedTargetDetails expectedTargetDetails, IMethod method,
ref TranslatedExpression target, ref ArgumentList argumentList, CallTransformation allowedTransforms,
bool writtenAsMemberAccess, out IMember? foundMember)
{ {
CallTransformation transform = CallTransformation.None; CallTransformation transform = CallTransformation.None;
IMember boundMember = writtenAsMemberAccess ? method.AccessorOwner! : method;
// initialize requireTarget flag // initialize requireTarget flag
bool requireTarget; bool requireTarget;
ResolveResult? targetResolveResult; ResolveResult? targetResolveResult;
if ((allowedTransforms & CallTransformation.RequireTarget) != 0) if (writtenAsMemberAccess)
{
if (settings.AlwaysQualifyMemberReferences || boundMember.SymbolKind == SymbolKind.Indexer
|| expressionBuilder.HidesVariableWithName(boundMember.Name))
{
requireTarget = true;
}
else if (method.IsStatic)
{
requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition);
}
else
{
requireTarget = target.Expression is not ThisReferenceExpression;
}
targetResolveResult = requireTarget ? target.ResolveResult : null;
}
else if ((allowedTransforms & CallTransformation.RequireTarget) != 0)
{ {
if (settings.AlwaysQualifyMemberReferences || expressionBuilder.HidesVariableWithName(method.Name)) if (settings.AlwaysQualifyMemberReferences || expressionBuilder.HidesVariableWithName(method.Name))
{ {
@ -1325,14 +1437,38 @@ namespace ICSharpCode.Decompiler.CSharp
} }
bool targetCasted = false; bool targetCasted = false;
bool argumentsCasted = false; bool argumentsCasted = writtenAsMemberAccess && argumentList.GetActualArgumentCount() == 0;
bool originalRequireTarget = requireTarget; bool originalRequireTarget = requireTarget;
bool skipTargetCast = method.Accessibility <= Accessibility.Protected && expressionBuilder.IsBaseTypeOfCurrentType(method.DeclaringTypeDefinition); bool skipTargetCast = !writtenAsMemberAccess
&& method.Accessibility <= Accessibility.Protected && expressionBuilder.IsBaseTypeOfCurrentType(method.DeclaringTypeDefinition);
OverloadResolutionErrors errors; OverloadResolutionErrors errors;
while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments, while (true)
argumentList.GetArgumentResolveResults().ToArray(), argumentList.GetArgumentNames(), argumentList.FirstOptionalArgumentIndex, out foundMethod,
out var bestCandidateIsExpandedForm)) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm)
{ {
bool expandedFormMismatch = false;
if (writtenAsMemberAccess)
{
errors = IsUnambiguousAccess(expectedTargetDetails, targetResolveResult, method,
argumentList.GetArgumentResolveResults(), argumentList.GetArgumentNames(), out foundMember);
}
else
{
errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments,
argumentList.GetArgumentResolveResults(), argumentList.GetArgumentNames(),
out var foundMethod, out bool bestCandidateIsExpandedForm);
foundMember = foundMethod;
expandedFormMismatch = bestCandidateIsExpandedForm != argumentList.IsExpandedForm;
}
if (errors == OverloadResolutionErrors.None && !expandedFormMismatch
&& OmittedArgumentsAreDefaultsOf(argumentList, foundMember))
{
break;
}
if (errors == OverloadResolutionErrors.None && argumentList.FirstOptionalArgumentIndex >= 0)
{
// The omitted arguments are not the defaults of the member found.
argumentList.FirstOptionalArgumentIndex = -1;
continue;
}
switch (errors) switch (errors)
{ {
case OverloadResolutionErrors.OutVarTypeMismatch: case OverloadResolutionErrors.OutVarTypeMismatch:
@ -1380,7 +1516,8 @@ namespace ICSharpCode.Decompiler.CSharp
} }
argumentsCasted = true; argumentsCasted = true;
argumentList.UseImplicitlyTypedOut = false; argumentList.UseImplicitlyTypedOut = false;
CastArguments(argumentList.Arguments, argumentList.ExpectedParameters); CastArguments(argumentList.Arguments, argumentList.GetActualArgumentCount(),
argumentList.ExpectedParameters);
} }
else if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && !requireTarget) else if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && !requireTarget)
{ {
@ -1399,7 +1536,7 @@ namespace ICSharpCode.Decompiler.CSharp
else else
{ {
targetCasted = true; targetCasted = true;
target = target.ConvertTo(method.DeclaringType, expressionBuilder); target = target.ConvertTo(boundMember.DeclaringType, expressionBuilder);
targetResolveResult = target.ResolveResult; targetResolveResult = target.ResolveResult;
} }
} }
@ -1420,7 +1557,7 @@ namespace ICSharpCode.Decompiler.CSharp
continue; continue;
} }
// We've given up. // We've given up.
foundMethod = method; foundMember = boundMember;
break; break;
} }
if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && requireTarget) if ((allowedTransforms & CallTransformation.RequireTarget) != 0 && requireTarget)
@ -1537,9 +1674,13 @@ namespace ICSharpCode.Decompiler.CSharp
return newObj; return newObj;
} }
private void CastArguments(IList<TranslatedExpression> arguments, IList<IParameter> expectedParameters) /// <summary>
/// Casts the first <paramref name="count"/> arguments in place - the retry loop reads them
/// back from the array on its next attempt. A setter's assigned value is past the count.
/// </summary>
private void CastArguments(TranslatedExpression[] arguments, int count, IParameter[] expectedParameters)
{ {
for (int i = 0; i < arguments.Count; i++) for (int i = 0; i < count; i++)
{ {
if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType()) if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType())
{ {
@ -1655,7 +1796,7 @@ namespace ICSharpCode.Decompiler.CSharp
OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, OverloadResolutionErrors IsUnambiguousCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
ResolveResult? target, IType[] typeArguments, ResolveResult[] arguments, ResolveResult? target, IType[] typeArguments, ResolveResult[] arguments,
string[]? argumentNames, int firstOptionalArgumentIndex, string[]? argumentNames,
out IParameterizedMember? foundMember, out bool bestCandidateIsExpandedForm) out IParameterizedMember? foundMember, out bool bestCandidateIsExpandedForm)
{ {
foundMember = null; foundMember = null;
@ -1666,10 +1807,6 @@ namespace ICSharpCode.Decompiler.CSharp
Log.WriteLine("IsUnambiguousCall: Performing overload resolution for " + method); Log.WriteLine("IsUnambiguousCall: Performing overload resolution for " + method);
Log.WriteCollection(" Arguments: ", arguments); Log.WriteCollection(" Arguments: ", arguments);
argumentNames = firstOptionalArgumentIndex < 0 || argumentNames == null
? argumentNames
: argumentNames.Take(firstOptionalArgumentIndex).ToArray();
var or = new OverloadResolution(resolver.Compilation, var or = new OverloadResolution(resolver.Compilation,
arguments, argumentNames, typeArguments, arguments, argumentNames, typeArguments,
conversions: expressionBuilder.resolver.conversions); conversions: expressionBuilder.resolver.conversions);
@ -1771,11 +1908,15 @@ namespace ICSharpCode.Decompiler.CSharp
return OverloadResolutionErrors.None; return OverloadResolutionErrors.None;
} }
bool IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult? target, IMethod method, /// <summary>
IList<TranslatedExpression> arguments, string[]? argumentNames, [NotNullWhen(true)] out IMember? foundMember) /// Resolves an access the way a call is resolved, so that the ladder can tell one that binds
/// to nothing from one that is merely missing an argument.
/// </summary>
OverloadResolutionErrors IsUnambiguousAccess(ExpectedTargetDetails expectedTargetDetails, ResolveResult? target, IMethod method,
ResolveResult[] arguments, string[]? argumentNames, out IMember? foundMember)
{ {
Log.WriteLine("IsUnambiguousAccess: Performing overload resolution for " + method); Log.WriteLine("IsUnambiguousAccess: Performing overload resolution for " + method);
Log.WriteCollection(" Arguments: ", arguments.Select(a => a.ResolveResult)); Log.WriteCollection(" Arguments: ", arguments);
foundMember = null; foundMember = null;
if (target == null) if (target == null)
@ -1784,7 +1925,7 @@ namespace ICSharpCode.Decompiler.CSharp
EmptyList<IType>.Instance, EmptyList<IType>.Instance,
isInvocationTarget: false) as MemberResolveResult; isInvocationTarget: false) as MemberResolveResult;
if (result == null || result.IsError) if (result == null || result.IsError)
return false; return OverloadResolutionErrors.AmbiguousMatch;
foundMember = result.Member; foundMember = result.Member;
} }
else else
@ -1793,15 +1934,15 @@ namespace ICSharpCode.Decompiler.CSharp
if (method.AccessorOwner!.SymbolKind == SymbolKind.Indexer) if (method.AccessorOwner!.SymbolKind == SymbolKind.Indexer)
{ {
var or = new OverloadResolution(resolver.Compilation, var or = new OverloadResolution(resolver.Compilation,
arguments.SelectArray(a => a.ResolveResult), arguments,
argumentNames: argumentNames, argumentNames: argumentNames,
typeArguments: Empty<IType>.Array, typeArguments: Empty<IType>.Array,
conversions: expressionBuilder.resolver.conversions); conversions: expressionBuilder.resolver.conversions);
or.AddMethodLists(lookup.LookupIndexers(target)); or.AddMethodLists(lookup.LookupIndexers(target));
if (or.BestCandidateErrors != OverloadResolutionErrors.None) if (or.BestCandidateErrors != OverloadResolutionErrors.None)
return false; return or.BestCandidateErrors;
if (or.IsAmbiguous) if (or.IsAmbiguous)
return false; return OverloadResolutionErrors.AmbiguousMatch;
foundMember = or.GetBestCandidateWithSubstitutedTypeArguments(); foundMember = or.GetBestCandidateWithSubstitutedTypeArguments();
} }
else else
@ -1811,61 +1952,61 @@ namespace ICSharpCode.Decompiler.CSharp
EmptyList<IType>.Instance, EmptyList<IType>.Instance,
isInvocation: false) as MemberResolveResult; isInvocation: false) as MemberResolveResult;
if (result == null || result.IsError) if (result == null || result.IsError)
return false; return OverloadResolutionErrors.AmbiguousMatch;
foundMember = result.Member; foundMember = result.Member;
} }
} }
return foundMember != null && IsAppropriateCallTarget(expectedTargetDetails, method.AccessorOwner, foundMember); if (foundMember == null || !IsAppropriateCallTarget(expectedTargetDetails, method.AccessorOwner!, foundMember))
{
foundMember = null;
return OverloadResolutionErrors.AmbiguousMatch;
}
return OverloadResolutionErrors.None;
}
/// <summary>
/// Whether the accessor's last parameter is the assigned value: a setter takes it, and so do
/// the two event accessors, written as += and -=.
/// </summary>
static bool TakesAssignedValueLast(IMethod method)
{
return method.AccessorKind is System.Reflection.MethodSemanticsAttributes.Setter
or System.Reflection.MethodSemanticsAttributes.Adder
or System.Reflection.MethodSemanticsAttributes.Remover;
}
/// <summary>
/// Whether the accessor is written as a property or indexer access. One with more parameters
/// than the access syntax has room for is written as a call, assigned value and all.
/// </summary>
static bool IsWrittenAsMemberAccess(IMethod method)
{
if (!method.IsAccessor)
return false;
if (method.AccessorOwner!.SymbolKind == SymbolKind.Indexer)
return true;
return method.Parameters.Count == (TakesAssignedValueLast(method) ? 1 : 0);
} }
ExpressionWithResolveResult HandleAccessorCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, ExpressionWithResolveResult HandleAccessorCall(ExpectedTargetDetails expectedTargetDetails, IMethod method,
TranslatedExpression target, List<TranslatedExpression> arguments, string[]? argumentNames) TranslatedExpression target, ArgumentList argumentList)
{ {
bool requireTarget; bool isSetter = argumentList.IsSetter;
if (settings.AlwaysQualifyMemberReferences || method.AccessorOwner!.SymbolKind == SymbolKind.Indexer || expressionBuilder.HidesVariableWithName(method.AccessorOwner.Name))
requireTarget = true;
else if (method.IsStatic)
requireTarget = !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition);
else
requireTarget = !(target.Expression is ThisReferenceExpression);
bool targetCasted = false;
bool isSetter = method.ReturnType.IsKnownType(KnownTypeCode.Void);
bool argumentsCasted = (isSetter && method.Parameters.Count == 1) || (!isSetter && method.Parameters.Count == 0);
var targetResolveResult = requireTarget ? target.ResolveResult : null;
TranslatedExpression value = default(TranslatedExpression); // Dropping every argument would turn an indexer access into a property access.
if (isSetter) if (argumentList.FirstOptionalArgumentIndex == 0 && method.AccessorOwner!.SymbolKind == SymbolKind.Indexer)
{ {
value = arguments.Last(); argumentList.FirstOptionalArgumentIndex = 1;
arguments.Remove(value);
} }
IMember? foundMember; var transform = GetRequiredTransformations(expectedTargetDetails, method, ref target, ref argumentList,
while (!IsUnambiguousAccess(expectedTargetDetails, targetResolveResult, method, arguments, argumentNames, out foundMember)) CallTransformation.RequireTarget, writtenAsMemberAccess: true, out var foundMember);
{ Debug.Assert(foundMember != null);
if (!argumentsCasted) bool requireTarget = (transform & CallTransformation.RequireTarget) != 0;
{
argumentsCasted = true;
CastArguments(arguments, method.Parameters.ToList());
}
else if (!requireTarget)
{
requireTarget = true;
targetResolveResult = target.ResolveResult;
}
else if (!targetCasted)
{
targetCasted = true;
target = target.ConvertTo(method.AccessorOwner!.DeclaringType, expressionBuilder);
targetResolveResult = target.ResolveResult;
}
else
{
foundMember = method.AccessorOwner!;
break;
}
}
var arguments = argumentList.GetArgumentExpressions().ToList();
// Not one of the arguments the ladder casts.
TranslatedExpression value = isSetter ? argumentList.Arguments[argumentList.Length - 1] : default;
var rr = new MemberResolveResult(target.ResolveResult, foundMember); var rr = new MemberResolveResult(target.ResolveResult, foundMember);
if (isSetter) if (isSetter)
@ -1874,7 +2015,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (arguments.Count != 0) if (arguments.Count != 0)
{ {
expr = new IndexerExpression(target.ResolveResult is InitializedObjectResolveResult ? null : target.Expression, arguments.Select(a => a.Expression)) expr = new IndexerExpression(target.ResolveResult is InitializedObjectResolveResult ? null : target.Expression, arguments)
.WithoutILInstruction().WithRR(rr); .WithoutILInstruction().WithRR(rr);
} }
else if (requireTarget) else if (requireTarget)
@ -1906,7 +2047,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
if (arguments.Count != 0) if (arguments.Count != 0)
{ {
return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression)) return new IndexerExpression(target.Expression, arguments)
.WithoutILInstruction().WithRR(rr); .WithoutILInstruction().WithRR(rr);
} }
else if (requireTarget) else if (requireTarget)
@ -1986,24 +2127,10 @@ namespace ICSharpCode.Decompiler.CSharp
} }
else else
{ {
while (IsUnambiguousCall(expectedTargetDetails, method, null, Empty<IType>.Array, // A constructor names its type, so neither qualification nor type arguments apply.
argumentList.GetArgumentResolveResults().ToArray(), TranslatedExpression noTarget = default;
argumentList.GetArgumentNames(), argumentList.FirstOptionalArgumentIndex, out _, GetRequiredTransformations(expectedTargetDetails, method, ref noTarget, ref argumentList,
out var bestCandidateIsExpandedForm) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm) CallTransformation.None, writtenAsMemberAccess: false, out _);
{
if (argumentList.AddNamesToPrimitiveValues)
{
argumentList.AddNamesToPrimitiveValues = false;
continue;
}
if (argumentList.FirstOptionalArgumentIndex >= 0)
{
argumentList.FirstOptionalArgumentIndex = -1;
continue;
}
CastArguments(argumentList.Arguments, argumentList.ExpectedParameters);
break; // make sure that we don't not end up in an infinite loop
}
IType? returnTypeOverride = null; IType? returnTypeOverride = null;
if (typeSystem.MainModule.TypeSystemOptions.HasFlag(TypeSystemOptions.NativeIntegersWithoutAttribute)) if (typeSystem.MainModule.TypeSystemOptions.HasFlag(TypeSystemOptions.NativeIntegersWithoutAttribute))
{ {

52
ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs

@ -28,13 +28,48 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public class NamedArgumentTransform : IStatementTransform public class NamedArgumentTransform : IStatementTransform
{ {
/// <summary>
/// How many arguments may carry a name: a setter's last one is the assigned value, which is
/// written as the right-hand side.
/// </summary>
static int NameableArgumentCount(CallInstruction call)
{
if (call.Method.AccessorKind is System.Reflection.MethodSemanticsAttributes.Setter
or System.Reflection.MethodSemanticsAttributes.Adder
or System.Reflection.MethodSemanticsAttributes.Remover)
{
return call.Arguments.Count - 1;
}
return call.Arguments.Count;
}
internal static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved) internal static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved)
{ {
Debug.Assert(child.Parent == call); Debug.Assert(child.Parent == call);
if (call.IsInstanceCall && child.ChildIndex == 0) if (call.IsInstanceCall && child.ChildIndex == 0)
return FindResult.Stop; // cannot use named arg to move expressionBeingMoved before this pointer return FindResult.Stop; // cannot use named arg to move expressionBeingMoved before this pointer
if (call.Method.IsOperator || call.Method.IsAccessor) if (call.Method.IsOperator)
return FindResult.Stop; // cannot use named arg for operators or accessors return FindResult.Stop; // cannot use named arg for operators
bool isIndexerSetter = false;
if (call.Method.IsAccessor)
{
// Only an indexer access has an argument list that can carry names.
if (call.Method.AccessorOwner!.SymbolKind != SymbolKind.Indexer)
return FindResult.Stop;
// A name replaces the call with a block: a call-inline-assign block is matched by
// the call it holds, and a compound assignment requires a call in its target.
if (call.Parent is Block { Kind: BlockKind.CallInlineAssign })
return FindResult.Stop;
if (call.Parent is CompoundAssignmentInstruction { TargetKind: CompoundTargetKind.Property } compoundAssignment
&& compoundAssignment.Target == call)
{
return FindResult.Stop;
}
// A setter's last argument is the assigned value, written as the right-hand side.
isIndexerSetter = call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Setter;
if (isIndexerSetter && child.ChildIndex == call.Arguments.Count - 1)
return FindResult.Stop;
}
if (call.Method is VarArgInstanceMethod) if (call.Method is VarArgInstanceMethod)
return FindResult.Stop; // CallBuilder doesn't support named args when using varargs return FindResult.Stop; // CallBuilder doesn't support named args when using varargs
if (call.Method.IsConstructor) if (call.Method.IsConstructor)
@ -45,7 +80,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
if (call.Method.Parameters.Any(p => string.IsNullOrEmpty(p.Name))) if (call.Method.Parameters.Any(p => string.IsNullOrEmpty(p.Name)))
return FindResult.Stop; // cannot use named arguments return FindResult.Stop; // cannot use named arguments
for (int i = child.ChildIndex; i < call.Arguments.Count; i++) int nameableArgumentCount = isIndexerSetter ? call.Arguments.Count - 1 : call.Arguments.Count;
Debug.Assert(nameableArgumentCount == NameableArgumentCount(call));
for (int i = child.ChildIndex; i < nameableArgumentCount; i++)
{ {
var r = ILInlining.FindLoadInNext(call.Arguments[i], v, expressionBeingMoved, InliningOptions.None); var r = ILInlining.FindLoadInNext(call.Arguments[i], v, expressionBeingMoved, InliningOptions.None);
if (r.Type == FindResultType.Found) if (r.Type == FindResultType.Found)
@ -87,11 +124,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
foreach (var arg in call.Arguments) // A block only holds what CanIntroduceNamedArgument admitted.
Debug.Assert(!call.Method.IsAccessor || call.Method.AccessorOwner!.SymbolKind == SymbolKind.Indexer);
int nameableArgumentCount = NameableArgumentCount(call);
for (int i = 0; i < nameableArgumentCount; i++)
{ {
if (arg.MatchLdLoc(v)) if (call.Arguments[i].MatchLdLoc(v))
{ {
return FindResult.NamedArgument(arg, arg); return FindResult.NamedArgument(call.Arguments[i], call.Arguments[i]);
} }
} }
return FindResult.Stop; return FindResult.Stop;

Loading…
Cancel
Save