Cleaned up design notes
This commit is contained in:
parent
7981ea1fb4
commit
6ce4c2bf44
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue