Skip to content

Commit 98ee6c3

Browse files
Merge pull request #3731 from siegfriedpammer/runtime-async
2 parents 00e21af + 391bf28 commit 98ee6c3

19 files changed

Lines changed: 2056 additions & 64 deletions

ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public enum CompilerOptions
7373
CheckForOverflowUnderflow = 0x20000,
7474
ProcessXmlDoc = 0x40000,
7575
UseRoslyn4_14_0 = 0x80000,
76+
EnableRuntimeAsync = 0x100000,
7677
UseMcsMask = UseMcs2_6_4 | UseMcs5_23,
7778
UseRoslynMask = UseRoslyn1_3_2 | UseRoslyn2_10_0 | UseRoslyn3_11_0 | UseRoslyn4_14_0 | UseRoslynLatest
7879
}
@@ -500,6 +501,10 @@ public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
500501
preprocessorSymbols.Add("LEGACY_CSC");
501502
preprocessorSymbols.Add("LEGACY_VBC");
502503
}
504+
if (flags.HasFlag(CompilerOptions.EnableRuntimeAsync))
505+
{
506+
preprocessorSymbols.Add("RUNTIMEASYNC");
507+
}
503508
return preprocessorSymbols;
504509
}
505510

@@ -605,13 +610,18 @@ public static async Task<CompilerResults> CompileCSharp(string sourceFileName, C
605610
if (roslynVersion != "legacy")
606611
{
607612
otherOptions += "/shared ";
608-
if (!targetNet40 && Version.Parse(RoslynToolset.SanitizeVersion(roslynVersion)).Major > 2)
613+
var version = Version.Parse(RoslynToolset.SanitizeVersion(roslynVersion));
614+
if (!targetNet40 && version.Major > 2)
609615
{
610616
if (flags.HasFlag(CompilerOptions.NullableEnable))
611617
otherOptions += "/nullable+ ";
612618
else
613619
otherOptions += "/nullable- ";
614620
}
621+
if (!targetNet40 && roslynVersion == roslynLatestVersion && flags.HasFlag(CompilerOptions.EnableRuntimeAsync))
622+
{
623+
otherOptions += "/features:runtime-async=on ";
624+
}
615625
}
616626

617627
if (flags.HasFlag(CompilerOptions.Library))
@@ -842,6 +852,8 @@ internal static string GetSuffix(CompilerOptions cscOptions)
842852
suffix += ".mcs2";
843853
if ((cscOptions & CompilerOptions.UseMcs5_23) != 0)
844854
suffix += ".mcs5";
855+
if ((cscOptions & CompilerOptions.EnableRuntimeAsync) != 0)
856+
suffix += ".runtimeasync";
845857
return suffix;
846858
}
847859

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,46 @@ public async Task CustomTaskType([ValueSource(nameof(roslyn2OrNewerOptions))] Co
537537
await RunForLibrary(cscOptions: cscOptions);
538538
}
539539

540+
[Test]
541+
public async Task RuntimeAsync([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
542+
{
543+
await RunForLibrary("Async", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
544+
}
545+
546+
[Test]
547+
public async Task RuntimeAsyncForeach([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
548+
{
549+
await RunForLibrary("AsyncForeach", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview | CompilerOptions.GeneratePdb);
550+
}
551+
552+
[Test]
553+
public async Task RuntimeAsyncMain([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
554+
{
555+
await Run("AsyncMain", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
556+
}
557+
558+
[Test]
559+
public async Task RuntimeAsyncStreams([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
560+
{
561+
await RunForLibrary("AsyncStreams", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
562+
}
563+
564+
[Test]
565+
public async Task RuntimeAsyncUsing([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
566+
{
567+
await RunForLibrary(
568+
"AsyncUsing",
569+
cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview,
570+
configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
571+
);
572+
}
573+
574+
[Test]
575+
public async Task RuntimeAsyncCustomTaskType([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
576+
{
577+
await RunForLibrary("CustomTaskType", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
578+
}
579+
540580
[Test]
541581
public async Task NullableRefTypes([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
542582
{

ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ public async Task SimpleVoidTaskMethod()
7474
Console.WriteLine("After");
7575
}
7676

77+
[MethodImpl(MethodImplOptions.NoInlining)]
78+
public async Task NoInliningTaskMethod()
79+
{
80+
await Task.Yield();
81+
}
82+
7783
public async Task TaskMethodWithoutAwait()
7884
{
7985
Console.WriteLine("No Await");
@@ -115,6 +121,24 @@ public async void AwaitInLoopCondition()
115121
}
116122
}
117123

124+
public async Task AwaitConfigureAwaitFalse(Task<int> task)
125+
{
126+
#if ROSLYN2
127+
Console.WriteLine(await task.ConfigureAwait(continueOnCapturedContext: false));
128+
#else
129+
Console.WriteLine(await task.ConfigureAwait(false));
130+
#endif
131+
}
132+
133+
public async Task<int> AwaitConfigureAwaitMixed(Task<int> task1, Task<int> task2)
134+
{
135+
#if ROSLYN2
136+
return await task1.ConfigureAwait(continueOnCapturedContext: false) + await task2.ConfigureAwait(continueOnCapturedContext: true);
137+
#else
138+
return await task1.ConfigureAwait(false) + await task2.ConfigureAwait(true);
139+
#endif
140+
}
141+
118142
#if CS60
119143
public async Task AwaitInCatch(bool b, Task<int> task1, Task<int> task2)
120144
{
@@ -359,6 +383,127 @@ public async Task<object> Issue2436()
359383
}
360384
return new object();
361385
}
386+
387+
public async Task TryCatchFinallyAllAwait()
388+
{
389+
try
390+
{
391+
await Task.CompletedTask;
392+
Console.WriteLine("try");
393+
}
394+
catch (Exception)
395+
{
396+
await Task.CompletedTask;
397+
Console.WriteLine("catch");
398+
}
399+
finally
400+
{
401+
await Task.CompletedTask;
402+
Console.WriteLine("finally");
403+
}
404+
}
405+
406+
public async Task ThrowInsideTryFinally()
407+
{
408+
try
409+
{
410+
throw new InvalidOperationException();
411+
}
412+
finally
413+
{
414+
await Task.Yield();
415+
}
416+
}
417+
418+
public async Task HeterogeneousMultiCatch1()
419+
{
420+
try
421+
{
422+
await Task.Yield();
423+
}
424+
catch (InvalidOperationException ex)
425+
{
426+
await Task.Yield();
427+
Console.WriteLine(ex.Message);
428+
}
429+
catch (ArgumentException ex2)
430+
{
431+
await Task.Yield();
432+
Console.WriteLine(ex2.Message);
433+
}
434+
}
435+
436+
public async Task HeterogeneousMultiCatch2()
437+
{
438+
try
439+
{
440+
await Task.Yield();
441+
}
442+
catch (InvalidOperationException ex)
443+
{
444+
await Task.Yield();
445+
Console.WriteLine(ex.Message);
446+
}
447+
catch
448+
{
449+
await Task.Yield();
450+
Console.WriteLine("other");
451+
}
452+
}
453+
454+
public async Task HeterogeneousMultiCatch3()
455+
{
456+
try
457+
{
458+
await Task.Yield();
459+
}
460+
catch (InvalidOperationException ex)
461+
{
462+
await Task.Yield();
463+
Console.WriteLine(ex.Message);
464+
}
465+
catch (Exception)
466+
{
467+
await Task.Yield();
468+
throw;
469+
}
470+
}
471+
#if RUNTIMEASYNC
472+
// The state-machine async lowering doesn't recognize return-from-try-with-await-in-finally
473+
// and decompiles these as `int result; try { ... } finally { ... } return result;`. The
474+
// runtime-async exception rewrite recovers the source-level form. Gate these tests so the
475+
// (state-machine) Async test doesn't run them against the more aggressive output.
476+
public async Task<int> ReturnFromTryFinally()
477+
{
478+
try
479+
{
480+
return 42;
481+
}
482+
finally
483+
{
484+
await Task.CompletedTask;
485+
}
486+
}
487+
488+
public async Task<int> ReturnFromInsideNestedTryFinally()
489+
{
490+
try
491+
{
492+
try
493+
{
494+
return 42;
495+
}
496+
finally
497+
{
498+
await Task.CompletedTask;
499+
}
500+
}
501+
finally
502+
{
503+
await Task.CompletedTask;
504+
}
505+
}
506+
#endif
362507
#endif
363508

364509
public static async Task<int> GetIntegerSumAsync(IEnumerable<int> items)

ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public enum LanguageVersion
3737
CSharp12_0 = 1200,
3838
CSharp13_0 = 1300,
3939
CSharp14_0 = 1400,
40-
Preview = 1400,
40+
CSharp15_0 = 1500,
41+
Preview = 1500,
4142
Latest = 0x7FFFFFFF
4243
}
4344
}

ICSharpCode.Decompiler/DecompilerSettings.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,16 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
177177
extensionMembers = false;
178178
firstClassSpanTypes = false;
179179
}
180+
if (languageVersion < CSharp.LanguageVersion.CSharp15_0)
181+
{
182+
runtimeAsync = false;
183+
}
180184
}
181185

182186
public CSharp.LanguageVersion GetMinimumRequiredVersion()
183187
{
188+
if (runtimeAsync)
189+
return CSharp.LanguageVersion.CSharp15_0;
184190
if (extensionMembers || firstClassSpanTypes)
185191
return CSharp.LanguageVersion.CSharp14_0;
186192
if (paramsCollections)
@@ -2167,7 +2173,7 @@ public bool InlineArrays {
21672173
/// <summary>
21682174
/// Gets/Sets whether C# 14.0 extension members should be transformed.
21692175
/// </summary>
2170-
[Category("C# 14.0 / VS 202x.yy")]
2176+
[Category("C# 14.0 / VS 2026")]
21712177
[Description("DecompilerSettings.ExtensionMembers")]
21722178
public bool ExtensionMembers {
21732179
get { return extensionMembers; }
@@ -2185,7 +2191,7 @@ public bool ExtensionMembers {
21852191
/// <summary>
21862192
/// Gets/Sets whether (ReadOnly)Span&lt;T&gt; should be treated like built-in types.
21872193
/// </summary>
2188-
[Category("C# 14.0 / VS 202x.yy")]
2194+
[Category("C# 14.0 / VS 2026")]
21892195
[Description("DecompilerSettings.FirstClassSpanTypes")]
21902196
public bool FirstClassSpanTypes {
21912197
get { return firstClassSpanTypes; }
@@ -2198,6 +2204,24 @@ public bool FirstClassSpanTypes {
21982204
}
21992205
}
22002206

2207+
bool runtimeAsync = true;
2208+
2209+
/// <summary>
2210+
/// Gets/Sets whether runtime async should be used.
2211+
/// </summary>
2212+
[Category("C# 15.0 / VS 202x.yy")]
2213+
[Description("DecompilerSettings.RuntimeAsync")]
2214+
public bool RuntimeAsync {
2215+
get { return runtimeAsync; }
2216+
set {
2217+
if (runtimeAsync != value)
2218+
{
2219+
runtimeAsync = value;
2220+
OnPropertyChanged();
2221+
}
2222+
}
2223+
}
2224+
22012225
bool separateLocalVariableDeclarations = false;
22022226

22032227
/// <summary>

ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@
149149
<Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" />
150150
<Compile Include="Documentation\XmlDocumentationElement.cs" />
151151
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
152+
<Compile Include="IL\ControlFlow\RuntimeAsyncExceptionRewriteTransform.cs" />
153+
<Compile Include="IL\ControlFlow\RuntimeAsyncManualAwaitTransform.cs" />
152154
<Compile Include="IL\Transforms\InterpolatedStringTransform.cs" />
153155
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
154156
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />

0 commit comments

Comments
 (0)