diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index ac3e8c51a..610011534 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -81,6 +81,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 711a307dc..8af4d4a13 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -307,6 +307,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions);
}
+ [Test]
+ public void AsyncStreams([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
[Test]
public void AsyncUsing([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs
new file mode 100644
index 000000000..58f8a40fc
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ public class AsyncStreams
+ {
+ public static async IAsyncEnumerable CountTo(int until)
+ {
+ for (int i = 0; i < until; i++) {
+ yield return i;
+ await Task.Delay(10);
+ }
+ }
+
+ public static async IAsyncEnumerable AlwaysThrow()
+ {
+ throw null;
+ yield break;
+ }
+
+ public static async IAsyncEnumerator InfiniteLoop()
+ {
+ while (true) {
+ }
+ yield break;
+ }
+
+ public static async IAsyncEnumerable InfiniteLoopWithAwait()
+ {
+ while (true) {
+ await Task.Delay(10);
+ }
+ yield break;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 3702f04d2..d1c3ee95d 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -1343,7 +1343,11 @@ namespace ICSharpCode.Decompiler.CSharp
if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) {
body.Add(new YieldBreakStatement());
}
- RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
+ if (function.IsAsync) {
+ RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine);
+ } else {
+ RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine);
+ }
if (function.StateMachineCompiledWithMono) {
RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden);
}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
index 7dfc1048a..def38d2ff 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -174,9 +174,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
CopyPropagation.Propagate(stloc, context);
}
new RemoveDeadVariableInit().Run(function, context);
- // Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess)
foreach (var block in function.Descendants.OfType()) {
+ // Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess)
ILInlining.InlineAllInBlock(function, block, context);
+ if (IsAsyncEnumerator) {
+ // Remove lone 'ldc.i4', those are sometimes left over after C# compiler
+ // optimizes out stores to the state variable.
+ block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4);
+ }
}
context.StepEndGroup();
}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
index 9fd519461..1f8233b6b 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
@@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.CancellationToken.ThrowIfCancellationRequested();
RemoveNopInstructions(block);
- RemoveDeadStackStores(block, aggressive: context.Settings.RemoveDeadStores);
+ RemoveDeadStackStores(block, context);
InlineVariableInReturnBlock(block, context);
// 1st pass SimplifySwitchInstruction before SimplifyBranchChains()
@@ -70,13 +70,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.Nop);
}
- private void RemoveDeadStackStores(Block block, bool aggressive)
+ private static void RemoveDeadStackStores(Block block, ILTransformContext context)
{
+ bool aggressive = context.Settings.RemoveDeadStores;
// Previously copy propagation did this;
// ideally the ILReader would already do this,
// for now do this here (even though it's not control-flow related).
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot) {
+ context.Step($"Remove dead stack store {stloc.Variable.Name}", stloc);
if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) {
Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags));
block.Instructions.RemoveAt(i++);
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
index 766cdc025..872f31af0 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
@@ -64,6 +64,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
IteratorStateMachine,
AsyncStateMachine,
AsyncMethodBuilder,
+ AsyncIteratorStateMachine,
// Field attributes:
FieldOffset,
@@ -131,6 +132,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IteratorStateMachineAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(AsyncStateMachineAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncMethodBuilderAttribute"),
+ new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"),
// Field attributes:
new TopLevelTypeName("System.Runtime.InteropServices", nameof(FieldOffsetAttribute)),
new TopLevelTypeName("System", nameof(NonSerializedAttribute)),