Different reference types: nullable and non-nullable
Fixes #1089 Fixes #1090
This is an alternative to #1105
The changes were done on top of the branch for #1105. The final commit has the updates to remove null oblivious types.
It removes the text for a "null-oblivious" type. Instead, it relies on the nullable context for how to interpret the nullability of a reference type T.
We may need three types, not two. For example:
void f(
#nullable disable
string x, // could x (oblivious) be seen as nullable type but non-nullable initial state?
#nullable enable
string y,
string? z)
{
if (...) {
// x state is not null? or oblivious?
g1(ref x); // okay
g1(ref y); // not okay
g1(ref z); // okay
// x state is possibly null
} else {
// x state is not null? or oblivious?
g2(ref x); // okay
g2(ref y); // okay
g2(ref z); // not okay
// x state is possibly null
}
}
void g1(ref string? x) {}
void g2(ref string x) {}
Very short version:
f("x", "y", "z");
void f(
#nullable disable
string x,
#nullable enable
string y,
string? z)
{
y = x; // no warning
y = z; // warning. Assignment of null to non-nullable variable.
}
Example of why I believe it makes sense to view a variable of type T declared in a nullable-disabled context as a nullable type:
# nullable disable
string x;
# nullable enable
x = "some value";
x = null;
This does not issue a warning, even for the last line. The state of x just before the last line is surely "definitely not null" as we've assigned a string literal to it... so to be able to assign null to it means its type can't be a not-null type.
#nullable enable
string s = M();
#nullable disable
// What is the type of the return value?
string M() => null;
Note that from 2024-8-20 through 2024-09-03, there was a lengthy thread on email re this topic, under the subject "ECMA TC49-TG2 agenda for September 4th."
I've addressed all open comments, and resolved all conversations. This is ready for a final (asynchronous) pass and approval.
@jskeet @Nigel-Ecma @MadsTorgersen @ericlippert @RexJaeschke @gafter