From 74ef7b1a9d615a539419364c8bf3433ceb9b217c Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 13:37:11 -0400 Subject: [PATCH 1/6] Support static abstract members in interfaces --- standard/conversions.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 39acaacc0..be8683bfd 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -741,9 +741,14 @@ A user-defined implicit conversion from an expression `E` to a type `T` is pro - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sᵢ` and `Tᵢ`, respectively. -- Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S₀` (if `S₀` exists and is a class or struct), the base classes of `S₀` (if `S₀` exists and is a class), and `T₀` (if `T₀` is a class or struct). A type is added to the set `D` only if an identity conversion to another type already included in the set doesn’t exist. - -- Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing `E` to a type encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. +- Find the set of applicable user-defined and lifted conversion operators, `U`. + - Find the set of types, `D1`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), and `T0` (if `T0` is a class or struct). + - Find the set of applicable user-defined and lifted conversion operators, `U1`. This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in `D1` that convert from a type encompassing `S` to a type encompassed by `T`. + - If `U1` is not empty, then `U` is `U1`. Otherwise, + - Find the set of types, `D2`, from which user-defined conversion operators will be considered. This set consists of `Sᵢ` *effective interface set* and their base interfaces (if `Sᵢ` is a type parameter), and `Tᵢ` *effective interface set* (if `Tᵢ` is a type parameter). + - Find the set of applicable user-defined and lifted conversion operators, `U2`. This set consists of the user-defined and lifted implicit conversion operators declared by the interfaces in `D2` that convert from a type encompassing `S` to a type encompassed by `T`. + - If `U2` is not empty, then `U` is `U2`. +- If `U` is empty, the conversion is undefined and a compile-time error occurs. - Find the most-specific source type, `Sₓ`, of the operators in `U`: - If `S` exists and any of the operators in `U` convert from `S`, then `Sₓ` is `S`. - Otherwise, `Sₓ` is the most-encompassed type in the combined set of source types of the operators in `U`. If exactly one most-encompassed type cannot be found, then the conversion is ambiguous and a compile-time error occurs. @@ -769,8 +774,14 @@ A user-defined explicit conversion from an expression `E` to a type `T` is pro - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sᵢ` and `Tᵢ`, respectively. -- Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S₀` (if `S₀` exists and is a class or struct), the base classes of `S₀` (if `S₀` exists and is a class), `T₀` (if `T₀` is a class or struct), and the base classes of `T₀` (if `T₀` is a class). A type is added to the set `D` only if an identity conversion to another type already included in the set doesn’t exist. -- Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing `E` or encompassed by `S` (if it exists) to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. +- Find the set of applicable user-defined and lifted conversion operators, `U`. + - Find the set of types, `D1`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), `T0` (if `T0` is a class or struct), and the base classes of `T0` (if `T0` is a class). + - Find the set of applicable user-defined and lifted conversion operators, `U1`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D1` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. + - If `U1` is not empty, then `U` is `U1`. Otherwise, + - Find the set of types, `D2`, from which user-defined conversion operators will be considered. This set consists of `Sᵢ` *effective interface set* and their base interfaces (if `Sᵢ` is a type parameter), and `Tᵢ` *effective interface set* and their base interfaces (if `Tᵢ` is a type parameter). + - Find the set of applicable user-defined and lifted conversion operators, `U2`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the interfaces in `D2` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. + - If `U2` is not empty, then `U` is `U2`. +- If `U` is empty, the conversion is undefined and a compile-time error occurs. - Find the most-specific source type, `Sₓ`, of the operators in `U`: - If `S` exists and any of the operators in `U` convert from `S`, then `Sₓ` is `S`. - Otherwise, if any of the operators in `U` convert from types that encompass `E`, then `Sₓ` is the most-encompassed type in the combined set of source types of those operators. If no most-encompassed type can be found, then the conversion is ambiguous and a compile-time error occurs. From d9615ce9d5844e6e2813afb7acb1a3476ef14ee6 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 13:40:11 -0400 Subject: [PATCH 2/6] Support static abstract members in interfaces --- standard/patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index 301adb22c..294c5da9a 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -447,9 +447,9 @@ relational_pattern Relational patterns support the relational operators `<`, `<=`, `>`, and `>=` on all of the built-in types that support such binary relational operators with both operands having the same type: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `nint`, `nuint`, and enums. -It is a compile-time error if `constant_expression`is `double.NaN`, `float.NaN`, or `null_literal`. +It is a compile-time error if *constant_expression*is `double.NaN`, `float.NaN`, or `null_literal`. -When the input value has a type for which a suitable built-in binary relational operator is defined, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise, the input value is converted to the type of `constant_expression` using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. The pattern is considered to not match if the conversion fails. If the conversion succeeds, the result of the pattern-matching operation is the result of evaluating the expression `e «op» v` where `e` is the converted input, «op» is the relational operator, and `v` is the `constant_expression`. +When the input value has a type for which a suitable built-in binary relational operator is defined, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise, the input value is converted to the type of *constant_expression* using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. It is a compile-time error if the input type is a type parameter constrained to, or a type inheriting, from `System.Numerics.INumberBase` and the input type has no suitable built-in binary relational operator defined. The pattern is considered to not match if the conversion fails. If the conversion succeeds, the result of the pattern-matching operation is the result of evaluating the expression `e «op» v` where `e` is the converted input, «op» is the relational operator, and `v` is the *constant_expression*. > *Example*: > From 46e714b117d27df3da044bf24b6b269a611fd056 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 13:42:50 -0400 Subject: [PATCH 3/6] Support static abstract members in interfaces --- standard/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 594d56fb5..6d4565ddd 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2184,9 +2184,9 @@ Grammar notes: > *Note*: The overlapping of, and priority between, alternatives here is solely for descriptive convenience; the grammar rules could be elaborated to remove the overlap. ANTLR, and other grammar systems, adopt the same convenience and so *method_body* has the specified semantics automatically. *end note* -A *method_declaration* may include a set of *attributes* ([§23](attributes.md#23-attributes)) and one of the permitted kinds of declared accessibility ([§15.3.6](classes.md#1536-access-modifiers)), the `new` ([§15.3.5](classes.md#1535-the-new-modifier)), `static` ([§15.6.3](classes.md#1563-static-and-instance-methods)), `virtual` ([§15.6.4](classes.md#1564-virtual-methods)), `override` ([§15.6.5](classes.md#1565-override-methods)), `sealed` ([§15.6.6](classes.md#1566-sealed-methods)), `abstract` ([§15.6.7](classes.md#1567-abstract-methods)), `extern` ([§15.6.8](classes.md#1568-external-methods)) and `async` ([§15.14](classes.md#1514-async-functions)). Additionally a *method_declaration* that is contained directly by a *struct_declaration* may include the `readonly` modifier ([§16.5.12](structs.md#16512-methods)). +A *method_declaration* may include a set of *attributes* ([§23](attributes.md#23-attributes)) and one of the permitted kinds of declared accessibility ([§15.3.6](classes.md#1536-access-modifiers)), the `new` ([§15.3.5](classes.md#1535-the-new-modifier)), `static` ([§15.6.3](classes.md#1563-static-and-instance-methods)), `virtual` ([§15.6.4](classes.md#1564-virtual-methods)), `override` ([§15.6.5](classes.md#1565-override-methods)), `sealed` ([§15.6.6](classes.md#1566-sealed-methods)), `abstract` ([§15.6.7](classes.md#1567-abstract-methods)), `extern` ([§15.6.8](classes.md#1568-external-methods)) and `async` ([§15.14](classes.md#1514-async-functions)) modifiers. Additionally a *method_declaration* that is contained directly by a *struct_declaration* may include the `readonly` modifier ([§16.5.12](structs.md#16512-methods)). -A declaration has a valid combination of modifiers if all of the following are true: +A *method_declaration* has a valid combination of modifiers if all of the following are true. (These rules are modified slightly in the context of an interface; see [§19.4.1](standard/interfaces.md#1941-general).): - The declaration includes a valid combination of access modifiers ([§15.3.6](classes.md#1536-access-modifiers)). - The declaration does not include the same modifier multiple times. From ffd3367d1a45c8582ed2cc3c99cd9267ffcb3620 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 13:54:28 -0400 Subject: [PATCH 4/6] Support static abstract members in interfaces --- standard/interfaces.md | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/standard/interfaces.md b/standard/interfaces.md index 666042de0..ec093b368 100644 --- a/standard/interfaces.md +++ b/standard/interfaces.md @@ -94,7 +94,7 @@ If a generic interface is declared in multiple parts ([§15.2.3](classes.md#1523 #### 19.2.3.2 Variance safety -The occurrence of variance annotations in the type parameter list of a type restricts the places where types can occur within the type declaration. However, these restrictions do not apply to occurrences of types within a declaration of a static member. +The occurrence of variance annotations in the type parameter list of a type restricts the places where types can occur within the type declaration. However, these restrictions do not apply to occurrences of types within a declaration of a non-virtual, non-abstract static member. A type `T` is ***output-unsafe*** if one of the following holds: @@ -233,15 +233,15 @@ interface_member_declaration ; ``` -This clause augments the description of members in classes ([§15.3](classes.md#153-class-members)) with the differences and restrictions for interfaces: +This subclause augments the description of members in classes ([§15.3](classes.md#153-class-members)) with the differences and restrictions for interfaces: - A *finalizer_declaration* is not allowed. - Instance constructors, *constructor_declaration*s, are not allowed. - All interface members implicitly have public access; however, an explicit access modifier ([§7.5.2](basic-concepts.md#752-declared-accessibility)) is permitted except on static constructors ([§15.12](classes.md#1512-static-constructors)). -- The `abstract` modifier is implied for interface function members ([§12.6](expressions.md#126-function-members)) without bodies; that modifier may be given explicitly. -- An interface instance function member whose declaration includes a body is an implicitly `virtual` member unless the `sealed` or `private` modifier is used. The `virtual` modifier may be given explicitly. +- The `abstract` modifier is implied for interface instance function members ([§12.6](expressions.md#126-function-members)) without bodies; that modifier may be given explicitly. For interface static function members without bodies the `abstract` modifier shall be present. +- An interface instance function member whose declaration includes a body is an implicitly `virtual` member unless the `sealed` or `private` modifier is used. The `virtual` modifier may be given explicitly. An interface static member whose declaration includes a body may have a `virtual` modifier. - A `private` or `sealed` function member of an interface shall have a body. -- A `private` function member shall not have the modifier `sealed`. +- A `private` instance function member shall not have the modifier `sealed`. - A derived interface may override an abstract or virtual member declared in a base interface. - An explicitly implemented function member shall not have the modifier `sealed`. @@ -429,7 +429,7 @@ A virtual method with implementation declared in an interface may be overridden ### 19.4.4 Interface properties -This clause augments the description of properties in classes [§15.7](classes.md#157-properties) for properties declared in interfaces. +This subclause augments the description of properties in classes [§15.7](classes.md#157-properties) for properties declared in interfaces. Interface properties are declared using *property_declaration*s ([§15.7.1](classes.md#1571-general)) with the following additional rules: @@ -440,25 +440,27 @@ Interface properties are declared using *property_declaration*s ([§15.7.1](clas > *Note*: As an interface cannot contain instance fields, an interface property cannot be an instance auto-property, as that would require the declaration of implicit hidden instance fields. *end note* - The type of an interface property shall be output-safe if there is a get accessor, and shall be input-safe if there is a set or init accessor. -- An interface method declaration that has a block body or expression body as a *method_body* is `virtual`; the `virtual` modifier is not required, but is allowed. -- An instance *property_declaration* that has no implementation is `abstract`; the `abstract` modifier is not required, but is allowed. It is *never* considered to be an automatically implemented property ([§15.7.4](classes.md#1574-automatically-implemented-properties)). +- An interface instance method declaration that has a block body or expression body as a *method_body* is `virtual`; the `virtual` modifier is not required, but is allowed. For a static method the `virtual` modifier is permitted. +- An instance *property_declaration* that has no implementation is `abstract`; the `abstract` modifier is not required, but is allowed. It is *never* considered to be an automatically implemented property ([§15.7.4](classes.md#1574-automatically-implemented-properties)). However, the `abstract` modifier shall be present if a static property is to be abstract. +- A *property_declaration* may contain the `sealed` modifier. ### 19.4.5 Interface events -This clause augments the description of events in classes [§15.8](classes.md#158-events) for events declared in interfaces. +This subclause augments the description of events in classes [§15.8](classes.md#158-events) for events declared in interfaces. Interface events are declared using *event_declaration*s ([§15.8.1](classes.md#1581-general)), with the following additional rules: - *event_modifier* shall not include `override`. - A derived interface may implement an abstract interface event declared in a base interface ([§15.8.5](classes.md#1585-virtual-sealed-override-and-abstract-accessors)). - It is a compile-time error for *variable_declarators* in an instance *event_declaration* to contain any *variable_initializer*s. -- An instance event with the `virtual` or `sealed` modifiers must declare accessors. It is *never* considered to be an automatically implemented field-like event ([§15.8.2](classes.md#1582-field-like-events)). -- An instance event with the `abstract` modifier must not declare accessors. +- An instance event with the `virtual` or `sealed` modifiers shall declare accessors. It is *never* considered to be an automatically implemented field-like event ([§15.8.2](classes.md#1582-field-like-events)). +- An instance event with the `abstract` modifier shall not declare accessors. +- A static event may have `abstract`, `virtual`, and `sealed` modifiers. - The type of an interface event shall be input-safe. ### 19.4.6 Interface indexers -This clause augments the description of indexers in classes [§15.9](classes.md#159-indexers) for indexers declared in interfaces. +This sub clause augments the description of indexers in classes [§15.9](classes.md#159-indexers) for indexers declared in interfaces. Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.md#159-indexers)), with the following additional rules: @@ -474,15 +476,19 @@ Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.m ### 19.4.7 Interface operators -This clause augments the description of *operator_declaration* members in classes [§15.10](classes.md#1510-operators) for operators declared in interfaces. +This subclause augments the description of *operator_declaration* members in classes [§15.10](classes.md#1510-operators) for operators declared in interfaces. For an *operator_declaration* in an interface the *operator_body* shall only be a block body ([§15.6.1](classes.md#1561-general)) or an expression body ([§15.6.1](classes.md#1561-general)). -It is a compile-time error for an interface to declare a conversion, equality, or inequality operator. +A static *operator_declaration* may have `abstract`, `virtual`, and `sealed` modifiers. + +In the context of a class or struct, at least one of the *fixed_parameter*s in a *unary_operator_declarator* and *binary_operator_declarator* is required to have type `T` or `T?`, where `T` is the instance type of the enclosing type. This requirement is relaxed in the context of an interface in that a restricted operand is allowed to be of a type parameter that counts as “the instance type of the enclosing type.” In order for a type parameter `T` to count as that it shall meet the following requirements: +- `T` is a direct type parameter on the interface in which the operator declaration occurs, and +- `T` is directly constrained by the instance type; i.e., the surrounding interface with its own type parameters used as type arguments. ### 19.4.8 Interface static constructors -This clause augments the description of static constructors in classes [§15.12](classes.md#1512-static-constructors) for static constructors declared in interfaces. +This subclause augments the description of static constructors in classes [§15.12](classes.md#1512-static-constructors) for static constructors declared in interfaces. The static constructor for a closed ([§8.4.3](types.md#843-open-and-closed-types)) interface executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following actions to occur within an application domain: @@ -498,7 +504,7 @@ To initialize a new closed interface type, first a new set of static fields for ### 19.4.9 Interface nested types -This clause augments the description of nested types in classes [§15.3.9](classes.md#1539-nested-types) for nested types declared in interfaces. +This subclause augments the description of nested types in classes [§15.3.9](classes.md#1539-nested-types) for nested types declared in interfaces. It is an error to declare a class type, struct type, or enum type within the scope of a type parameter that was declared with a *variance_annotation* ([§19.2.3.1](interfaces.md#19231-general)). @@ -949,6 +955,8 @@ The qualified interface member name of an explicit interface member implementati > > *end example* +An explicit interface member implementation that implements a static member shall itself be static. + ### 19.6.3 Uniqueness of implemented interfaces The interfaces implemented by a generic type declaration shall remain unique for all possible constructed types. Without this rule, it would be impossible to determine the correct method to call for certain constructed types. From 5ee4f9cbd8444dce5f9c411176336b732bcbe2df Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 14:11:26 -0400 Subject: [PATCH 5/6] fix md formatting --- standard/interfaces.md | 1 + 1 file changed, 1 insertion(+) diff --git a/standard/interfaces.md b/standard/interfaces.md index ec093b368..0e1166b96 100644 --- a/standard/interfaces.md +++ b/standard/interfaces.md @@ -483,6 +483,7 @@ For an *operator_declaration* in an interface the *operator_body* shall only be A static *operator_declaration* may have `abstract`, `virtual`, and `sealed` modifiers. In the context of a class or struct, at least one of the *fixed_parameter*s in a *unary_operator_declarator* and *binary_operator_declarator* is required to have type `T` or `T?`, where `T` is the instance type of the enclosing type. This requirement is relaxed in the context of an interface in that a restricted operand is allowed to be of a type parameter that counts as “the instance type of the enclosing type.” In order for a type parameter `T` to count as that it shall meet the following requirements: + - `T` is a direct type parameter on the interface in which the operator declaration occurs, and - `T` is directly constrained by the instance type; i.e., the surrounding interface with its own type parameters used as type arguments. From 1ad0292772e4c2b396feadb20e6d7e50b8fe6be7 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 17 Mar 2026 14:15:55 -0400 Subject: [PATCH 6/6] fix link --- standard/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index 6d4565ddd..2c29f60e1 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2186,7 +2186,7 @@ Grammar notes: A *method_declaration* may include a set of *attributes* ([§23](attributes.md#23-attributes)) and one of the permitted kinds of declared accessibility ([§15.3.6](classes.md#1536-access-modifiers)), the `new` ([§15.3.5](classes.md#1535-the-new-modifier)), `static` ([§15.6.3](classes.md#1563-static-and-instance-methods)), `virtual` ([§15.6.4](classes.md#1564-virtual-methods)), `override` ([§15.6.5](classes.md#1565-override-methods)), `sealed` ([§15.6.6](classes.md#1566-sealed-methods)), `abstract` ([§15.6.7](classes.md#1567-abstract-methods)), `extern` ([§15.6.8](classes.md#1568-external-methods)) and `async` ([§15.14](classes.md#1514-async-functions)) modifiers. Additionally a *method_declaration* that is contained directly by a *struct_declaration* may include the `readonly` modifier ([§16.5.12](structs.md#16512-methods)). -A *method_declaration* has a valid combination of modifiers if all of the following are true. (These rules are modified slightly in the context of an interface; see [§19.4.1](standard/interfaces.md#1941-general).): +A *method_declaration* has a valid combination of modifiers if all of the following are true. (These rules are modified slightly in the context of an interface; see [§19.4.1](interfaces.md#1941-general).): - The declaration includes a valid combination of access modifiers ([§15.3.6](classes.md#1536-access-modifiers)). - The declaration does not include the same modifier multiple times.