From 2574f4143bef6e1b1b2f1513a33e84c23739d816 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 4 Jun 2024 14:31:23 -0700 Subject: [PATCH] Delay reporting of pattern-based using diagnostics --- .../Portable/Binder/UsingStatementBinder.cs | 16 +- .../Emit/CodeGen/CodeGenAwaitUsingTests.cs | 142 ++++++++++++++++++ .../Test/Emit3/RefStructInterfacesTests.cs | 9 +- 3 files changed, 158 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index 8bf71321ff48f..c0e5997ad30d9 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -190,14 +190,15 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI // Pattern-based binding // If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern. + BindingDiagnosticBag? patternDiagnostics = null; if (type is object && (type.IsRefLikeType || hasAwait)) { BoundExpression? receiver = fromExpression ? expressionOpt : new BoundLocal(syntax, declarationsOpt[0].LocalSymbol, null, type) { WasCompilerGenerated = true }; - BindingDiagnosticBag patternDiagnostics = originalBinder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureDisposalPattern) - ? diagnostics + patternDiagnostics = originalBinder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureDisposalPattern) + ? BindingDiagnosticBag.GetInstance(diagnostics) : BindingDiagnosticBag.Discarded; MethodSymbol disposeMethod = originalBinder.TryFindDisposePatternMethod(receiver, syntax, hasAwait, patternDiagnostics, out bool expanded); if (disposeMethod is object) @@ -228,6 +229,8 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI { awaitableType = disposeMethod.ReturnType; } + + diagnostics.AddRange(patternDiagnostics); return true; } } @@ -245,7 +248,13 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI awaitableType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); } - return !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); + var wasSuccess = !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); + if (!wasSuccess) + { + diagnostics.AddRange(patternDiagnostics); + } + + return wasSuccess; } if (type is null || !type.IsErrorType()) @@ -261,6 +270,7 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI Error(diagnostics, errorCode, syntax, declarationTypeOpt ?? expressionOpt!.Display); } + diagnostics.AddRange(patternDiagnostics); return false; } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs index 7282e99ed475f..47be86467ccb2 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs @@ -3519,5 +3519,147 @@ public ValueTask DisposeAsync() comp = CreateCompilationWithTasksExtensions(source, options: TestOptions.ReleaseExe); CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73691")] + public void PatternBasedFails_WithInterfaceImplementation() + { + var source = """ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +await using var x = new Class1(); + +internal class Class1 : IAsyncDisposable +{ + async ValueTask IAsyncDisposable.DisposeAsync() + { + System.Console.Write("DISPOSED"); + await Task.Yield(); + } +} + +internal static class EnumerableExtensions +{ + public static ValueTask DisposeAsync(this IEnumerable objects) + { + throw null; + } +} +"""; + var comp = CreateCompilationWithTasksExtensions([source, IAsyncDisposableDefinition]); + CompileAndVerify(comp, expectedOutput: "DISPOSED").VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73691")] + public void PatternBasedFails_NoInterfaceImplementation() + { + var source = """ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +await using var x = new Class1(); + +internal class Class1 { } + +internal static class EnumerableExtensions +{ + public static ValueTask DisposeAsync(this IEnumerable objects) + { + throw null; + } +} +"""; + var comp = CreateCompilationWithTasksExtensions([source, IAsyncDisposableDefinition]); + comp.VerifyEmitDiagnostics( + // (5,1): error CS8410: 'Class1': type used in an asynchronous using statement must be implicitly convertible to 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x = new Class1(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x = new Class1();").WithArguments("Class1").WithLocation(5, 1), + // (5,1): error CS1929: 'Class1' does not contain a definition for 'DisposeAsync' and the best extension method overload 'EnumerableExtensions.DisposeAsync(IEnumerable)' requires a receiver of type 'System.Collections.Generic.IEnumerable' + // await using var x = new Class1(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "await using var x = new Class1();").WithArguments("Class1", "DisposeAsync", "EnumerableExtensions.DisposeAsync(System.Collections.Generic.IEnumerable)", "System.Collections.Generic.IEnumerable").WithLocation(5, 1)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73691")] + public void PatternBasedFails_WithInterfaceImplementation_UseSite() + { + // We attempt to bind pattern-based disposal (and collect diagnostics) + // then we bind to the IAsyncDisposable interface, which reports a use-site error + // and so we add the collected diagnostics + var source = """ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +internal class Class1 : IAsyncDisposable +{ + ValueTask IAsyncDisposable.DisposeAsync() + { + throw null; + } + + public async Task MethodWithCompilerError() + { + await using var x = new Class1(); + } +} + +internal static class EnumerableExtensions +{ + public static ValueTask DisposeAsync(this IEnumerable objects) + { + throw null; + } +} + +namespace System.Threading.Tasks +{ + public struct ValueTask + { + public Awaiter GetAwaiter() => null; + public class Awaiter : System.Runtime.CompilerServices.INotifyCompletion + { + public void OnCompleted(Action a) { } + public bool IsCompleted => true; + public void GetResult() { } + } + } +} +"""; + + var ilSrc = """ +.class interface public auto ansi abstract beforefieldinit System.IAsyncDisposable +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute::.ctor(string) = ( 01 00 02 68 69 00 00 ) + .method public hidebysig newslot abstract virtual instance valuetype [mscorlib]System.Threading.Tasks.ValueTask DisposeAsync () cil managed + { + } +} +"""; + var comp = CreateCompilationWithIL(source, ilSrc); + comp.VerifyEmitDiagnostics( + // (5,16): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // internal class Class1 : IAsyncDisposable + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "Class1").WithArguments("System.IAsyncDisposable", "hi").WithLocation(5, 16), + // (5,25): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // internal class Class1 : IAsyncDisposable + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "IAsyncDisposable").WithArguments("System.IAsyncDisposable", "hi").WithLocation(5, 25), + // (7,15): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // ValueTask IAsyncDisposable.DisposeAsync() + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "IAsyncDisposable").WithArguments("System.IAsyncDisposable", "hi").WithLocation(7, 15), + // (7,32): error CS0539: 'Class1.DisposeAsync()' in explicit interface declaration is not found among members of the interface that can be implemented + // ValueTask IAsyncDisposable.DisposeAsync() + Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "DisposeAsync").WithArguments("Class1.DisposeAsync()").WithLocation(7, 32), + // (14,9): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // await using var x = new Class1(); + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "await using var x = new Class1();").WithArguments("System.IAsyncDisposable", "hi").WithLocation(14, 9), + // (14,9): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // await using var x = new Class1(); + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "await").WithArguments("System.IAsyncDisposable", "hi").WithLocation(14, 9), + // (14,9): error CS9041: 'IAsyncDisposable' requires compiler feature 'hi', which is not supported by this version of the C# compiler. + // await using var x = new Class1(); + Diagnostic(ErrorCode.ERR_UnsupportedCompilerFeature, "await using var x = new Class1();").WithArguments("System.IAsyncDisposable", "hi").WithLocation(14, 9)); + } } } diff --git a/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs b/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs index 88487d7c94dac..1b8766fc31fbb 100644 --- a/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs @@ -13557,13 +13557,10 @@ static async Task Main() } "; var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.ReleaseExe); - - // https://github.com/dotnet/roslyn/issues/72819: The failure is likely unexpected, but is not specific to `allow ref struct` scenario. - comp.VerifyDiagnostics( - // (36,22): error CS0121: The call is ambiguous between the following methods or properties: 'IMyAsyncDisposable1.DisposeAsync()' and 'IMyAsyncDisposable2.DisposeAsync()' + comp.VerifyEmitDiagnostics( + // (36,22): error CS9244: The type 'T' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T' in the generic type or method 'Activator.CreateInstance()' // await using (new T()) - Diagnostic(ErrorCode.ERR_AmbigCall, "new T()").WithArguments("IMyAsyncDisposable1.DisposeAsync()", "IMyAsyncDisposable2.DisposeAsync()").WithLocation(36, 22) - ); + Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "new T()").WithArguments("System.Activator.CreateInstance()", "T", "T").WithLocation(36, 22)); } [ConditionalFact(typeof(NoUsedAssembliesValidation))] // https://github.com/dotnet/roslyn/issues/73563