Skip to content

Commit a7525ff

Browse files
BillWagnerNigel-Ecmajskeet
authored
Specify generic constraints added to support nullable reference types in C# 8 (#1178)
* Incorporate some text from #700 Bring in the normative text from #700. Some text is removed because of the decision on normative language in our September meeting. * fix build issues * Edit pass * Respond to feedback. * port grammar, part 1 * Update standard/types.md Co-authored-by: Nigel-Ecma <[email protected]> * fix merge / rebase mishap * Edits based on meeting feedback. * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * grammar fixes * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * one last minor fix * Rework description based on last meeting Rework the description of nullable annotations on generic type parameters and generic type arguments. We decided that these annotations should be specified in terms of only generating warnings, but never changing the semantics of a program. * Use `nullable_type_attribute` in all type grammar We'd used `'?'` and `nullable_type_attribute` in different places for the `?` annotation. Define `nullable_type_attribute` at first use, and use that consistently. * Apply suggestions from code review Co-authored-by: Jon Skeet <[email protected]> * small grammar fix Offline comment from @Nigel-Ecma * updates from 10/30 meeting This covers part 1, the comments in the files tab * address comments in converstation tab This commit addresses the comments in the conversation tab from the 10/30 meeting. * additional feedback This commit incorporates the comments on the conversation tab. * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * Replace nullable_type_attribute with nullable_type_annotation * typos --------- Co-authored-by: Nigel-Ecma <[email protected]> Co-authored-by: Jon Skeet <[email protected]>
1 parent c10ff18 commit a7525ff

File tree

3 files changed

+95
-28
lines changed

3 files changed

+95
-28
lines changed

standard/classes.md

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -396,33 +396,32 @@ type_parameter_constraints_clauses
396396
: type_parameter_constraints_clause
397397
| type_parameter_constraints_clauses type_parameter_constraints_clause
398398
;
399-
399+
400400
type_parameter_constraints_clause
401401
: 'where' type_parameter ':' type_parameter_constraints
402402
;
403403
404404
type_parameter_constraints
405-
: primary_constraint
406-
| secondary_constraints
405+
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
406+
| secondary_constraints (',' constructor_constraint)?
407407
| constructor_constraint
408-
| primary_constraint ',' secondary_constraints
409-
| primary_constraint ',' constructor_constraint
410-
| secondary_constraints ',' constructor_constraint
411-
| primary_constraint ',' secondary_constraints ',' constructor_constraint
412408
;
413409
414410
primary_constraint
415-
: class_type
416-
| 'class'
411+
: class_type nullable_type_annotation?
412+
| 'class' nullable_type_annotation?
417413
| 'struct'
414+
| 'notnull'
418415
| 'unmanaged'
419416
;
420417
418+
secondary_constraint
419+
: interface_type nullable_type_annotation?
420+
| type_parameter nullable_type_annotation?
421+
;
422+
421423
secondary_constraints
422-
: interface_type
423-
| type_parameter
424-
| secondary_constraints ',' interface_type
425-
| secondary_constraints ',' type_parameter
424+
: secondary_constraint (',' secondary_constraint)*
426425
;
427426
428427
constructor_constraint
@@ -434,12 +433,66 @@ Each *type_parameter_constraints_clause* consists of the token `where`, followed
434433

435434
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()`.
436435

437-
A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***value type constraint*** `struct`, or the ***unmanaged type constraint*** `unmanaged`.
436+
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*.
438437

439-
A secondary constraint can be a *type_parameter* or *interface_type*.
438+
A secondary constraint can be an *interface_type* or *type_parameter*, optionally followed by a *nullable_type_annotation*. The presence of the nullable_type_annotatione* 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.
440439

441440
The reference type constraint specifies that a type argument used for the type parameter shall be a reference type. All class types, interface types, delegate types, array types, and type parameters known to be a reference type (as defined below) satisfy this constraint.
442441

442+
The class type, reference type constraint, and secondary constraints can include the nullable type annotation. The presence or absence of this annotation on the type parameter indicates the nullability expectations for the type argument:
443+
444+
- If the constraint does not include the nullable type annotation, the type argument is expected to be a non-nullable reference type. A compiler may issue a warning if the type argument is a nullable reference type.
445+
- If the constraint includes the nullable type annotation, the constraint is satisfied by both a non-nullable reference type and a nullable reference type.
446+
447+
The nullability of the type argument need not match the nullability of the type parameter. The compiler may issue a warning if the nullability of the type parameter doesn't match the nullability of the type argument.
448+
449+
> *Note*: To specify that a type argument is a nullable reference type, don't 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*
450+
451+
<!-- Remove in C# 9, when this is allowed -->
452+
The nullable type annotation, `?`, can't be used on an unconstrained type argument.
453+
454+
For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`.
455+
456+
> *Example*: The following examples show how the nullability of a type argument impacts the nullability of a declaration of its type parameter:
457+
>
458+
> <!-- Example: {template:"standalone-lib-without-using", name:"RepeatedNullable"} -->
459+
> ```csharp
460+
> public class C
461+
> {
462+
> }
463+
>
464+
> public static class Extensions
465+
> {
466+
> public static void M<T>(this T? arg) where T : notnull
467+
> {
468+
>
469+
> }
470+
> }
471+
>
472+
> public class Test
473+
> {
474+
> public void M()
475+
> {
476+
> C? mightBeNull = new C();
477+
> C notNull = new C();
478+
>
479+
> int number = 5;
480+
> int? missing = null;
481+
>
482+
> mightBeNull.M(); // arg is C?
483+
> notNull.M(); // arg is C?
484+
> number.M(); // arg is int?
485+
> missing.M(); // arg is int?
486+
> }
487+
> }
488+
> ```
489+
>
490+
> When the type argument is a non-nullable type, the `?` type annotation indicates that the parameter is the corresponding nullable type. When the type argument is already a nullable reference type, the parameter is that same nullable type.
491+
>
492+
> *end example*
493+
494+
The ***not null*** constraint specifies that a type argument used for the type parameter should be a non-nullable value type or a non-nullable reference type. A type argument that isn't a non-nullable value type or a non-nullable reference type is allowed, but the compiler may produce a diagnostic warning.
495+
443496
The value type constraint specifies that a type argument used for the type parameter shall be a non-nullable value type. All non-nullable struct types, enum types, and type parameters having the value type constraint satisfy this constraint. Note that although classified as a value type, a nullable value type ([§8.3.12](types.md#8312-nullable-value-types)) does not satisfy the value type constraint. A type parameter having the value type constraint shall not also have the *constructor_constraint*, although it may be used as a type argument for another type parameter with a *constructor_constraint*.
444497
445498
> *Note*: The `System.Nullable<T>` type specifies the non-nullable value type constraint for `T`. Thus, recursively constructed types of the forms `T??` and `Nullable<Nullable<T>>` are prohibited. *end note*
@@ -604,7 +657,7 @@ The ***effective interface set*** of a type parameter `T` is defined as follows
604657
- If `T` has no *interface_type* constraints but has *type_parameter* constraints, its effective interface set is the union of the effective interface sets of its *type_parameter* constraints.
605658
- If `T` has both *interface_type* constraints and *type_parameter* constraints, its effective interface set is the union of the set of dynamic erasures of its *interface_type* constraints and the effective interface sets of its *type_parameter* constraints.
606659
607-
A type parameter is *known to be a reference type* if it has the reference type constraint or its effective base class is not `object` or `System.ValueType`.
660+
A type parameter is *known to be a reference type* if it has the reference type constraint or its effective base class is not `object` or `System.ValueType`. A type parameter is *known to be a non-nullable reference type* if it is known to be a reference type and has the non-nullable reference type constraint.
608661
609662
Values of a constrained type parameter type can be used to access the instance members implied by the constraints.
610663
@@ -671,7 +724,7 @@ class_body
671724

672725
The modifier `partial` is used when defining a class, struct, or interface type in multiple parts. The `partial` modifier is a contextual keyword ([§6.4.4](lexical-structure.md#644-keywords)) and only has special meaning immediately before one of the keywords `class`, `struct`, or `interface`.
673726

674-
Each part of a ***partial type*** declaration shall include a `partial` modifier and shall be declared in the same namespace or containing type as the other parts. The `partial` modifier indicates that additional parts of the type declaration might exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for the only declaration of a type to include the `partial` modifier.
727+
Each part of a ***partial type*** declaration shall include a `partial` modifier and shall be declared in the same namespace or containing type as the other parts. The `partial` modifier indicates that additional parts of the type declaration might exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for the only declaration of a type to include the `partial` modifier. It is valid for only one declaration of a partial type to include the base class or implemented interfaces. However, all declarations of a base class or implemented interfaces must match, including the nullability of any specified type arguments.
675728

676729
All parts of a partial type shall be compiled together such that the parts can be merged at compile-time. Partial types specifically do not allow already compiled types to be extended.
677730

@@ -878,7 +931,7 @@ All members of a generic class can use type parameters from any enclosing class,
878931
> class C<V>
879932
> {
880933
> public V f1;
881-
> public C<V> f2 = null;
934+
> public C<V> f2;
882935
>
883936
> public C(V x)
884937
> {
@@ -1055,17 +1108,17 @@ Non-nested types can have `public` or `internal` declared accessibility and have
10551108
> private class Node
10561109
> {
10571110
> public object Data;
1058-
> public Node Next;
1111+
> public Node? Next;
10591112
>
1060-
> public Node(object data, Node next)
1113+
> public Node(object data, Node? next)
10611114
> {
10621115
> this.Data = data;
10631116
> this.Next = next;
10641117
> }
10651118
> }
10661119
>
1067-
> private Node first = null;
1068-
> private Node last = null;
1120+
> private Node? first = null;
1121+
> private Node? last = null;
10691122
>
10701123
> // Public interface
10711124
> public void AddToFront(object o) {...}

standard/expressions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2929,13 +2929,15 @@ When the operand of a *typeof_expression* is a sequence of tokens that satisfies
29292929
- Evaluate the resulting *type_name*, while ignoring all type parameter constraints.
29302930
- The *unbound_type_name* resolves to the unbound generic type associated with the resulting constructed type ([§8.4](types.md#84-constructed-types)).
29312931

2932+
It is an error for the type name to be a nullable reference type.
2933+
29322934
The result of the *typeof_expression* is the `System.Type` object for the resulting unbound generic type.
29332935

29342936
The third form of *typeof_expression* consists of a `typeof` keyword followed by a parenthesized `void` keyword. The result of an expression of this form is the `System.Type` object that represents the absence of a type. The type object returned by `typeof(void)` is distinct from the type object returned for any type.
29352937

29362938
> *Note*: This special `System.Type` object is useful in class libraries that allow reflection onto methods in the language, where those methods wish to have a way to represent the return type of any method, including `void` methods, with an instance of `System.Type`. *end note*
29372939
2938-
The `typeof` operator can be used on a type parameter. The result is the `System.Type` object for the run-time type that was bound to the type parameter. The `typeof` operator can also be used on a constructed type or an unbound generic type ([§8.4.4](types.md#844-bound-and-unbound-types)). The `System.Type` object for an unbound generic type is not the same as the `System.Type` object of the instance type ([§15.3.2](classes.md#1532-the-instance-type)). The instance type is always a closed constructed type at run-time so its `System.Type` object depends on the run-time type arguments in use. The unbound generic type, on the other hand, has no type arguments, and yields the same `System.Type` object regardless of runtime type arguments.
2940+
The `typeof` operator can be used on a type parameter. It is a compile time error if the type name is known to be a nullable reference type. The result is the `System.Type` object for the run-time type that was bound to the type parameter. If the run-time type is a nullable reference type, the result is the corresponding non-nullable reference type. The `typeof` operator can also be used on a constructed type or an unbound generic type ([§8.4.4](types.md#844-bound-and-unbound-types)). The `System.Type` object for an unbound generic type is not the same as the `System.Type` object of the instance type ([§15.3.2](classes.md#1532-the-instance-type)). The instance type is always a closed constructed type at run-time so its `System.Type` object depends on the run-time type arguments in use. The unbound generic type, on the other hand, has no type arguments, and yields the same `System.Type` object regardless of runtime type arguments.
29392941

29402942
> *Example*: The example
29412943
>

standard/types.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,13 @@ delegate_type
7676
;
7777
7878
nullable_reference_type
79-
: non_nullable_reference_type '?'
79+
: non_nullable_reference_type nullable_type_annotation
8080
;
81+
82+
nullable_type_annotation
83+
: '?'
84+
;
85+
8186
```
8287

8388
*pointer_type* is available only in unsafe code ([§23.3](unsafe-code.md#233-pointer-types)). *nullable_reference_type* is discussed further in [§8.9](types.md#89-reference-types-and-nullability).
@@ -206,7 +211,7 @@ enum_type
206211
;
207212
208213
nullable_value_type
209-
: non_nullable_value_type '?'
214+
: non_nullable_value_type nullable_type_annotation
210215
;
211216
```
212217

@@ -538,10 +543,11 @@ type_arguments
538543
539544
type_argument
540545
: type
546+
| type_parameter nullable_type_annotation?
541547
;
542548
```
543549
544-
Each type argument shall satisfy any constraints on the corresponding type parameter ([§15.2.5](classes.md#1525-type-parameter-constraints)).
550+
Each type argument shall satisfy any constraints on the corresponding type parameter ([§15.2.5](classes.md#1525-type-parameter-constraints)). A reference type argument whose nullability doesnt match the nullability of the type parameter satisfies the constraint; however a warning may be issued.
545551
546552
### 8.4.3 Open and closed types
547553
@@ -720,7 +726,7 @@ An *unmanaged_type* is any type that isn’t a *reference_type*, a *type_paramet
720726

721727
### 8.9.1 General
722728

723-
A *nullable reference type* is denoted by appending a `?` to a valid non-nullable reference type name. There is no semantic difference between a non-nullable reference type and its corresponding nullable type. Both a nullable reference and a non-nullable reference can contain either a reference to an object or `null`. The presence or absence of the `?` annotation declares whether an expression is intended to permit null values or not. A compiler can provide diagnostics when an expression is not used according to that intent. The null state of an expression is defined in [§8.9.5](types.md#895-nullabilities-and-null-states). An identity conversion exists among a nullable reference type and its corresponding non-nullable reference type ([§10.2.2](conversions.md#1022-identity-conversion)).
729+
A *nullable reference type* is denoted by appending a *nullable_type_annotation* (`?`) to a non-nullable reference type. There is no semantic difference between a non-nullable reference type and its corresponding nullable type, both can either be a reference to an object or `null`. The presence or absence of the *nullable_type_annotation* declares whether an expression is intended to permit null values or not. A compiler may provide diagnostics when an expression is not used according to that intent. The null state of an expression is defined in [§8.9.5](types.md#895-nullabilities-and-null-states). An identity conversion exists among a nullable reference type and its corresponding non-nullable reference type ([§10.2.2](conversions.md#1022-identity-conversion)).
724730

725731
There are two forms of nullability for reference types:
726732

@@ -729,7 +735,7 @@ There are two forms of nullability for reference types:
729735

730736
> *Note:* The types `R` and `R?` are represented by the same underlying type, `R`. A variable of that underlying type can either contain a reference to an object or be the value `null`, which indicates “no reference.” *end note*
731737
732-
The syntactic distinction between a *nullable reference type* and its corresponding *non-nullable reference type* enables a compiler to generate diagnostics. A compiler shall allow the `?` annotation as defined in [§8.2.1](types.md#821-general). The diagnostics shall be limited to warnings. Neither the presence or absence of nullable annotations, nor the state of the nullable context can change the compile time or runtime behavior of a program except for changes in any diagnostic messages generated at compile time.
738+
The syntactic distinction between a *nullable reference type* and its corresponding *non-nullable reference type* enables a compiler to generate diagnostics. A compiler must allow the *nullable_type_annotation* as defined in [§8.2.1](types.md#821-general). The diagnostics must be limited to warnings. Neither the presence or absence of nullable annotations, nor the state of the nullable context can change the compile time or runtime behavior of a program except for changes in any diagnostic messages generated at compile time.
733739

734740
### 8.9.2 Non-nullable reference types
735741

@@ -762,6 +768,9 @@ When the nullable context is ***disabled***:
762768
- No warning shall be generated when a variable of an unannotated reference type is initialized with, or assigned a value of, `null`.
763769
- No warning shall be generated when a variable of a reference type that possibly has the null value.
764770
- For any reference type `T`, the annotation `?` in `T?` generates a message and the type `T?` is the same as `T`.
771+
- For any type parameter constraint `where T : C?`, the annotation `?` in `C?` generates a message and the type `C?` is the same as `C`.
772+
- For any type parameter constraint `where T : U?`, the annotation `?` in `U?` generates a message and the type `U?` is the same as `U`.
773+
- The generic constraint `class?` generates a warning message. The type parameter must be a reference type.
765774
> *Note*: This message is characterized as “informational” rather than “warning,” so as not to confuse it with the state of the nullable warning setting, which is unrelated. *end note*
766775
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) has no effect.
767776

@@ -839,6 +848,7 @@ When the nullable context is ***enabled***:
839848
- For any reference type `T`, the annotation `?` in `T?` makes `T?` a nullable type, whereas the unannotated `T` is non-nullable.
840849
- The compiler can use static flow analysis to determine the null state of any reference variable. When nullable warnings are enabled, a reference variables null state ([§8.9.5](types.md#895-nullabilities-and-null-states)) is either *not null*, *maybe null*, or *maybe default* and
841850
- The null-forgiving operator `!` ([§12.8.9](expressions.md#1289-null-forgiving-expressions)) sets the null state of its operand to *not null*.
851+
- The compiler can issue a warning if the nullability of a type parameter doesn't match the nullability of its corresponding type argument.
842852
843853
### 8.9.5 Nullabilities and null states
844854
@@ -861,6 +871,8 @@ The ***default null state*** of an expression is determined by its type, and the
861871
- Not null when its declaration is in text where the annotations flag is disabled.
862872
- The default null state of a non-nullable reference type is not null.
863873
874+
> *Note:* The *maybe default* state is used with unconstrained type parameters when the type is a non-nullable type, such as `string` and the expression `default(T)` is the null value. Because null is not in the domain for the non-nullable type, the state is maybe default. *end note*
875+
864876
A diagnostic can be produced when a variable ([§9.2.1](variables.md#921-general)) of a non-nullable reference type is initialized or assigned to an expression that is maybe null when that variable is declared in text where the annotation flag is enabled.
865877
866878
> *Example*: Consider the following method where a parameter is nullable and that value is assigned to a non-nullable type:

0 commit comments

Comments
 (0)