From 066fbfca335b2c0511f2efd9acfd7a0d96783edf Mon Sep 17 00:00:00 2001 From: jnm2 Date: Wed, 30 Jul 2025 20:07:43 -0400 Subject: [PATCH 1/9] Fix grammar to allow arrays of nullable reference types --- standard/types.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/standard/types.md b/standard/types.md index 31ccd4773..b3fb54f86 100644 --- a/standard/types.md +++ b/standard/types.md @@ -54,16 +54,13 @@ interface_type ; array_type - : non_array_type rank_specifier+ + : array_type nullable_type_annotation rank_specifier+ + | non_array_type rank_specifier+ ; non_array_type : value_type - | class_type - | interface_type - | delegate_type - | 'dynamic' - | type_parameter + | (class_type | interface_type | delegate_type | 'dynamic' | type_parameter) nullable_type_annotation? | pointer_type // unsafe code support ; From aa5e4b5a22ba1792a8191a96660f14562958b136 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Wed, 30 Jul 2025 21:00:41 -0400 Subject: [PATCH 2/9] Document the exception to the rule that nullable annotations can be ignored by compilers --- standard/types.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index b3fb54f86..a405918aa 100644 --- a/standard/types.md +++ b/standard/types.md @@ -729,7 +729,25 @@ There are two forms of nullability for reference types: > *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: + +A compiler must respect the effect that *nullable_type_annotation* has on the ordering of array rank specifiers. Whereas `A[][,]` is a single *array_type* with two *rank_specifier*s, the presence of a nullable annotation between the rank specifiers in `A[]?[,]` causes it to no longer be a single *array_type*, but rather two: an outer *array_type* with a single *rank_specifier* of `[,]`, and an element type of `A[]?` which is a *nullable_reference_type* containing an inner *array_type* with a single *rank_specifier* of `[]`. Because of this, `A[]?[,]` and `A[,][]` are represented by the same underlying type, while `A[]?[,]` and `A[][,]` are not. + +> *Example*: The array ranks are interrupted by the '?' in the parameter type, changing the meaning of the underlying array type: +> +> +> ```csharp +> #nullable enable +> class C +> { +> void M(string[][,]?[,,][,,,] arrays) +> { +> string? value = arrays[3, 3, 3][4, 4, 4, 4]?[1][2, 2]; +> } +> } +> ``` +> +> *end example* ### 8.9.2 Non-nullable reference types From 74b51aa3127a61857cd9c6acdb73bda7e3f659c5 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sat, 9 Aug 2025 16:11:14 -0400 Subject: [PATCH 3/9] Add new rule to avoid parenthesized grammar --- standard/types.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/standard/types.md b/standard/types.md index a405918aa..ec8954413 100644 --- a/standard/types.md +++ b/standard/types.md @@ -59,11 +59,19 @@ array_type ; non_array_type - : value_type - | (class_type | interface_type | delegate_type | 'dynamic' | type_parameter) nullable_type_annotation? + : 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 + ; + rank_specifier : '[' ','* ']' ; From 07ed101753be4f01fbb77f854a6ac08158e50046 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Thu, 30 Oct 2025 20:47:01 -0400 Subject: [PATCH 4/9] Specify more fully and in the right places --- standard/arrays.md | 27 +++++++++++++++++++++++++++ standard/types.md | 33 ++++++++++++++------------------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index ccfcfe278..c1d766259 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -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* + +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. + +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 underlying array type of `T[R₃][R₄][R₁][R₂]`. To obtain the underlying 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. + +> *Example*: +> +> The following table demonstrates the effect on the underlying array type caused by breaking up array types by inserting nullable annotations: +> +> | Annotated | Underlying | +> |----------------------------------|-------------------------------------------| +> | `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. diff --git a/standard/types.md b/standard/types.md index ec8954413..91981382c 100644 --- a/standard/types.md +++ b/standard/types.md @@ -737,25 +737,7 @@ There are two forms of nullability for reference types: > *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, with one exception: - -A compiler must respect the effect that *nullable_type_annotation* has on the ordering of array rank specifiers. Whereas `A[][,]` is a single *array_type* with two *rank_specifier*s, the presence of a nullable annotation between the rank specifiers in `A[]?[,]` causes it to no longer be a single *array_type*, but rather two: an outer *array_type* with a single *rank_specifier* of `[,]`, and an element type of `A[]?` which is a *nullable_reference_type* containing an inner *array_type* with a single *rank_specifier* of `[]`. Because of this, `A[]?[,]` and `A[,][]` are represented by the same underlying type, while `A[]?[,]` and `A[][,]` are not. - -> *Example*: The array ranks are interrupted by the '?' in the parameter type, changing the meaning of the underlying array type: -> -> -> ```csharp -> #nullable enable -> class C -> { -> void M(string[][,]?[,,][,,,] arrays) -> { -> string? value = arrays[3, 3, 3][4, 4, 4, 4]?[1][2, 2]; -> } -> } -> ``` -> -> *end example* +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. ### 8.9.2 Non-nullable reference types @@ -1121,6 +1103,8 @@ A compiler may issue a warning when nullability annotations differ between two t 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`.) + > > ```csharp > #nullable enable @@ -1158,6 +1142,17 @@ A compiler may follow rules for interface variance ([§18.2.3.3](interfaces.md#1 > 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 +> } > } > ``` > From 4e6f0ca063b60a926bac240754e565d930bb3541 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Thu, 30 Oct 2025 20:56:13 -0400 Subject: [PATCH 5/9] Polish --- standard/arrays.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index c1d766259..69fd34e2c 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -54,19 +54,19 @@ At run-time, a value of an array type can be `null` or a reference to an instanc 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. +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* 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. -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 underlying array type of `T[R₃][R₄][R₁][R₂]`. To obtain the underlying 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. +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. > *Example*: > -> The following table demonstrates the effect on the underlying array type caused by breaking up array types by inserting nullable annotations: +> The following table demonstrates the effect on the unannotated array type caused by breaking up array types by inserting nullable annotations: > -> | Annotated | Underlying | +> | Annotated | Unannotated | > |----------------------------------|-------------------------------------------| > | `T?[][,][,,]` | `T[][,][,,]` (not intervening, no change) | > | `T[][,][,,]?` | `T[][,][,,]` (not intervening, no change) | From 2b2b21b6d48fe000acc59f379d05061370f14611 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Thu, 30 Oct 2025 21:08:05 -0400 Subject: [PATCH 6/9] Polish --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index 69fd34e2c..7f557d678 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -66,7 +66,7 @@ The unannotated array type of an array of nullable arrays cannot be found by sim > > The following table demonstrates the effect on the unannotated array type caused by breaking up array types by inserting nullable annotations: > -> | Annotated | Unannotated | +> | Annotated | Unannotated | > |----------------------------------|-------------------------------------------| > | `T?[][,][,,]` | `T[][,][,,]` (not intervening, no change) | > | `T[][,][,,]?` | `T[][,][,,]` (not intervening, no change) | From dcc7a87145bf753f12020c4609e9a2448340e948 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 18 Nov 2025 20:44:24 -0500 Subject: [PATCH 7/9] Remove allusion to '?' as causing separate array_types --- standard/arrays.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index 7f557d678..574c768f7 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -52,9 +52,9 @@ At run-time, a value of an array type can be `null` or a reference to an instanc ### §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 nullable annotation `?` may be placed on an array type, as in `T[R]?`. Such an annotated 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. +`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* From 0ae67115b80db6b687e351f9681bd9886be6e4bb Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 18 Nov 2025 20:47:27 -0500 Subject: [PATCH 8/9] Make normative the mention itself of the exception to the general rule --- standard/arrays.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index 574c768f7..4fa612de7 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -54,9 +54,7 @@ At run-time, a value of an array type can be `null` or a reference to an instanc The nullable annotation `?` may be placed on an array type, as in `T[R]?`. Such an annotated array type may be used as the element type of another array type, as in `T[R]?[R₂]`. -`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* +`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. 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. 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. From e065f6caab55f212c33178264c870f34fbaa252a Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 18 Nov 2025 20:52:28 -0500 Subject: [PATCH 9/9] Break algorithm into numbered steps and add examples --- standard/arrays.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index 4fa612de7..9bfde42ad 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -58,7 +58,14 @@ The nullable annotation `?` may be placed on an array type, as in `T[R]?`. Such 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. -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. +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, the following steps are followed: + +1. Take the ranks on the outermost array type in order from left to right. (From `T[R₁][R₂]?[R₃][R₄]`, take `[R₃]` and then `[R₄]`.) +2. Move to the array type inside the nullable element type and take its ranks in order from left to right. (From `T[R₁][R₂]`, take `[R₁]` and then `[R₂]`.) +3. Repeat in this fashion until the element type is no longer a nullable array type. (`T`) +4. 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. (On `T`, place `[R₃]`, then `[R₄]`, then `[R₁]`, then `[R₂]`, obtaining the final result of `T[R₃][R₄][R₁][R₂]`.) > *Example*: >