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
203 lines
4.1 KiB
C#
203 lines
4.1 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
|
{
|
|
internal class DynamicAwait
|
|
{
|
|
private static Task DoAsync(object o)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static dynamic GetDynamic()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private static Task<int> GetIntAsync(object o)
|
|
{
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
private static Task<object> GetObjAsync()
|
|
{
|
|
return Task.FromResult<object>(null);
|
|
}
|
|
|
|
// --- dynamic operations around a normal (real-Task) await ---
|
|
|
|
private static async Task SetMemberBeforeAwait(dynamic d)
|
|
{
|
|
d.Before = 1;
|
|
await DoAsync(null);
|
|
}
|
|
|
|
private static async Task SetMemberAfterAwait(dynamic d)
|
|
{
|
|
await DoAsync(null);
|
|
d.After = 1;
|
|
}
|
|
|
|
private static async Task SetMemberAroundAwait(dynamic d)
|
|
{
|
|
d.Before = 1;
|
|
await DoAsync(null);
|
|
d.After = 2;
|
|
}
|
|
|
|
private static async Task InvokeBeforeAwait(dynamic d)
|
|
{
|
|
d.Before();
|
|
await DoAsync(null);
|
|
}
|
|
|
|
private static async Task InvokeAfterAwait(dynamic d)
|
|
{
|
|
await DoAsync(null);
|
|
d.After();
|
|
}
|
|
|
|
private static async Task DynamicInLoopWithAwait(dynamic d)
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
d.Add(i);
|
|
await DoAsync(null);
|
|
}
|
|
}
|
|
|
|
private static async Task DynamicWhileConditionWithAwait(dynamic d)
|
|
{
|
|
while ((bool)d.HasNext)
|
|
{
|
|
await DoAsync(null);
|
|
}
|
|
}
|
|
|
|
private static async Task TwoAwaitsWithDynamicBetween(dynamic d)
|
|
{
|
|
await DoAsync(null);
|
|
d.Middle = 1;
|
|
await DoAsync(null);
|
|
}
|
|
|
|
private static async Task DynamicConditionGuardingAwait(dynamic d)
|
|
{
|
|
if ((bool)d.Flag)
|
|
{
|
|
await DoAsync(null);
|
|
}
|
|
}
|
|
|
|
private static async Task DynamicAwaitInTryCatch(dynamic d)
|
|
{
|
|
try
|
|
{
|
|
d.Before = 1;
|
|
await DoAsync(null);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
// --- dynamic feeding the awaited expression (awaiter stays a real Task) ---
|
|
|
|
private static async Task DynamicConvertedArgumentToAwaitedCall(dynamic d)
|
|
{
|
|
await DoAsync((string)d.Value);
|
|
}
|
|
|
|
private static async Task AwaitTaskFromDynamicCast(dynamic d)
|
|
{
|
|
await (Task)d.GetTask();
|
|
}
|
|
|
|
private static async Task<int> AwaitIntTaskFromDynamicCast(dynamic d)
|
|
{
|
|
return await (Task<int>)d.GetIntTask();
|
|
}
|
|
|
|
// --- awaited result feeding a dynamic operation ---
|
|
|
|
private static async Task StoreAwaitResultInDynamicMember(dynamic d)
|
|
{
|
|
d.Result = await GetIntAsync(null);
|
|
}
|
|
|
|
private static async Task PassAwaitResultToDynamicInvoke(dynamic d)
|
|
{
|
|
d.Consume(await GetObjAsync());
|
|
}
|
|
|
|
private static async Task DynamicIndexerWithAwaitedIndex(dynamic d)
|
|
{
|
|
d[await GetIntAsync(null)] = 1;
|
|
}
|
|
|
|
private static async Task<object> DynamicBinaryOpWithAwaitedOperand(dynamic d)
|
|
{
|
|
return d + await GetIntAsync(null);
|
|
}
|
|
|
|
private static async Task<int> DynamicConvertOfAwaitedResult()
|
|
{
|
|
return (int)(dynamic)(await GetObjAsync());
|
|
}
|
|
|
|
// --- real-world shapes from the referenced issues ---
|
|
|
|
// awaited dynamic stored in a dynamic local, then used (#1388)
|
|
private static async Task<dynamic> AwaitDynamicIntoUsedLocal()
|
|
{
|
|
dynamic val = await GetDynamic();
|
|
val.UseMe();
|
|
return val;
|
|
}
|
|
|
|
// dynamic-dispatched awaited call with a dynamic-converted result (#1928)
|
|
private static async Task<string> AwaitDynamicConvertResult(dynamic d)
|
|
{
|
|
return (string)(await d.RunAsync());
|
|
}
|
|
|
|
// --- static-type call target spilled across an await ---
|
|
|
|
private static void StaticMember(dynamic d, int value)
|
|
{
|
|
}
|
|
|
|
private static async Task StaticTargetWithAwaitedArgument(object o)
|
|
{
|
|
DynamicAwait.StaticMember((dynamic)o, await GetIntAsync(null));
|
|
}
|
|
|
|
// --- fully dynamic awaiter (the awaitable itself is dynamic) ---
|
|
|
|
private static async Task AwaitDynamicValue(dynamic d)
|
|
{
|
|
await d;
|
|
}
|
|
|
|
private static async Task AwaitDynamicMethodResult(dynamic d)
|
|
{
|
|
await d.RunAsync();
|
|
}
|
|
|
|
private static async Task AwaitDynamicLocalWithStatementBefore()
|
|
{
|
|
// Optimized builds drop the local's debug name, so the decompiler regenerates one from the type.
|
|
#if OPT
|
|
dynamic dynamic = GetDynamic();
|
|
Console.WriteLine("before await");
|
|
await dynamic;
|
|
#else
|
|
dynamic x = GetDynamic();
|
|
Console.WriteLine("before await");
|
|
await x;
|
|
#endif
|
|
}
|
|
}
|
|
}
|