103 lines
6.2 KiB
Markdown
103 lines
6.2 KiB
Markdown
C# Language Design Notes for Nov 16, 2016
|
|
=========================================
|
|
|
|
In this meeting we looked at the proposal for nullable reference types in [nullable-reference-types.md](https://github.com/dotnet/csharplang/tree/7981ea1fb4d89571fd41e3b75f7c9f7fc178e837/proposals/nullable-reference-types.md).
|
|
|
|
The below has few answers and many questions. It is essentially a laundry list of things to work on as we design the feature.
|
|
|
|
|
|
Nullable types
|
|
==============
|
|
|
|
## Calling it "types"
|
|
MVP Feedback: There's a bit of a dissonance between calling it "types" and not having guarantees. Maybe talk about it more like analyzers.
|
|
|
|
Should this be part of a general annotations framework? Do we want to keep doing this specifically per feature or have a general framework for it? Maybe. Let's keep that in the back of our minds. We don't want to be blocked by figuring that out right now.
|
|
|
|
## Overload resolution
|
|
It's not the intention that knowing something is not-null influences overload resolution.
|
|
|
|
## Tracking nested reference fields and properties
|
|
We need to decide whether to track `x.y` when both are reference types, and to what extent we will track aliasing. Guarantees vs usefulness.
|
|
|
|
## The dammit operator
|
|
Should it generate a runtime check? There are pros and cons.
|
|
|
|
It would sort of break with a principle that nullability stuff has no runtime semantics, and also impose runtime cost (though that can be eliminated by a smart compiler, when the next thing that follows - e.g. dereferencing - also already starts with a null check).
|
|
|
|
On the other hand it might be good to verify non-nullness right then and there, rather than experience random fallout later. Is it an "I know what I'm doing" or a "check that I know what I'm doing" operator?
|
|
|
|
## Nested annotations
|
|
|
|
Is it really that useful to have nested nullability annotations, as in `string?[]` or `List<string?>`? On arrays you almost always have null elements. We suspect that it *is* useful, and would detract from the combo with generics if not there, but on the other hand it is a great simplification to only track nullability at the top level.
|
|
|
|
One problem with nested nullability is that casts will not be able to check the nullability of type arguments. A cast cannot distinguish `(List<string>)o` from `(List<string?>)o`, since the runtime object in `o` won't know if it was instantiated with `string` or `string?`. So the notion that "all is good after a successful cast" is lost.
|
|
|
|
If we *do* allow nullable type parameters, should you be able to say `!` on type parameters that may be nullable?
|
|
|
|
For generics we should find a few samples that we believe are representative, and focus on them. May be able to reuse ones from the Midori project.
|
|
|
|
## Warning types
|
|
The distinction between nullable and non-null warnings is not useful, and some of them (conversions) kind of belong to both.
|
|
|
|
## When are types the same
|
|
Issue: We need to think about when types are considered to be the same for declarative reasons (overrides, etc)
|
|
|
|
It's similar to tuples in that we have runtime types ornated with extra compile time information. That's a good analogy for a check list, but we should feel free to reach different conclusions.
|
|
|
|
## null literals
|
|
Rules should apply not just to the null literal but to null constants
|
|
|
|
## The construction rule
|
|
There's a rule that checks that all non-nullable reference fields and properties are initialized by a constructor.
|
|
|
|
It needs to also look recursively through fields and properties of value types for nested non-null reference types.
|
|
|
|
We need to decide *when* to check during a constructor. It could be before calling helpers on the instance (since they may assume non-nullability) or only at the end. Usefulness vs guarantee!
|
|
|
|
## Arrays
|
|
|
|
Should we disallow/discourage arrays of non-null, to try to avoid the array hole where you have an array of a non-nullable element type, initialized entirely with nulls?
|
|
|
|
What should people do to go from `T?[]` to `T[]`? Can `!` be applied for the recursive case? Otherwise, casts may be necessary.
|
|
|
|
## Default
|
|
Is `default(T)` of type `T` or `T?`? If it gives a warning, you could shut it up with `!`.
|
|
|
|
## Referenced assemblies
|
|
Should it be evident from an assembly whether unannotated means non-null or not?
|
|
|
|
The compiler would have to be able to embrace the "don't care" old fashioned kind of reference type, even when opted in to non-nullability.
|
|
|
|
What are the semantics around such "shut-up types", and sources of them?
|
|
|
|
## Unconstrained type parameters
|
|
When T is constrained by U, we need to ensure that T is assignable to T, and T to U. It's more complicated than just saying warn from both sides.
|
|
|
|
## Are the warnings part of the language
|
|
Are this set of warnings part of the language, or are they a "compiler feature" - essentially some built-in analyzers. In the latter case we might allow them to evolve in a breaking way.
|
|
|
|
## Is nullability always explicit?
|
|
It is probably fine that you can get nullable reference types implicitly (e.g. inference from `string`, `null`).
|
|
|
|
## Locals
|
|
Not annotating locals would reduce the number of places that would need to be fixed when turning nullability on. They would then have their nullability inferred instead. It's a trade-off between expressiveness and upgrade burden. This goes to granularity of opt-in as well. It would cause a readability disconnect between locals and fields.
|
|
|
|
Should `var?` be allowed? should `var` always be nullable? Should it infer its nullability? How to best express intent here?
|
|
|
|
## Parameters
|
|
There's a proposal to allow `!` on parameter types `M(T! x)` to cause a runtime null check to be generated. That would be better written as `M(T x!)`, putting the `! on the parameter, since it's about the parameter itself, and would actually name the parameter in the exception thrown!
|
|
|
|
## Expressing TryGet
|
|
Could get away with non-language flourishes, such as attributes. "DefinitelyAssignedWhenTrue".
|
|
|
|
## Applying flow analysis to value types
|
|
The proposal of also doing the flow analysis for nullable value types is more concerning, because it's adding new runtime semantics.
|
|
|
|
Also, it may work for dereferencing those values, but not so well if it is an argument: Does it influence overload resolution? What if there are many arguments?
|
|
|
|
Flow for nullable value types is a nice-to-have add-on, not essential to the proposal.
|
|
|
|
|
|
|
|
|