-
Notifications
You must be signed in to change notification settings - Fork 84
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
Specify generic constraints added to support nullable reference types in C# 8 #1178
base: draft-v8
Are you sure you want to change the base?
Changes from 4 commits
c2bb8dd
29dc043
39e136a
98d3f1d
cdd12d4
b8b3545
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 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -412,17 +412,18 @@ type_parameter_constraints | |||||||||||||
; | ||||||||||||||
primary_constraint | ||||||||||||||
: class_type | ||||||||||||||
| 'class' | ||||||||||||||
: class_type '?'? | ||||||||||||||
| 'class' '?'? | ||||||||||||||
| 'struct' | ||||||||||||||
| 'notnull' | ||||||||||||||
| 'unmanaged' | ||||||||||||||
; | ||||||||||||||
secondary_constraints | ||||||||||||||
: interface_type | ||||||||||||||
| type_parameter | ||||||||||||||
| secondary_constraints ',' interface_type | ||||||||||||||
| secondary_constraints ',' type_parameter | ||||||||||||||
: interface_type '?'? | ||||||||||||||
| type_parameter '?'? | ||||||||||||||
| secondary_constraints ',' interface_type '?'? | ||||||||||||||
| secondary_constraints ',' type_parameter '?'? | ||||||||||||||
; | ||||||||||||||
constructor_constraint | ||||||||||||||
|
@@ -434,16 +435,20 @@ Each *type_parameter_constraints_clause* consists of the token `where`, followed | |||||||||||||
|
||||||||||||||
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`, or the ***unmanaged type constraint*** `unmanaged`. | ||||||||||||||
A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***nullable reference type constraint*** `class?`, the ***not null*** constraint `notnull`, the ***value type constraint*** `struct` or the ***unmanaged type constraint*** `unmanaged`. | ||||||||||||||
|
||||||||||||||
A secondary constraint can be a *type_parameter* or *interface_type*. | ||||||||||||||
A secondary constraint can be a *type_parameter* or *interface_type*, either optionally followed by `?`. | ||||||||||||||
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 doesn’t read well to me in this context, given the comment on 426 above I’ll offer two alternatives:
Suggested change
Or if the line 426 added rule is adopted:
Suggested change
|
||||||||||||||
|
||||||||||||||
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. | ||||||||||||||
The reference type constraint specifies that a type argument used for the type parameter shall be a non-nullable reference type. All non-nullable class types, non-nullable interface types, non-nullable delegate types, non-nullable array types, and type parameters known to be a non-nullable reference type (as defined below) satisfy this constraint. | ||||||||||||||
|
||||||||||||||
The nullable reference type constraint specifies that a type argument shall be either a non-nullable reference type or a nullable 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. | ||||||||||||||
|
||||||||||||||
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*. | ||||||||||||||
|
||||||||||||||
> *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* | ||||||||||||||
BillWagner marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
The ***not null*** constraint specifies that a type argument used for the type parameter shall be a non-nullable value type or a non-nullable reference type. All non-nullable class types, interface types, delegate types, array types, struct types, enum types, and type parameters known to be a non-nullable value type or non-nullable reference type satisfy this constraint. | ||||||||||||||
|
||||||||||||||
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 unmanaged type constraint specifies that a type argument used for the type parameter shall be a non-nullable unmanaged type ([§8.8](types.md#88-unmanaged-types)). | ||||||||||||||
|
@@ -604,7 +609,9 @@ The ***effective interface set*** of a type parameter `T` is defined as follows | |||||||||||||
- 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. | ||||||||||||||
- 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. | ||||||||||||||
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 has the non-nullable reference type constraint or its effective base class is not `object` or `System.ValueType`. | ||||||||||||||
A type parameter is *known to be a reference type* if it has the non-nullable reference type constraint, reference type constraint or its effective base class is not `object` or `System.ValueType`. | ||||||||||||||
Comment on lines
+612
to
+614
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. Could this be simplified to:
Suggested change
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.
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 we're not to use contractions, so "it is". |
||||||||||||||
Values of a constrained type parameter type can be used to access the instance members implied by the constraints. | ||||||||||||||
|
@@ -878,7 +885,7 @@ All members of a generic class can use type parameters from any enclosing class, | |||||||||||||
> class C<V> | ||||||||||||||
> { | ||||||||||||||
> public V f1; | ||||||||||||||
> public C<V> f2 = null; | ||||||||||||||
> public C<V> f2 = null!; | ||||||||||||||
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. It is unclear to me why in the original I suggest a comment explaining the suppression, or the use of 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 it would be better to drop the initialization. |
||||||||||||||
> | ||||||||||||||
> public C(V x) | ||||||||||||||
> { | ||||||||||||||
|
@@ -1055,17 +1062,17 @@ Non-nested types can have `public` or `internal` declared accessibility and have | |||||||||||||
> private class Node | ||||||||||||||
> { | ||||||||||||||
> public object Data; | ||||||||||||||
> public Node Next; | ||||||||||||||
> public Node? Next; | ||||||||||||||
> | ||||||||||||||
> public Node(object data, Node next) | ||||||||||||||
> public Node(object data, Node? next) | ||||||||||||||
> { | ||||||||||||||
> this.Data = data; | ||||||||||||||
> this.Next = next; | ||||||||||||||
> } | ||||||||||||||
> } | ||||||||||||||
> | ||||||||||||||
> private Node first = null; | ||||||||||||||
> private Node last = null; | ||||||||||||||
> private Node? first = null; | ||||||||||||||
> private Node? last = null; | ||||||||||||||
> | ||||||||||||||
> // Public interface | ||||||||||||||
> public void AddToFront(object o) {...} | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -532,7 +532,7 @@ type_argument | |||||
; | ||||||
``` | ||||||
|
||||||
Each type argument shall satisfy any constraints on the corresponding type parameter ([§15.2.5](classes.md#1525-type-parameter-constraints)). | ||||||
Each type argument shall satisfy any constraints on the corresponding type parameter ([§15.2.5](classes.md#1525-type-parameter-constraints)). A type argument whose nullability doesn't match the nullability of the type parameter satisfies the constraint with the exception that a nullable value type does not satisfy the value type constraint. A warning may be issued when the nullability of a type argument does not satisfy the nullability requirements of the constraint. | ||||||
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 think this needs to be phrased in terms of value type exceptions, the point being that reference type nullability is a “soft” constraint. Maybe:
Suggested change
I see that @KalleOlaviNiemitalo has commented on the use of “shall” here, I'll defer that to @RexJaeschke. |
||||||
|
||||||
### 8.4.3 Open and closed types | ||||||
|
||||||
|
@@ -601,8 +601,20 @@ A type parameter is an identifier designating a value type or reference type tha | |||||
type_parameter | ||||||
: identifier | ||||||
; | ||||||
|
||||||
nullable_type_parameter | ||||||
: non_nullable_non_value_type_parameter '?' | ||||||
; | ||||||
|
||||||
non_nullable_non_value_type_parameter | ||||||
: type_parameter | ||||||
; | ||||||
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 grammar is wrong/pointless, as @KalleOlaviNiemitalo has commented the added rules are not used anywhere. Unfortunately I’m not sure what the intended semantics are here. Guessing: is the addition of the nullable type attribute meant to require the type argument to be a nullable reference and/or value type, i.e. it’s an additional kind of primary_constraint or secondary_constraint? 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. That was my mistake. I added the additional rules in the latest commit. Note that the grammar additions for |
||||||
``` | ||||||
|
||||||
The *non_nullable_non_value_type_parameter* in *nullable_type_parameter* shall be a type parameter that isn’t constrained to be a value type. | ||||||
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. "shall" can give the wrong idea that the compiler would report an error otherwise. Could clarify with a note saying that the same syntax can be used with a type parameter that is constrained to be a value type, but it means Nullable<T> in that case. 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. Also waiting for more eyes here. I see your point, but I don't have the right suggestion yet. |
||||||
|
||||||
In *nullable_type_parameter*, the annotation `?` indicates the intent that nullable type corresponding to the type arguments of this type are nullable. The absence of the annotation `?` indicates the intent that type arguments of this type are non-nullable. | ||||||
|
||||||
Since a type parameter can be instantiated with many different type arguments, type parameters have slightly different operations and restrictions than other types. | ||||||
|
||||||
> *Note*: These include: | ||||||
|
@@ -705,3 +717,17 @@ An *unmanaged_type* is any type that isn’t a *reference_type*, a *type_paramet | |||||
- `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`. | ||||||
- Any *enum_type*. | ||||||
- Any user-defined *struct_type* that is not a constructed type and contains instance fields of *unmanaged_type*s only. | ||||||
|
||||||
## §Generics-and-nullable-placeholder More nullable context text | ||||||
|
||||||
> This is a placeholder for text that will be added in the clause on "nullable context" described in `#1124`. I'll rebase and edit once that's done. | ||||||
|
||||||
- Add the note for *maybe default*: | ||||||
|
||||||
> *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* | ||||||
|
||||||
Add to rules on types and nullable context flags: | ||||||
|
||||||
When the *annotations* flag is disabled: | ||||||
|
||||||
- The `class?` constraint generates a warning. |
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.
I think we should review the grammar here. I can't add this as a suggested changed as it covers non-altered lines, so just code. The rules here pre-date the use of ANTLR, if we ANTLRize them and remove unneeded left-recursion (as we do elsewhere) we get:
Optionally to improve readability we could replace the
'?'?
by adding a rule:The introduced * nullable_type_attribute* would then also need to be used in other grammar rules where the literal
'?'
is used as a type attribute and not in places where it is not, e.g. for the null conditional operations.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.
I like the second one. When I was working through the grammar, I found
'?'?
hard to mentally parse and read through the grammar.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.
+1 for a rule for nullable_type_attribute.