mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
A dynamic call site that names a type passes typeof(T) as its target. When a later argument contains control flow, the compiler stores that typeof in a temporary. ILInlining does not undo this: it inlines a store only into the immediately following instruction, so an intervening statement leaves the typeof behind, and the expression builder printed the temporary instead of the type. Substituting the typeof back into the target slot is not an option either, because a call there has side effects and blocks inlining of the remaining arguments. The type is therefore stored on the instruction. Object creation already did this, but kept the field private, so the expression builder re-derived the type from the target argument and failed on the spilled form. Member invocation gets the same field. Both drop the target from Arguments, so the dead-argument handling removes the store; ArgumentInfo keeps its entry for the target, because the invocation symbol is built from it. The type of an object creation is not nullable: the transform returns before constructing the instruction when it cannot match the typeof, and substitutes the unknown type otherwise. An unresolvable type therefore reaches the expression builder as the unresolved type it is, and prints as `?` like any other, rather than being indistinguishable from a missing one. Those two are also the only binder method kinds that ever see CSharpArgumentInfoFlags.IsStaticType. Every other way of naming a type as the receiver binds statically and leaves only a conversion of the dynamic operand behind. Assisted-by: Claude:claude-opus-5[1m]:Claude Code
690 lines
14 KiB
C#
690 lines
14 KiB
C#
using System;
|
|
#if CS120
|
|
using System.Collections.Generic;
|
|
#endif
|
|
|
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|
{
|
|
internal class DynamicTests
|
|
{
|
|
|
|
private class Base
|
|
{
|
|
public Base(object baseObj)
|
|
{
|
|
}
|
|
}
|
|
|
|
private class Derived : Base
|
|
{
|
|
public Derived(dynamic d)
|
|
: base((object)d)
|
|
{
|
|
}
|
|
}
|
|
|
|
private class CtorTarget
|
|
{
|
|
public CtorTarget(dynamic d, int i)
|
|
{
|
|
}
|
|
}
|
|
|
|
private struct MyValueType
|
|
{
|
|
private readonly dynamic _getOnlyProperty;
|
|
public dynamic Field;
|
|
#if CS60
|
|
public dynamic GetOnlyProperty => _getOnlyProperty;
|
|
#else
|
|
public dynamic GetOnlyProperty {
|
|
get {
|
|
return _getOnlyProperty;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public dynamic Property { get; set; }
|
|
|
|
public void Method(dynamic a)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
public interface I
|
|
{
|
|
}
|
|
|
|
private static dynamic field;
|
|
private static volatile dynamic volatileField;
|
|
private static object objectField;
|
|
public dynamic Property { get; set; }
|
|
|
|
public DynamicTests()
|
|
{
|
|
}
|
|
|
|
public DynamicTests(dynamic test)
|
|
{
|
|
}
|
|
|
|
public DynamicTests(DynamicTests test)
|
|
{
|
|
}
|
|
|
|
private static void CallWithOut(out dynamic d)
|
|
{
|
|
d = null;
|
|
}
|
|
|
|
#if CS70
|
|
private static void CallWithIn(in dynamic d)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if CS120
|
|
private static void CallWithRefReadonly(ref readonly Dictionary<object, dynamic> d)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
private static void CallWithRef(ref dynamic d)
|
|
{
|
|
}
|
|
|
|
private static void RefCallSiteTests()
|
|
{
|
|
#if CS70
|
|
CallWithOut(out var d);
|
|
CallWithIn(in d);
|
|
#else
|
|
dynamic d;
|
|
CallWithOut(out d);
|
|
#endif
|
|
CallWithRef(ref d);
|
|
d.SomeCall();
|
|
}
|
|
|
|
private static void InvokeConstructor()
|
|
{
|
|
DynamicTests dynamicTests = new DynamicTests();
|
|
dynamic val = new DynamicTests();
|
|
val.Test(new UnauthorizedAccessException());
|
|
dynamic val2 = new DynamicTests(val);
|
|
val2.Get(new DynamicTests((DynamicTests)val));
|
|
val2.Call(new DynamicTests((dynamic)dynamicTests));
|
|
}
|
|
|
|
private static dynamic InlineAssign(object a, out dynamic b)
|
|
{
|
|
return b = ((dynamic)a).Test;
|
|
}
|
|
|
|
private static dynamic SelfReference(dynamic d)
|
|
{
|
|
return d[d, d] = d;
|
|
}
|
|
|
|
private static dynamic LongArgumentListFunc(dynamic d)
|
|
{
|
|
// Func`13
|
|
return d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
|
}
|
|
|
|
private static void LongArgumentListAction(dynamic d)
|
|
{
|
|
// Action`13
|
|
d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
|
}
|
|
|
|
private static void DynamicThrow()
|
|
{
|
|
try
|
|
{
|
|
throw (Exception)field;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex.ToString());
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static void MemberAccess(dynamic a)
|
|
{
|
|
a.Test1();
|
|
a.GenericTest<int, int>();
|
|
a.Test2(1);
|
|
a.Test3(a.InnerTest(1, 2, 3, 4, 5));
|
|
a.Test4(2, null, a.Index[0]);
|
|
a.Test5(a, a.Number, a.String);
|
|
a[0] = 3;
|
|
a.Index[a.Number] = 5;
|
|
a.Index[a.Number] += 5;
|
|
a.Setter = new DynamicTests();
|
|
a.Setter2 = 5;
|
|
}
|
|
|
|
private static void StructMemberAccess(MyValueType valueType)
|
|
{
|
|
valueType.Field = 0;
|
|
valueType.Field += 5;
|
|
valueType.Field[1] = 5;
|
|
valueType.Field.CallMe();
|
|
DynamicTests.Casts(valueType.GetOnlyProperty);
|
|
valueType.GetOnlyProperty.CallMe();
|
|
valueType.Property = 0;
|
|
valueType.Property += 5;
|
|
valueType.Property[1] = 5;
|
|
valueType.Property.CallMe(5.ToDynamic((object)valueType.Property.Call()));
|
|
valueType.Method(valueType.GetOnlyProperty + valueType.Field);
|
|
}
|
|
|
|
private static void RequiredCasts()
|
|
{
|
|
((dynamic)objectField).A = 5;
|
|
((dynamic)objectField).B += 5;
|
|
((dynamic)objectField).Call();
|
|
((object)field).ToString();
|
|
field.Call("Hello World");
|
|
field.Call((object)"Hello World");
|
|
field.Call((dynamic)"Hello World");
|
|
}
|
|
|
|
private void StaticCallWithDynamicArgument(dynamic d)
|
|
{
|
|
M3(d + 5);
|
|
}
|
|
|
|
private static void StaticCallWithDynamicArgumentInStaticContext(dynamic d)
|
|
{
|
|
DynamicTests.M3(d + 5);
|
|
}
|
|
|
|
private static void DynamicCallWithString()
|
|
{
|
|
field.Call("Hello World");
|
|
}
|
|
|
|
private static void DynamicCallWithNamedArgs()
|
|
{
|
|
field.Call(a: "Hello World");
|
|
}
|
|
|
|
private static void DynamicCallWithRefOutArg(int a, out int b)
|
|
{
|
|
field.Call(ref a, out b);
|
|
}
|
|
|
|
private static void DynamicCallWithStringCastToObj()
|
|
{
|
|
field.Call((object)"Hello World");
|
|
}
|
|
|
|
private static void DynamicCallWithStringCastToDynamic()
|
|
{
|
|
field.Call((dynamic)"Hello World");
|
|
}
|
|
|
|
private static void DynamicCallWithStringCastToDynamic2()
|
|
{
|
|
field.Call((dynamic)"Hello World", 5, null);
|
|
}
|
|
|
|
private static void DynamicCallWithStringCastToDynamic3()
|
|
{
|
|
field.Call((dynamic)"Hello World", 5u, null);
|
|
}
|
|
|
|
private static void Invocation(dynamic a, dynamic b)
|
|
{
|
|
a(null, b.Test());
|
|
}
|
|
|
|
private static dynamic Test1(dynamic a)
|
|
{
|
|
dynamic val = a.IndexedProperty;
|
|
return val[0];
|
|
}
|
|
|
|
private static dynamic Test2(dynamic a)
|
|
{
|
|
return a.IndexedProperty[0];
|
|
}
|
|
|
|
private static void ArithmeticBinaryOperators(dynamic a, dynamic b)
|
|
{
|
|
DynamicTests.MemberAccess(a + b);
|
|
DynamicTests.MemberAccess(a + 1);
|
|
DynamicTests.MemberAccess(a + null);
|
|
DynamicTests.MemberAccess(a - b);
|
|
DynamicTests.MemberAccess(a - 1);
|
|
DynamicTests.MemberAccess(a - null);
|
|
DynamicTests.MemberAccess(a * b);
|
|
DynamicTests.MemberAccess(a * 1);
|
|
DynamicTests.MemberAccess(a * null);
|
|
DynamicTests.MemberAccess(a / b);
|
|
DynamicTests.MemberAccess(a / 1);
|
|
DynamicTests.MemberAccess(a / null);
|
|
DynamicTests.MemberAccess(a % b);
|
|
DynamicTests.MemberAccess(a % 1);
|
|
DynamicTests.MemberAccess(a % null);
|
|
}
|
|
|
|
private static void CheckedArithmeticBinaryOperators(dynamic a, dynamic b)
|
|
{
|
|
checked
|
|
{
|
|
DynamicTests.MemberAccess(a + b);
|
|
DynamicTests.MemberAccess(a + 1);
|
|
DynamicTests.MemberAccess(a + null);
|
|
DynamicTests.MemberAccess(a - b);
|
|
DynamicTests.MemberAccess(a - 1);
|
|
DynamicTests.MemberAccess(a - null);
|
|
DynamicTests.MemberAccess(a * b);
|
|
DynamicTests.MemberAccess(a * 1);
|
|
DynamicTests.MemberAccess(a * null);
|
|
DynamicTests.MemberAccess(a / b);
|
|
DynamicTests.MemberAccess(a / 1);
|
|
DynamicTests.MemberAccess(a / null);
|
|
DynamicTests.MemberAccess(a % b);
|
|
DynamicTests.MemberAccess(a % 1);
|
|
DynamicTests.MemberAccess(a % null);
|
|
}
|
|
}
|
|
|
|
private static void UncheckedArithmeticBinaryOperators(dynamic a, dynamic b)
|
|
{
|
|
checked
|
|
{
|
|
DynamicTests.MemberAccess(a + b);
|
|
DynamicTests.MemberAccess(a + 1);
|
|
DynamicTests.MemberAccess(a + null);
|
|
DynamicTests.MemberAccess(unchecked(a - b));
|
|
DynamicTests.MemberAccess(a - 1);
|
|
DynamicTests.MemberAccess(a - null);
|
|
DynamicTests.MemberAccess(unchecked(a * b));
|
|
DynamicTests.MemberAccess(a * 1);
|
|
DynamicTests.MemberAccess(a * null);
|
|
DynamicTests.MemberAccess(a / b);
|
|
DynamicTests.MemberAccess(a / 1);
|
|
DynamicTests.MemberAccess(a / null);
|
|
DynamicTests.MemberAccess(a % b);
|
|
DynamicTests.MemberAccess(a % 1);
|
|
DynamicTests.MemberAccess(a % null);
|
|
}
|
|
}
|
|
|
|
private static void BitwiseAndShiftBinaryOperators(dynamic a, dynamic b)
|
|
{
|
|
DynamicTests.MemberAccess(a & b);
|
|
DynamicTests.MemberAccess(a & 1);
|
|
DynamicTests.MemberAccess(a & null);
|
|
DynamicTests.MemberAccess(a | b);
|
|
DynamicTests.MemberAccess(a | 1);
|
|
DynamicTests.MemberAccess(a | null);
|
|
DynamicTests.MemberAccess(a ^ b);
|
|
DynamicTests.MemberAccess(a ^ 1);
|
|
DynamicTests.MemberAccess(a ^ null);
|
|
DynamicTests.MemberAccess(a << b);
|
|
DynamicTests.MemberAccess(a << 1);
|
|
DynamicTests.MemberAccess(a >> b);
|
|
DynamicTests.MemberAccess(a >> 1);
|
|
}
|
|
|
|
private static void RelationalOperators(dynamic a, dynamic b)
|
|
{
|
|
DynamicTests.MemberAccess(a == b);
|
|
DynamicTests.MemberAccess(a == 1);
|
|
DynamicTests.MemberAccess(a == null);
|
|
DynamicTests.MemberAccess(a != b);
|
|
DynamicTests.MemberAccess(a != 1);
|
|
DynamicTests.MemberAccess(a != null);
|
|
DynamicTests.MemberAccess(a < b);
|
|
DynamicTests.MemberAccess(a < 1);
|
|
DynamicTests.MemberAccess(a < null);
|
|
DynamicTests.MemberAccess(a > b);
|
|
DynamicTests.MemberAccess(a > 1);
|
|
DynamicTests.MemberAccess(a > null);
|
|
DynamicTests.MemberAccess(a >= b);
|
|
DynamicTests.MemberAccess(a >= 1);
|
|
DynamicTests.MemberAccess(a >= null);
|
|
DynamicTests.MemberAccess(a <= b);
|
|
DynamicTests.MemberAccess(a <= 1);
|
|
DynamicTests.MemberAccess(a <= null);
|
|
}
|
|
|
|
private static void Casts(dynamic a)
|
|
{
|
|
Console.WriteLine();
|
|
MemberAccess((int)a);
|
|
MemberAccess(checked((int)a));
|
|
}
|
|
|
|
private static void M(object o)
|
|
{
|
|
}
|
|
|
|
private static void M2(dynamic d)
|
|
{
|
|
}
|
|
|
|
private static void M3(int i)
|
|
{
|
|
}
|
|
|
|
private static byte[] GetData()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private static void M4(dynamic d, int i)
|
|
{
|
|
}
|
|
|
|
private static dynamic M5(dynamic d, int i)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private static void M6<T>(dynamic d, int i)
|
|
{
|
|
}
|
|
|
|
#if CS60
|
|
// #3704: the call target of a dynamic member access on a static type is typeof(DynamicTests).
|
|
// The array is loaded into a temporary that survives as its own statement, which strands the
|
|
// typeof between the two, so it cannot reach the call site by ordinary inlining.
|
|
private static void StaticTargetBehindSurvivingTemporary(dynamic d)
|
|
{
|
|
#if EXPECTED_OUTPUT
|
|
byte[] data = GetData();
|
|
DynamicTests.M4(d, (data != null) ? data.Length : 0);
|
|
#else
|
|
DynamicTests.M4(d, GetData()?.Length ?? 0);
|
|
#endif
|
|
}
|
|
|
|
private static dynamic StaticTargetResultUsed(dynamic d)
|
|
{
|
|
#if EXPECTED_OUTPUT
|
|
byte[] data = GetData();
|
|
return DynamicTests.M5(d, (data != null) ? data.Length : 0);
|
|
#else
|
|
return DynamicTests.M5(d, GetData()?.Length ?? 0);
|
|
#endif
|
|
}
|
|
|
|
private static void StaticTargetWithTypeArguments(dynamic d)
|
|
{
|
|
#if EXPECTED_OUTPUT
|
|
byte[] data = GetData();
|
|
DynamicTests.M6<int>(d, (data != null) ? data.Length : 0);
|
|
#else
|
|
DynamicTests.M6<int>(d, GetData()?.Length ?? 0);
|
|
#endif
|
|
}
|
|
|
|
private static void StaticCtorBehindSurvivingTemporary(dynamic d)
|
|
{
|
|
#if EXPECTED_OUTPUT
|
|
byte[] data = GetData();
|
|
new CtorTarget(d, (data != null) ? data.Length : 0);
|
|
#else
|
|
new CtorTarget(d, GetData()?.Length ?? 0);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
private static void NotDynamicDispatch(dynamic d)
|
|
{
|
|
DynamicTests.M(d);
|
|
M((object)d);
|
|
DynamicTests.M2(d);
|
|
M2((object)d);
|
|
}
|
|
|
|
private static void CompoundAssignment(dynamic a, dynamic b)
|
|
{
|
|
a.Setter2 += 5;
|
|
a.Setter2 -= 1;
|
|
a.Setter2 *= 2;
|
|
a.Setter2 /= 5;
|
|
a.Setter2 += b;
|
|
a.Setter2 -= b;
|
|
a.Setter2 *= b;
|
|
a.Setter2 /= b;
|
|
a.Setter2 %= b;
|
|
a.Setter2 &= b;
|
|
a.Setter2 |= b;
|
|
a.Setter2 ^= b;
|
|
a.Setter2 <<= b;
|
|
a.Setter2 >>= b;
|
|
field.Setter += 5;
|
|
field.Setter -= 5;
|
|
}
|
|
|
|
private static void InlineCompoundAssignment(dynamic a, dynamic b)
|
|
{
|
|
Console.WriteLine(a.Setter2 += 5);
|
|
Console.WriteLine(a.Setter2 -= 1);
|
|
Console.WriteLine(a.Setter2 *= 2);
|
|
Console.WriteLine(a.Setter2 /= 5);
|
|
Console.WriteLine(a.Setter2 += b);
|
|
Console.WriteLine(a.Setter2 -= b);
|
|
Console.WriteLine(a.Setter2 *= b);
|
|
Console.WriteLine(a.Setter2 /= b);
|
|
}
|
|
|
|
private static void UnaryOperators(dynamic a)
|
|
{
|
|
// TODO : beautify inc/dec on locals and fields
|
|
//a--;
|
|
//a++;
|
|
//--a;
|
|
//++a;
|
|
DynamicTests.Casts(-a);
|
|
DynamicTests.Casts(+a);
|
|
DynamicTests.Casts(~a);
|
|
}
|
|
|
|
private static void Loops(dynamic list)
|
|
{
|
|
foreach (dynamic item in list)
|
|
{
|
|
DynamicTests.UnaryOperators(item);
|
|
}
|
|
}
|
|
|
|
private static void If(dynamic a, dynamic b)
|
|
{
|
|
if (a == b)
|
|
{
|
|
Console.WriteLine("Equal");
|
|
}
|
|
}
|
|
|
|
private static void If2(dynamic a, dynamic b)
|
|
{
|
|
if (a == null || b == null)
|
|
{
|
|
Console.WriteLine("One is null");
|
|
}
|
|
}
|
|
|
|
private static void If3(dynamic a, dynamic b)
|
|
{
|
|
if (a == null && b == null)
|
|
{
|
|
Console.WriteLine("Both are null");
|
|
}
|
|
}
|
|
|
|
private static void If4(dynamic a, dynamic b)
|
|
{
|
|
if ((a == null || b == null) && GetDynamic(1) && !(GetDynamic(2) && GetDynamic(3)))
|
|
{
|
|
Console.WriteLine("then");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("else");
|
|
}
|
|
}
|
|
|
|
private static bool ConstantTarget(dynamic a)
|
|
{
|
|
return true.Equals(a);
|
|
}
|
|
|
|
#if CS110 && NET70
|
|
private static nint NewIntPtr(dynamic a)
|
|
{
|
|
return new nint(a);
|
|
}
|
|
#else
|
|
private static IntPtr NewIntPtr(dynamic a)
|
|
{
|
|
return new IntPtr(a);
|
|
}
|
|
#endif
|
|
|
|
private static dynamic GetDynamic(int i)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private static bool GetBool(int i)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
private static dynamic LogicAnd()
|
|
{
|
|
return GetDynamic(1) && GetDynamic(2);
|
|
}
|
|
|
|
private static dynamic LogicAnd(dynamic a, dynamic b)
|
|
{
|
|
return a && b;
|
|
}
|
|
|
|
private static void LogicAndExtended(int i, dynamic d)
|
|
{
|
|
Console.WriteLine(GetDynamic(1) && GetDynamic(2));
|
|
Console.WriteLine(GetDynamic(1) && GetBool(2));
|
|
Console.WriteLine(GetBool(1) && GetDynamic(2));
|
|
Console.WriteLine(i == 1 && d == null);
|
|
}
|
|
|
|
private static dynamic LogicOr()
|
|
{
|
|
return GetDynamic(1) || GetDynamic(2);
|
|
}
|
|
|
|
private static dynamic LogicOr(dynamic a, dynamic b)
|
|
{
|
|
return a || b;
|
|
}
|
|
|
|
private static void LogicOrExtended(int i, dynamic d)
|
|
{
|
|
Console.WriteLine(GetDynamic(1) || GetDynamic(2));
|
|
Console.WriteLine(GetDynamic(1) || GetBool(2));
|
|
Console.WriteLine(GetBool(1) || GetDynamic(2));
|
|
Console.WriteLine(i == 1 || d == null);
|
|
}
|
|
|
|
private static int ImplicitCast(object o)
|
|
{
|
|
return (dynamic)o;
|
|
}
|
|
|
|
private static int ExplicitCast(object o)
|
|
{
|
|
return (int)(dynamic)o;
|
|
}
|
|
|
|
private static dynamic GetI()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public I Test()
|
|
{
|
|
return GetI();
|
|
}
|
|
|
|
public I Test1()
|
|
{
|
|
return (I)GetI();
|
|
}
|
|
|
|
public I Test2()
|
|
{
|
|
return (I)(object)GetI();
|
|
}
|
|
|
|
#if CS72
|
|
public void RefParams(ref object a, ref dynamic b, ref dynamic c)
|
|
{
|
|
}
|
|
public void RefParams2(in object a, ref dynamic b, out dynamic c)
|
|
{
|
|
c = null;
|
|
}
|
|
|
|
public ref dynamic RefReturn(ref object o)
|
|
{
|
|
return ref o;
|
|
}
|
|
|
|
public ref readonly dynamic RefReadonlyReturn(in object o)
|
|
{
|
|
return ref o;
|
|
}
|
|
#endif
|
|
|
|
private static void WhileDynamic(dynamic a)
|
|
{
|
|
while (a)
|
|
{
|
|
Console.WriteLine("x");
|
|
}
|
|
}
|
|
|
|
#if CS60
|
|
private static void NullConditionalInvocation(dynamic a)
|
|
{
|
|
a?.Call();
|
|
}
|
|
#endif
|
|
|
|
private static void DynamicEventAssignment(dynamic a, EventHandler b)
|
|
{
|
|
a.Event += b;
|
|
a.Event -= b;
|
|
}
|
|
|
|
private static void DynamicEventAssignmentResultUsed(dynamic a, EventHandler b)
|
|
{
|
|
Console.WriteLine(a.Event += b);
|
|
}
|
|
|
|
private static dynamic DynamicEventAssignmentResultReturned(dynamic a, EventHandler b)
|
|
{
|
|
return a.Event += b;
|
|
}
|
|
}
|
|
|
|
internal static class Extension
|
|
{
|
|
public static dynamic ToDynamic(this int i, dynamic info)
|
|
{
|
|
throw null;
|
|
}
|
|
}
|
|
}
|