Correct subject title
5.6 KiB
C# Language Design Notes for August 20, 2018
Agenda
Nullable open issues:
- Remaining questions on suppression operator (and possibly cast)
- Does a dereference update the null-state?
- Null contract attributes
- Expanding the feature
- Is T? where T : class? allowed or meaningful?
- Typing judgments containing oblivious types
- Unconstrained T in List then
FirstOrDefault()
. What attribute to annotateFirstOrDefault
?
Discussion
Remaining questions on suppression operator
1.1 Suppression of nested nullability
Q: Should !
suppress warnings for nested nullability?
There's a question here about the interplay of casting and the !
operator,
since they do somewhat similar things. The problem with the current design is
that neither casting nor !
give any warnings/errors at either compile time
or runtime for nested nullability. We think it would be useful to have at
least one mechanism that provides more safety around nullable.
Conclusion
The user should get a warning if you cast away nested nullability. No warning
if you use '!'. This provides a safety mechanism for casts and still allows
for an "I Know Better" command, which was the primary motivation for !
.
1.2 Meaningless !
operators
Q: Should nonNull!
result in a warning for unnecessary !
?
Conclusion
These constructs are meaningless, but unlikely to do something the user
didn't want. No compiler warnings for !!...
or nonNull!
. Maybe an IDE
feature.
Result of obliviousValue!
Conclusion
Top-level non-null, suppressed warnings for nested.
Dereference of nullable types
Consider the following example:
string? x = y;
var z = x.Substring(1);
...
Conclusion
There's clearly a problem with this example that will generate a warning: x
is a nullable reference type, but it's being dereferenced without a null
check. The first question is: what is the type of x
after the call? The
answer is non-nullable string
, but the more detailed answer is this results
in a split state. The specification recognizes that dereferencing a null
value results in a NullReferenceException, so state should be split into
exceptional and non-exceptional flow.
To spell it out in a more detailed example:
try
{
string? x = y;
var z = x.Substring(1);
...
}
catch
{
...
}
After the Substring
call, x
is non-nullable, but in the catch block, x
is nullable, as there was a potential NullReferenceException if x
was null.
Additionally, like other flow control warnings, we will only provide a warning
about dereference of nullable type once. So if x
is dereferenced again in
the catch block, another warning will not be produced.
Null contract attributes
We've started building a list of attributes that are useful for annotating
existing code to note when null or non-null types are produced. For instance,
string.IsNullOrEmpty()
will always be true if the receiver was null. It would
be useful to mark this method with an attribute which can indicate this to the
compiler, so
if (!x.IsNullOrEmpty())
{
...
}
should let the compiler know that x
is not null within the if
block.
Q: Do we want to do a larger review of all these attributes?
Conclusion
Let's take all the current attributes and start prototyping. We'll adapt as we move forward.
Expanding the feature
We have two potential extensions here.
The first is possibly allowing a !
annotation on a parameter name, which
indicates that the compiler should produce a dynamic null check on entry to
the method. For instance, a method void M(string s!) { }
would not only
indicate that s
is meant to be a non-nullable reference type if the
nullable reference type feature is fully enabled, it would also insert code
at the beginning of the method to throw an ArgumentNullException
if null
is passed anyway.
The second is how to treat nullable value types. There are two extensions we
are considering for nullable value types. The first is about extending the
analysis. This is as simple as extending flow analysis to update null state
of nullable value types based on information of null checks. For instance,
accessing .Value
on a Nullable<T>
could produce a warning if the value
was accessed without checking for null first. An even more advanced extension
would allow Nullable<T>
values to be accessed without going through .Value
if the variable is proved to not be null.
Conclusion
Jared's going to write up a proposal on the compiler-inserted dynamic checks.
For Nullable<T>
, we agree with extending the analysis. We're not sure about
the silent call of .Value
or automatic conversion.
class?
constraint
Q: Is void M<T>(T? t) where T : class?
allowed?
Conclusion
Rule: you can only use ?
on types you know to be non-nullable.
T : class?
is possibly nullable, so you can't use T?
.
Typing judgments containing oblivious types
Mainly comes down to the type of x
in var x = oblivious;
.
We need more time for this. Return later.
Annotating List<T>.FirstOrDefault()
*Q: Unconstrained T in List<T>
then FirstOrDefault()
. What attribute is
used to annotate FirstOrDefault
?
Conclusion
[MaybeNull], since T
is unconstrained and could either be a nullable or
non-nullable type. More information may be available after type substitution.