2018-09-04 22:56:26 +02:00
|
|
|
# C# Language Design Notes for August 20, 2018
|
2018-09-04 18:56:59 +02:00
|
|
|
|
|
|
|
## Agenda
|
|
|
|
|
|
|
|
Nullable open issues:
|
|
|
|
|
|
|
|
1. Remaining questions on [suppression operator](https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Froslyn%2Fissues%2F28271&data=02%7C01%7C%7C6defe1e21ab54cce8d0008d606be5d23%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636703812006445395&sdata=DAdh5dev1mnr%2F5zxtvuJVcHP%2Bzewrzz4z9iuGkl%2BUHg%3D&reserved=0) (and possibly cast)
|
|
|
|
2. Does a dereference update the null-state?
|
|
|
|
3. Null contract attributes
|
|
|
|
4. Expanding the feature
|
|
|
|
5. Is T? where T : class? allowed or meaningful?
|
2018-09-25 20:29:37 +02:00
|
|
|
6. Typing judgments containing oblivious types
|
2019-05-25 07:31:46 +02:00
|
|
|
7. Unconstrained T in List\<T> then `FirstOrDefault()`. What attribute to annotate `FirstOrDefault`?
|
2018-09-04 18:56:59 +02:00
|
|
|
|
|
|
|
# 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:
|
|
|
|
|
|
|
|
```C#
|
|
|
|
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:
|
|
|
|
|
|
|
|
```C#
|
|
|
|
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
|
|
|
|
|
|
|
|
```C#
|
|
|
|
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.
|