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. ///