From 5d2578aaf3a63bb644b7ceeaab94722d684c83ed Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 18 Mar 2024 17:45:27 -0700 Subject: [PATCH] Prefer pattern-based over interface-based disposal in await using --- .../Compiler Breaking Changes - DotNet 8.md | 23 + .../Portable/Binder/UsingStatementBinder.cs | 39 +- .../Emit/CodeGen/CodeGenAwaitUsingTests.cs | 895 +++++++++++++++--- .../CodeGen/CodeGenUsingDeclarationTests.cs | 31 +- .../IOperationTests_IUsingStatement.cs | 344 ++++++- .../Semantics/UsingDeclarationTests.cs | 5 +- 6 files changed, 1177 insertions(+), 160 deletions(-) diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md index 6bca00047ff0e..21a6b0e2e4cb1 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md @@ -101,3 +101,26 @@ static class C public static string M(I2 o, in int x) => "2"; } ``` + +## Prefer pattern-based over interface-based disposal in async `using` + +***Introduced in Visual Studio 2022 version 17.10p3*** + +An async `using` prefers to bind using a pattern-based `DisposeAsync()` method rather than the interface-based `IAsyncDisposable.DisposeAsync()`. + +For instance, the public `DisposeAsync()` method will be picked, rather than the private interface implementation: +```csharp +await using (var x = new C()) { } + +public class C : System.IAsyncDisposable +{ + ValueTask IAsyncDisposable.DisposeAsync() => throw null; // no longer picked + + public async ValueTask DisposeAsync() + { + Console.WriteLine("PICKED"); + await Task.Yield(); + } +} +``` + diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index d1b9d601166b8..9994e259166c2 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -183,31 +183,13 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeInfo, out TypeSymbol? awaitableType) { - TypeSymbol disposableInterface = getDisposableInterface(hasAwait); - Debug.Assert((object)disposableInterface != null); - - CompoundUseSiteInfo useSiteInfo = originalBinder.GetNewCompoundUseSiteInfo(diagnostics); - Conversion iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteInfo); patternDisposeInfo = null; awaitableType = null; - - diagnostics.Add(syntax, useSiteInfo); - - if (iDisposableConversion.IsImplicit) - { - if (hasAwait) - { - awaitableType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); - } - - return !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); - } - Debug.Assert(!fromExpression || expressionOpt != null); TypeSymbol? type = fromExpression ? expressionOpt!.Type : declarationTypeOpt; + // Pattern-based binding // If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern. - // We won't need to try and bind a second time if it fails, as async dispose can't be pattern based (ref structs are not allowed in async methods) if (type is object && (type.IsRefLikeType || hasAwait)) { BoundExpression? receiver = fromExpression @@ -250,6 +232,25 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI } } + // Interface binding + TypeSymbol disposableInterface = getDisposableInterface(hasAwait); + Debug.Assert((object)disposableInterface != null); + + CompoundUseSiteInfo useSiteInfo = originalBinder.GetNewCompoundUseSiteInfo(diagnostics); + Conversion iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteInfo); + + diagnostics.Add(syntax, useSiteInfo); + + if (iDisposableConversion.IsImplicit) + { + if (hasAwait) + { + awaitableType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); + } + + return !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); + } + if (type is null || !type.IsErrorType()) { // Retry with a different assumption about whether the `using` is async diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs index 546d49a7fea95..c388178fb6771 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen public class CodeGenAwaitUsingTests : CSharpTestBase { [Fact] - public void TestWithCSharp7_3() + public void TestWithCSharp7_3_01() { string source = @" class C : System.IAsyncDisposable @@ -27,7 +27,7 @@ async System.Threading.Tasks.Task M() { } } - public System.Threading.Tasks.ValueTask DisposeAsync() + System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() { throw null; } @@ -41,6 +41,35 @@ public System.Threading.Tasks.ValueTask DisposeAsync() ); } + [Fact] + public void TestWithCSharp7_3_02() + { + string source = @" +class C : System.IAsyncDisposable +{ + async System.Threading.Tasks.Task M() + { + await using (var x = new C()) + { + } + } + public System.Threading.Tasks.ValueTask DisposeAsync() + { + throw null; + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, parseOptions: TestOptions.Regular7_3); + comp.VerifyDiagnostics( + // (6,9): error CS8652: The feature 'asynchronous using' is not available in C# 7.3. Please use language version 8.0 or greater. + // await using (var x = new C()) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "await").WithArguments("asynchronous using", "8.0").WithLocation(6, 9), + // 0.cs(6,22): error CS8370: Feature 'pattern-based disposal' is not available in C# 7.3. Please use language version 8.0 or greater. + // await using (var x = new C()) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "var x = new C()").WithArguments("pattern-based disposal", "8.0").WithLocation(6, 22) + ); + } + [Fact] public void TestInNonAsyncVoidMethod() { @@ -247,7 +276,7 @@ public System.Threading.Tasks.ValueTask DisposeAsync() } [Fact] - public void TestWithObsoleteDisposeAsync() + public void TestWithObsoleteDisposeAsync_01() { string source = @" class C : System.IAsyncDisposable @@ -259,7 +288,7 @@ public static async System.Threading.Tasks.Task Main() } } [System.Obsolete] - public async System.Threading.Tasks.ValueTask DisposeAsync() + async System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() { await System.Threading.Tasks.Task.Yield(); } @@ -270,6 +299,33 @@ public async System.Threading.Tasks.ValueTask DisposeAsync() // https://github.com/dotnet/roslyn/issues/30257 Confirm whether this behavior is ok (currently matching behavior of obsolete Dispose in non-async using) } + [Fact] + public void TestWithObsoleteDisposeAsync_02() + { + string source = @" +class C : System.IAsyncDisposable +{ + public static async System.Threading.Tasks.Task Main() + { + await using (var x = new C()) + { + } + } + [System.Obsolete] + public async System.Threading.Tasks.ValueTask DisposeAsync() + { + await System.Threading.Tasks.Task.Yield(); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // 0.cs(6,22): warning CS0612: 'C.DisposeAsync()' is obsolete + // await using (var x = new C()) + Diagnostic(ErrorCode.WRN_DeprecatedSymbol, "var x = new C()").WithArguments("C.DisposeAsync()").WithLocation(6, 22) + ); + } + [Fact] public void TestWithObsoleteDispose() { @@ -665,7 +721,7 @@ public void Dispose() } [Fact] - public void TestBadDisposeAsync() + public void TestBadDisposeAsync_01() { string source = @" namespace System @@ -682,7 +738,7 @@ async System.Threading.Tasks.Task M() await using (new C()) { } await using (var x = new C()) { return 1; } } - public int DisposeAsync() + int System.IAsyncDisposable.DisposeAsync() { throw null; } @@ -701,12 +757,47 @@ public int DisposeAsync() } [Fact] - public void TestMissingTaskType() + public void TestBadDisposeAsync_02() + { + string source = @" +namespace System +{ + public interface IAsyncDisposable + { + int DisposeAsync(); // bad return type + } +} +class C : System.IAsyncDisposable +{ + async System.Threading.Tasks.Task M() + { + await using (new C()) { } + await using (var x = new C()) { return 1; } + } + public int DisposeAsync() + { + throw null; + } +} +"; + var comp = CreateCompilationWithTasksExtensions(source); + comp.VerifyDiagnostics( + // (13,22): error CS1061: 'int' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) + // await using (new C()) { } + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "new C()").WithArguments("int", "GetAwaiter").WithLocation(13, 22), + // (14,22): error CS1061: 'int' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) + // await using (var x = new C()) { return 1; } + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "var x = new C()").WithArguments("int", "GetAwaiter").WithLocation(14, 22) + ); + } + + [Fact] + public void TestMissingTaskType_01() { string lib_cs = @" public class Base : System.IAsyncDisposable { - public System.Threading.Tasks.ValueTask DisposeAsync() + System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() { System.Console.Write(""DisposeAsync""); return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); @@ -737,6 +828,39 @@ public static async System.Threading.Tasks.Task Main() ); } + [Fact] + public void TestMissingTaskType_02() + { + string lib_cs = @" +public class Base : System.IAsyncDisposable +{ + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } +} +"; + + string comp_cs = @" +public class C : Base +{ + public static async System.Threading.Tasks.Task Main() + { + await using (var x = new C()) + { + System.Console.Write(""body ""); + return 1; + } + } +} +"; + var libComp = CreateCompilationWithTasksExtensions(lib_cs + IAsyncDisposableDefinition); + var comp = CreateCompilationWithTasksExtensions(comp_cs, references: new[] { libComp.EmitToImageReference() }, options: TestOptions.DebugExe); + comp.MakeTypeMissing(WellKnownType.System_Threading_Tasks_ValueTask); + comp.VerifyEmitDiagnostics(); + } + [Fact] public void TestWithDeclaration() { @@ -952,7 +1076,7 @@ public System.Threading.Tasks.ValueTask DisposeAsync() } [Fact] - public void TestWithExpression() + public void TestWithExpression_01() { string source = @" class C : System.IAsyncDisposable @@ -965,7 +1089,7 @@ public static async System.Threading.Tasks.Task Main() return; } } - public System.Threading.Tasks.ValueTask DisposeAsync() + System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() { System.Console.Write(""DisposeAsync""); return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); @@ -1126,84 +1250,14 @@ .locals init (int V_0, } [Fact] - public void TestWithNullExpression() - { - string source = @" -class C -{ - public static async System.Threading.Tasks.Task Main() - { - await using (null) - { - System.Console.Write(""body""); - return; - } - } -} -"; - var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "body"); - } - - [Fact] - public void TestWithMethodName() - { - string source = @" -class C -{ - public static async System.Threading.Tasks.Task Main() - { - await using (Main) - { - } - } -} -"; - var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }); - comp.VerifyDiagnostics( - // (6,22): error CS8410: 'method group': type used in an async using statement must be implicitly convertible to 'System.IAsyncDisposable' - // await using (Main) - Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "Main").WithArguments("method group").WithLocation(6, 22) - ); - } - - [Fact] - public void TestWithDynamicExpression() + public void TestWithExpression_02() { string source = @" class C : System.IAsyncDisposable { public static async System.Threading.Tasks.Task Main() { - dynamic d = new C(); - await using (d) - { - System.Console.Write(""body ""); - return; - } - } - public System.Threading.Tasks.ValueTask DisposeAsync() - { - System.Console.Write(""DisposeAsync""); - return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); - } -} -"; - var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, references: new[] { CSharpRef }); - comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); - } - - [Fact] - public void TestWithStructExpression() - { - string source = @" -struct S : System.IAsyncDisposable -{ - public static async System.Threading.Tasks.Task Main() - { - await using (new S()) + await using (new C()) { System.Console.Write(""body ""); return; @@ -1219,67 +1273,487 @@ public System.Threading.Tasks.ValueTask DisposeAsync() var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); comp.VerifyDiagnostics(); var verifier = CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); - verifier.VerifyIL("S.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" + verifier.VerifyIL("C.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 298 (0x12a) + // Code size 306 (0x132) .maxstack 3 .locals init (int V_0, object V_1, System.Runtime.CompilerServices.ValueTaskAwaiter V_2, System.Threading.Tasks.ValueTask V_3, - S.
d__0 V_4, + C.
d__0 V_4, System.Exception V_5, int V_6) IL_0000: ldarg.0 - IL_0001: ldfld ""int S.
d__0.<>1__state"" + IL_0001: ldfld ""int C.
d__0.<>1__state"" IL_0006: stloc.0 .try { IL_0007: ldloc.0 IL_0008: brfalse.s IL_000c IL_000a: br.s IL_0011 - IL_000c: br IL_0098 + IL_000c: br IL_0099 IL_0011: nop IL_0012: ldarg.0 - IL_0013: ldflda ""S S.
d__0.<>s__1"" - IL_0018: initobj ""S"" - IL_001e: ldarg.0 - IL_001f: ldnull - IL_0020: stfld ""object S.
d__0.<>s__2"" - IL_0025: ldarg.0 - IL_0026: ldc.i4.0 - IL_0027: stfld ""int S.
d__0.<>s__3"" + IL_0013: newobj ""C..ctor()"" + IL_0018: stfld ""C C.
d__0.<>s__1"" + IL_001d: ldarg.0 + IL_001e: ldnull + IL_001f: stfld ""object C.
d__0.<>s__2"" + IL_0024: ldarg.0 + IL_0025: ldc.i4.0 + IL_0026: stfld ""int C.
d__0.<>s__3"" .try { - IL_002c: nop - IL_002d: ldstr ""body "" - IL_0032: call ""void System.Console.Write(string)"" - IL_0037: nop - IL_0038: br.s IL_003a - IL_003a: ldarg.0 - IL_003b: ldc.i4.1 - IL_003c: stfld ""int S.
d__0.<>s__3"" - IL_0041: leave.s IL_004d + IL_002b: nop + IL_002c: ldstr ""body "" + IL_0031: call ""void System.Console.Write(string)"" + IL_0036: nop + IL_0037: br.s IL_0039 + IL_0039: ldarg.0 + IL_003a: ldc.i4.1 + IL_003b: stfld ""int C.
d__0.<>s__3"" + IL_0040: leave.s IL_004c } catch object { - IL_0043: stloc.1 - IL_0044: ldarg.0 - IL_0045: ldloc.1 - IL_0046: stfld ""object S.
d__0.<>s__2"" - IL_004b: leave.s IL_004d + IL_0042: stloc.1 + IL_0043: ldarg.0 + IL_0044: ldloc.1 + IL_0045: stfld ""object C.
d__0.<>s__2"" + IL_004a: leave.s IL_004c } - IL_004d: ldarg.0 - IL_004e: ldflda ""S S.
d__0.<>s__1"" - IL_0053: constrained. ""S"" - IL_0059: callvirt ""System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()"" - IL_005e: stloc.3 - IL_005f: ldloca.s V_3 - IL_0061: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" - IL_0066: stloc.2 - IL_0067: ldloca.s V_2 - IL_0069: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" - IL_006e: brtrue.s IL_00b4 + IL_004c: ldarg.0 + IL_004d: ldfld ""C C.
d__0.<>s__1"" + IL_0052: brfalse.s IL_00bd + IL_0054: ldarg.0 + IL_0055: ldfld ""C C.
d__0.<>s__1"" + IL_005a: callvirt ""System.Threading.Tasks.ValueTask C.DisposeAsync()"" + IL_005f: stloc.3 + IL_0060: ldloca.s V_3 + IL_0062: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0067: stloc.2 + IL_0068: ldloca.s V_2 + IL_006a: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_006f: brtrue.s IL_00b5 + IL_0071: ldarg.0 + IL_0072: ldc.i4.0 + IL_0073: dup + IL_0074: stloc.0 + IL_0075: stfld ""int C.
d__0.<>1__state"" + IL_007a: ldarg.0 + IL_007b: ldloc.2 + IL_007c: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_0081: ldarg.0 + IL_0082: stloc.s V_4 + IL_0084: ldarg.0 + IL_0085: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_008a: ldloca.s V_2 + IL_008c: ldloca.s V_4 + IL_008e: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.
d__0)"" + IL_0093: nop + IL_0094: leave IL_0131 + IL_0099: ldarg.0 + IL_009a: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_009f: stloc.2 + IL_00a0: ldarg.0 + IL_00a1: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_00a6: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00ac: ldarg.0 + IL_00ad: ldc.i4.m1 + IL_00ae: dup + IL_00af: stloc.0 + IL_00b0: stfld ""int C.
d__0.<>1__state"" + IL_00b5: ldloca.s V_2 + IL_00b7: call ""void System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00bc: nop + IL_00bd: ldarg.0 + IL_00be: ldfld ""object C.
d__0.<>s__2"" + IL_00c3: stloc.1 + IL_00c4: ldloc.1 + IL_00c5: brfalse.s IL_00e2 + IL_00c7: ldloc.1 + IL_00c8: isinst ""System.Exception"" + IL_00cd: stloc.s V_5 + IL_00cf: ldloc.s V_5 + IL_00d1: brtrue.s IL_00d5 + IL_00d3: ldloc.1 + IL_00d4: throw + IL_00d5: ldloc.s V_5 + IL_00d7: call ""System.Runtime.ExceptionServices.ExceptionDispatchInfo System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(System.Exception)"" + IL_00dc: callvirt ""void System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()"" + IL_00e1: nop + IL_00e2: ldarg.0 + IL_00e3: ldfld ""int C.
d__0.<>s__3"" + IL_00e8: stloc.s V_6 + IL_00ea: ldloc.s V_6 + IL_00ec: ldc.i4.1 + IL_00ed: beq.s IL_00f1 + IL_00ef: br.s IL_00f3 + IL_00f1: leave.s IL_011d + IL_00f3: ldarg.0 + IL_00f4: ldnull + IL_00f5: stfld ""object C.
d__0.<>s__2"" + IL_00fa: ldarg.0 + IL_00fb: ldnull + IL_00fc: stfld ""C C.
d__0.<>s__1"" + IL_0101: leave.s IL_011d + } + catch System.Exception + { + IL_0103: stloc.s V_5 + IL_0105: ldarg.0 + IL_0106: ldc.i4.s -2 + IL_0108: stfld ""int C.
d__0.<>1__state"" + IL_010d: ldarg.0 + IL_010e: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_0113: ldloc.s V_5 + IL_0115: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_011a: nop + IL_011b: leave.s IL_0131 + } + IL_011d: ldarg.0 + IL_011e: ldc.i4.s -2 + IL_0120: stfld ""int C.
d__0.<>1__state"" + IL_0125: ldarg.0 + IL_0126: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_012b: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0130: nop + IL_0131: ret +} +"); + } + + [Fact] + public void TestWithExpression_03() + { + string source = @" +class C +{ + public static async System.Threading.Tasks.Task Main() + { + await using (new C()) + { + System.Console.Write(""body ""); + return; + } + } + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + verifier.VerifyIL("C.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" +{ + // Code size 306 (0x132) + .maxstack 3 + .locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.
d__0 V_4, + System.Exception V_5, + int V_6) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.
d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_000c + IL_000a: br.s IL_0011 + IL_000c: br IL_0099 + IL_0011: nop + IL_0012: ldarg.0 + IL_0013: newobj ""C..ctor()"" + IL_0018: stfld ""C C.
d__0.<>s__1"" + IL_001d: ldarg.0 + IL_001e: ldnull + IL_001f: stfld ""object C.
d__0.<>s__2"" + IL_0024: ldarg.0 + IL_0025: ldc.i4.0 + IL_0026: stfld ""int C.
d__0.<>s__3"" + .try + { + IL_002b: nop + IL_002c: ldstr ""body "" + IL_0031: call ""void System.Console.Write(string)"" + IL_0036: nop + IL_0037: br.s IL_0039 + IL_0039: ldarg.0 + IL_003a: ldc.i4.1 + IL_003b: stfld ""int C.
d__0.<>s__3"" + IL_0040: leave.s IL_004c + } + catch object + { + IL_0042: stloc.1 + IL_0043: ldarg.0 + IL_0044: ldloc.1 + IL_0045: stfld ""object C.
d__0.<>s__2"" + IL_004a: leave.s IL_004c + } + IL_004c: ldarg.0 + IL_004d: ldfld ""C C.
d__0.<>s__1"" + IL_0052: brfalse.s IL_00bd + IL_0054: ldarg.0 + IL_0055: ldfld ""C C.
d__0.<>s__1"" + IL_005a: callvirt ""System.Threading.Tasks.ValueTask C.DisposeAsync()"" + IL_005f: stloc.3 + IL_0060: ldloca.s V_3 + IL_0062: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0067: stloc.2 + IL_0068: ldloca.s V_2 + IL_006a: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_006f: brtrue.s IL_00b5 + IL_0071: ldarg.0 + IL_0072: ldc.i4.0 + IL_0073: dup + IL_0074: stloc.0 + IL_0075: stfld ""int C.
d__0.<>1__state"" + IL_007a: ldarg.0 + IL_007b: ldloc.2 + IL_007c: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_0081: ldarg.0 + IL_0082: stloc.s V_4 + IL_0084: ldarg.0 + IL_0085: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_008a: ldloca.s V_2 + IL_008c: ldloca.s V_4 + IL_008e: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.
d__0)"" + IL_0093: nop + IL_0094: leave IL_0131 + IL_0099: ldarg.0 + IL_009a: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_009f: stloc.2 + IL_00a0: ldarg.0 + IL_00a1: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.
d__0.<>u__1"" + IL_00a6: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00ac: ldarg.0 + IL_00ad: ldc.i4.m1 + IL_00ae: dup + IL_00af: stloc.0 + IL_00b0: stfld ""int C.
d__0.<>1__state"" + IL_00b5: ldloca.s V_2 + IL_00b7: call ""void System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00bc: nop + IL_00bd: ldarg.0 + IL_00be: ldfld ""object C.
d__0.<>s__2"" + IL_00c3: stloc.1 + IL_00c4: ldloc.1 + IL_00c5: brfalse.s IL_00e2 + IL_00c7: ldloc.1 + IL_00c8: isinst ""System.Exception"" + IL_00cd: stloc.s V_5 + IL_00cf: ldloc.s V_5 + IL_00d1: brtrue.s IL_00d5 + IL_00d3: ldloc.1 + IL_00d4: throw + IL_00d5: ldloc.s V_5 + IL_00d7: call ""System.Runtime.ExceptionServices.ExceptionDispatchInfo System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(System.Exception)"" + IL_00dc: callvirt ""void System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()"" + IL_00e1: nop + IL_00e2: ldarg.0 + IL_00e3: ldfld ""int C.
d__0.<>s__3"" + IL_00e8: stloc.s V_6 + IL_00ea: ldloc.s V_6 + IL_00ec: ldc.i4.1 + IL_00ed: beq.s IL_00f1 + IL_00ef: br.s IL_00f3 + IL_00f1: leave.s IL_011d + IL_00f3: ldarg.0 + IL_00f4: ldnull + IL_00f5: stfld ""object C.
d__0.<>s__2"" + IL_00fa: ldarg.0 + IL_00fb: ldnull + IL_00fc: stfld ""C C.
d__0.<>s__1"" + IL_0101: leave.s IL_011d + } + catch System.Exception + { + IL_0103: stloc.s V_5 + IL_0105: ldarg.0 + IL_0106: ldc.i4.s -2 + IL_0108: stfld ""int C.
d__0.<>1__state"" + IL_010d: ldarg.0 + IL_010e: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_0113: ldloc.s V_5 + IL_0115: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_011a: nop + IL_011b: leave.s IL_0131 + } + IL_011d: ldarg.0 + IL_011e: ldc.i4.s -2 + IL_0120: stfld ""int C.
d__0.<>1__state"" + IL_0125: ldarg.0 + IL_0126: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_012b: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0130: nop + IL_0131: ret +} +"); + } + + [Fact] + public void TestWithNullExpression() + { + string source = @" +class C +{ + public static async System.Threading.Tasks.Task Main() + { + await using (null) + { + System.Console.Write(""body""); + return; + } + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "body"); + } + + [Fact] + public void TestWithMethodName() + { + string source = @" +class C +{ + public static async System.Threading.Tasks.Task Main() + { + await using (Main) + { + } + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }); + comp.VerifyDiagnostics( + // (6,22): error CS8410: 'method group': type used in an async using statement must be implicitly convertible to 'System.IAsyncDisposable' + // await using (Main) + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "Main").WithArguments("method group").WithLocation(6, 22) + ); + } + + [Fact] + public void TestWithDynamicExpression() + { + string source = @" +class C : System.IAsyncDisposable +{ + public static async System.Threading.Tasks.Task Main() + { + dynamic d = new C(); + await using (d) + { + System.Console.Write(""body ""); + return; + } + } + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, references: new[] { CSharpRef }); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + } + + [Fact] + public void TestWithStructExpression_01() + { + string source = @" +struct S : System.IAsyncDisposable +{ + public static async System.Threading.Tasks.Task Main() + { + await using (new S()) + { + System.Console.Write(""body ""); + return; + } + } + System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + verifier.VerifyIL("S.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" +{ + // Code size 298 (0x12a) + .maxstack 3 + .locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + S.
d__0 V_4, + System.Exception V_5, + int V_6) + IL_0000: ldarg.0 + IL_0001: ldfld ""int S.
d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_000c + IL_000a: br.s IL_0011 + IL_000c: br IL_0098 + IL_0011: nop + IL_0012: ldarg.0 + IL_0013: ldflda ""S S.
d__0.<>s__1"" + IL_0018: initobj ""S"" + IL_001e: ldarg.0 + IL_001f: ldnull + IL_0020: stfld ""object S.
d__0.<>s__2"" + IL_0025: ldarg.0 + IL_0026: ldc.i4.0 + IL_0027: stfld ""int S.
d__0.<>s__3"" + .try + { + IL_002c: nop + IL_002d: ldstr ""body "" + IL_0032: call ""void System.Console.Write(string)"" + IL_0037: nop + IL_0038: br.s IL_003a + IL_003a: ldarg.0 + IL_003b: ldc.i4.1 + IL_003c: stfld ""int S.
d__0.<>s__3"" + IL_0041: leave.s IL_004d + } + catch object + { + IL_0043: stloc.1 + IL_0044: ldarg.0 + IL_0045: ldloc.1 + IL_0046: stfld ""object S.
d__0.<>s__2"" + IL_004b: leave.s IL_004d + } + IL_004d: ldarg.0 + IL_004e: ldflda ""S S.
d__0.<>s__1"" + IL_0053: constrained. ""S"" + IL_0059: callvirt ""System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()"" + IL_005e: stloc.3 + IL_005f: ldloca.s V_3 + IL_0061: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0066: stloc.2 + IL_0067: ldloca.s V_2 + IL_0069: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_006e: brtrue.s IL_00b4 IL_0070: ldarg.0 IL_0071: ldc.i4.0 IL_0072: dup @@ -1364,6 +1838,174 @@ .locals init (int V_0, }"); } + [Fact] + public void TestWithStructExpression_02() + { + string source = @" +struct S : System.IAsyncDisposable +{ + public static async System.Threading.Tasks.Task Main() + { + await using (new S()) + { + System.Console.Write(""body ""); + return; + } + } + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + verifier.VerifyIL("S.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" +{ + // Code size 292 (0x124) + .maxstack 3 + .locals init (int V_0, + object V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + S.
d__0 V_4, + System.Exception V_5, + int V_6) + IL_0000: ldarg.0 + IL_0001: ldfld ""int S.
d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_000c + IL_000a: br.s IL_0011 + IL_000c: br IL_0092 + IL_0011: nop + IL_0012: ldarg.0 + IL_0013: ldflda ""S S.
d__0.<>s__1"" + IL_0018: initobj ""S"" + IL_001e: ldarg.0 + IL_001f: ldnull + IL_0020: stfld ""object S.
d__0.<>s__2"" + IL_0025: ldarg.0 + IL_0026: ldc.i4.0 + IL_0027: stfld ""int S.
d__0.<>s__3"" + .try + { + IL_002c: nop + IL_002d: ldstr ""body "" + IL_0032: call ""void System.Console.Write(string)"" + IL_0037: nop + IL_0038: br.s IL_003a + IL_003a: ldarg.0 + IL_003b: ldc.i4.1 + IL_003c: stfld ""int S.
d__0.<>s__3"" + IL_0041: leave.s IL_004d + } + catch object + { + IL_0043: stloc.1 + IL_0044: ldarg.0 + IL_0045: ldloc.1 + IL_0046: stfld ""object S.
d__0.<>s__2"" + IL_004b: leave.s IL_004d + } + IL_004d: ldarg.0 + IL_004e: ldflda ""S S.
d__0.<>s__1"" + IL_0053: call ""System.Threading.Tasks.ValueTask S.DisposeAsync()"" + IL_0058: stloc.3 + IL_0059: ldloca.s V_3 + IL_005b: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0060: stloc.2 + IL_0061: ldloca.s V_2 + IL_0063: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0068: brtrue.s IL_00ae + IL_006a: ldarg.0 + IL_006b: ldc.i4.0 + IL_006c: dup + IL_006d: stloc.0 + IL_006e: stfld ""int S.
d__0.<>1__state"" + IL_0073: ldarg.0 + IL_0074: ldloc.2 + IL_0075: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter S.
d__0.<>u__1"" + IL_007a: ldarg.0 + IL_007b: stloc.s V_4 + IL_007d: ldarg.0 + IL_007e: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder S.
d__0.<>t__builder"" + IL_0083: ldloca.s V_2 + IL_0085: ldloca.s V_4 + IL_0087: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref S.
d__0)"" + IL_008c: nop + IL_008d: leave IL_0123 + IL_0092: ldarg.0 + IL_0093: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter S.
d__0.<>u__1"" + IL_0098: stloc.2 + IL_0099: ldarg.0 + IL_009a: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter S.
d__0.<>u__1"" + IL_009f: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00a5: ldarg.0 + IL_00a6: ldc.i4.m1 + IL_00a7: dup + IL_00a8: stloc.0 + IL_00a9: stfld ""int S.
d__0.<>1__state"" + IL_00ae: ldloca.s V_2 + IL_00b0: call ""void System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00b5: nop + IL_00b6: ldarg.0 + IL_00b7: ldfld ""object S.
d__0.<>s__2"" + IL_00bc: stloc.1 + IL_00bd: ldloc.1 + IL_00be: brfalse.s IL_00db + IL_00c0: ldloc.1 + IL_00c1: isinst ""System.Exception"" + IL_00c6: stloc.s V_5 + IL_00c8: ldloc.s V_5 + IL_00ca: brtrue.s IL_00ce + IL_00cc: ldloc.1 + IL_00cd: throw + IL_00ce: ldloc.s V_5 + IL_00d0: call ""System.Runtime.ExceptionServices.ExceptionDispatchInfo System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(System.Exception)"" + IL_00d5: callvirt ""void System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()"" + IL_00da: nop + IL_00db: ldarg.0 + IL_00dc: ldfld ""int S.
d__0.<>s__3"" + IL_00e1: stloc.s V_6 + IL_00e3: ldloc.s V_6 + IL_00e5: ldc.i4.1 + IL_00e6: beq.s IL_00ea + IL_00e8: br.s IL_00ec + IL_00ea: leave.s IL_010f + IL_00ec: ldarg.0 + IL_00ed: ldnull + IL_00ee: stfld ""object S.
d__0.<>s__2"" + IL_00f3: leave.s IL_010f + } + catch System.Exception + { + IL_00f5: stloc.s V_5 + IL_00f7: ldarg.0 + IL_00f8: ldc.i4.s -2 + IL_00fa: stfld ""int S.
d__0.<>1__state"" + IL_00ff: ldarg.0 + IL_0100: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder S.
d__0.<>t__builder"" + IL_0105: ldloc.s V_5 + IL_0107: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_010c: nop + IL_010d: leave.s IL_0123 + } + IL_010f: ldarg.0 + IL_0110: ldc.i4.s -2 + IL_0112: stfld ""int S.
d__0.<>1__state"" + IL_0117: ldarg.0 + IL_0118: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder S.
d__0.<>t__builder"" + IL_011d: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0122: nop + IL_0123: ret +}"); + } + [Fact] public void Struct_ExplicitImplementation() { @@ -1833,12 +2475,9 @@ public async System.Threading.Tasks.ValueTask DisposeAsync() [Fact] [WorkItem(32316, "https://github.com/dotnet/roslyn/issues/32316")] - public void TestPatternBasedDisposal_InterfacePreferredOverInstanceMethod() + [WorkItem("https://github.com/dotnet/roslyn/issues/72573")] + public void TestPatternBasedDisposal_InterfaceNotPreferredOverInstanceMethod() { - // SPEC: In the situation where a type can be implicitly converted to IDisposable and also fits the disposable pattern, - // then IDisposable will be preferred. - // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/using.md#pattern-based-using - string source = @" public class C : System.IAsyncDisposable { @@ -1852,14 +2491,14 @@ public static async System.Threading.Tasks.Task Main() System.Console.Write(""return""); return 1; } - async System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() + System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync() + => throw null; + public async System.Threading.Tasks.ValueTask DisposeAsync() { System.Console.Write($""dispose_start ""); await System.Threading.Tasks.Task.Yield(); System.Console.Write($""dispose_end ""); } - public System.Threading.Tasks.ValueTask DisposeAsync() - => throw null; } "; var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs index 5f9ec11066ab2..eeb219d30cbc1 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs @@ -1320,14 +1320,14 @@ static async Task Main() } [Fact] - public void UsingDeclarationAsyncMissingValueTask() + public void UsingDeclarationAsyncMissingValueTask_01() { var source = @" using System; using System.Threading.Tasks; class C1 : IAsyncDisposable { - public ValueTask DisposeAsync() + ValueTask IAsyncDisposable.DisposeAsync() { return new ValueTask(Task.CompletedTask); } @@ -1350,6 +1350,33 @@ static async Task Main() ); } + [Fact] + public void UsingDeclarationAsyncMissingValueTask_02() + { + var source = @" +using System; +using System.Threading.Tasks; +class C1 : IAsyncDisposable +{ + public ValueTask DisposeAsync() + { + return new ValueTask(Task.CompletedTask); + } +} + +class C2 +{ + static async Task Main() + { + await using C1 c1 = new C1(); + } +}"; + + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }); + comp.MakeTypeMissing(WellKnownType.System_Threading_Tasks_ValueTask); + comp.VerifyEmitDiagnostics(); + } + [Fact] public void UsingDeclarationAsync_WithOptionalParameter() { diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs index cb6982d66aee2..03330c6a0b824 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs @@ -93,7 +93,7 @@ public static async Task M1(IAsyncDisposable disposable) } "; string expectedOperationTree = @" -IUsingOperation (IsAsynchronous) (OperationKind.Using, Type: null) (Syntax: 'await using ... }') +IUsingOperation (IsAsynchronous) (DisposeMethod: System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Using, Type: null) (Syntax: 'await using ... }') Locals: Local_1: System.IAsyncDisposable c Resources: IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsImplicit) (Syntax: 'var c = disposable') @@ -126,6 +126,124 @@ public static async Task M1(IAsyncDisposable disposable) VerifyOperationTreeAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedOperationTree, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.AsyncStreams)] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] + public void IUsingAwaitStatement_SimpleAwaitUsing_PatternBased() + { + string source = @" +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +class C +{ + public async Task M1() + { + /**/await using (var c = this) + { + Console.WriteLine(c.ToString()); + }/**/ + } + + public async System.Threading.Tasks.ValueTask DisposeAsync() + { + await Task.Yield(); + } +} +"; + string expectedOperationTree = @" +IUsingOperation (IsAsynchronous) (DisposeMethod: System.Threading.Tasks.ValueTask C.DisposeAsync()) (OperationKind.Using, Type: null) (Syntax: 'await using ... }') + Locals: Local_1: C c + Resources: + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsImplicit) (Syntax: 'var c = this') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'var c = this') + Declarators: + IVariableDeclaratorOperation (Symbol: C c) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'c = this') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= this') + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Initializer: + null + Body: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'Console.Wri ... oString());') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Console.Wri ... ToString())') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'c.ToString()') + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'c.ToString()') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"; + + var expectedDiagnostics = DiagnosticDescription.None; + VerifyOperationTreeAndDiagnosticsForTest(source + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.AsyncStreams)] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] + public void IUsingAwaitStatement_SimpleAwaitUsing_InterfaceBased() + { + string source = @" +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +class C : IAsyncDisposable +{ + public async Task M1() + { + /**/await using (var c = this) + { + Console.WriteLine(c.ToString()); + }/**/ + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await Task.Yield(); + } +} +"; + string expectedOperationTree = @" +IUsingOperation (IsAsynchronous) (OperationKind.Using, Type: null) (Syntax: 'await using ... }') + Locals: Local_1: C c + Resources: + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsImplicit) (Syntax: 'var c = this') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'var c = this') + Declarators: + IVariableDeclaratorOperation (Symbol: C c) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'c = this') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= this') + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Initializer: + null + Body: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'Console.Wri ... oString());') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Console.Wri ... ToString())') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'c.ToString()') + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'c.ToString()') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"; + + var expectedDiagnostics = DiagnosticDescription.None; + VerifyOperationTreeAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedOperationTree, expectedDiagnostics); + } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] public void UsingFlow_SimpleAwaitUsing() @@ -159,9 +277,9 @@ public static async Task M1(IAsyncDisposable disposable) Predecessors: [B0] Statements (1) ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') - Left: + Left: ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') - Right: + Right: IParameterReferenceOperation: disposable (OperationKind.ParameterReference, Type: System.IAsyncDisposable) (Syntax: 'disposable') Next (Regular) Block[B2] Entering: {R2} {R3} @@ -171,14 +289,14 @@ public static async Task M1(IAsyncDisposable disposable) Predecessors: [B1] Statements (1) IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'Console.Wri ... oString());') - Expression: + Expression: IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Console.Wri ... ToString())') - Instance Receiver: + Instance Receiver: null Arguments(1): IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'c.ToString()') IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'c.ToString()') - Instance Receiver: + Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.IAsyncDisposable) (Syntax: 'c') Arguments(0) InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -194,16 +312,16 @@ public static async Task M1(IAsyncDisposable disposable) Statements (0) Jump if True (Regular) to Block[B5] IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'c = disposable') - Operand: + Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') Next (Regular) Block[B4] Block[B4] - Block Predecessors: [B3] Statements (1) IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'c = disposable') - Expression: - IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'c = disposable') - Instance Receiver: + Expression: + IInvocationOperation ( System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'c = disposable') + Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = disposable') Arguments(0) Next (Regular) Block[B5] @@ -222,6 +340,212 @@ public static async Task M1(IAsyncDisposable disposable) VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] + public void UsingFlow_SimpleAwaitUsing_PatternBased() + { + string source = @" +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +class C +{ + public async Task M1() + /**/{ + await using (var c = this) + { + Console.WriteLine(c.ToString()); + } + }/**/ + + public async ValueTask DisposeAsync() + { + await System.Threading.Tasks.Task.Yield(); + } +} +"; + + string expectedGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = this') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Right: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Next (Regular) Block[B2] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'Console.Wri ... oString());') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Console.Wri ... ToString())') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'c.ToString()') + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'c.ToString()') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B6] + Finalizing: {R4} + Leaving: {R3} {R2} {R1} + } + .finally {R4} + { + Block[B3] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B5] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'c = this') + Operand: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'c = this') + Expression: + IInvocationOperation ( System.Threading.Tasks.ValueTask C.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'c = this') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Arguments(0) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B6] - Exit + Predecessors: [B2] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow, CompilerFeature.AsyncStreams)] + [Fact, WorkItem(30362, "https://github.com/dotnet/roslyn/issues/30362")] + public void UsingFlow_SimpleAwaitUsing_InterfaceBased() + { + string source = @" +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +class C : IAsyncDisposable +{ + public async Task M1() + /**/{ + await using (var c = this) + { + Console.WriteLine(c.ToString()); + } + }/**/ + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await System.Threading.Tasks.Task.Yield(); + } +} +"; + + string expectedGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = this') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Right: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Next (Regular) Block[B2] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'Console.Wri ... oString());') + Expression: + IInvocationOperation (void System.Console.WriteLine(System.String value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Console.Wri ... ToString())') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'c.ToString()') + IInvocationOperation (virtual System.String System.Object.ToString()) (OperationKind.Invocation, Type: System.String) (Syntax: 'c.ToString()') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B6] + Finalizing: {R4} + Leaving: {R3} {R2} {R1} + } + .finally {R4} + { + Block[B3] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B5] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'c = this') + Operand: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IAwaitOperation (OperationKind.Await, Type: System.Void, IsImplicit) (Syntax: 'c = this') + Expression: + IInvocationOperation (virtual System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()) (OperationKind.Invocation, Type: System.Threading.Tasks.ValueTask, IsImplicit) (Syntax: 'c = this') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IAsyncDisposable, IsImplicit) (Syntax: 'c = this') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = this') + Arguments(0) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B6] - Exit + Predecessors: [B2] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source + s_IAsyncEnumerable + IOperationTests_IForEachLoopStatement.s_ValueTask, expectedGraph, expectedDiagnostics); + } + [CompilerTrait(CompilerFeature.IOperation)] [Fact] public void IUsingStatement_MultipleNewVariable() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs index 7646942ebd539..da09f7cdd28d2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UsingDeclarationTests.cs @@ -867,7 +867,10 @@ static async Task Main() "; var expected = new[] { - // (8,15): error CS8652: The feature 'using declarations' is not available in C# 7.3. Please use language version 8.0 or greater. + // 0.cs(8,9): error CS8370: Feature 'pattern-based disposal' is not available in C# 7.3. Please use language version 8.0 or greater. + // await using IAsyncDisposable x = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "await using IAsyncDisposable x = null;").WithArguments("pattern-based disposal", "8.0").WithLocation(8, 9), + // 0.cs(8,15): error CS8370: Feature 'using declarations' is not available in C# 7.3. Please use language version 8.0 or greater. // await using IAsyncDisposable x = null; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "using").WithArguments("using declarations", "8.0").WithLocation(8, 15) };