Skip to content

Conversation

@jskeet
Copy link
Contributor

@jskeet jskeet commented Jan 27, 2026

Alternative to #1386.

Fixes #1385.

@jskeet
Copy link
Contributor Author

jskeet commented Jan 27, 2026

I'm fully expecting there to be some more work here, but I'm hoping it's at least close.

@jskeet jskeet added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Jan 27, 2026
@jskeet
Copy link
Contributor Author

jskeet commented Jan 27, 2026

@Nigel-Ecma: I believe the grammar test failure is now a matter of changing the expected tokens (due to the introduction of non_array_non_nullable_type) - let me know if this is something I can fix myself, or whether it needs your help :)

Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach. I have a couple comments to consider and discuss.

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. Other than in the meaning of array types, 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 meaning of array types is significantly impacted by the presence of *nullable_type_annotation* within an *array_type*, as described in [§17.2.1](arrays.md#1721-general).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider a different wording. The impact is that the memory layout changes. That's "significant" only if a program does anything that depends on the layout or order of multi-dimensional arrays. (Not an edge case, but not everything either).

One sugestion:

Suggested change
The meaning of array types is significantly impacted by the presence of *nullable_type_annotation* within an *array_type*, as described in [§17.2.1](arrays.md#1721-general).
The memory layout of array types is impacted by the presence of *nullable_type_annotation* within an *array_type*, as described in [§17.2.1](arrays.md#1721-general).

Copy link
Contributor Author

@jskeet jskeet Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more than just the memory layout - it's what the element type of the array is. Memory layout makes it sound like too much like an implementation detail IMO.

I think the difference between declarations string[][,] x and string[,][] y is significant - which means the difference between string[][,] x and string[]?[,] y is also significant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jskeet that memory layout is not the significant impact here, I expect few users even consider it per se. The most obvious impact is that the order in which a multi-rank array[1] is indexed is changed – add a nullable annotation to a multi-rank array type and every index operation needs to be changed, I’d call that significant! And probably a sufficiently error prone undertaking to recommend avoiding it.

So I would start with Jon’s original wording. Does it need to say that this holds even if an implementation does no nullable analysis and therefore effectively ignores all nullable annotations? I think it probably needs to say that somewhere and here seems a reasonable place.


[1] Yes, under the model being described there are (I think) no multi-rank arrays and so it is technically index operations which index multiple single rank arrays, but I guess you all knew that! :-)

In effect, the *rank_specifier*s are read from left to right *before* the final non-array element type.

> *Example*: The type in `T[][,,][,]` is a single-dimensional array of three-dimensional arrays of two-dimensional arrays of `int`. *end example*
> *Example*: The following code shows several variable declarations, including a mixture of single-dimensional arrays, multi-dimensional arrays, and arrays of arrays, with some using nullable reference types. In each case, the rank and element type is described, and then demonstrated with a second variable declaration which is initialized using an element access expression.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per my other comment, consider expanding the example to show the memory order of a foreach across the multiple dimensions of these arrays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's chat about the first comment before making a change here :) (I suspect we might want a separate example, keeping this one just on "lots of examples with rank and element type".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my comment on the other comment if anything is done here it would be the indexing, not memory, order.

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve made a few comments but obviously there is a fair bit to go yet – e.g. conversions. Some of it is still confusing, but it seems a good start to dressing this up ;-)

The grammar productions for array types are provided in [§8.2.1](types.md#821-general).

An array type is written as a *non_array_type* followed by one or more *rank_specifier*s.
An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is includes a nullable array type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra word? Maybe the intention was:

Suggested change
An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is includes a nullable array type.
An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is a nullable array type.

What might be taken from a reading of this is an array element type is either a non-array or a nullable array, but not an array, but that has been a common fiction – see comment on line 40.

A *non_array_type* is any *type* that is not itself an *array_type*.

The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*.
When determining the rank and element type of array type as specified below, only the *rank_specifier*s in the top-most production are considered, so in the production `array_type nullable_type_annotation rank_specifier+`, any `rank_specifier` within the `array_type` is ignored.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First I think the usual term would be outer-most rather than top-most. But maybe this could be written as the number of ranks ignoring any in the element type – as it is an array of element type after all!

When determining the rank and element type of array type as specified below, only the *rank_specifier*s in the top-most production are considered, so in the production `array_type nullable_type_annotation rank_specifier+`, any `rank_specifier` within the `array_type` is ignored.

The element type of an array type is the type that results from deleting the leftmost *rank_specifier*:
The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have:

  • An array has one [...] – called a rank specifier;
    • i.e. there are no multiple rank specifier arrays (they are arrays of arrays)
  • A [...] has zero or more ,s (commas)
    • The number of “gaps” (i.e. the number of commas + 1) is called the rank; and
    • and the things the rank is counting are called dimensions e.g. [x,y] has a rank of 2, has 2 dimensions, and is commonly referred to as a 2-dimensional array.

Is that the model you are describing?


- An array type of the form `T[R]` is an array with rank `R` and a non-array element type `T`.
- An array type of the form `T[R][R₁]...[Rₓ]` is an array with rank `R` and an element type `T[R₁]...[Rₓ]`.
The element type of an array type is the type that results from deleting the leftmost *rank_specifier*.
Copy link
Contributor

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 is clear, though offhand I’ve little idea how to fix it. By leftmost you mean the first one after the element type, which by line 38 you are ignoring. But here a straight reading of “The element type on an array ” does not ignore the element type.

Maybe you could stick with the previous T[R][R₁]...[Rₓ] where T is a non_array_type or array_type nullable_type_annotation but that sounds complicated…

In effect, the *rank_specifier*s are read from left to right *before* the final non-array element type.

> *Example*: The type in `T[][,,][,]` is a single-dimensional array of three-dimensional arrays of two-dimensional arrays of `int`. *end example*
> *Example*: The following code shows several variable declarations, including a mixture of single-dimensional arrays, multi-dimensional arrays, and arrays of arrays, with some using nullable reference types. In each case, the rank and element type is described, and then demonstrated with a second variable declaration which is initialized using an element access expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my comment on the other comment if anything is done here it would be the indexing, not memory, order.

| non_array_type rank_specifier+
;

non_array_type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is now the time to mention that non_array_type includes System.Array (a type required by C#) which is the supertype of all arrays?

Probably not ;-)

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. Other than in the meaning of array types, 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 meaning of array types is significantly impacted by the presence of *nullable_type_annotation* within an *array_type*, as described in [§17.2.1](arrays.md#1721-general).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jskeet that memory layout is not the significant impact here, I expect few users even consider it per se. The most obvious impact is that the order in which a multi-rank array[1] is indexed is changed – add a nullable annotation to a multi-rank array type and every index operation needs to be changed, I’d call that significant! And probably a sufficiently error prone undertaking to recommend avoiding it.

So I would start with Jon’s original wording. Does it need to say that this holds even if an implementation does no nullable analysis and therefore effectively ignores all nullable annotations? I think it probably needs to say that somewhere and here seems a reasonable place.


[1] Yes, under the model being described there are (I think) no multi-rank arrays and so it is technically index operations which index multiple single rank arrays, but I guess you all knew that! :-)


> *Note:* A nullable context where both flags are disabled matches the previous standard behavior for reference types. *end note*

The rank and element of an array type declared using *nullable_type_annotation* is not affected by the nullable context.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a suitable x-ref?

@Nigel-Ecma
Copy link
Contributor

@Nigel-Ecma: I believe the grammar test failure is now a matter of changing the expected tokens (due to the introduction of non_array_non_nullable_type) - let me know if this is something I can fix myself, or whether it needs your help :)

With the proviso that I haven’t downloaded this and run the failing tests it does look like the errors reported are valid changes in the expect parse due to the revised grammar.

If this is the case then it will just need a few updates to the “reference” parse files (the ones that are machine generated and ignored in PR reviews). You can generate them yourself but you might prefer one of us with the tools already downloaded to run and produce them does it! It can wait until the PR is approved and before it is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

meeting: discuss This issue should be discussed at the next TC49-TG2 meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Arrays of nullable references

3 participants