Skip to content

Commit c6ef6e8

Browse files
authored
Fix scoped variance checks involving ref struct interface implementation (#78883)
1 parent c42e474 commit c6ef6e8

File tree

4 files changed

+163
-5
lines changed

4 files changed

+163
-5
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2286,7 +2286,7 @@ private static void CheckParameterModifierMismatchMethodConversion(SyntaxNode sy
22862286
return;
22872287
}
22882288

2289-
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod))
2289+
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod, lambdaOrMethod.TryGetThisParameter(out var thisParameter) ? thisParameter : null))
22902290
{
22912291
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
22922292
delegateMethod,

src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ static void checkValidMethodOverride(
11541154
MethodSymbol overridingMethod,
11551155
BindingDiagnosticBag diagnostics)
11561156
{
1157-
if (RequiresValidScopedOverrideForRefSafety(overriddenMethod))
1157+
if (RequiresValidScopedOverrideForRefSafety(overriddenMethod, overridingMethod.TryGetThisParameter(out var thisParam) ? thisParam : null))
11581158
{
11591159
CheckValidScopedOverride(
11601160
overriddenMethod,
@@ -1378,7 +1378,7 @@ static bool isValidNullableConversion(
13781378
/// Returns true if the method signature must match, with respect to scoped for ref safety,
13791379
/// in overrides, interface implementations, or delegate conversions.
13801380
/// </summary>
1381-
internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method)
1381+
internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method, ParameterSymbol? overrideThisParameter)
13821382
{
13831383
if (method is null)
13841384
{
@@ -1404,7 +1404,8 @@ internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? metho
14041404
// - 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
14051405
// ...
14061406
int nRefParametersRequired;
1407-
if (method.ReturnType.IsRefLikeOrAllowsRefLikeType() ||
1407+
if ((overrideThisParameter is { RefKind: RefKind.Ref or RefKind.Out } && overrideThisParameter.Type.IsRefLikeOrAllowsRefLikeType()) ||
1408+
method.ReturnType.IsRefLikeOrAllowsRefLikeType() ||
14081409
(method.RefKind is RefKind.Ref or RefKind.RefReadOnly))
14091410
{
14101411
nRefParametersRequired = 1;

src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,7 @@ static void checkMethodOverride(
18731873
reportMismatchInParameterType,
18741874
(implementingType, isExplicit));
18751875

1876-
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(implementedMethod))
1876+
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(implementedMethod, implementingMethod.TryGetThisParameter(out var thisParameter) ? thisParameter : null))
18771877
{
18781878
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
18791879
implementedMethod,

src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11300,5 +11300,162 @@ public static void M({{scoped2}} {{modifier}} R r) { }
1130011300
""";
1130111301
CreateCompilation(source).VerifyDiagnostics();
1130211302
}
11303+
11304+
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")]
11305+
[InlineData("public void UseSpan(Span<int> span)", 17)]
11306+
[InlineData("void I.UseSpan(Span<int> span)", 12)]
11307+
public void RefStructInterface_ScopedDifference(string implSignature, int column)
11308+
{
11309+
// This is a case where interface methods need to be treated specially in scoped variance checks.
11310+
// 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.
11311+
var source = $$"""
11312+
using System;
11313+
11314+
interface I
11315+
{
11316+
void UseSpan(scoped Span<int> span);
11317+
}
11318+
11319+
ref struct RS : I
11320+
{
11321+
public Span<int> Span { get; set; }
11322+
public RS(Span<int> span) => Span = span;
11323+
11324+
{{implSignature}} // 1
11325+
{
11326+
this.Span = span;
11327+
}
11328+
}
11329+
""";
11330+
11331+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
11332+
comp.VerifyEmitDiagnostics(
11333+
// (13,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member.
11334+
// public void UseSpan(Span<int> span)
11335+
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(13, column));
11336+
}
11337+
11338+
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78711")]
11339+
[InlineData("public readonly void UseSpan(Span<int> span)")]
11340+
[InlineData("readonly void I.UseSpan(Span<int> span)")]
11341+
public void RefStructInterface_ScopedDifference_ReadonlyImplementation(string implSignature)
11342+
{
11343+
var source = $$"""
11344+
using System;
11345+
11346+
interface I
11347+
{
11348+
void UseSpan(scoped Span<int> span);
11349+
}
11350+
11351+
ref struct RS : I
11352+
{
11353+
public Span<int> Span { get; set; }
11354+
public RS(Span<int> span) => Span = span;
11355+
11356+
{{implSignature}}
11357+
{
11358+
Span = span; // 1
11359+
}
11360+
}
11361+
""";
11362+
11363+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
11364+
comp.VerifyEmitDiagnostics(
11365+
// (15,9): error CS1604: Cannot assign to 'Span' because it is read-only
11366+
// Span = span; // 1
11367+
Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "Span").WithArguments("Span").WithLocation(15, 9));
11368+
}
11369+
11370+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")]
11371+
public void SimilarScenarioAs78711InvolvingNonReceiverParameter()
11372+
{
11373+
// Demonstrate a scenario similar to https://github.com/dotnet/roslyn/issues/78711 involving a non-receiver parameter has consistent behavior
11374+
// 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.
11375+
var source = """
11376+
using System;
11377+
11378+
interface I
11379+
{
11380+
void UseSpan1(ref RS rs, scoped Span<int> span);
11381+
void UseSpan2(ref readonly RS rs, scoped Span<int> span);
11382+
}
11383+
11384+
class C : I
11385+
{
11386+
public void UseSpan1(ref RS rs, Span<int> span) // 1
11387+
{
11388+
rs.Span = span;
11389+
}
11390+
11391+
public void UseSpan2(ref readonly RS rs, Span<int> span)
11392+
{
11393+
rs.Span = span; // 2
11394+
}
11395+
}
11396+
11397+
ref struct RS
11398+
{
11399+
public Span<int> Span { get; set; }
11400+
public RS(Span<int> span) => Span = span;
11401+
}
11402+
""";
11403+
11404+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
11405+
comp.VerifyEmitDiagnostics(
11406+
// (11,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member.
11407+
// public void UseSpan1(ref RS rs, Span<int> span) // 1
11408+
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan1").WithArguments("span").WithLocation(11, 17),
11409+
// (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
11410+
// rs.Span = span; // 2
11411+
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "rs.Span").WithArguments("variable", "rs").WithLocation(18, 9));
11412+
}
11413+
11414+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78711")]
11415+
public void Repro_78711()
11416+
{
11417+
var source = """
11418+
using System;
11419+
11420+
public static class Demo
11421+
{
11422+
public static void Show()
11423+
{
11424+
var stru = new Stru();
11425+
11426+
Unsafe(ref stru);
11427+
11428+
Console.Out.WriteLine(stru.Span);
11429+
}
11430+
11431+
private static void Unsafe<T>(ref T stru) where T : IStru, allows ref struct
11432+
{
11433+
Span<char> span = stackalloc char[10];
11434+
11435+
"bug".CopyTo(span);
11436+
11437+
stru.UseSpan(span);
11438+
}
11439+
}
11440+
11441+
internal interface IStru
11442+
{
11443+
public void UseSpan(scoped Span<char> span);
11444+
}
11445+
11446+
internal ref struct Stru : IStru
11447+
{
11448+
public Span<char> Span;
11449+
11450+
public void UseSpan(Span<char> span) => Span = span; // 1
11451+
}
11452+
""";
11453+
11454+
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
11455+
comp.VerifyEmitDiagnostics(
11456+
// (33,17): error CS8987: The 'scoped' modifier of parameter 'span' doesn't match overridden or implemented member.
11457+
// public void UseSpan(Span<char> span) => Span = span;
11458+
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "UseSpan").WithArguments("span").WithLocation(33, 17));
11459+
}
1130311460
}
1130411461
}

0 commit comments

Comments
 (0)