Skip to content

Commit

Permalink
Delay reporting of pattern-based using diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Jun 6, 2024
1 parent bc5ea08 commit 2574f41
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 9 deletions.
16 changes: 13 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -228,6 +229,8 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI
{
awaitableType = disposeMethod.ReturnType;
}

diagnostics.AddRange(patternDiagnostics);
return true;
}
}
Expand All @@ -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())
Expand All @@ -261,6 +270,7 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI
Error(diagnostics, errorCode, syntax, declarationTypeOpt ?? expressionOpt!.Display);
}

diagnostics.AddRange(patternDiagnostics);
return false;
}

Expand Down
142 changes: 142 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitUsingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object> 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<object> 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<object>)' requires a receiver of type 'System.Collections.Generic.IEnumerable<object>'
// 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<object>)", "System.Collections.Generic.IEnumerable<object>").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<object> 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));
}
}
}
9 changes: 3 additions & 6 deletions src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()'
// 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", "T").WithLocation(36, 22));
}

[ConditionalFact(typeof(NoUsedAssembliesValidation))] // https://github.com/dotnet/roslyn/issues/73563
Expand Down

0 comments on commit 2574f41

Please sign in to comment.