diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index ee80c9724963b..3cb962361b12a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -2286,7 +2286,7 @@ private static void CheckParameterModifierMismatchMethodConversion(SyntaxNode sy return; } - if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod)) + if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod, lambdaOrMethod.TryGetThisParameter(out var thisParameter) ? thisParameter : null)) { SourceMemberContainerTypeSymbol.CheckValidScopedOverride( delegateMethod, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs index 381c7927bbdba..0e51438a5ce3c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs @@ -1154,7 +1154,7 @@ static void checkValidMethodOverride( MethodSymbol overridingMethod, BindingDiagnosticBag diagnostics) { - if (RequiresValidScopedOverrideForRefSafety(overriddenMethod)) + if (RequiresValidScopedOverrideForRefSafety(overriddenMethod, overridingMethod.TryGetThisParameter(out var thisParam) ? thisParam : null)) { CheckValidScopedOverride( overriddenMethod, @@ -1378,7 +1378,7 @@ static bool isValidNullableConversion( /// Returns true if the method signature must match, with respect to scoped for ref safety, /// in overrides, interface implementations, or delegate conversions. /// - internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method) + internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method, ParameterSymbol? overrideThisParameter) { if (method is null) { @@ -1404,7 +1404,8 @@ internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? metho // - The method returns a `ref struct` or returns a `ref` or `ref readonly`, or the method has a `ref` or `out` parameter of `ref struct` type, and // ... int nRefParametersRequired; - if (method.ReturnType.IsRefLikeOrAllowsRefLikeType() || + if ((overrideThisParameter is { RefKind: RefKind.Ref or RefKind.Out } && overrideThisParameter.Type.IsRefLikeOrAllowsRefLikeType()) || + method.ReturnType.IsRefLikeOrAllowsRefLikeType() || (method.RefKind is RefKind.Ref or RefKind.RefReadOnly)) { nRefParametersRequired = 1; diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs index 4a7aa0f1cf630..af338d16da8bb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs @@ -1873,7 +1873,7 @@ static void checkMethodOverride( reportMismatchInParameterType, (implementingType, isExplicit)); - if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(implementedMethod)) + if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(implementedMethod, implementingMethod.TryGetThisParameter(out var thisParameter) ? thisParameter : null)) { SourceMemberContainerTypeSymbol.CheckValidScopedOverride( implementedMethod, diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index bf231df859a11..977c987f503b7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -11300,5 +11300,162 @@ public static void M({{scoped2}} {{modifier}} R r) { } """; CreateCompilation(source).VerifyDiagnostics(); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + [InlineData("public void UseSpan(Span span)", 17)] + [InlineData("void I.UseSpan(Span span)", 12)] + public void RefStructInterface_ScopedDifference(string implSignature, int column) + { + // This is a case where interface methods need to be treated specially in scoped variance checks. + // Because a ref struct can implement the interface, we need to compare the signatures as if the interface has a receiver parameter which is ref-to-ref-struct. + var source = $$""" + using System; + + interface I + { + void UseSpan(scoped Span span); + } + + ref struct RS : I + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + + {{implSignature}} // 1 + { + this.Span = span; + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (13,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan(Span span) + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(13, column)); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + [InlineData("public readonly void UseSpan(Span span)")] + [InlineData("readonly void I.UseSpan(Span span)")] + public void RefStructInterface_ScopedDifference_ReadonlyImplementation(string implSignature) + { + var source = $$""" + using System; + + interface I + { + void UseSpan(scoped Span span); + } + + ref struct RS : I + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + + {{implSignature}} + { + Span = span; // 1 + } + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (15,9): error CS1604: Cannot assign to 'Span' because it is read-only + // Span = span; // 1 + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "Span").WithArguments("Span").WithLocation(15, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + public void SimilarScenarioAs78711InvolvingNonReceiverParameter() + { + // Demonstrate a scenario similar to https://github.com/dotnet/roslyn/issues/78711 involving a non-receiver parameter has consistent behavior + // In this case, the interface and implementation parameters cannot differ by type. But it as close as we can get to the receiver parameter case. + var source = """ + using System; + + interface I + { + void UseSpan1(ref RS rs, scoped Span span); + void UseSpan2(ref readonly RS rs, scoped Span span); + } + + class C : I + { + public void UseSpan1(ref RS rs, Span span) // 1 + { + rs.Span = span; + } + + public void UseSpan2(ref readonly RS rs, Span span) + { + rs.Span = span; // 2 + } + } + + ref struct RS + { + public Span Span { get; set; } + public RS(Span span) => Span = span; + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (11,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan1(ref RS rs, Span span) // 1 + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan1").WithArguments("span").WithLocation(11, 17), + // (18,9): error CS8332: Cannot assign to a member of variable 'rs' or use it as the right hand side of a ref assignment because it is a readonly variable + // rs.Span = span; // 2 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "rs.Span").WithArguments("variable", "rs").WithLocation(18, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")] + public void Repro_78711() + { + var source = """ + using System; + + public static class Demo + { + public static void Show() + { + var stru = new Stru(); + + Unsafe(ref stru); + + Console.Out.WriteLine(stru.Span); + } + + private static void Unsafe(ref T stru) where T : IStru, allows ref struct + { + Span span = stackalloc char[10]; + + "bug".CopyTo(span); + + stru.UseSpan(span); + } + } + + internal interface IStru + { + public void UseSpan(scoped Span span); + } + + internal ref struct Stru : IStru + { + public Span Span; + + public void UseSpan(Span span) => Span = span; // 1 + } + """; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (33,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member. + // public void UseSpan(Span span) => Span = span; + Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(33, 17)); + } } }