csharplang/meetings/2018/LDM-2018-08-20.md
Nick Schonning 6cd82c21ed fix: MD033/no-inline-html
Inline HTML get swallowed in MD and HTML rendering
2019-05-25 01:31:46 -04:00

5.6 KiB

C# Language Design Notes for August 20, 2018

Agenda

Nullable open issues:

  1. Remaining questions on suppression operator (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?
  6. Typing judgments containing oblivious types
  7. Unconstrained T in List<T> then FirstOrDefault(). What attribute to annotate FirstOrDefault?

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.