Cleaned up design notes

This commit is contained in:
Mads Torgersen 2017-01-09 17:24:36 -08:00
parent 7981ea1fb4
commit 6ce4c2bf44

View file

@ -1,72 +1,102 @@
C# Language Design Notes for Nov 16, 2016
=========================================
_Raw notes - to be polished_
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
==============
MVP Feedback - a bit of a dissonance between calling it "types" and not having guarantees. Talk about it more like analyzers.
## 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.
General annotations? 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.
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.
Issue: 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.
## 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.
Issue: The dammit operator: should it generate a runtime check. There are pros and cons.
## The dammit operator
Should it generate a runtime check? There are pros and cons.
Issue: Is the nested check really useful? On arrays you almost always have null elements.
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).
Issue: Casts are assertions, but will not be able to check the nullness of type arguments.
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?
Issue: "!" on type parameters that may be nullable?
## 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.
Issue: We need to think about where types need to be the same for declarative reasons (overrides, etc)
## 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 literal" => "null constant"
## null literals
Rules should apply not just to the null literal but to null constants
constructor check needs to also look through value types for nested non-null reference types.
## The construction rule
There's a rule that checks that all non-nullable reference fields and properties are initialized by a constructor.
Issue: constructor check could be before calling helpers or only at the end.
It needs to also look recursively through fields and properties of value types for nested non-null reference types.
Issue: the array hole: should we disallow/discourage arrays of non-null?
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!
Issue: What should people do to go from `T?[]` to `T[]`? Can `!` be applied for the recursive case? Otherwise, casts may be necessary.
## Arrays
Issue: `default(T)` is of type `T` or `T?`? you could shut it up with `!`.
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?
Issue: Should it be evident from an assembly whether unannotated means non-null or not? Could be acceptable to paint with a broad brush. The "don't care" third type would have to be embraced.
What should people do to go from `T?[]` to `T[]`? Can `!` be applied for the recursive case? Otherwise, casts may be necessary.
Issue: unconstrained type parameters: need to ensure that T is assignable to T, and T to U when T is constrained by U. It's more complicated than just saying warn from both sides.
## Default
Is `default(T)` of type `T` or `T?`? If it gives a warning, you could shut it up with `!`.
Issue: Is this set of warnings part of the language, or are they a "compiler feature" - essentially some built-in analyzers. In the latter case they might evolve in a breaking way.
## Referenced assemblies
Should it be evident from an assembly whether unannotated means non-null or not?
For generics we should find a few samples that we believe are representative, and focus on them. May reuse ones from Midori.
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.
Note: Fine that you can get nullable reference types implicitly (e.g. inference from string, null).
What are the semantics around such "shut-up types", and sources of them?
Issue: What are the semantics around 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.
Note: not annotating locals would reduce the number of places that would need to be fixed. It's a trade-off between expressiveness and upgrade burden. This goes to granularity of opt-in as well. Readability disconnect between locals and fields.
## 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.
Issue: should `var?` be allowed, should `var` always be nullable? How to express intent here.
## Is nullability always explicit?
It is probably fine that you can get nullable reference types implicitly (e.g. inference from `string`, `null`).
Note: For the proposal `T! x` may be better as `T x!`, since it's about the parameter itself, and would actually name the parameter in the exception thrown!
## 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.
TryGet: Could get away with non-language flourishes, such as attributes. "DefinitelyAssignedWhenTrue".
Should `var?` be allowed? should `var` always be nullable? Should it infer its nullability? How to best express intent here?
Issue: Doing the flow analysis thing for nullable value types is more concerning, because it's adding new runtime semantics.
## 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!
Issue: flow on value types may work for dereferencing, but not so well if it is an argument: what if there are many?
## Expressing TryGet
Could get away with non-language flourishes, such as attributes. "DefinitelyAssignedWhenTrue".
Flow is a nice-to-have add-on.
## 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.