From 156efe98099a9d9247f0fc5ad5ec9ad7f3f8f5be Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 14 Mar 2025 11:28:46 -0700 Subject: [PATCH] Extensions: flesh out behavior for some pattern-based binding scenarios --- .../Portable/Binder/Binder_Conversions.cs | 4 +- .../Portable/Binder/Binder_Deconstruct.cs | 3 +- .../Portable/Binder/Binder_Expressions.cs | 21 +- .../Portable/Binder/Binder_Invocation.cs | 9 +- .../Portable/Binder/Binder_Statements.cs | 6 +- .../Portable/Binder/ForEachLoopBinder.cs | 3 +- .../Test/Emit3/Semantics/ExtensionTests.cs | 221 ++++++++++++++---- 7 files changed, 215 insertions(+), 52 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index ad6e080572e33..d3b2a77135c4a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -1361,7 +1361,8 @@ static bool bindMethodGroupInvocation( var resolution = addMethodBinder.ResolveMethodGroup( methodGroup, expression, WellKnownMemberNames.CollectionInitializerAddMethodName, analyzedArguments, useSiteInfo: ref useSiteInfo, - options: OverloadResolution.Options.DynamicResolution | OverloadResolution.Options.DynamicConvertsToAnything); + options: OverloadResolution.Options.DynamicResolution | OverloadResolution.Options.DynamicConvertsToAnything, + acceptOnlyMethods: true); diagnostics.Add(expression, useSiteInfo); @@ -1369,6 +1370,7 @@ static bool bindMethodGroupInvocation( if (resolution.IsNonMethodExtensionMember(out Symbol? extensionMember)) { + Debug.Assert(false); // Should not get here given the 'acceptOnlyMethods' argument value used in 'ResolveMethodGroup' call above ReportMakeInvocationExpressionBadMemberKind(syntax, WellKnownMemberNames.CollectionInitializerAddMethodName, methodGroup, diagnostics); addMethods = []; result = false; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index b8ead0eef4f39..36f16a496685c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -669,7 +669,8 @@ private BoundExpression MakeDeconstructInvocationExpression( // Those placeholders are also recorded in the outVar for easy access below, by the `SetInferredType` call on the outVar nodes. BoundExpression result = BindMethodGroupInvocation( rightSyntax, rightSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null, - ignoreNormalFormIfHasValidParamsParameter: false, anyApplicableCandidates: out anyApplicableCandidates); + ignoreNormalFormIfHasValidParamsParameter: false, anyApplicableCandidates: out anyApplicableCandidates, + disallowExpandedNonArrayParams: false, acceptOnlyMethods: true); result.WasCompilerGenerated = true; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index fc602212e1978..68a1487b33756 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7994,7 +7994,7 @@ private BoundExpression MakeMemberAccessValue(BoundExpression expr, BindingDiagn // Note: we're resolving without arguments, which means we're not treating the member access as invoked var resolution = this.ResolveExtension( syntax, name, analyzedArguments: null, receiver, typeArgumentsOpt, options: OverloadResolution.Options.None, - returnRefKind: default, returnType: null, ref useSiteInfo); + returnRefKind: default, returnType: null, ref useSiteInfo, acceptOnlyMethods: false); diagnostics.Add(syntax, useSiteInfo); @@ -8492,6 +8492,7 @@ protected MethodGroupResolution ResolveExtension( RefKind returnRefKind, TypeSymbol? returnType, ref CompoundUseSiteInfo useSiteInfo, + bool acceptOnlyMethods, in CallingConventionInfo callingConvention = default) { Debug.Assert(left.Type is not null); @@ -8534,6 +8535,7 @@ protected MethodGroupResolution ResolveExtension( typeArgumentsWithAnnotations, returnType, returnRefKind, lookupResult, analyzedArguments, ref actualMethodArguments, ref actualReceiverArguments, ref useSiteInfo, ref firstResult, options, callingConvention, classicExtensionLookupResult, lookupOptions, binder: this, scope: scope, diagnostics: diagnostics, + acceptOnlyMethods: acceptOnlyMethods, result: out MethodGroupResolution result)) { lookupResult.Free(); @@ -8582,6 +8584,7 @@ static bool tryResolveExtensionInScope( LookupOptions lookupOptions, Binder binder, ExtensionScope scope, + bool acceptOnlyMethods, BindingDiagnosticBag diagnostics, out MethodGroupResolution result) { @@ -8610,7 +8613,9 @@ static bool tryResolveExtensionInScope( // 3. resolve properties Debug.Assert(arity == 0 || lookupResult.Symbols.All(s => s.Kind != SymbolKind.Property)); - OverloadResolutionResult? propertyResult = arity != 0 ? null : resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo); + + // PROTOTYPE: Regarding 'acceptOnlyMethods', consider if it would be better to add a special 'LookupOptions' value to filter out properties during lookup + OverloadResolutionResult? propertyResult = arity != 0 || acceptOnlyMethods ? null : resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo); // 4. determine member kind if (!methodResult.HasAnyApplicableMethod && propertyResult?.HasAnyApplicableMember != true) @@ -10465,7 +10470,9 @@ void makeCall(SyntaxNode syntax, BoundExpression receiver, MethodSymbol method, { WasCompilerGenerated = true }; indexerOrSliceAccess = BindMethodGroupInvocation(syntax, syntax, method.Name, boundMethodGroup, analyzedArguments, - diagnostics, queryClause: null, ignoreNormalFormIfHasValidParamsParameter: true, anyApplicableCandidates: out bool _).MakeCompilerGenerated(); + diagnostics, queryClause: null, ignoreNormalFormIfHasValidParamsParameter: true, anyApplicableCandidates: out bool _, + disallowExpandedNonArrayParams: false, + acceptOnlyMethods: true).MakeCompilerGenerated(); // PROTOTYPE: Test effect of acceptOnlyMethods value analyzedArguments.Free(); } @@ -10593,7 +10600,9 @@ internal MethodGroupResolution ResolveMethodGroup( return ResolveMethodGroup( node, node.Syntax, node.Name, analyzedArguments, ref useSiteInfo, - options, returnRefKind: returnRefKind, returnType: returnType, + options, + acceptOnlyMethods: true, // PROTOTYPE: Confirm this value is appropriate for all consumers of the enclosing method and test effect of this value for all of them + returnRefKind: returnRefKind, returnType: returnType, callingConventionInfo: callingConventionInfo); } @@ -10604,6 +10613,7 @@ internal MethodGroupResolution ResolveMethodGroup( AnalyzedArguments analyzedArguments, ref CompoundUseSiteInfo useSiteInfo, OverloadResolution.Options options, + bool acceptOnlyMethods, RefKind returnRefKind = default, TypeSymbol returnType = null, in CallingConventionInfo callingConventionInfo = default) @@ -10611,6 +10621,7 @@ internal MethodGroupResolution ResolveMethodGroup( var methodResolution = ResolveMethodGroupInternal( node, expression, memberName, analyzedArguments, ref useSiteInfo, options, + acceptOnlyMethods: acceptOnlyMethods, returnRefKind: returnRefKind, returnType: returnType, callingConvention: callingConventionInfo); if (methodResolution.IsEmpty && !methodResolution.HasAnyErrors) @@ -10634,6 +10645,7 @@ private MethodGroupResolution ResolveMethodGroupInternal( AnalyzedArguments analyzedArguments, ref CompoundUseSiteInfo useSiteInfo, OverloadResolution.Options options, + bool acceptOnlyMethods, RefKind returnRefKind = default, TypeSymbol returnType = null, in CallingConventionInfo callingConvention = default) @@ -10653,6 +10665,7 @@ private MethodGroupResolution ResolveMethodGroupInternal( var extensionMethodResolution = ResolveExtension( expression, memberName, analyzedArguments, methodGroup.ReceiverOpt, methodGroup.TypeArgumentsOpt, options, returnRefKind: returnRefKind, returnType: returnType, ref useSiteInfo, + acceptOnlyMethods: acceptOnlyMethods, in callingConvention); bool preferExtensionMethodResolution = false; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 4364196db4bbd..2b5c9895ab0a7 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -355,7 +355,8 @@ private BoundExpression BindInvocationExpression( diagnostics, queryClause, ignoreNormalFormIfHasValidParamsParameter: ignoreNormalFormIfHasValidParamsParameter, disallowExpandedNonArrayParams: disallowExpandedNonArrayParams, - anyApplicableCandidates: out _); + anyApplicableCandidates: out _, + acceptOnlyMethods: false); } else if ((object)(delegateType = GetDelegateType(boundExpression)) != null) { @@ -696,7 +697,8 @@ private BoundExpression BindMethodGroupInvocation( CSharpSyntaxNode queryClause, bool ignoreNormalFormIfHasValidParamsParameter, out bool anyApplicableCandidates, - bool disallowExpandedNonArrayParams = false) + bool disallowExpandedNonArrayParams, + bool acceptOnlyMethods) // For example, do not accept extension property value invocations (delegates returned by a property can be invoked, etc.) { // // !!! ATTENTION !!! @@ -713,7 +715,8 @@ private BoundExpression BindMethodGroupInvocation( useSiteInfo: ref useSiteInfo, options: (ignoreNormalFormIfHasValidParamsParameter ? OverloadResolution.Options.IgnoreNormalFormIfHasValidParamsParameter : OverloadResolution.Options.None) | (disallowExpandedNonArrayParams ? OverloadResolution.Options.DisallowExpandedNonArrayParams : OverloadResolution.Options.None) | - (analyzedArguments.HasDynamicArgument ? OverloadResolution.Options.DynamicResolution : OverloadResolution.Options.None)); + (analyzedArguments.HasDynamicArgument ? OverloadResolution.Options.DynamicResolution : OverloadResolution.Options.None), + acceptOnlyMethods: acceptOnlyMethods); diagnostics.Add(expression, useSiteInfo); if (resolution.IsNonMethodExtensionMember(out Symbol extensionMember)) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 6f967c7253550..535959ca12a0a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -766,7 +766,7 @@ internal MethodSymbol TryFindDisposePatternMethod(BoundExpression expr, SyntaxNo out var disposeMethod, out isExpanded); - if (disposeMethod?.IsExtensionMethod == true) + if (disposeMethod is not null && (disposeMethod.IsExtensionMethod || disposeMethod.GetIsNewExtensionMember())) { // Extension methods should just be ignored, rather than rejected after-the-fact // Tracked by https://github.com/dotnet/roslyn/issues/32767 @@ -4153,7 +4153,9 @@ internal PatternLookupResult PerformPatternMethodLookup(BoundExpression receiver bindingDiagnostics, queryClause: null, ignoreNormalFormIfHasValidParamsParameter: true, - anyApplicableCandidates: out _); + anyApplicableCandidates: out _, + disallowExpandedNonArrayParams: false, + acceptOnlyMethods: true); analyzedArguments.Free(); diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 35ce85bb14fb7..117eaf3bafec4 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -1511,7 +1511,8 @@ private MethodArgumentInfo FindForEachPatternMethodViaExtension(SyntaxNode synta options: OverloadResolution.Options.None, returnRefKind: default, returnType: null, - ref extensionUseSiteInfo); + ref extensionUseSiteInfo, + acceptOnlyMethods: true); // PROTOTYPE: Test effect of acceptOnlyMethods value diagnostics.Add(syntax, extensionUseSiteInfo); diagnostics.AddRange(methodGroupResolutionResult.Diagnostics); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 69ca80d39ad8a..a360113431748 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -14388,9 +14388,10 @@ public class MyCollection : IEnumerable """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (4,18): error CS0118: 'Add' is a property but is used like a method + // (4,18): error CS1061: 'MyCollection' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'MyCollection' could be found (are you missing a using directive or an assembly reference?) // MyCollection c = [42]; - Diagnostic(ErrorCode.ERR_BadSKknown, "[42]").WithArguments("Add", "property", "method").WithLocation(4, 18)); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "[42]").WithArguments("MyCollection", "Add").WithLocation(4, 18) + ); } [Fact] @@ -18523,16 +18524,22 @@ public static class E2 public void ExtensionMemberLookup_PatternBased_Deconstruct_DelegateTypeProperty() { var src = """ -var (x, y) = new C(); -System.Console.Write((x, y)); +var (x1, y1) = new C1(); -class C { } +var (x2, y2) = new C2(); + +class C1 { } + +class C2 +{ + public D Deconstruct => (out int i, out int j) => { i = 42; j = 43; }; +} delegate void D(out int i, out int j); static class E { - extension(C c) + extension(C1 c) { public D Deconstruct => (out int i, out int j) => { i = 42; j = 43; }; } @@ -18540,14 +18547,39 @@ static class E """; var comp = CreateCompilation(src); // PROTOTYPE revisit pattern-based deconstruction - CompileAndVerify(comp, expectedOutput: "(42, 43)").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (1,6): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'x1'. + // var (x1, y1) = new C1(); + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "x1").WithArguments("x1").WithLocation(1, 6), + // (1,10): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'y1'. + // var (x1, y1) = new C1(); + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "y1").WithArguments("y1").WithLocation(1, 10), + + // PROTOTYPE: It looks like the following error is not reported for instance scenario. Noise? + + // (1,16): error CS1061: 'C1' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?) + // var (x1, y1) = new C1(); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "new C1()").WithArguments("C1", "Deconstruct").WithLocation(1, 16), + + // (1,16): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C1', with 2 out parameters and a void return type. + // var (x1, y1) = new C1(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C1()").WithArguments("C1", "2").WithLocation(1, 16), + // (3,6): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'x2'. + // var (x2, y2) = new C2(); + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "x2").WithArguments("x2").WithLocation(3, 6), + // (3,10): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'y2'. + // var (x2, y2) = new C2(); + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "y2").WithArguments("y2").WithLocation(3, 10), + // (3,16): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C2', with 2 out parameters and a void return type. + // var (x2, y2) = new C2(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C2()").WithArguments("C2", "2").WithLocation(3, 16) + ); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree); var deconstruction = tree.GetRoot().DescendantNodes().OfType().First(); - Assert.Equal("void D.Invoke(out System.Int32 i, out System.Int32 j)", - model.GetDeconstructionInfo(deconstruction).Method.ToTestDisplayString()); + Assert.Null(model.GetDeconstructionInfo(deconstruction).Method); } [Fact(Skip = "PROTOTYPE Asserts in BindDynamicInvocation")] @@ -18626,14 +18658,17 @@ public void ExtensionMemberLookup_PatternBased_Dispose_Async_NoMethod() using System.Threading.Tasks; /**/ -await using var x = new C(); +await using var x1 = new C1(); /**/ -class C { } +await using var x2 = new C2(); + +class C1 { } +class C2 { } static class E { - extension(C c) + extension(C1 c) { public async Task DisposeAsync() { @@ -18641,33 +18676,102 @@ public async Task DisposeAsync() await Task.Yield(); } } + + public static async Task DisposeAsync(this C2 c) + { + System.Console.Write("RAN"); + await Task.Yield(); + } } """; var comp = CreateCompilation(src); // PROTOTYPE confirm when spec'ing pattern-based disposal - CompileAndVerify(comp, expectedOutput: "RAN").VerifyDiagnostics(); + + var expectedDiagnostics = new[] { + // (4,1): error CS8410: 'C1': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x1 = new C1(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x1 = new C1();").WithArguments("C1").WithLocation(4, 1), + // (7,1): error CS8410: 'C2': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x2 = new C2(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x2 = new C2();").WithArguments("C2").WithLocation(7, 1) + }; + + comp.VerifyDiagnostics(expectedDiagnostics); string expectedOperationTree = """ -IUsingDeclarationOperation(IsAsynchronous: True, DisposeMethod: System.Threading.Tasks.Task E.<>E__0.DisposeAsync()) (OperationKind.UsingDeclaration, Type: null) (Syntax: 'await using ... = new C();') -DeclarationGroup: - IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsImplicit) (Syntax: 'await using ... = new C();') - IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'var x = new C()') - Declarators: - IVariableDeclaratorOperation (Symbol: C x) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'x = new C()') - Initializer: - IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null) (Syntax: '= new C()') - IObjectCreationOperation (Constructor: C..ctor()) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C()') - Arguments(0) - Initializer: - null - Initializer: - null +IUsingDeclarationOperation(IsAsynchronous: True) (OperationKind.UsingDeclaration, Type: null, IsInvalid) (Syntax: 'await using ... = new C1();') + DeclarationGroup: + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null, IsInvalid, IsImplicit) (Syntax: 'await using ... = new C1();') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null, IsInvalid) (Syntax: 'var x1 = new C1()') + Declarators: + IVariableDeclaratorOperation (Symbol: C1 x1) (OperationKind.VariableDeclarator, Type: null, IsInvalid) (Syntax: 'x1 = new C1()') + Initializer: + IVariableInitializerOperation (OperationKind.VariableInitializer, Type: null, IsInvalid) (Syntax: '= new C1()') + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1()') + Arguments(0) + Initializer: + null + Initializer: + null """; - var expectedDiagnostics = DiagnosticDescription.None; VerifyOperationTreeAndDiagnosticsForTest(src, expectedOperationTree, expectedDiagnostics); } + [Fact] + public void TestPatternBasedDisposal_ExtensionMethod() + { + string source = @" +public class C +{ + public static async System.Threading.Tasks.Task Main() + { + await using (var x = new C()) + { + } + + return 1; + } +} +public static class Extensions +{ + extension (C c) + { + public System.Threading.Tasks.ValueTask DisposeAsync() + => throw null; + } +} +"; + // extension methods do not contribute to pattern-based disposal + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // 0.cs(6,22): error CS8410: 'C': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using (var x = new C()) + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "var x = new C()").WithArguments("C").WithLocation(6, 22) + ); + } + + [Fact] + public void PatternBased_Dispose_Async_DelegateTypeProperty() + { + var src = """ +using System.Threading.Tasks; + +await using var x = new C(); + +class C +{ + public System.Func DisposeAsync => async () => { System.Console.Write("ran2"); await Task.Yield(); }; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (3,1): error CS8410: 'C': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x = new C(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x = new C();").WithArguments("C").WithLocation(3, 1) + ); + } + [Fact] public void ExtensionMemberLookup_PatternBased_Dispose_Async_DelegateTypeProperty() { @@ -18688,8 +18792,11 @@ static class E """; var comp = CreateCompilation(src); // PROTOTYPE(instance) confirm when spec'ing pattern-based disposal - comp.VerifyEmitDiagnostics(); - // PROTOTYPE metadata is undone (produces unverifiable IL) + comp.VerifyEmitDiagnostics( + // (3,1): error CS8410: 'C': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x = new C(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x = new C();").WithArguments("C").WithLocation(3, 1) + ); } [Fact] @@ -18721,7 +18828,11 @@ public async Task DisposeAsync() """; // PROTOTYPE confirm when spec'ing pattern-based disposal var comp = CreateCompilation(src); - CompileAndVerify(comp, expectedOutput: "RAN").VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (4,1): error CS8410: 'C': type used in an asynchronous using statement must implement 'System.IAsyncDisposable' or implement a suitable 'DisposeAsync' method. + // await using var x = new C(); + Diagnostic(ErrorCode.ERR_NoConvToIAsyncDisp, "await using var x = new C();").WithArguments("C").WithLocation(4, 1) + ); // PROTOTYPE verify IOperation } @@ -18730,20 +18841,34 @@ public async Task DisposeAsync() public void ExtensionMemberLookup_PatternBased_Dispose_RefStruct() { var src = """ -using var x = new S(); +using var x1 = new S1(); +using var x2 = new S2(); + +ref struct S1 { } -ref struct S { } +ref struct S2 +{ +} static class E { - extension(S s) + extension(S1 s) { public void Dispose() { } } + + public static void Dispose(this S2 s) { } } """; var comp = CreateCompilation(src); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (1,1): error CS1674: 'S1': type used in a using statement must implement 'System.IDisposable'. + // using var x1 = new S1(); + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "using var x1 = new S1();").WithArguments("S1").WithLocation(1, 1), + // (2,1): error CS1674: 'S2': type used in a using statement must implement 'System.IDisposable'. + // using var x2 = new S2(); + Diagnostic(ErrorCode.ERR_NoConvToIDisp, "using var x2 = new S2();").WithArguments("S2").WithLocation(2, 1) + ); } [Fact] @@ -18785,20 +18910,30 @@ unsafe class C { public static void Main() { - fixed (int* p = new Fixable()) + fixed (int* p = new Fixable1()) + { + System.Console.WriteLine(p[1]); + } + + fixed (int* p = new Fixable2()) { System.Console.WriteLine(p[1]); } } } -class Fixable { } +class Fixable1 { } + +class Fixable2 +{ + public MyDelegate GetPinnableReference => throw null; +} delegate ref int MyDelegate(); static class E { - extension(Fixable f) + extension(Fixable1 f) { public MyDelegate GetPinnableReference => throw null; } @@ -18806,8 +18941,14 @@ static class E "; var comp = CreateCompilation(text, options: TestOptions.UnsafeReleaseExe); // PROTOTYPE confirm when spec'ing pattern-based fixed - comp.VerifyEmitDiagnostics(); - // PROTOTYPE metadata is undone + comp.VerifyEmitDiagnostics( + // (6,25): error CS8385: The given expression cannot be used in a fixed statement + // fixed (int* p = new Fixable1()) + Diagnostic(ErrorCode.ERR_ExprCannotBeFixed, "new Fixable1()").WithLocation(6, 25), + // (11,25): error CS8385: The given expression cannot be used in a fixed statement + // fixed (int* p = new Fixable2()) + Diagnostic(ErrorCode.ERR_ExprCannotBeFixed, "new Fixable2()").WithLocation(11, 25) + ); } [Fact]