Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider silly cyclic assignment in scoped variance #76261

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1384,8 +1384,20 @@ internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? metho

// https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch
// The compiler will report a diagnostic for _unsafe scoped mismatches_ across overrides, interface implementations, and delegate conversions when:
// - 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
// - The method has a `ref` or `out` parameter of `ref struct` type with a mismatch of adding `[UnscopedRef]` (not removing `scoped`).
// (In this case, a silly cyclic assignment is possible, hence no other parameters are necessary.)
// ...
if (parameters.Any(static p =>
p is { EffectiveScope: ScopedKind.None, RefKind: RefKind.Ref } or { EffectiveScope: ScopedKind.ScopedRef, RefKind: RefKind.Out } &&
p.Type.IsRefLikeOrAllowsRefLikeType()))
{
return true;
}

// ...
// - Or both of these are true:
// - 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() ||
(method.RefKind is RefKind.Ref or RefKind.RefReadOnly))
Expand All @@ -1401,8 +1413,8 @@ internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? metho
return false;
}

// ...
// - The method has at least one additional `ref`, `in`, `ref readonly`, or `out` parameter, or a parameter of `ref struct` type.
// ...
// - The method has at least one additional `ref`, `in`, `ref readonly`, or `out` parameter, or a parameter of `ref struct` type.
int nRefParameters = parameters.Count(p => p.RefKind is RefKind.Ref or RefKind.In or RefKind.RefReadOnlyParameter or RefKind.Out);
if (nRefParameters >= nRefParametersRequired)
{
Expand Down
273 changes: 273 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10358,5 +10358,278 @@ public static void M([UnscopedRef] ref S s)
""";
CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics();
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Disallowed_ImplicitInterface(
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
using System.Diagnostics.CodeAnalysis;

interface I
{
void M({{modifier}} R r);
}

class C : I
{
public void M([UnscopedRef] {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (10,17): error CS8987: The 'scoped' modifier of parameter 'r' doesn't match overridden or implemented member.
// public void M([UnscopedRef] ref R r) { }
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "M").WithArguments("r").WithLocation(10, 17));
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Disallowed_ExplicitInterface(
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
using System.Diagnostics.CodeAnalysis;

interface I
{
void M({{modifier}} R r);
}

class C : I
{
void I.M([UnscopedRef] {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (10,12): error CS8987: The 'scoped' modifier of parameter 'r' doesn't match overridden or implemented member.
// void I.M([UnscopedRef] ref R r) { }
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "M").WithArguments("r").WithLocation(10, 12));
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Disallowed_Override(
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
using System.Diagnostics.CodeAnalysis;

abstract class B
{
public abstract void M({{modifier}} R r);
}

class C : B
{
public override void M([UnscopedRef] {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (10,26): error CS8987: The 'scoped' modifier of parameter 'r' doesn't match overridden or implemented member.
// public override void M([UnscopedRef] ref R r) { }
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "M").WithArguments("r").WithLocation(10, 26));
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Disallowed_DelegateConversion(
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
using System.Diagnostics.CodeAnalysis;

D d = C.M;

delegate void D({{modifier}} R r);

static class C
{
public static void M([UnscopedRef] {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (3,7): error CS8986: The 'scoped' modifier of parameter 'r' doesn't match target 'D'.
// D d = C.M;
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfTarget, "C.M").WithArguments("r", "D").WithLocation(3, 7));
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "[System.Diagnostics.CodeAnalysis.UnscopedRef]")]
[InlineData("", "")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Allowed_ImplicitInterface(string attr1, string attr2)
{
var source = $$"""
interface I
{
void M({{attr1}} ref R r);
}

class C : I
{
public void M({{attr2}} ref R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics();
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_ScopedRef_Allowed_ImplicitInterface(
[CombinatorialValues("scoped", "")] string scoped1,
[CombinatorialValues("scoped", "")] string scoped2,
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
interface I
{
void M({{scoped1}} {{modifier}} R r);
}

class C : I
{
public void M({{scoped2}} {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation(source).VerifyDiagnostics();
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "[System.Diagnostics.CodeAnalysis.UnscopedRef]")]
[InlineData("", "")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Allowed_ExplicitInterface(string attr1, string attr2)
{
var source = $$"""
interface I
{
void M({{attr1}} ref R r);
}

class C : I
{
void I.M({{attr2}} ref R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics();
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_ScopedRef_Allowed_ExplicitInterface(
[CombinatorialValues("scoped", "")] string scoped1,
[CombinatorialValues("scoped", "")] string scoped2,
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
interface I
{
void M({{scoped1}} {{modifier}} R r);
}

class C : I
{
void I.M({{scoped2}} {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation(source).VerifyDiagnostics();
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "[System.Diagnostics.CodeAnalysis.UnscopedRef]")]
[InlineData("", "")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Allowed_Override(string attr1, string attr2)
{
var source = $$"""
abstract class B
{
public abstract void M({{attr1}} ref R r);
}

class C : B
{
public override void M({{attr2}} ref R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics();
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_ScopedRef_Allowed_Override(
[CombinatorialValues("scoped", "")] string scoped1,
[CombinatorialValues("scoped", "")] string scoped2,
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
abstract class B
{
public abstract void M({{scoped1}} {{modifier}} R r);
}

class C : B
{
public override void M({{scoped2}} {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation(source).VerifyDiagnostics();
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "")]
[InlineData("[System.Diagnostics.CodeAnalysis.UnscopedRef]", "[System.Diagnostics.CodeAnalysis.UnscopedRef]")]
[InlineData("", "")]
public void SelfAssignment_ScopeVariance_UnscopedRef_Allowed_DelegateConversion(string attr1, string attr2)
{
var source = $$"""
D d = C.M;

delegate void D({{attr1}} ref R r);

static class C
{
public static void M({{attr2}} ref R r) { }
}

ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics();
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/76100")]
public void SelfAssignment_ScopeVariance_ScopedRef_Allowed_DelegateConversion(
[CombinatorialValues("scoped", "")] string scoped1,
[CombinatorialValues("scoped", "")] string scoped2,
[CombinatorialValues("ref", "out")] string modifier)
{
var source = $$"""
D d = C.M;

delegate void D({{scoped1}} {{modifier}} R r);

static class C
{
public static void M({{scoped2}} {{modifier}} R r) { }
}

ref struct R;
""";
CreateCompilation(source).VerifyDiagnostics();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests like this?

interface I1 {
  ref int M(); 
}
struct S {
  int field;
  [UnscopedRef] ref int M() => ref field;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, RefFieldTests.UnscopedRefAttribute_InterfaceImplementation_01.

}
}
}