diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
index 1ad55293d..84c9b7d05 100644
--- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
+++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
@@ -197,6 +197,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.Xml.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.CSharp.dll")),
MetadataReference.CreateFromFile(typeof(ValueTuple).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(ValueTask).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Span<>).Assembly.Location),
};
});
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index fe066f25f..37b69bf18 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 401c23221..eb840ec1b 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -299,6 +299,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions);
}
+ [Test]
+ public void CustomTaskType([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
[Test]
public void NullableRefTypes([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs
new file mode 100644
index 000000000..65309ac46
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ public class CustomTaskType
+ {
+ private int memberField;
+
+ public async ValueTask SimpleVoidTaskMethod()
+ {
+ Console.WriteLine("Before");
+ await Task.Delay(TimeSpan.FromSeconds(1.0));
+ Console.WriteLine("After");
+ }
+
+ public async ValueTask TaskMethodWithoutAwait()
+ {
+ Console.WriteLine("No Await");
+ }
+
+ public async ValueTask CapturingThis()
+ {
+ await Task.Delay(memberField);
+ }
+
+ public async ValueTask CapturingThisWithoutAwait()
+ {
+ Console.WriteLine(memberField);
+ }
+
+ public async ValueTask SimpleBoolTaskMethod()
+ {
+ Console.WriteLine("Before");
+ await Task.Delay(TimeSpan.FromSeconds(1.0));
+ Console.WriteLine("After");
+ return true;
+ }
+
+ public async void TwoAwaitsWithDifferentAwaiterTypes()
+ {
+ Console.WriteLine("Before");
+ if (await SimpleBoolTaskMethod()) {
+ await Task.Delay(TimeSpan.FromSeconds(1.0));
+ }
+ Console.WriteLine("After");
+ }
+
+ public async void AwaitInLoopCondition()
+ {
+ while (await SimpleBoolTaskMethod()) {
+ Console.WriteLine("Body");
+ }
+ }
+
+ public async ValueTask AwaitInCatch(bool b, ValueTask task1, ValueTask task2)
+ {
+ try {
+ Console.WriteLine("Start try");
+ await task1;
+ Console.WriteLine("End try");
+ } catch (Exception) {
+ if (!b) {
+ await task2;
+ } else {
+ Console.WriteLine("No await");
+ }
+ }
+ }
+
+ public async ValueTask AwaitInFinally(bool b, ValueTask task1, ValueTask task2)
+ {
+ try {
+ Console.WriteLine("Start try");
+ await task1;
+ Console.WriteLine("End try");
+ } finally {
+ if (!b) {
+ await task2;
+ } else {
+ Console.WriteLine("No await");
+ }
+ }
+ }
+
+ public static async ValueTask GetIntegerSumAsync(IEnumerable items)
+ {
+ await Task.Delay(100);
+ int num = 0;
+ foreach (int item in items) {
+ num += item;
+ }
+ return num;
+ }
+
+ public static Func> AsyncLambda()
+ {
+ return async () => await GetIntegerSumAsync(new int[3] {
+ 1,
+ 2,
+ 3
+ });
+ }
+
+ public static Func> AsyncDelegate()
+ {
+ return async delegate {
+ await Task.Delay(10);
+ return 2;
+ };
+ }
+
+ public static async ValueTask AsyncLocalFunctions()
+ {
+ return await Nested(1) + await Nested(2);
+
+ async ValueTask Nested(int i)
+ {
+ await Task.Delay(i);
+ return i;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
index fd4e3d4dd..ec20ef25c 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -199,24 +199,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false;
taskType = function.Method.ReturnType;
builderType = startCall.Method.DeclaringTypeDefinition;
- const string ns = "System.Runtime.CompilerServices";
if (taskType.IsKnownType(KnownTypeCode.Void)) {
methodType = AsyncMethodType.Void;
underlyingReturnType = taskType;
- if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncVoidMethodBuilder"))
+ if (builderType?.FullTypeName != new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncVoidMethodBuilder"))
return false;
- } else if (taskType.IsKnownType(KnownTypeCode.Task)) {
+ } else if (TaskType.IsNonGenericTaskType(taskType, out var builderTypeName)) {
methodType = AsyncMethodType.Task;
underlyingReturnType = context.TypeSystem.FindType(KnownTypeCode.Void);
- if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncTaskMethodBuilder", 0))
+ if (builderType?.FullTypeName != builderTypeName)
return false;
- } else if (taskType.IsKnownType(KnownTypeCode.TaskOfT)) {
+ } else if (TaskType.IsGenericTaskType(taskType, out builderTypeName)) {
methodType = AsyncMethodType.TaskOfT;
- underlyingReturnType = TaskType.UnpackTask(context.TypeSystem, taskType);
- if (builderType?.FullTypeName != new TopLevelTypeName(ns, "AsyncTaskMethodBuilder", 1))
+ if (taskType.IsKnownType(KnownTypeCode.TaskOfT))
+ underlyingReturnType = TaskType.UnpackTask(context.TypeSystem, taskType);
+ else
+ underlyingReturnType = startCall.Method.DeclaringType.TypeArguments[0];
+ if (builderType?.FullTypeName != builderTypeName)
return false;
} else {
- return false; // TODO: generalized async return type
+ return false;
}
if (startCall.Arguments.Count != 2)
return false;
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
index 209d198eb..766cdc025 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
@@ -63,6 +63,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
IsByRefLike,
IteratorStateMachine,
AsyncStateMachine,
+ AsyncMethodBuilder,
// Field attributes:
FieldOffset,
@@ -129,6 +130,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
new TopLevelTypeName("System.Runtime.CompilerServices", "IsByRefLikeAttribute"),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IteratorStateMachineAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(AsyncStateMachineAttribute)),
+ new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncMethodBuilderAttribute"),
// Field attributes:
new TopLevelTypeName("System.Runtime.InteropServices", nameof(FieldOffsetAttribute)),
new TopLevelTypeName("System", nameof(NonSerializedAttribute)),
diff --git a/ICSharpCode.Decompiler/TypeSystem/TaskType.cs b/ICSharpCode.Decompiler/TypeSystem/TaskType.cs
index 5aca29c9d..63aa880d0 100644
--- a/ICSharpCode.Decompiler/TypeSystem/TaskType.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/TaskType.cs
@@ -54,7 +54,67 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
return false;
}
-
+
+ ///
+ /// Gets whether the specified type is a Task-like type.
+ ///
+ public static bool IsCustomTask(IType type, out IType builderType)
+ {
+ builderType = null;
+ ITypeDefinition def = type.GetDefinition();
+ if (def != null) {
+ if (def.TypeParameterCount > 1)
+ return false;
+ var attribute = def.GetAttribute(KnownAttribute.AsyncMethodBuilder);
+ if (attribute == null || attribute.FixedArguments.Length != 1)
+ return false;
+ var arg = attribute.FixedArguments[0];
+ if (!arg.Type.IsKnownType(KnownTypeCode.Type))
+ return false;
+ builderType = (IType)arg.Value;
+ return true;
+ }
+ return false;
+ }
+
+ const string ns = "System.Runtime.CompilerServices";
+
+ ///
+ /// Gets whether the specified type is a non-generic Task-like type.
+ ///
+ /// Returns the full type-name of the builder type, if successful.
+ public static bool IsNonGenericTaskType(IType task, out FullTypeName builderTypeName)
+ {
+ if (task.IsKnownType(KnownTypeCode.Task)) {
+ builderTypeName = new TopLevelTypeName(ns, "AsyncTaskMethodBuilder");
+ return true;
+ }
+ if (IsCustomTask(task, out var builderType)) {
+ builderTypeName = new FullTypeName(builderType.ReflectionName);
+ return builderTypeName.TypeParameterCount == 0;
+ }
+ builderTypeName = default;
+ return false;
+ }
+
+ ///
+ /// Gets whether the specified type is a generic Task-like type.
+ ///
+ /// Returns the full type-name of the builder type, if successful.
+ public static bool IsGenericTaskType(IType task, out FullTypeName builderTypeName)
+ {
+ if (task.IsKnownType(KnownTypeCode.TaskOfT)) {
+ builderTypeName = new TopLevelTypeName(ns, "AsyncTaskMethodBuilder", 1);
+ return true;
+ }
+ if (IsCustomTask(task, out var builderType)) {
+ builderTypeName = new FullTypeName(builderType.ReflectionName);
+ return builderTypeName.TypeParameterCount == 1;
+ }
+ builderTypeName = default;
+ return false;
+ }
+
///
/// Creates a task type.
///