-
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?
Conversation
Bring in the normative text from dotnet#700. Some text is removed because of the decision on normative language in our September meeting.
@jskeet I added the meeting discuss label on this. I doubt that it's final, but it's worth a first look. |
standard/classes.md
Outdated
| 'unmanaged' | ||
; | ||
secondary_constraints | ||
: interface_type | ||
: interface_type '?'? | ||
| type_parameter |
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.
Shouldn't secondary_constraints allow type_parameter ?
as the first secondary constraint? Seems strange to only allow the ?
in subsequent secondary constraints. E.g.
public class C<T, U, V>
where T : U?, V
{
}
vs.
public class C<T, U, V>
where T : U, V?
{
}
standard/types.md
Outdated
@@ -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 struct 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Does not satisfy the value type constraint. struct
is the keyword in the constraint syntax but it's not the name of the constraint.
``` | ||
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 comment
The 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 comment
The 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.
standard/types.md
Outdated
``` | ||
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. | ||
|
||
In *nullable_type_parameter*, the annotation `?` indicates the intent that type arguments of this type are nullable. The absence of the annotation `?` indicates the intent that type arguments of this type are non-nullable. |
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'm not sure that the intention is for the type argument to be nullable.
class C {
void M<T>()
where T : class
{
T?[] a = {};
}
void N()
{
M<object>();
}
}
In the declaration T?[] a = {};
, T?
is a nullable_type_parameter. The corresponding type argument is object
in M<object>();
, but that one isn't nullable.
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.
On second thought, I'm not sure T?
is a nullable_type_parameter after all. It matches the nullable_type_parameter grammar but nullable_type_parameter doesn't seem to be referenced by any other grammar rule, so it is not applicable in this context (nor in any other).
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'm waiting for others to weigh in here as well. I see your point, but I don't have an obvious suggestion yet.
(I'll wait for responses to @KalleOlaviNiemitalo's questions before reviewing, if that's okay.) |
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.
Some comments plus ANTLRizing some grammar rules.
I haven’t considered per se what should (not) be in the Standard in regards to the specification of NRT.
: interface_type '?'? | ||
| type_parameter '?'? | ||
| secondary_constraints ',' interface_type '?'? | ||
| secondary_constraints ',' type_parameter '?'? |
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:
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type '?'?
| 'class' '?'?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type '?'?
| type_parameter '?'?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
Optionally to improve readability we could replace the '?'?
by adding a rule:
nullable_type_attribute
: '?'
;
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type nullable_type_attribute?
| 'class' nullable_type_attribute?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type nullable_type_attribute?
| type_parameter nullable_type_attribute?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
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.
|
||
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 comment
The 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:
A secondary constraint can be a *type_parameter* or *interface_type*, either optionally followed by `?`. | |
A secondary constraint can be a *type_parameter* or *interface_type*, either optionally nullable. |
Or if the line 426 added rule is adopted:
A secondary constraint can be a *type_parameter* or *interface_type*, either optionally followed by `?`. | |
A secondary constraint can be a *type_parameter* or *interface_type*, optionally followed by a *nullable_type_attribute*. |
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`. |
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.
Could this be simplified to:
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`. | |
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 reference type constraint or its known to be a non-nullable reference type. |
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.
its
=> it's
in the suggestion (line 614) - but then I like it.
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're not to use contractions, so "it is".
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
It is unclear to me why in the original f2
was initialised, and f1
not, when both are set in the constructor. Now a suppression has been added…
I suggest a comment explaining the suppression, or the use of default
(which leaves the explanation for the section on default
to handle), or just drop the initialisation.
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 it would be better to drop the initialization.
@@ -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 comment
The 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:
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. | |
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 doesn’t match the nullability of the type parameter satisfies the constraint; however a warning may be issued. |
I see that @KalleOlaviNiemitalo has commented on the use of “shall” here, I'll defer that to @RexJaeschke.
standard/types.md
Outdated
|
||
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 comment
The 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 comment
The 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 non_nullable_reference_type
and nullable_reference_type
are in #1089
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 Nigel's suggestions in general. My guess is that the currently-unused rules are intended to be used in the future? Would be good to know.
: interface_type '?'? | ||
| type_parameter '?'? | ||
| secondary_constraints ',' interface_type '?'? | ||
| secondary_constraints ',' type_parameter '?'? |
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.
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`. |
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.
its
=> it's
in the suggestion (line 614) - but then I like it.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to drop the initialization.
Which rules do you think are unused? I didn't see any. |
|
Got it. I'd missed some edits. See latest commit. |
Co-authored-by: Nigel-Ecma <perryresearch@zoot.net.nz>
Add the
class?
andnotnull
constraint.The
default
constraint is added in C# 9, so is not included.