Skip to content

Commit ff59bc0

Browse files
authored
Allow abstract and sealed modifiers on static methods/properties/events in interfaces. (#52228)
Spec: https://github.com/dotnet/csharplang/blob/9a0dddbf0643993db0da8f48436f2aac60bc20b1/proposals/statics-in-interfaces.md Related to #52202. Relevant quotes from the spec: #### Abstract virtual members Static interface members other than fields are allowed to also have the `abstract` modifier. Abstract static members are not allowed to have a body (or in the case of properties, the accessors are not allowed to have a body). ``` c# interface I<T> where T : I<T> { static abstract void M(); static abstract T P { get; set; } static abstract event Action E; static abstract T operator +(T l, T r); } ``` #### Explicitly non-virtual static members For symmetry with non-virtual instance members, static members should be allowed an optional `sealed` modifier, even though they are non-virtual by default: ``` c# interface I0 { static sealed void M() => Console.WriteLine("Default behavior"); static sealed int f = 0; static sealed int P1 { get; set; } static sealed int P2 { get => f; set => f = value; } static sealed event Action E1; static sealed event Action E2 { add => E1 += value; remove => E1 -= value; } static sealed I0 operator +(I0 l, I0 r) => l; } ```
1 parent b610de2 commit ff59bc0

28 files changed

Lines changed: 4533 additions & 212 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Static Abstract Members In Interfaces
2+
=====================================
3+
4+
An interface is allowed to specify abstract static members that implementing classes and structs are then
5+
required to provide an explicit or implicit implementation of. The members can be accessed off of type
6+
parameters that are constrained by the interface.
7+
8+
Proposal:
9+
- https://github.com/dotnet/csharplang/issues/4436
10+
- https://github.com/dotnet/csharplang/blob/main/proposals/statics-in-interfaces.md
11+
12+
Feature branch: https://github.com/dotnet/roslyn/tree/features/StaticAbstractMembersInInterfaces
13+
14+
Test plan: https://github.com/dotnet/roslyn/issues/52221

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@
667667
<value>Type '{1}' already defines a member called '{0}' with the same parameter types</value>
668668
</data>
669669
<data name="ERR_StaticNotVirtual" xml:space="preserve">
670-
<value>A static member '{0}' cannot be marked as override, virtual, or abstract</value>
670+
<value>A static member cannot be marked as '{0}'</value>
671671
</data>
672672
<data name="ERR_OverrideNotNew" xml:space="preserve">
673673
<value>A member '{0}' marked as override cannot be marked as new or virtual</value>
@@ -6600,4 +6600,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
66006600
<data name="ERR_FunctionPointerTypesInAttributeNotSupported" xml:space="preserve">
66016601
<value>Using a function pointer type in a 'typeof' in an attribute is not supported.</value>
66026602
</data>
6603+
<data name="IDS_FeatureStaticAbstractMembersInInterfaces" xml:space="preserve">
6604+
<value>static abstract members in interfaces</value>
6605+
</data>
66036606
</root>

src/Compilers/CSharp/Portable/Errors/MessageID.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ internal enum MessageID
216216
IDS_FeatureVarianceSafetyForStaticInterfaceMembers = MessageBase + 12791,
217217
IDS_FeatureConstantInterpolatedStrings = MessageBase + 12792,
218218
IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction = MessageBase + 12793,
219+
IDS_FeatureStaticAbstractMembersInInterfaces = MessageBase + 12794,
219220
}
220221

221222
// Message IDs may refer to strings that need to be localized.
@@ -324,6 +325,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
324325
{
325326
// C# preview features.
326327
case MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction:
328+
case MessageID.IDS_FeatureStaticAbstractMembersInInterfaces: // semantic check
327329
return LanguageVersion.Preview;
328330
// C# 9.0 features.
329331
case MessageID.IDS_FeatureLambdaDiscardParameters: // semantic check

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

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ internal static DeclarationModifiers CheckModifiers(
3939
out bool modifierErrors)
4040
{
4141
modifierErrors = false;
42+
DeclarationModifiers reportStaticNotVirtualForModifiers = DeclarationModifiers.None;
43+
44+
if ((modifiers & allowedModifiers & DeclarationModifiers.Static) != 0)
45+
{
46+
reportStaticNotVirtualForModifiers = allowedModifiers & (DeclarationModifiers.Override | DeclarationModifiers.Virtual);
47+
allowedModifiers &= ~reportStaticNotVirtualForModifiers;
48+
}
49+
4250
DeclarationModifiers errorModifiers = modifiers & ~allowedModifiers;
4351
DeclarationModifiers result = modifiers & allowedModifiers;
4452

@@ -55,6 +63,16 @@ internal static DeclarationModifiers CheckModifiers(
5563
ReportPartialError(errorLocation, diagnostics, modifierTokens);
5664
break;
5765

66+
case DeclarationModifiers.Override:
67+
case DeclarationModifiers.Virtual:
68+
if ((reportStaticNotVirtualForModifiers & oneError) == 0)
69+
{
70+
goto default;
71+
}
72+
73+
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, errorLocation, ModifierUtils.ConvertSingleModifierToSyntaxText(oneError));
74+
break;
75+
5876
default:
5977
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, errorLocation, ConvertSingleModifierToSyntaxText(oneError));
6078
break;
@@ -94,27 +112,63 @@ internal static void ReportDefaultInterfaceImplementationModifiers(
94112
Location errorLocation,
95113
BindingDiagnosticBag diagnostics)
96114
{
97-
if (!hasBody && (modifiers & defaultInterfaceImplementationModifiers) != 0)
115+
if ((modifiers & defaultInterfaceImplementationModifiers) != 0)
98116
{
99117
LanguageVersion availableVersion = ((CSharpParseOptions)errorLocation.SourceTree.Options).LanguageVersion;
100-
LanguageVersion requiredVersion = MessageID.IDS_DefaultInterfaceImplementation.RequiredVersion();
101-
if (availableVersion < requiredVersion)
118+
LanguageVersion requiredVersion;
119+
120+
if ((modifiers & defaultInterfaceImplementationModifiers & DeclarationModifiers.Static) != 0 &&
121+
(modifiers & defaultInterfaceImplementationModifiers & (DeclarationModifiers.Sealed | DeclarationModifiers.Abstract)) != 0)
102122
{
103-
DeclarationModifiers errorModifiers = modifiers & defaultInterfaceImplementationModifiers;
104-
var requiredVersionArgument = new CSharpRequiredLanguageVersion(requiredVersion);
105-
var availableVersionArgument = availableVersion.ToDisplayString();
106-
while (errorModifiers != DeclarationModifiers.None)
123+
var reportModifiers = DeclarationModifiers.Sealed | DeclarationModifiers.Abstract;
124+
if ((modifiers & defaultInterfaceImplementationModifiers & (DeclarationModifiers.Sealed | DeclarationModifiers.Abstract)) == (DeclarationModifiers.Sealed | DeclarationModifiers.Abstract))
125+
{
126+
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, errorLocation, ConvertSingleModifierToSyntaxText(DeclarationModifiers.Sealed));
127+
reportModifiers &= ~DeclarationModifiers.Sealed;
128+
}
129+
130+
requiredVersion = MessageID.IDS_FeatureStaticAbstractMembersInInterfaces.RequiredVersion();
131+
if (availableVersion < requiredVersion)
107132
{
108-
DeclarationModifiers oneError = errorModifiers & ~(errorModifiers - 1);
109-
Debug.Assert(oneError != DeclarationModifiers.None);
110-
errorModifiers = errorModifiers & ~oneError;
111-
diagnostics.Add(ErrorCode.ERR_InvalidModifierForLanguageVersion, errorLocation,
112-
ConvertSingleModifierToSyntaxText(oneError),
113-
availableVersionArgument,
114-
requiredVersionArgument);
133+
report(modifiers, reportModifiers, errorLocation, diagnostics, availableVersion, requiredVersion);
134+
}
135+
136+
return; // below we will either ask for an earlier version of the language, or will not report anything
137+
}
138+
139+
if (hasBody)
140+
{
141+
if ((modifiers & defaultInterfaceImplementationModifiers & DeclarationModifiers.Static) != 0)
142+
{
143+
Binder.CheckFeatureAvailability(errorLocation.SourceTree, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, errorLocation);
144+
}
145+
}
146+
else
147+
{
148+
requiredVersion = MessageID.IDS_DefaultInterfaceImplementation.RequiredVersion();
149+
if (availableVersion < requiredVersion)
150+
{
151+
report(modifiers, defaultInterfaceImplementationModifiers, errorLocation, diagnostics, availableVersion, requiredVersion);
115152
}
116153
}
117154
}
155+
156+
static void report(DeclarationModifiers modifiers, DeclarationModifiers unsupportedModifiers, Location errorLocation, BindingDiagnosticBag diagnostics, LanguageVersion availableVersion, LanguageVersion requiredVersion)
157+
{
158+
DeclarationModifiers errorModifiers = modifiers & unsupportedModifiers;
159+
var requiredVersionArgument = new CSharpRequiredLanguageVersion(requiredVersion);
160+
var availableVersionArgument = availableVersion.ToDisplayString();
161+
while (errorModifiers != DeclarationModifiers.None)
162+
{
163+
DeclarationModifiers oneError = errorModifiers & ~(errorModifiers - 1);
164+
Debug.Assert(oneError != DeclarationModifiers.None);
165+
errorModifiers = errorModifiers & ~oneError;
166+
diagnostics.Add(ErrorCode.ERR_InvalidModifierForLanguageVersion, errorLocation,
167+
ConvertSingleModifierToSyntaxText(oneError),
168+
availableVersionArgument,
169+
requiredVersionArgument);
170+
}
171+
}
118172
}
119173

120174
internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(DeclarationModifiers mods, bool hasBody, bool isExplicitInterfaceImplementation)
@@ -126,7 +180,11 @@ internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(Declara
126180
mods |= DeclarationModifiers.Sealed;
127181
}
128182
}
129-
else if ((mods & (DeclarationModifiers.Static | DeclarationModifiers.Private | DeclarationModifiers.Partial | DeclarationModifiers.Virtual | DeclarationModifiers.Abstract)) == 0)
183+
else if ((mods & DeclarationModifiers.Static) != 0)
184+
{
185+
mods &= ~DeclarationModifiers.Sealed;
186+
}
187+
else if ((mods & (DeclarationModifiers.Private | DeclarationModifiers.Partial | DeclarationModifiers.Virtual | DeclarationModifiers.Abstract)) == 0)
130188
{
131189
Debug.Assert(!isExplicitInterfaceImplementation);
132190

@@ -162,7 +220,7 @@ internal static DeclarationModifiers AdjustModifiersForAnInterfaceMember(Declara
162220
return mods;
163221
}
164222

165-
private static string ConvertSingleModifierToSyntaxText(DeclarationModifiers modifier)
223+
internal static string ConvertSingleModifierToSyntaxText(DeclarationModifiers modifier)
166224
{
167225
switch (modifier)
168226
{

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ private DeclarationModifiers MakeModifiers(SyntaxTokenList modifiers, bool expli
504504

505505
protected void CheckModifiersAndType(BindingDiagnosticBag diagnostics)
506506
{
507+
Debug.Assert(!IsStatic || (!IsVirtual && !IsOverride)); // Otherwise 'virtual' and 'override' should have been reported and cleared earlier.
508+
507509
Location location = this.Locations[0];
508510
var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly);
509511
bool isExplicitInterfaceImplementationInInterface = ContainingType.IsInterface && IsExplicitInterfaceImplementation;
@@ -512,10 +514,10 @@ protected void CheckModifiersAndType(BindingDiagnosticBag diagnostics)
512514
{
513515
diagnostics.Add(ErrorCode.ERR_VirtualPrivate, location, this);
514516
}
515-
else if (IsStatic && (IsOverride || IsVirtual || IsAbstract))
517+
else if (IsStatic && IsAbstract && !ContainingType.IsInterface)
516518
{
517-
// A static member '{0}' cannot be marked as override, virtual, or abstract
518-
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, this);
519+
// A static member '{0}' cannot be marked as 'abstract'
520+
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, ModifierUtils.ConvertSingleModifierToSyntaxText(DeclarationModifiers.Abstract));
519521
}
520522
else if (IsReadOnly && IsStatic)
521523
{

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,8 @@ protected void CheckFeatureAvailabilityAndRuntimeSupport(SyntaxNode declarationS
969969
{
970970
if (_containingType.IsInterface)
971971
{
972-
if (hasBody || IsExplicitInterfaceImplementation)
972+
if ((!IsStatic || MethodKind is MethodKind.StaticConstructor) &&
973+
(hasBody || IsExplicitInterfaceImplementation))
973974
{
974975
Binder.CheckFeatureAvailability(declarationSyntax, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, location);
975976
}
@@ -978,6 +979,8 @@ protected void CheckFeatureAvailabilityAndRuntimeSupport(SyntaxNode declarationS
978979
{
979980
diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, location);
980981
}
982+
983+
// PROTOTYPE(StaticAbstractMembersInInterfaces): Check runtime capability.
981984
}
982985
}
983986

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,8 @@ private static DeclarationModifiers AddImpliedModifiers(DeclarationModifiers mod
498498

499499
private void CheckModifiers(bool isExplicitInterfaceImplementation, bool isVararg, bool hasBody, Location location, BindingDiagnosticBag diagnostics)
500500
{
501+
Debug.Assert(!IsStatic || (!IsVirtual && !IsOverride)); // Otherwise 'virtual' and 'override' should have been reported and cleared earlier.
502+
501503
bool isExplicitInterfaceImplementationInInterface = isExplicitInterfaceImplementation && ContainingType.IsInterface;
502504

503505
if (IsPartial && HasExplicitAccessModifier)
@@ -525,10 +527,10 @@ private void CheckModifiers(bool isExplicitInterfaceImplementation, bool isVarar
525527
{
526528
diagnostics.Add(ErrorCode.ERR_VirtualPrivate, location, this);
527529
}
528-
else if (IsStatic && (IsOverride || IsVirtual || IsAbstract))
530+
else if (IsStatic && IsAbstract && !ContainingType.IsInterface)
529531
{
530-
// A static member '{0}' cannot be marked as override, virtual, or abstract
531-
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, this);
532+
// A static member '{0}' cannot be marked as 'abstract'
533+
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, ModifierUtils.ConvertSingleModifierToSyntaxText(DeclarationModifiers.Abstract));
532534
}
533535
else if (IsOverride && (IsNew || IsVirtual))
534536
{

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -846,16 +846,18 @@ private void CheckAccessibility(Location location, BindingDiagnosticBag diagnost
846846

847847
private void CheckModifiers(bool isExplicitInterfaceImplementation, Location location, bool isIndexer, BindingDiagnosticBag diagnostics)
848848
{
849+
Debug.Assert(!IsStatic || (!IsVirtual && !IsOverride)); // Otherwise 'virtual' and 'override' should have been reported and cleared earlier.
850+
849851
bool isExplicitInterfaceImplementationInInterface = isExplicitInterfaceImplementation && ContainingType.IsInterface;
850852

851853
if (this.DeclaredAccessibility == Accessibility.Private && (IsVirtual || (IsAbstract && !isExplicitInterfaceImplementationInInterface) || IsOverride))
852854
{
853855
diagnostics.Add(ErrorCode.ERR_VirtualPrivate, location, this);
854856
}
855-
else if (IsStatic && (IsOverride || IsVirtual || IsAbstract))
857+
else if (IsStatic && IsAbstract && !ContainingType.IsInterface)
856858
{
857-
// A static member '{0}' cannot be marked as override, virtual, or abstract
858-
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, this);
859+
// A static member '{0}' cannot be marked as 'abstract'
860+
diagnostics.Add(ErrorCode.ERR_StaticNotVirtual, location, ModifierUtils.ConvertSingleModifierToSyntaxText(DeclarationModifiers.Abstract));
859861
}
860862
else if (IsStatic && HasReadOnlyModifier)
861863
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private SourceUserDefinedConversionSymbol(
4646
containingType,
4747
location,
4848
syntax,
49-
MakeDeclarationModifiers(syntax, location, diagnostics),
49+
MakeDeclarationModifiers(containingType.IsInterface, syntax, location, diagnostics),
5050
hasBody: syntax.HasAnyBody(),
5151
isExpressionBodied: syntax.Body == null && syntax.ExpressionBody != null,
5252
isIterator: SyntaxFacts.HasYieldOperations(syntax.Body),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ private SourceUserDefinedOperatorSymbol(
4444
containingType,
4545
location,
4646
syntax,
47-
MakeDeclarationModifiers(syntax, location, diagnostics),
47+
MakeDeclarationModifiers(containingType.IsInterface, syntax, location, diagnostics),
4848
hasBody: syntax.HasAnyBody(),
4949
isExpressionBodied: syntax.Body == null && syntax.ExpressionBody != null,
5050
isIterator: SyntaxFacts.HasYieldOperations(syntax.Body),

0 commit comments

Comments
 (0)