Skip to content

Commit 6638931

Browse files
dibarbetjjonescz
andauthored
Revert "Remove scope variance exceptions" (#76667)
This reverts commit b86d831. Co-authored-by: Jan Jones <[email protected]>
1 parent 56a3e41 commit 6638931

File tree

7 files changed

+127
-524
lines changed

7 files changed

+127
-524
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -172,40 +172,6 @@ struct S
172172
}
173173
```
174174

175-
## Variance of `scoped` and `[UnscopedRef]` is more strict
176-
177-
***Introduced in Visual Studio 2022 version 17.13***
178-
179-
Scope can be changed when overriding a method, implementing an interface, or converting a lambda/method to a delegate under
180-
[some conditions](https://github.com/dotnet/csharplang/blob/05064c2a9567b7a58a07e526dff403ece1866541/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch)
181-
(roughly, `scoped` can be added and `[UnscopedRef]` can be removed).
182-
Previously, the compiler did not report an error/warning for such mismatch under some circumstances, but it is now always reported.
183-
Note that the error is downgraded to a warning in `unsafe` contexts and also (in scenarios where it would be a breaking change) with LangVersion 12 or lower.
184-
185-
```cs
186-
D1 d1 = (ref int i) => { }; // previously no mismatch error reported, now:
187-
// error CS8986: The 'scoped' modifier of parameter 'i' doesn't match target 'D1'.
188-
189-
D2 d2 = (ref int i) => ref i; // an error was and continues to be reported:
190-
// error CS8986: The 'scoped' modifier of parameter 'i' doesn't match target 'D2'.
191-
192-
delegate void D1(scoped ref int x);
193-
delegate ref int D2(scoped ref int x);
194-
```
195-
196-
```cs
197-
using System.Diagnostics.CodeAnalysis;
198-
199-
D1 d1 = ([UnscopedRef] ref int i) => { }; // previously no mismatch error reported, now:
200-
// error CS8986: The 'scoped' modifier of parameter 'i' doesn't match target 'D1'.
201-
202-
D2 d2 = ([UnscopedRef] ref int i) => ref i; // an error was and continues to be reported:
203-
// error CS8986: The 'scoped' modifier of parameter 'i' doesn't match target 'D2'.
204-
205-
delegate void D1(ref int x);
206-
delegate ref int D2(ref int x);
207-
```
208-
209175
## `Microsoft.CodeAnalysis.EmbeddedAttribute` is validated on declaration
210176

211177
***Introduced in Visual Studio 2022 version 17.13***

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

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,23 +2274,26 @@ private static void CheckParameterModifierMismatchMethodConversion(SyntaxNode sy
22742274
return;
22752275
}
22762276

2277-
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
2278-
delegateMethod,
2279-
lambdaOrMethod,
2280-
diagnostics,
2281-
static (diagnostics, delegateMethod, lambdaOrMethod, parameter, _, typeAndSyntax) =>
2282-
{
2283-
diagnostics.Add(
2284-
SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(delegateMethod, lambdaOrMethod) ?
2285-
ErrorCode.ERR_ScopedMismatchInParameterOfTarget :
2286-
ErrorCode.WRN_ScopedMismatchInParameterOfTarget,
2287-
typeAndSyntax.Syntax.Location,
2288-
new FormattedSymbol(parameter, SymbolDisplayFormat.ShortFormat),
2289-
typeAndSyntax.Type);
2290-
},
2291-
(Type: targetType, Syntax: syntax),
2292-
allowVariance: true,
2293-
invokedAsExtensionMethod: invokedAsExtensionMethod);
2277+
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod))
2278+
{
2279+
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
2280+
delegateMethod,
2281+
lambdaOrMethod,
2282+
diagnostics,
2283+
static (diagnostics, delegateMethod, lambdaOrMethod, parameter, _, typeAndSyntax) =>
2284+
{
2285+
diagnostics.Add(
2286+
SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(delegateMethod, lambdaOrMethod) ?
2287+
ErrorCode.ERR_ScopedMismatchInParameterOfTarget :
2288+
ErrorCode.WRN_ScopedMismatchInParameterOfTarget,
2289+
typeAndSyntax.Syntax.Location,
2290+
new FormattedSymbol(parameter, SymbolDisplayFormat.ShortFormat),
2291+
typeAndSyntax.Type);
2292+
},
2293+
(Type: targetType, Syntax: syntax),
2294+
allowVariance: true,
2295+
invokedAsExtensionMethod: invokedAsExtensionMethod);
2296+
}
22942297

22952298
SourceMemberContainerTypeSymbol.CheckRefReadonlyInMismatch(
22962299
delegateMethod, lambdaOrMethod, diagnostics,

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

Lines changed: 67 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,22 +1149,25 @@ static void checkValidMethodOverride(
11491149
MethodSymbol overridingMethod,
11501150
BindingDiagnosticBag diagnostics)
11511151
{
1152-
CheckValidScopedOverride(
1153-
overriddenMethod,
1154-
overridingMethod,
1155-
diagnostics,
1156-
static (diagnostics, overriddenMethod, overridingMethod, overridingParameter, _, location) =>
1157-
{
1158-
diagnostics.Add(
1159-
ReportInvalidScopedOverrideAsError(overriddenMethod, overridingMethod) ?
1160-
ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation :
1161-
ErrorCode.WRN_ScopedMismatchInParameterOfOverrideOrImplementation,
1162-
location,
1163-
new FormattedSymbol(overridingParameter, SymbolDisplayFormat.ShortFormat));
1164-
},
1165-
overridingMemberLocation,
1166-
allowVariance: true,
1167-
invokedAsExtensionMethod: false);
1152+
if (RequiresValidScopedOverrideForRefSafety(overriddenMethod))
1153+
{
1154+
CheckValidScopedOverride(
1155+
overriddenMethod,
1156+
overridingMethod,
1157+
diagnostics,
1158+
static (diagnostics, overriddenMethod, overridingMethod, overridingParameter, _, location) =>
1159+
{
1160+
diagnostics.Add(
1161+
ReportInvalidScopedOverrideAsError(overriddenMethod, overridingMethod) ?
1162+
ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation :
1163+
ErrorCode.WRN_ScopedMismatchInParameterOfOverrideOrImplementation,
1164+
location,
1165+
new FormattedSymbol(overridingParameter, SymbolDisplayFormat.ShortFormat));
1166+
},
1167+
overridingMemberLocation,
1168+
allowVariance: true,
1169+
invokedAsExtensionMethod: false);
1170+
}
11681171

11691172
CheckValidNullableMethodOverride(overridingMethod.DeclaringCompilation, overriddenMethod, overridingMethod, diagnostics,
11701173
ReportBadReturn,
@@ -1367,55 +1370,60 @@ static bool isValidNullableConversion(
13671370

13681371
#nullable enable
13691372
/// <summary>
1370-
/// Returns true if a scoped mismatch should be reported as an error rather than a warning.
1373+
/// Returns true if the method signature must match, with respect to scoped for ref safety,
1374+
/// in overrides, interface implementations, or delegate conversions.
13711375
/// </summary>
1372-
internal static bool ReportInvalidScopedOverrideAsError(MethodSymbol baseMethod, MethodSymbol overrideMethod)
1376+
internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method)
13731377
{
1374-
// https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch
1375-
// The diagnostic is reported as an error if the mismatched signatures are both using C#11 ref safety rules; otherwise, the diagnostic is a warning.
1376-
return baseMethod.UseUpdatedEscapeRules && overrideMethod.UseUpdatedEscapeRules &&
1377-
// We have removed exceptions to the scoped mismatch error reporting, but to avoid breaks
1378-
// we report the new scenarios (previously exempted) as warnings in C# 12 and earlier.
1379-
// https://github.com/dotnet/roslyn/issues/76100
1380-
(overrideMethod.DeclaringCompilation.LanguageVersion > LanguageVersion.CSharp12 || usedToBeReported(baseMethod));
1381-
1382-
static bool usedToBeReported(MethodSymbol method)
1383-
{
1384-
var parameters = method.Parameters;
1385-
1386-
// https://github.com/dotnet/csharplang/blob/1f7f23f/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch
1387-
// The compiler will report a diagnostic for _unsafe scoped mismatches_ across overrides, interface implementations, and delegate conversions when:
1388-
// - 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
1389-
// ...
1390-
int nRefParametersRequired;
1391-
if (method.ReturnType.IsRefLikeOrAllowsRefLikeType() ||
1392-
(method.RefKind is RefKind.Ref or RefKind.RefReadOnly))
1393-
{
1394-
nRefParametersRequired = 1;
1395-
}
1396-
else if (parameters.Any(p => (p.RefKind is RefKind.Ref or RefKind.Out) && p.Type.IsRefLikeOrAllowsRefLikeType()))
1397-
{
1398-
nRefParametersRequired = 2; // including the parameter found above
1399-
}
1400-
else
1401-
{
1402-
return false;
1403-
}
1378+
if (method is null)
1379+
{
1380+
return false;
1381+
}
14041382

1405-
// ...
1406-
// - The method has at least one additional `ref`, `in`, `ref readonly`, or `out` parameter, or a parameter of `ref struct` type.
1407-
int nRefParameters = parameters.Count(p => p.RefKind is RefKind.Ref or RefKind.In or RefKind.RefReadOnlyParameter or RefKind.Out);
1408-
if (nRefParameters >= nRefParametersRequired)
1409-
{
1410-
return true;
1411-
}
1412-
else if (parameters.Any(p => p.RefKind == RefKind.None && p.Type.IsRefLikeOrAllowsRefLikeType()))
1413-
{
1414-
return true;
1415-
}
1383+
var parameters = method.Parameters;
14161384

1385+
// https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch
1386+
// The compiler will report a diagnostic for _unsafe scoped mismatches_ across overrides, interface implementations, and delegate conversions when:
1387+
// - 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
1388+
// ...
1389+
int nRefParametersRequired;
1390+
if (method.ReturnType.IsRefLikeOrAllowsRefLikeType() ||
1391+
(method.RefKind is RefKind.Ref or RefKind.RefReadOnly))
1392+
{
1393+
nRefParametersRequired = 1;
1394+
}
1395+
else if (parameters.Any(p => (p.RefKind is RefKind.Ref or RefKind.Out) && p.Type.IsRefLikeOrAllowsRefLikeType()))
1396+
{
1397+
nRefParametersRequired = 2; // including the parameter found above
1398+
}
1399+
else
1400+
{
14171401
return false;
14181402
}
1403+
1404+
// ...
1405+
// - The method has at least one additional `ref`, `in`, `ref readonly`, or `out` parameter, or a parameter of `ref struct` type.
1406+
int nRefParameters = parameters.Count(p => p.RefKind is RefKind.Ref or RefKind.In or RefKind.RefReadOnlyParameter or RefKind.Out);
1407+
if (nRefParameters >= nRefParametersRequired)
1408+
{
1409+
return true;
1410+
}
1411+
else if (parameters.Any(p => p.RefKind == RefKind.None && p.Type.IsRefLikeOrAllowsRefLikeType()))
1412+
{
1413+
return true;
1414+
}
1415+
1416+
return false;
1417+
}
1418+
1419+
/// <summary>
1420+
/// Returns true if a scoped mismatch should be reported as an error rather than a warning.
1421+
/// </summary>
1422+
internal static bool ReportInvalidScopedOverrideAsError(MethodSymbol baseMethod, MethodSymbol overrideMethod)
1423+
{
1424+
// https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch
1425+
// The diagnostic is reported as an error if the mismatched signatures are both using C#11 ref safety rules; otherwise, the diagnostic is a warning.
1426+
return baseMethod.UseUpdatedEscapeRules && overrideMethod.UseUpdatedEscapeRules;
14191427
}
14201428

14211429
/// <summary>

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,23 +1855,25 @@ static void checkMethodOverride(
18551855
reportMismatchInParameterType,
18561856
(implementingType, isExplicit));
18571857

1858-
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
1859-
implementedMethod,
1860-
implementingMethod,
1861-
diagnostics,
1862-
static (diagnostics, implementedMethod, implementingMethod, implementingParameter, _, arg) =>
1863-
{
1864-
diagnostics.Add(
1865-
SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(implementedMethod, implementingMethod) ?
1866-
ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation :
1867-
ErrorCode.WRN_ScopedMismatchInParameterOfOverrideOrImplementation,
1868-
GetImplicitImplementationDiagnosticLocation(implementedMethod, arg.implementingType, implementingMethod),
1869-
new FormattedSymbol(implementingParameter, SymbolDisplayFormat.ShortFormat));
1870-
},
1871-
(implementingType, isExplicit),
1872-
allowVariance: true,
1873-
invokedAsExtensionMethod: false);
1874-
1858+
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(implementedMethod))
1859+
{
1860+
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
1861+
implementedMethod,
1862+
implementingMethod,
1863+
diagnostics,
1864+
static (diagnostics, implementedMethod, implementingMethod, implementingParameter, _, arg) =>
1865+
{
1866+
diagnostics.Add(
1867+
SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(implementedMethod, implementingMethod) ?
1868+
ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation :
1869+
ErrorCode.WRN_ScopedMismatchInParameterOfOverrideOrImplementation,
1870+
GetImplicitImplementationDiagnosticLocation(implementedMethod, arg.implementingType, implementingMethod),
1871+
new FormattedSymbol(implementingParameter, SymbolDisplayFormat.ShortFormat));
1872+
},
1873+
(implementingType, isExplicit),
1874+
allowVariance: true,
1875+
invokedAsExtensionMethod: false);
1876+
}
18751877
SourceMemberContainerTypeSymbol.CheckRefReadonlyInMismatch(
18761878
implementedMethod, implementingMethod, diagnostics,
18771879
static (diagnostics, implementedMethod, implementingMethod, implementingParameter, _, arg) =>

src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21358,7 +21358,7 @@ class Helper<T>
2135821358
delegate void D2(T x);
2135921359

2136021360
static D1 d11 = M1;
21361-
static D1 d12 = M2; // 1
21361+
static D1 d12 = M2;
2136221362
static D2 d21 = M1;
2136321363
static D2 d22 = M2;
2136421364

@@ -21372,7 +21372,7 @@ class Helper
2137221372
delegate void D2(Span<int> x);
2137321373

2137421374
static D1 d11 = M1;
21375-
static D1 d12 = M2; // 2
21375+
static D1 d12 = M2;
2137621376
static D2 d21 = M1;
2137721377
static D2 d22 = M2;
2137821378

@@ -21381,13 +21381,7 @@ static void M2(Span<int> x) {}
2138121381
}
2138221382
";
2138321383
var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics);
21384-
comp.VerifyEmitDiagnostics(
21385-
// (11,21): error CS8986: The 'scoped' modifier of parameter 'x' doesn't match target 'Helper<T>.D1'.
21386-
// static D1 d12 = M2; // 1
21387-
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfTarget, "M2").WithArguments("x", "Helper<T>.D1").WithLocation(11, 21),
21388-
// (25,21): error CS8986: The 'scoped' modifier of parameter 'x' doesn't match target 'Helper.D1'.
21389-
// static D1 d12 = M2; // 2
21390-
Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfTarget, "M2").WithArguments("x", "Helper.D1").WithLocation(25, 21));
21384+
comp.VerifyEmitDiagnostics();
2139121385
}
2139221386

2139321387
[Fact]

0 commit comments

Comments
 (0)