Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ public async Task Async([ValueSource(nameof(noMonoOptions))] CompilerOptions opt
await RunCS(options: options);
}

[Test]
public async Task AsyncAwaitPatterns([ValueSource(nameof(noMonoOptions))] CompilerOptions options)
{
await RunCS(options: options);
}

[Test]
public async Task LINQRaytracer([ValueSource(nameof(defaultOptions))] CompilerOptions options)
{
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,12 @@ public async Task AsyncStreams([ValueSource(nameof(roslyn3OrNewerOptions))] Comp
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task AsyncAwaitPatterns([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task AsyncUsing([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// Copyright (c) 2026 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#pragma warning disable 1998
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
// The Pretty fixture of the same name pins how awaits are printed; this one pins what they
// have to mean: copy semantics of struct awaitables, and the evaluation order around the
// suspension point.
public class AsyncAwaitPatterns
{
public struct CountingAwaitable
{
public int Counter;

public TaskAwaiter<int> GetAwaiter()
{
Counter++;
Console.WriteLine(" GetAwaiter, Counter is now " + Counter);
return Task.FromResult(0).GetAwaiter();
}
}

public class Holder
{
public CountingAwaitable Mutable;
public readonly CountingAwaitable ReadOnly;

public CountingAwaitable Property {
get { return Mutable; }
}
}

private int[] array = new int[4];
private int index;
private int field;

public static void Main()
{
new AsyncAwaitPatterns().Run().Wait();
}

public async Task Run()
{
await MutableStructField();
await ReadOnlyStructField();
await StructProperty();
await CompoundAssignmentToArrayElement();
await AssignmentAfterAwait();
await ArgumentEvaluationOrder();
await RefArgumentEvaluationOrder();
await AwaitInLoop();
await AwaitInTernary(true);
await AwaitInTernary(false);
#if CS60
await AwaitInCatchAndFinally();
#endif
Console.WriteLine("done");
}

private Task<int> Value(int v)
{
Console.WriteLine(" Value(" + v + ")");
return Task.FromResult(v);
}

private int Index(string tag)
{
Console.WriteLine(" Index(" + tag + ") -> " + index);
return index;
}

private int[] Array(string tag)
{
Console.WriteLine(" Array(" + tag + ")");
return array;
}

private int Side()
{
Console.WriteLine(" Side()");
return 100;
}

private static string Combine(int a, int b, int c)
{
return a + "/" + b + "/" + c;
}

private static void AddTo(ref int slot, int addend)
{
Console.WriteLine(" AddTo(" + slot + ", " + addend + ")");
slot += addend;
}

// GetAwaiter is called on the field itself, so its mutation sticks.
public async Task MutableStructField()
{
Console.WriteLine("MutableStructField");
Holder holder = new Holder();
await holder.Mutable;
await holder.Mutable;
Console.WriteLine(" Counter = " + holder.Mutable.Counter);
}

// A readonly field is defensively copied, so the mutation is discarded.
public async Task ReadOnlyStructField()
{
Console.WriteLine("ReadOnlyStructField");
Holder holder = new Holder();
await holder.ReadOnly;
await holder.ReadOnly;
Console.WriteLine(" Counter = " + holder.ReadOnly.Counter);
}

// A property returns a copy, so the mutation is discarded as well.
public async Task StructProperty()
{
Console.WriteLine("StructProperty");
Holder holder = new Holder();
await holder.Property;
await holder.Property;
Console.WriteLine(" Counter = " + holder.Mutable.Counter);
}

// Target and index are evaluated before the await, not after it.
public async Task CompoundAssignmentToArrayElement()
{
Console.WriteLine("CompoundAssignmentToArrayElement");
array = new int[4];
index = 0;
Array("lhs")[Index("lhs")] += await Value(5);
index = 1;
Console.WriteLine(" array = " + string.Join(",", array));
}

public async Task AssignmentAfterAwait()
{
Console.WriteLine("AssignmentAfterAwait");
array = new int[4];
index = 2;
int[] target = Array("target");
int i = Index("i");
index = 3;
target[i] = await Value(7);
Console.WriteLine(" array = " + string.Join(",", array));
}

public async Task ArgumentEvaluationOrder()
{
Console.WriteLine("ArgumentEvaluationOrder");
Console.WriteLine(" " + Combine(await Value(1), Side(), await Value(2)));
}

public async Task RefArgumentEvaluationOrder()
{
Console.WriteLine("RefArgumentEvaluationOrder");
field = 0;
AddTo(ref field, await Value(6));
Console.WriteLine(" field = " + field);
}

public async Task AwaitInLoop()
{
Console.WriteLine("AwaitInLoop");
for (int i = 0; i < 4; i++)
{
if (i == 1)
{
continue;
}
if (i == 3)
{
break;
}
Console.WriteLine(" loop " + await Value(i));
}
}

public async Task AwaitInTernary(bool condition)
{
Console.WriteLine("AwaitInTernary(" + condition + ")");
Console.WriteLine(" " + (condition ? await Value(1) : await Value(2)));
}

#if CS60
public async Task AwaitInCatchAndFinally()
{
Console.WriteLine("AwaitInCatchAndFinally");
try
{
await Value(1);
throw new InvalidOperationException("boom");
}
catch (InvalidOperationException ex)
{
Console.WriteLine(" caught " + ex.Message);
await Value(2);
}
finally
{
Console.WriteLine(" finally");
await Value(3);
}
}
#endif
}
}
Loading
Loading