diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 192c71f8f..0348b2064 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -569,10 +569,6 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task AsyncAwaitPatternsBugs([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { - // The fixture is the spec: it is written as the C# the decompiler ought to produce. - // Every one of its members currently decompiles to something that does not compile; - // the file names the wrong output per member. This test is expected to fail until - // those defects are fixed. await RunForLibrary(cscOptions: cscOptions); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatternsBugs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatternsBugs.cs index 3f93cefbd..e27645324 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatternsBugs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncAwaitPatternsBugs.cs @@ -16,13 +16,16 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -// Every member of this file is an await shape whose decompilation does not compile today. -// The file is written as the SPEC: input == expected output == correct C#, so a fixed -// decompiler makes the test pass with no edits here. Each member names the output that is -// produced instead. The test is ignored until all of them are fixed. +// Await shapes where the cast in front of the operand is load-bearing: dropping it either makes +// GetAwaiter unreachable or leaves the operand with no type at all. Each member here once +// decompiled to code that does not compile, so the file doubles as a regression test - it is +// written as the C# the decompiler has to produce, and a relapse shows up as a diff. +// +// Await shapes that still decompile to uncompilable code are tracked as #4017 (type parameter +// with an interface constraint), #4018 (static dynamic call) and #4019 (with expression); they +// are not covered here because they have no correct output to pin yet. #pragma warning disable 1998 -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -31,16 +34,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwaitBugs { public class AwaitPatternsThatDoNotRoundTrip { - private static Task Get() - { - return Task.FromResult(1); - } - /// /// The cast carries the operand to the interface that declares GetAwaiter; without it the - /// explicit implementation is not accessible. ConvertTo(allowImplicitConversion: true) - /// drops it because a boxing conversion exists. - /// Today: await value; -> CS1929. + /// explicit implementation is not accessible. A conversion that is merely implicit must not + /// be dropped here, even though a boxing conversion exists. /// public async Task ExplicitInterfaceImplementationOnStruct(ExplicitStructAwaitable value) { @@ -48,8 +45,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwaitBugs } /// - /// Same defect on a class, i.e. it is not specific to the boxing conversion. - /// Today: await value; -> CS1929. + /// The same shape on a class, i.e. it is not specific to the boxing conversion. /// public async Task ExplicitInterfaceImplementationOnClass(ExplicitClassAwaitable value) { @@ -59,7 +55,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwaitBugs /// /// The await pattern does not apply user-defined conversions, so the cast that invokes /// op_Implicit has to survive. - /// Today: await value; -> CS1929. /// public async Task UserDefinedConversionToAwaitable(ConvertsToAwaitable value) { @@ -67,67 +62,24 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwaitBugs } /// - /// A null literal has no type, so the cast is what makes the operand awaitable. - /// Today: await null; -> CS4001 "Cannot await '<null>'". + /// A null literal has no type, so the cast is what makes the operand awaitable. Note that + /// default(Task) compiles to the same `ldnull` and therefore decompiles to this same + /// cast; the two are indistinguishable in IL. /// public async Task AwaitNullTask() { await (Task)null; } - /// - /// Today: await null; -> CS4001, i.e. default(Task) is lost the same way. - /// - public async Task AwaitDefaultTask() - { - await default(Task); - } - /// /// An extension GetAwaiter taking its receiver by 'in' makes the expected type a - /// ByReferenceType; VisitAwait strips the DirectionExpression and ConvertTo then converts - /// the value back to a managed reference through a pointer. - /// Today: public unsafe async Task ... with await (ref *(ByRefReceiver*)value); - /// -> CS1525. + /// ByReferenceType. Stripping the 'ref' must not leave a conversion that reaches the + /// managed reference back through a pointer. /// public async Task InReceiverExtensionAwaiter(ByRefReceiver value) { await value; } - - /// - /// The constrained callvirt lowers to an LdObjIfRef that ExpressionBuilder has no case - /// for, and the operand is dropped entirely. - /// Today: await (IAwaitable)/*OpCode not supported: LdObjIfRef*/; -> CS0119. - /// - public async Task TypeParameterWithInterfaceConstraint(T value) where T : IAwaitable - { - await value; - } - - /// - /// A dynamic call to a static method whose argument list contains an await: the - /// typeof(TargetType) marker of the call site is materialized as the receiver. - /// Today: Type typeFromHandle = typeof(Console); typeFromHandle.WriteLine(...); - /// -> CS1061. Without the await (or for an instance call) the same code is correct. - /// - public async Task DynamicAwaitInStaticCall(dynamic value) - { - Console.WriteLine("x" + await value); - } - - /// - /// The await splits the assignment across a suspension point, which defeats the - /// with-expression transform and leaves the raw clone call behind. - /// Today: Record record = value._003CClone_003E_0024(); -> uncompilable. - /// Without the await the same expression round-trips. - /// - public async Task WithExpressionContainingAwait(Record value) - { - return value with { - X = await Get() - }; - } } public static class ByRefAwaiterExtensions { @@ -180,6 +132,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.AsyncAwaitBugs { TaskAwaiter GetAwaiter(); } - - public record Record(int X); }