Skip to content
Merged
31 changes: 31 additions & 0 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,34 @@ class extension
}
```

## Extended partial members are now virtual

***Introduced in Visual Studio 2022 version 17.15***

We have fixed [an inconsistency](https://github.com/dotnet/roslyn/issues/77346)
where interface members would not be implicitly `virtual` and `public` if they were also marked `partial`.
Note that Visual Basic and other languages not supporting default interface members will start requiring to implement such `partial` interface members.

This change has an effect only when language version is set to C# 14 or later.
To keep the previous behavior in C# 14 and later, explicitly mark `partial` interface members as `sealed` and `private`.

```cs
((I)new C()).M(); // wrote 1 previously, writes 2 now

partial interface I
{
public partial void M();
public partial void M() // implicitly virtual now
{
System.Console.Write(1);
}
}

class C : I
{
public void M() // overrides I.M
{
System.Console.Write(2);
}
}
```
36 changes: 21 additions & 15 deletions src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,26 @@ internal static void CheckFeatureAvailabilityForPartialEventsAndConstructors(Loc
}
#nullable disable

internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(DeclarationModifiers mods, bool hasBody, bool isExplicitInterfaceImplementation)
internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(DeclarationModifiers mods, bool hasBody, bool isExplicitInterfaceImplementation, bool forMethod, CSharpSyntaxNode syntaxNode)
{
// In C# 13 and earlier, interface partial members were not implicitly public and virtual and that is fixed only in C# 14 and later to avoid a breaking change.
bool newPartialBehavior = (mods & DeclarationModifiers.Partial) == 0 || ((CSharpParseOptions)syntaxNode.SyntaxTree.Options).LanguageVersion > LanguageVersion.CSharp13;

if ((mods & DeclarationModifiers.AccessibilityMask) == 0)
{
// Partial methods without accessibility modifiers (i.e., not the "extended partial methods") are implicitly private and not virtual.
var plainPartialMethod = forMethod && (mods & DeclarationModifiers.Partial) != 0;

if (!plainPartialMethod && !isExplicitInterfaceImplementation && newPartialBehavior)
{
mods |= DeclarationModifiers.Public;
}
else
{
mods |= DeclarationModifiers.Private;
}
}

if (isExplicitInterfaceImplementation)
{
if ((mods & DeclarationModifiers.Abstract) != 0)
Expand All @@ -258,11 +276,11 @@ internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(Declara
{
mods &= ~DeclarationModifiers.Sealed;
}
else if ((mods & (DeclarationModifiers.Private | DeclarationModifiers.Partial | DeclarationModifiers.Virtual | DeclarationModifiers.Abstract)) == 0)
else if ((mods & (DeclarationModifiers.Private | DeclarationModifiers.Virtual | DeclarationModifiers.Abstract)) == 0 && newPartialBehavior)
{
Debug.Assert(!isExplicitInterfaceImplementation);

if (hasBody || (mods & (DeclarationModifiers.Extern | DeclarationModifiers.Sealed)) != 0)
if (hasBody || (mods & (DeclarationModifiers.Extern | DeclarationModifiers.Partial | DeclarationModifiers.Sealed)) != 0)
{
if ((mods & DeclarationModifiers.Sealed) == 0)
{
Expand All @@ -279,18 +297,6 @@ internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(Declara
}
}

if ((mods & DeclarationModifiers.AccessibilityMask) == 0)
{
if ((mods & DeclarationModifiers.Partial) == 0 && !isExplicitInterfaceImplementation)
{
mods |= DeclarationModifiers.Public;
}
else
{
mods |= DeclarationModifiers.Private;
}
}

return mods;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal SourceEventSymbol(
_syntaxRef = syntax.GetReference();

var isExplicitInterfaceImplementation = interfaceSpecifierSyntaxOpt != null;
_modifiers = MakeModifiers(modifiers, isExplicitInterfaceImplementation, isFieldLike, _location, diagnostics, out _, out _hasExplicitAccessModifier);
_modifiers = MakeModifiers(syntax, modifiers, isExplicitInterfaceImplementation, isFieldLike, _location, diagnostics, out _, out _hasExplicitAccessModifier);
this.CheckAccessibility(_location, diagnostics, isExplicitInterfaceImplementation);
}

Expand Down Expand Up @@ -492,7 +492,7 @@ private void CheckAccessibility(Location location, BindingDiagnosticBag diagnost
ModifierUtils.CheckAccessibility(_modifiers, this, isExplicitInterfaceImplementation, diagnostics, location);
}

private DeclarationModifiers MakeModifiers(SyntaxTokenList modifiers, bool explicitInterfaceImplementation,
private DeclarationModifiers MakeModifiers(CSharpSyntaxNode syntax, SyntaxTokenList modifiers, bool explicitInterfaceImplementation,
bool isFieldLike, Location location,
BindingDiagnosticBag diagnostics, out bool modifierErrors,
out bool hasExplicitAccessModifier)
Expand Down Expand Up @@ -569,7 +569,7 @@ private DeclarationModifiers MakeModifiers(SyntaxTokenList modifiers, bool expli
// Proper errors must have been reported by now.
if (isInterface)
{
mods = ModifierUtils.AdjustModifiersForAnInterfaceMember(mods, !isFieldLike, explicitInterfaceImplementation);
mods = ModifierUtils.AdjustModifiersForAnInterfaceMember(mods, !isFieldLike, explicitInterfaceImplementation, forMethod: false, syntax);
}

return mods;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,18 +776,20 @@ private static (DeclarationModifiers mods, bool hasExplicitAccessMod) MakeModifi
defaultInterfaceImplementationModifiers,
location, diagnostics);

mods = AddImpliedModifiers(mods, isInterface, methodKind, hasBody);
mods = AddImpliedModifiers(syntax, mods, isInterface, methodKind, hasBody);
return (mods, hasExplicitAccessMod);
}

private static DeclarationModifiers AddImpliedModifiers(DeclarationModifiers mods, bool containingTypeIsInterface, MethodKind methodKind, bool hasBody)
private static DeclarationModifiers AddImpliedModifiers(MethodDeclarationSyntax syntax, DeclarationModifiers mods, bool containingTypeIsInterface, MethodKind methodKind, bool hasBody)
{
// Let's overwrite modifiers for interface and explicit interface implementation methods with what they are supposed to be.
// Proper errors must have been reported by now.
if (containingTypeIsInterface)
{
mods = ModifierUtils.AdjustModifiersForAnInterfaceMember(mods, hasBody,
methodKind == MethodKind.ExplicitInterfaceImplementation);
methodKind == MethodKind.ExplicitInterfaceImplementation,
forMethod: true,
syntax);
}
else if (methodKind == MethodKind.ExplicitInterfaceImplementation)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private static SourcePropertySymbol Create(
SyntaxTokenList modifiersTokenList = GetModifierTokensSyntax(syntax);
bool isExplicitInterfaceImplementation = explicitInterfaceSpecifier is object;
var (modifiers, hasExplicitAccessMod) = MakeModifiers(
syntax,
containingType,
modifiersTokenList,
isExplicitInterfaceImplementation,
Expand Down Expand Up @@ -369,6 +370,7 @@ private static AccessorDeclarationSyntax GetSetAccessorDeclaration(BasePropertyD
}

private static (DeclarationModifiers modifiers, bool hasExplicitAccessMod) MakeModifiers(
BasePropertyDeclarationSyntax syntax,
NamedTypeSymbol containingType,
SyntaxTokenList modifiers,
bool isExplicitInterfaceImplementation,
Expand Down Expand Up @@ -471,7 +473,7 @@ private static (DeclarationModifiers modifiers, bool hasExplicitAccessMod) MakeM
// Proper errors must have been reported by now.
if (isInterface)
{
mods = ModifierUtils.AdjustModifiersForAnInterfaceMember(mods, accessorsHaveImplementation, isExplicitInterfaceImplementation);
mods = ModifierUtils.AdjustModifiersForAnInterfaceMember(mods, accessorsHaveImplementation, isExplicitInterfaceImplementation, forMethod: false, syntax);
}

if (isIndexer)
Expand Down
Loading