-
Notifications
You must be signed in to change notification settings - Fork 97
[Version 9.0] Feature support for unconstrained type parameter annotations #1470
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
base: draft-v9
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,28 +3,28 @@ | |
| ## 15.1 General | ||
|
|
||
| A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, finalizers, and static constructors), and nested types. Class types support inheritance, a mechanism whereby a ***derived class*** can extend and specialize a ***base class***. | ||
|
|
||
| Structs ([§16](structs.md#16-structs)) and interfaces ([§19](interfaces.md#19-interfaces)) have members similar to classes but with certain restrictions. This clause defines the declarations for classes and class members. The clauses for structs and interfaces define the restrictions for those types in terms of the corresponding declarations in class types. | ||
|
|
||
| ## 15.2 Class declarations | ||
|
|
||
| ### 15.2.1 General | ||
|
|
||
| A *class_declaration* is a *type_declaration* ([§14.7](namespaces.md#147-type-declarations)) that declares a new class. | ||
|
|
||
| ```ANTLR | ||
| class_declaration | ||
| : attributes? class_modifier* 'partial'? 'class' identifier | ||
| type_parameter_list? class_base? type_parameter_constraints_clause* | ||
| class_body ';'? | ||
| ; | ||
| ``` | ||
|
|
||
| A *class_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *class_modifier*s ([§15.2.2](classes.md#1522-class-modifiers)), followed by an optional `partial` modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `class` and an *identifier* that names the class, followed by an optional *type_parameter_list* ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *class_base* specification ([§15.2.4](classes.md#1524-class-base-specification)), followed by an optional set of *type_parameter_constraints_clause*s ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *class_body* ([§15.2.6](classes.md#1526-class-body)), optionally followed by a semicolon. | ||
|
|
||
| A class declaration shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. | ||
|
|
||
| A class declaration that supplies a *type_parameter_list* is a generic class declaration. Additionally, any class nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). | ||
|
|
||
| ### 15.2.2 Class modifiers | ||
|
|
||
|
|
@@ -409,6 +409,7 @@ | |
| | 'struct' | ||
| | 'notnull' | ||
| | 'unmanaged' | ||
| | 'default' | ||
| ; | ||
|
|
||
| secondary_constraint | ||
|
|
@@ -429,7 +430,9 @@ | |
|
|
||
| The list of constraints given in a `where` clause can include any of the following components, in this order: a single primary constraint, one or more secondary constraints, and the constructor constraint, `new()`. | ||
|
|
||
| A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***value type constraint*** `struct`, the ***not null constraint*** `notnull` or the ***unmanaged type constraint*** `unmanaged`. The class type and the reference type constraint can include the *nullable_type_annotation*. | ||
| A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***value type constraint*** `struct`, the ***not null constraint*** `notnull`, the ***unmanaged type constraint*** `unmanaged`, or `default`. The class type and the reference type constraint can include the *nullable_type_annotation*. | ||
|
|
||
| It is a compile-time error to use a `default` constraint other than on a method override or explicit implementation. It is a compile-time error to use a `default` constraint when the corresponding type parameter in the overridden or interface method is constrained to a reference type or value type. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to check, "is constrained to a reference type or value type" includes a class type constraint, right? (So I can't write It's all slightly confusing that there's the reference type constraint which is spelled |
||
|
|
||
| A secondary constraint can be an *interface_type* or *type_parameter*, optionally followed by a *nullable_type_annotation*. The presence of the *nullable_type_annotation* indicates that the type argument is allowed to be the nullable reference type that corresponds to a non-nullable reference type that satisfies the constraint. | ||
|
|
||
|
|
@@ -444,12 +447,14 @@ | |
|
|
||
| > *Note*: To specify that a type argument is a nullable reference type, do not add the nullable type annotation as a constraint (use `T : class` or `T : BaseClass`), but use `T?` throughout the generic declaration to indicate the corresponding nullable reference type for the type argument. *end note* | ||
|
|
||
| <!-- Remove in C# 9, when `?` is allowed on any type parameter. --> | ||
| The nullable type annotation, `?`, can only be used on a type parameter that has the value type constraint, the reference type constraint without the *nullable_type_annotation*, or a class type constraint without the *nullable_type_annotation*. | ||
| Except when a type parameter is explicitly constrained to value types, the nullable type annotation `?` can only be applied to a type parameter when the nullable annotations flag is enabled ([§6.5.9](lexical-structure.md#659-nullable-directive)). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Constrained to non-nullable value types? |
||
|
|
||
| For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`. | ||
|
|
||
| <!-- Add in C# 9, when `?` is allowed on nullable reference type parameters. --> | ||
| <!-- For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`. --> | ||
| > *Note*: On a type parameter, a return type `T?` has the same nullability effect as `[MaybeNull] T`, and a parameter type `T?` has the same nullability effect as `[AllowNull] T`. *end note* | ||
| !-- markdownlint-disable MD028 --> | ||
|
|
||
| <!-- markdownlint-enable MD028 --> | ||
| > *Example*: The following examples show how the nullability of a type argument impacts the nullability of a declaration of its type parameter: | ||
| > | ||
| > <!-- Example: {template:"standalone-lib-without-using", name:"RepeatedNullable"} --> | ||
|
|
@@ -534,6 +539,8 @@ | |
|
|
||
| Because `unmanaged` is not a keyword, in *primary_constraint* the unmanaged constraint is always syntactically ambiguous with *class_type*. For compatibility reasons, if a name lookup ([§12.8.4](expressions.md#1284-simple-names)) of the name `unmanaged` succeeds it is treated as a `class_type`. Otherwise it is treated as the unmanaged constraint. | ||
|
|
||
| The `default` constraint applies to a type parameter whose inherited constraints do not establish it as a reference type or a value type; its effect on `T?` in override methods and explicit interface method implementations is specified in [§15.6.5](classes.md#1565-override-methods) and [§19.6.2](interfaces.md#1962-explicit-interface-member-implementations). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "applies to a type parameter" feels a bit odd here. Is it actually "can be applied to a type parameter" or have I missed something?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And possibly remove "method" from "explicit interface method implementations"? |
||
|
|
||
| Pointer types are never allowed to be type arguments, and do not satisfy any type constraints, even unmanaged, despite being unmanaged types. | ||
|
|
||
| If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal “base type” that every type argument used for that type parameter shall support. Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile-time. The type argument supplied shall satisfy the conditions described in [§8.4.5](types.md#845-satisfying-constraints). | ||
|
|
@@ -2165,7 +2172,7 @@ | |
|
|
||
| A generic method is a method whose declaration includes a *type_parameter_list*. This specifies the type parameters for the method. The optional *type_parameter_constraints_clause*s specify the constraints for the type parameters. | ||
|
|
||
| A generic *method_declaration*, either with an `override` modifier, or for an explicit interface member implementation, inherits type parameter constraints from the overridden method or interface member respectively. Such declarations may only have *type_parameter_constraints_clause*s containing the *primary_constraint*s `class` and `struct`, the meaning of which in this context is defined in [§15.6.5](classes.md#1565-override-methods) and [§19.6.2](interfaces.md#1962-explicit-interface-member-implementations) for overriding methods and explicit interface implementations respectively. | ||
| A generic *method_declaration*, either with an `override` modifier, or for an explicit interface member implementation, inherits type parameter constraints from the overridden method or interface member respectively. Such declarations may only have *type_parameter_constraints_clause*s containing the *primary_constraint*s `class`, `struct`, and `default`, the meaning of which in this context is defined in [§15.6.5](classes.md#1565-override-methods) and [§19.6.2](interfaces.md#1962-explicit-interface-member-implementations) for overriding methods and explicit interface implementations respectively. | ||
|
|
||
| The *member_name* specifies the name of the method. Unless the method is an explicit interface member implementation ([§19.6.2](interfaces.md#1962-explicit-interface-member-implementations)), the *member_name* is simply an *identifier*. | ||
|
|
||
|
|
@@ -2757,9 +2764,10 @@ | |
| - The overridden base method is not a sealed method. | ||
| - There is an identity conversion between the return type of the overridden base method and the override method. | ||
| - The override declaration and the overridden base method have the same declared accessibility. In other words, an override declaration cannot change the accessibility of the virtual method. However, if the overridden base method is protected internal and it is declared in a different assembly than the assembly containing the override declaration then the override declaration’s declared accessibility shall be protected. | ||
| - A *type_parameter_constraints_clause* may only consist of the `class` or `struct` *primary_constraint*s applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. Any type of the form `T?` in the overriding method’s signature, where `T` is a type parameter, is interpreted as follows: | ||
| - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type; otherwise | ||
| - If either there is no added constraint, or a `struct` constraint is added, for the type parameter `T` then `T?` is a nullable value type. | ||
| - A *type_parameter_constraints_clause* may only consist of the `class`, `struct`, or `default` *primary_constraint*s. The `class` and `struct` constraints are applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. The `default` constraint is applied to *type_parameter*s that are not constrained to either reference or value types. Any type of the form `T?` in the overriding method’s signature, where `T` is a type parameter, is interpreted as follows: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again the "are applied" and "is applied" feel slightly confusing to me. |
||
| - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type. | ||
| - If either a `struct` constraint is added, or no constraint is added and the inherited constraint is a value type constraint, for the type parameter `T` then `T?` is a nullable value type. | ||
| - If a `default` constraint is added for type parameter `T` then `T?` represents a nullable instance of the corresponding reference type when `T` is a reference type, and an instance of `T` when `T` is a value type. If `T` is substituted with an annotated type `U?`, then `T?` represents `U?`, not `U??`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this bullet at the moment. |
||
|
|
||
| > *Example*: The following demonstrates how the overriding rules work for generic classes: | ||
| > | ||
|
|
@@ -2809,6 +2817,28 @@ | |
| > ``` | ||
| > | ||
| > Without the type parameter constraint `where T : class`, the base method with the reference-typed type parameter cannot be overridden. *end example* | ||
| <!-- markdownlint-disable MD028 --> | ||
|
|
||
| <!-- markdownlint-enable MD028 --> | ||
| > *Example*: The following demonstrates how the `default` constraint works when overriding a method with an unconstrained type parameter: | ||
| > | ||
| > <!-- Example: {template:"standalone-lib-without-using", name:"OverrideMethods6"} --> | ||
| > ```csharp | ||
| > #nullable enable | ||
| > class A2 | ||
| > { | ||
| > public virtual void F2<T>(T? t) where T : struct { } | ||
| > public virtual void F2<T>(T? t) { } | ||
| > } | ||
| > | ||
| > class B2 : A2 | ||
| > { | ||
| > public override void F2<T>(T? t) /*where T : struct*/ { } | ||
| > public override void F2<T>(T? t) where T : default { } | ||
| > } | ||
| > ``` | ||
| > | ||
| > The `default` constraint on `B2.F2` is required to override the unconstrained `A2.F2` with a `T?` parameter. Without it, `T?` in the second override would be interpreted as a nullable value type, so the declaration would not override the unconstrained `A2.F2`. *end example* | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a really good example - but wow this is a confusing bit of the language. (At least for me...) I'm hoping this is a corner case that needs to be specified because that's what we do, but which most people never run into. |
||
|
|
||
| An override declaration can access the overridden base method using a *base_access* ([§12.8.15](expressions.md#12815-base-access)). | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| # 19 Interfaces | ||
|
|
||
| ## 19.1 General | ||
|
|
||
| An interface defines a contract. A class or struct that implements an interface shall adhere to its contract. An interface may inherit from multiple base interfaces, and a class or struct may implement multiple interfaces. | ||
|
|
||
|
|
@@ -855,10 +855,11 @@ | |
|
|
||
| An explicit interface method implementation inherits any type parameter constraints from the interface. | ||
|
|
||
| A *type_parameter_constraints_clause* on an explicit interface method implementation may only consist of the `class` or `struct` *primary_constraint*s applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. Any type of the form `T?` in the signature of the explicit interface method implementation, where `T` is a type parameter, is interpreted as follows: | ||
| A *type_parameter_constraints_clause* on an explicit interface method implementation may only consist of the `class`, `struct`, or `default` *primary_constraint*s. The `class` and `struct` constraints are applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. The `default` constraint is applied to *type_parameter*s that are not constrained to either reference or value types. Any type of the form `T?` in the signature of the explicit interface method implementation, where `T` is a type parameter, is interpreted as follows: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, "is applied" feels confusing to me. |
||
|
|
||
| - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type; otherwise | ||
| - If either there is no added constraint, or a `struct` constraint is added, for the type parameter `T` then `T?` is a nullable value type. | ||
| - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type. | ||
| - If either a `struct` constraint is added, or no constraint is added and the inherited constraint is a value type constraint, for the type parameter `T` then `T?` is a nullable value type. | ||
| - If a `default` constraint is added for type parameter `T` then `T?` represents a nullable instance of the corresponding reference type when `T` is a reference type, and an instance of `T` when `T` is a value type. If `T` is substituted with an annotated type `U?`, then `T?` represents `U?`, not `U??`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per classes.md line 2770, I don't understand this bullet point. |
||
|
|
||
| > *Example*: The following demonstrates how the rules work when type parameters are involved: | ||
| > | ||
|
|
@@ -881,6 +882,28 @@ | |
| > Without the type parameter constraint `where T : class`, the base method with the reference-typed type parameter cannot be overridden. *end example* | ||
| <!-- markdownlint-disable MD028 --> | ||
|
|
||
| <!-- markdownlint-enable MD028 --> | ||
| > *Example*: The following demonstrates how the `default` constraint works when explicitly implementing an interface method with an unconstrained type parameter: | ||
| > | ||
| > <!-- Example: {template:"standalone-lib-without-using", name:"ExplicitInterfaceMemberImplementations7"} --> | ||
| > ```csharp | ||
| > #nullable enable | ||
| > interface I2 | ||
| > { | ||
| > void F2<T>(T? t) where T : struct; | ||
| > void F2<T>(T? t); | ||
| > } | ||
| > | ||
| > class C2 : I2 | ||
| > { | ||
| > void I2.F2<T>(T? t) /*where T : struct*/ { } | ||
| > void I2.F2<T>(T? t) where T : default { } | ||
| > } | ||
| > ``` | ||
| > | ||
| > The `default` constraint on `C2.F2` is required to implement the unconstrained `I2.F2` with a `T?` parameter. Without it, `T?` in the second implementation would be interpreted as a nullable value type, so the declaration would not implement the unconstrained `I2.F2`. *end example* | ||
| <!-- markdownlint-disable MD028 --> | ||
|
|
||
| <!-- markdownlint-enable MD028 --> | ||
| > *Note*: Explicit interface member implementations have different accessibility characteristics than other members. Because explicit interface member implementations are never accessible through a qualified interface member name in a method invocation or a property access, they are in a sense private. However, since they can be accessed through the interface, they are in a sense also as public as the interface in which they are declared. | ||
| > Explicit interface member implementations serve two primary purposes: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| # 8 Types | ||
|
|
||
| ## 8.1 General | ||
|
|
||
| The types of the C# language are divided into two main categories: ***reference type*** and ***value type***. A value type or a reference type may be a ***generic type***, which takes one or more ***type parameter***s. Type parameters can designate both value types and reference types. | ||
|
|
||
| ```ANTLR | ||
| type | ||
|
|
@@ -20,8 +20,8 @@ | |
| > *Note*: When a variable is a reference or output parameter, it does not have its own storage but references the storage of another variable. In this case, the ref or out variable is effectively an alias for another variable and not a distinct variable. *end note* | ||
|
|
||
| C#’s type system is unified such that *a value of any type can be treated as an object*. Every type in C# directly or indirectly derives from the `object` class type, and `object` is the ultimate base class of all types. Values of reference types are treated as objects simply by viewing the values as type `object`. Values of value types are treated as objects by performing boxing and unboxing operations ([§8.3.13](types.md#8313-boxing-and-unboxing)). | ||
|
|
||
| For convenience, throughout this specification, some library type names are written without using their full name qualification. Refer to [§C.5](standard-library.md#c5-library-type-abbreviations) for more information. | ||
|
|
||
| ## 8.2 Reference types | ||
|
|
||
|
|
@@ -737,7 +737,7 @@ | |
| > - A type parameter cannot be used anywhere within an attribute ([§23.2.1](attributes.md#2321-general)). | ||
| > - A type parameter cannot be used in a member access ([§12.8.7](expressions.md#1287-member-access)) or type name ([§7.8](basic-concepts.md#78-namespace-and-type-names)) to identify a static member or a nested type. | ||
| > - A type parameter can only be used as an *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) if the type parameter is constrained by the unmanaged constraint ([§15.2.5](classes.md#1525-type-parameter-constraints)). | ||
| > - Nullable annotations (`?`) aren’t allowed on an instance of a type parameter unless that type parameter is constrained to be either a reference type or a value type ([§15.2.5](classes.md#1525-type-parameter-constraints)). | ||
| > - Except when a type parameter is explicitly constrained to value types, the nullable type annotation (`?`) can only be applied to an instance of a type parameter when the nullable annotations flag is enabled ([§6.5.9](lexical-structure.md#659-nullable-directive), [§15.2.5](classes.md#1525-type-parameter-constraints)). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I understand what "instance" means here, but that an alternative would be better. (It means "a situation where it occurs in the source code" right?) I don't have a better word right now, but we should brainstorm it. |
||
| > | ||
| > *end note* | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps "explicit implementation" => "explicit interface implementation"? Not really required, just a thought.