-
Notifications
You must be signed in to change notification settings - Fork 93
Allow arrays of nullable reference types #1386
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-v8
Are you sure you want to change the base?
Changes from all commits
066fbfc
aa5e4b5
74b51aa
07ed101
4e6f0ca
2b2b21b
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 |
|---|---|---|
|
|
@@ -50,6 +50,33 @@ At run-time, a value of an array type can be `null` or a reference to an instanc | |
|
|
||
| > *Note*: Following the rules of [§17.6](arrays.md#176-array-covariance), the value may also be a reference to a covariant array type. *end note* | ||
|
|
||
| ### §arrays-of-nullable-arrays Arrays of nullable arrays | ||
|
|
||
| The nullable annotation `?` may be placed on an array type, as in `T[R]?`. Such an array type may be used as the element type of another array type, as in `T[R]?[R₂]`. | ||
|
|
||
| The intervening nullable annotation `?` separates the grammar into multiple *array_types*. `T[R₁][R₂]?[R₃][R₄]` is not a single *array_type* with four ranks. Rather, it is two *array_type*s, each of which has two ranks. The outer *array_type* has ranks `[R₃][R₄]`, read left to right, and its element type is `T[R₁][R₂]?`. The element type is another *array_type* with a nullable annotation, and this inner *array_type* has ranks `[R₁][R₂]`, read left to right. | ||
|
|
||
| > *Note*: This is the sole exception to the general rule that the meaning of a program remains the same when nullable reference types annotations are removed. *end note* | ||
|
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 normative part of the spec so cannot be a Note (which is just informative). You also need to change wherever this “general rule” is relied upon or mentioned (e.g. §8.9.5.3) because the “exception” invalidates the rule – it can in general never be relied upon. |
||
|
|
||
| Every reference type which contains nullable annotations has a corresponding unannotated type with no semantic difference (§8.9.1). The corresponding unannotated type for an array of nullable arrays is a single array type which recursively collects all the ranks of all the nested *array_type*s. | ||
|
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. With array types there is not in general a single “corresponding nullable type” (§8.9.1) – there are multiple, e.g.: static void ArrayEquivalentTypes(int[][,]?[,,]?[,,,]?[,,,,] a1)
{
int[,,][][,]?[,,,]?[,,,,] a2 = a1;
int[,,,][,,][][,]?[,,,,] a3_1 = a1;
int[,,,][,,][][,]?[,,,,] a3_2 = a2;
int[,,,,][,,,][,,][][,] a4_1 = a1;
int[,,,,][,,,][,,][][,] a4_2 = a2;
int[,,,,][,,,][,,][][,] a4_3_1 = a3_1;
int[,,,,][,,,][,,][][,] a4_3_2 = a3_2;
int[,,][][,]?[,,,]?[,,,,] b3_1 = a3_1;
int[,,][][,]?[,,,]?[,,,,] b3_2 = a3_2;
int[,,][][,]?[,,,]?[,,,,] b4_1 = a4_1;
int[,,][][,]?[,,,]?[,,,,] b4_2 = a4_2;
int[,,][][,]?[,,,]?[,,,,] b4_3_1 = a4_3_1;
int[,,][][,]?[,,,]?[,,,,] b4_3_2 = a4_3_2;
int[,,,][,,][][,]?[,,,,] c4_1 = a4_1;
int[,,,][,,][][,]?[,,,,] c4_2 = a4_2;
int[,,,][,,][][,]?[,,,,] c4_3_1 = a4_3_1;
int[,,,][,,][][,]?[,,,,] c4_3_2 = a4_3_2;
// etc...
}This bundle of joy ;-) has four array types that are semantically equivalent to each other, and that’s not the limit by a long shot. It needs to be specified that any two types (which need not be distinct) selected from a semantically equivalent set are implicitly convertible to each other. (Depending on implementation some, but not all, of the conversions may elicit a nullable warning.) And while we are here, consider: static void ArrayDifferentTypesSameLiteral()
{
int[]?[,] a1 =
{
{ new int[] { 1, 2 },
new int[] { 3, 4 }
}
};
int[,][] a2 =
{
{ new int[] { 1, 2 },
new int[] { 3, 4 }
}
};Different array types which are semantically equivalent can be init’ed using the same array literal. That will need to be specified. |
||
|
|
||
| The unannotated array type of an array of nullable arrays cannot be found by simply removing the nullable annotations `?` from the grammar and reparsing. This is because array ranks are read left to right while nested *array_type* productions are read outside-in, with outer array type ranks to the right, inner array type ranks to the left. Thus, the type `T[R₁][R₂]?[R₃][R₄]` has an unannotated array type of `T[R₃][R₄][R₁][R₂]`. To obtain the unannotated array type of an array of nullable arrays, first take the ranks on the outermost array type in order from left to right, then move to the array type inside the nullable element type and take its ranks in order from left to right. Repeat until the element type is no longer a nullable array type. Then take this remaining element type and place on it all the collected ranks in order from first to last to obtain the unannotated array 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. I’m not yet convinced regarding the algorithm presentation, maybe bullets/numbered steps or pseudo-code would help? Regardless at minimum I think the example should show the result of each iteration |
||
|
|
||
| > *Example*: | ||
| > | ||
| > The following table demonstrates the effect on the unannotated array type caused by breaking up array types by inserting nullable annotations: | ||
| > | ||
| > | Annotated | Unannotated | | ||
| > |----------------------------------|-------------------------------------------| | ||
| > | `T?[][,][,,]` | `T[][,][,,]` (not intervening, no change) | | ||
| > | `T[][,][,,]?` | `T[][,][,,]` (not intervening, no change) | | ||
| > | `T[]?[,]?[,,]` | `T[,,][,][]` | | ||
| > | `T[]?[,][,,]` | `T[,][,,][]` | | ||
| > | `T[][,]?[,,]` | `T[,,][][,]` | | ||
| > | `T[][,]?[,,][,,,]?[,,,,][,,,,,]` | `T[,,,,][,,,,,][,,][,,,][][,]` | | ||
| > | ||
| > *end example* | ||
|
|
||
| ### 17.2.2 The System.Array type | ||
|
|
||
| The type `System.Array` is the abstract base type of all array types. An implicit reference conversion ([§10.2.8](conversions.md#1028-implicit-reference-conversions)) exists from any array type to `System.Array` and to any interface type implemented by `System.Array`. An explicit reference conversion ([§10.3.5](conversions.md#1035-explicit-reference-conversions)) exists from `System.Array` and any interface type implemented by `System.Array` to any array type. `System.Array` is not itself an *array_type*. Rather, it is a *class_type* from which all *array_type*s are derived. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,17 +54,22 @@ | |
| ; | ||
|
|
||
| array_type | ||
| : non_array_type rank_specifier+ | ||
| : array_type nullable_type_annotation rank_specifier+ | ||
| | non_array_type rank_specifier+ | ||
|
Comment on lines
56
to
+58
Contributor
Author
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 has the property that when parsing @Nigel-Ecma It's not mutual left recursion! 😁
jnm2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ; | ||
|
|
||
| non_array_type | ||
| : value_type | ||
| : non_array_non_nullable_type nullable_type_annotation? | ||
| | pointer_type // unsafe code support | ||
| ; | ||
|
|
||
| non_array_non_nullable_type | ||
| : non_nullable_value_type | ||
| | class_type | ||
| | interface_type | ||
| | delegate_type | ||
| | 'dynamic' | ||
| | type_parameter | ||
| | pointer_type // unsafe code support | ||
| ; | ||
|
|
||
| rank_specifier | ||
|
|
@@ -732,7 +737,7 @@ | |
|
|
||
| > *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* | ||
|
|
||
| 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. | ||
| 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, with one exception: arrays of nullable arrays are not parsed as a single *array_type*, but rather as multiple nested *array_type*s. The corresponding *non-nullable reference type* of an array of nullable arrays is not the single array type that would be parsed if the nullable annotations were removed; see §arrays-of-nullable-arrays. | ||
|
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 will surprise everybody ;-) and say “compiler” -> ”implementation”. Not changing the compile time behavior is no longer correct, add/remove a nullable annotation on an array type without changing all associated index operations will produce compile-time errors. I’m not sure you can say changing the number of array_type productions in the parse is an exception per se – the annotations do not change the described array shape in anyway (one might argue that the existing description of arrays is less than clear on the shape, if so adding nullable arrays is the time to fix that). |
||
|
|
||
| ### 8.9.2 Non-nullable reference types | ||
|
|
||
|
|
@@ -946,13 +951,13 @@ | |
| > public void M(string s) | ||
| > { | ||
| > int length = s.Length; // No warning. s is not null | ||
| > | ||
| > _ = s == null; // Null check by testing equality. The null state of s is maybe null | ||
| > length = s.Length; // Warning, and changes the null state of s to not null | ||
| > | ||
| > _ = s?.Length; // The ?. is a null check and changes the null state of s to maybe null | ||
| > if (s.Length > 4) // Warning. Changes null state of s to not null | ||
| > { | ||
| > _ = s?[4]; // ?[] is a null check and changes the null state of s to maybe null | ||
| > _ = s.Length; // Warning. s is maybe null | ||
| > } | ||
|
|
@@ -1012,7 +1017,7 @@ | |
| > { | ||
| > var t = new Test(); | ||
| > if (t.DisappearingProperty != null) | ||
| > { | ||
| > int len = t.DisappearingProperty.Length; // No warning. A compiler can assume property is stateful | ||
| > } | ||
| > } | ||
|
|
@@ -1098,6 +1103,8 @@ | |
|
|
||
| A compiler may follow rules for interface variance ([§18.2.3.3](interfaces.md#18233-variance-conversion)), delegate variance ([§20.4](delegates.md#204-delegate-compatibility)), and array covariance ([§17.6](arrays.md#176-array-covariance)) in determining whether to issue a warning for type conversions. | ||
|
|
||
| (See §arrays-of-nullable-arrays for the specification of the corresponding non-nullable array type used in `M7` and `M8`.) | ||
|
|
||
| > <!-- Example: {template:"code-in-class-lib", name:"NullVariance", ignoredWarnings:["CS8619"]} --> | ||
| > ```csharp | ||
| > #nullable enable | ||
|
|
@@ -1135,6 +1142,17 @@ | |
| > string[] v1 = p; // Warning | ||
| > string[] v2 = p!; // No warning | ||
| > } | ||
| > | ||
| > public void M7(string[][,] p) | ||
| > { | ||
| > string[,]?[] v1 = p; // No warning | ||
| > } | ||
| > | ||
| > public void M6(string[]?[,] p) | ||
| > { | ||
| > string[,][] v1 = p; // Warning | ||
| > string[,][] v2 = p!; // No 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.
Saying nullable annotation “separates the grammar” is incorrect, from a grammar perspective the rule allows an array_type to contain another array_type.