Skip to content

Fix small spec bugs regarding nullable reference type parameters #1389

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

Open
wants to merge 3 commits into
base: draft-v8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions standard/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@
: 'where' type_parameter ':' type_parameter_constraints
;

type_parameter_constraints

Check warning on line 398 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L398

MDC032::Line length 83 > maximum 81
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
Expand Down Expand Up @@ -442,10 +442,11 @@

> *Note*: To specify that a type argument is a nullable reference type, don’t add the nullable type annotation as a constraint (use `T : class` or `T : BaseClass`), but use `T?` throughout the generic declaration to indicate the corresponding nullable reference type for the type argument. *end note*

<!-- Remove in C# 9, when this is allowed -->
The nullable type annotation, `?`, can’t be used on an unconstrained type argument.
Copy link
Contributor Author

@jnm2 jnm2 Aug 9, 2025

Choose a reason for hiding this comment

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

Here is an example of a type parameter that is not unconstrained, and thus was not disallowed by the original statement, which is still not allowed to use ?:

#nullable enable

class C
{
    void M<T>(T? test) where T : System.IDisposable
    {
    }
}

Roslyn:

A nullable type parameter must be known to be a value type or non-nullable reference type unless language version '9.0' or greater is used. Consider changing the language version or adding a 'class', 'struct', or type constraint.

<!-- Remove in C# 9, when `?` is allowed on any type parameter. -->
The nullable type annotation, `?`, can only be used on a type parameter that has the value type constraint, the reference type constraint without the *nullable_type_annotation*, or a class type constraint without the *nullable_type_annotation*.

For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`.
<!-- Add in C# 9, when `?` is allowed on nullable reference type parameters. -->
<!-- For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`. -->
Comment on lines +448 to +449
Copy link
Contributor Author

@jnm2 jnm2 Aug 9, 2025

Choose a reason for hiding this comment

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

Instances of T? do not exist when the type argument is a nullable reference type C? in C# 8, e.g.:

#nullable enable

class C
{
    void M<T>(T? test) where T : class?
    {
    }
}

Roslyn:

A nullable type parameter must be known to be a value type or non-nullable reference type unless language version '9.0' or greater is used. Consider changing the language version or adding a 'class', 'struct', or type constraint.


> *Example*: The following examples show how the nullability of a type argument impacts the nullability of a declaration of its type parameter:
>
Expand Down Expand Up @@ -503,15 +504,15 @@
> static void M()
> {
> // nonnull constraint allows nonnullable struct type argument
> A<int> x1;

Check warning on line 507 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L507

MDC032::Line length 87 > maximum 81
> // possible warning: nonnull constraint prohibits nullable struct type argument
> A<int?> x2;
> // nonnull constraint allows nonnullable class type argument
> A<C> x3;

Check warning on line 511 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L511

MDC032::Line length 86 > maximum 81
> // possible warning: nonnull constraint prohibits nullable class type argument
> A<C?> x4;

Check warning on line 513 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L513

MDC032::Line length 84 > maximum 81
> // nonnullable base class requirement allows nonnullable class type argument
> B1<C> x5;

Check warning on line 515 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L515

MDC032::Line length 102 > maximum 81
> // possible warning: nonnullable base class requirement prohibits nullable class type argument
> B1<C?> x6;
> // nullable base class requirement allows nonnullable class type argument
Expand Down Expand Up @@ -3455,7 +3456,7 @@
> static void Main()
> {
> field = 10;
> Console.WriteLine(Property); // Prints 10

Check warning on line 3459 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L3459

MDC032::Line length 83 > maximum 81
> Property = 20; // This invokes the get accessor, then assigns
> // via the resulting variable reference
> Console.WriteLine(field); // Prints 20
Expand Down Expand Up @@ -4570,7 +4571,7 @@
: '!'
;

overloadable_unary_operator

Check warning on line 4574 in standard/classes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/classes.md#L4574

MDC032::Line length 82 > maximum 81
: '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
;

Expand Down
2 changes: 1 addition & 1 deletion standard/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

### 8.2.1 General

A reference type is a class type, an interface type, an array type, a delegate type, or the `dynamic` type. For each non-nullable reference type, there is a corresponding nullable reference type noted by appending the `?` to the type name.
A reference type is a class type, an interface type, an array type, a delegate type, the `dynamic` type, or any type parameter that is constrained to be a reference type (that is, any type parameter with the reference type constraint or a class type constraint ([§15.2.5](classes.md#1525-type-parameter-constraints))). For each non-nullable reference type, there is a corresponding nullable reference type noted by appending the `?` to the type name.
Copy link
Contributor Author

@jnm2 jnm2 Aug 9, 2025

Choose a reason for hiding this comment

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

This borrows the existing language which appears in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#8312-nullable-value-types:

Conversely, a non-nullable value type is any value type other than System.Nullable<T> and its shorthand T? (for any T), plus any type parameter that is constrained to be a non-nullable value type (that is, any type parameter with a value type constraint (§15.2.5)).


```ANTLR
reference_type
Expand Down Expand Up @@ -946,13 +946,13 @@
> public void M(string s)
> {
> int length = s.Length; // No warning. s is not null
>

Check warning on line 949 in standard/types.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/types.md#L949

MDC032::Line length 87 > maximum 81
> _ = 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
>

Check warning on line 952 in standard/types.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/types.md#L952

MDC032::Line length 90 > maximum 81
> _ = 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
> {

Check warning on line 955 in standard/types.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/types.md#L955

MDC032::Line length 87 > maximum 81
> _ = s?[4]; // ?[] is a null check and changes the null state of s to maybe null
> _ = s.Length; // Warning. s is maybe null
> }
Expand Down Expand Up @@ -1012,7 +1012,7 @@
> {
> var t = new Test();
> if (t.DisappearingProperty != null)
> {

Check warning on line 1015 in standard/types.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/types.md#L1015

MDC032::Line length 110 > maximum 81
> int len = t.DisappearingProperty.Length; // No warning. A compiler can assume property is stateful
> }
> }
Expand Down
Loading