Add raw notes

This commit is contained in:
Mads Torgersen 2017-12-05 17:06:17 -08:00
parent 95c9267d0d
commit a0eaa09408
11 changed files with 978 additions and 0 deletions

View file

@ -0,0 +1,80 @@
# C# Language Design Notes for Oct 9, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
# 882 Negated if or negative patterns
Three approaches
1. bang outside if condition (then should I do that on while etc, too) `if !(o is int i)`
2. negative patterns (but not very useful recursively) `not int i`
3. `is not` as an expression operator
# 867
Avoid some statement cliffs...
Put it in X.X with a note to consider again when we have match expressions
# 414
There's a "there" there.
We think this should be addressed, and will keep the chanpioning issue to represent it.
However, it should be different:
1. It should not be strongly tied to the `Dictionary<K, V>` type, but be target typed
2. We should look at initializing immutable objects (also for object and collection initializers)
3. We already have index initializers. Are they good enough?
# 973 Declaration expressions
Last time, we had two issues:
1. Weren't ready to commit to scoping rules
2. Weren't sure that we could get decent error recovery on syntax
1 is dealt with.
2 was more that it was hard to show intellisense because more things were legal
Scenario is introduce a local variable in expressions without having to use trivial pattern matching. Also ref.
We feel like we need to spend more time with it to judge its value. 8.0 for now to trigger that discussion.
# 881 and 33
Fits with nullable in 8.0
# 185
Settle this in the 7.3 timeframe
# 187 Blittable
# 435
# 287
# 32
# 125
Missing, but not much ask for it
# 111
We would want to deal with single parameters. A problem is that discards do not shadow today, whereas identifiers do. We may want to change that.
# 191
Need more motivation
# 190
Some open design discussions
#

View file

@ -0,0 +1,156 @@
# C# Language Design Notes for Oct 11, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
We looked at the design review feedback for nullable reference types.
# Philosophy
Takeaways: Let unconstrained type parameters be its own special case; don't let it degrade the experience.
This is linting, make most existing code work, and don't shout at people too much.
## Conclusion
agree, but let's consider a paranoid mode; "Xtreme". Could do that later.
Everybody who want the Xtreme mode would be happier with something than nothing. Should see what reaction is, and then dfecide whether to do it in-compiler, as analyzers or not at all.
# Switches
Feedback: Don't have many switches. Just on or off.
Off would mean suppress the warnings. The syntax would still be allowed.
## Conclusion
THis is a good philosophy. (We might allow Xtreme). We don't need to prototype the switch; it would always be on.
# Dotted names
Feedback is we should track dotted names, and be very forgiving about what invalidates null state.
Invalidation: assigning to a prefix, or passing it by out or ref.
## Conclusion
(Could go different with Xtreme mode)
# Type narrowing
```
void M(string? n)
{
if (n == null) return;
var s = n; // string
var l = s.Length;
n = null; // ok
s = null; // ???
}
```
(Separate design discussion for IDE about what to show for n).
``` c#
T[] MakeArray<T>(T v1, T v2)
{
return new T[] { v1, v2 };
}
void M(string? n)
{
if (n == null) return;
var a = MakeArray(n, n); // string[] or string?[] ?
var l = a[0].Length; // ???
}
```
``` c#
T N<T>(ref T r, T t)
{
}
void M(string? n)
{
if (n == null) return;
var s = N(ref n, n);
}
```
This one is interesting because even though n goes in as an lvalue, the method cannot legally assign a null to it.
## Conclusion
We want to start treating the values of nullable variables with nonnull state as nonnullable in type inference.
# Dammit operator type narrowing
``` c#
T[] MakeArray<T>(T v)
{
return new T[] { v };
}
void M(string? n)
{
var a = MakeArray(n!); // string[] ?
var l = a[0].Length; // ???
}
```
Null suppression:
``` c#
List<string?> l = GetList()!; // returns List<string>
```
## Conclusion
`! should keep its warning suppression, but also narrow the outermost type when it can, to match the new behavior when null-state is non-null.
There's hesitation because of the muddiness of using `!` for two different things. But we don't have a better idea. Explicit casts are quite verbose. We may need to revisit later.
# Dammit operator stickiness
Dammit operator lacks "stickiness" - you have to keep applying it (or introduce another local).
Idea is to maybe have some top-level "assertion" that would declare a thing not-null for the whole scope.
Other idea is to have `s!` influence null state for flow analysis, staying "valid" for as long as a non-null state would have.
``` c#
string? s = ...
if (s != null && s.Length == 5) ... // It flows here
M((s!, s); // why not here?
```
It would sort of make `s!` mean the same as `s = s!`.
There are also cases where you *wouldn't* want it to be sticky, e.g. when you are using `!` to shut a specific unannotated API that is lacking a `?`.
## Conclusion
We don't know what, if anything, to do here.
# Array covariance
## Conclusion
We'll think about this more.
# Null warnings
Agree with feedback
# Libraries

View file

@ -0,0 +1,80 @@
# C# Language Design Notes for Oct 16, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
"Being on the same floor as SPJ is a good way to shake out difficult corner cases"
1. LINQ
2. Shapes
# Applying to LINQ
Mixed results.
Looking for perf, and ways to do more selective specialization.
## Sum
Specialized to some numeric types, but not all. 500 lines of code. In those, the loop is unspecialized too.
One page of code!
A new design dimension: should I use interfaces or concepts: pay for abstraction or pay for specialization
Generic soup: this may be a superficial design issue, or even tooling issue.
For instance, the `AssociatedType`s, other languages allow them to be retrieved by dot notation. Jeremy Siek paper "Associated Types and ...". `TColl.TEnum` etc.
From experience, abstracting over enumerators tends to need associated types or higher-kinded types.
Shouldn't be too discouraged by being smoked by LINQOptimizer. That one optimizes big queries, but what keeps people away from LINQ is more the death by a thousand paper cuts of using LINQ all the time. Roslyn avoids things that allocate, which today means not using LINQ. THis could be the thing that would allow it to.
## Select
The return type is associated. The problem is that adding new instances can change the return type from afar, upsetting the consuming code.
Improves by 2/3rds when the array specialization is used.
## SelectMany
The generic type inference gets very messy here. It shows that concept inference needs to be interleaved with type inference in a way that we are still only loosely grasping.
This is a place where LINQ allocates a lot, whereas this allocates hardly anything. We go at .75 the time even unspecialized. Also, the specialized version is twice as fast as the unspecialized.
It shows that if you open up for specializations to be plopped in, there's quite a lot to gain.
## Conclusion
Some promise on optimization. Pinches of salt here and there. The approach definitely seems to have promise.
More tests to do.
# Shapes
Concepts can tie in to the richness of expression in C# around different kinds of operations (operators, conversions, constructors...)
Interesting to consider whether there's more of a specialization relationship between concepts and instances, rather than a type/instance relationship.
If this went further, there's a very large laundry list.
# Conclusion
We *really* would like to be able to do the post-hoc implementation of concepts. This

View file

@ -0,0 +1,93 @@
# C# Language Design Notes for Oct 18, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
# 189
``` c#
from s in strings
let (b, i) = (int.TryParse(s, out var x), x)
where b
select i
```
Problem is that out vars also aren't yet allowed.
We don't allow declaration expressions in
1. queries
2. constructor initializers
3. field/property initializers
All of these were because we didn't settle the scope question. Time to do that.
Let's put these as issues. And let's put those issues as 7.3. And let's put this one as 7.3.
# 100
The difficulty of this depends on the level of ambition. It has to play in to applicability of overloads this is passed to. If we allow member initializers, then we need to bind those
``` c#
M(new { X = 7, Y = new { A = "Hello" } });
x = new () { Y = { e1, e2 } };
```
If we're lucky, this is just about whether the conversion exists or not.
Would there be a way that this would even influence generic type inference?
``` c#
M(() => new (1));
```
We'd need to think about this.
It may be that there's a subset of the feature that's simpler. We would need that subset to not preclude going further later.
Spooky action at a distance
``` c#
M(Foo)
M(Goo)
M(new (1))
```
Goo has a constructor that takes an int. Adding such a constructor to Foo will break the code.
Now, adding a constructor is equivalent to adding a conversion in terms of the breaks it can entail.
May also need more betterness rules.
It could be that it's better to limit the feature so it does not participate in type inference and betterness, and can always be checked as a conversion. It might even be worth considering it only specifically to where one target type is known (so no overload applicability).
For now, let's put it in 8.0. We don't believe it's going to make 7.3, but that makes us still consider it for design time.
# 179
For people who care about perf, they already need 3 or 4 overloads, and this would be yet another overload peopple will yell at them to add.
For no parameters today, you no longer need an overload to avoid allocation, because we now use `Array.Empty`.
`params IEnumerable<T>` would be even less performant than the array one, because enumeration allocates.
The point of it more is that if I want to take an `IEnumerable<T>` anyway, then it's a convenience to add params and take the arguments individually.
Not important enough to prioritize time for the design soon. Let's make this X.X.
# How to continue
Scan 7.X for remaining 7.3 items and ignore the rest for a bit.

View file

@ -0,0 +1,111 @@
# C# Language Design Notes for Oct 25, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## #98
## 34 and 35
34 should bot be concurrent safe, just like the other compound assignment.
Bundle with 8.0, but could push out. Seems to align with nullable reference types
# 32
Reconcile 32 with 1020
Criteria:
- Loose ends
- External expectation
# ref as iteration variable
Not currently allowed, should probably have a proposal (Andy)
# 185 keep in 3 to prioritize
# 45
Push out to 8.0 for realism, but still prioritize design time
# 933, 1046, and uninitialized ref local
These should happen together in 7.3
# 111 Punt to 8.X
# 1020 946 945 keep
# 882 pattern-related, goto 8.0
# 435
Keep in 7.3, see if we can settle design
# 190
Relatively obvious design, with some gnarly bits (dynamic, conversion)
Usability gap with tuples let's keep it.
# 189
Let is more important than from. It lets you use out variables
``` c#
from s in strings
let t = (b: int.TryParse(out var n), n)
where t.b
select t.n
```
Could be
``` c#
from s in strings
let (b, i) = (int.TryParse(out var n), n)
where b
select i
```
There's a bit of design work, especially if we also want the from clause.
We could allow out vars but not the deconstruction, and it would still be useful.
Could save dec for later. It's actually orthogonal.
Action:
Carve out deconstruction, push to 8.X
# 187
On the brink, but keeping for now; need to be convinced of value
# 185
Keep pushing on it, got to get the train going on `Range`
`x..y` does new Range(x, y) or Range.Create(x, y)
Consider whether it should be a new kind of operator instead.
# 104
Micro-feature: Just allow `System.Enum` as a constraint
Mini-feature: allow `enum` as a constraint, translate to `System.Enum, struct`
Keep this in, but it is very cuttable.
# 98
It's a zero-conceptual-overhead feature.
Original designers left in space between meanings for a purpose. But that's not so compelling to us anymore.
Because it touches overload resolution, it might be better alligned with a .0 release. But we're not compelled by that.
Let's keep it, but again, it's cuttable.

View file

@ -0,0 +1,54 @@
# C# Language Design Notes for Nov 6, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
# Roslyn 20870
Protecting the client from unintended dependencies. But also protects from servicing. Today people going through reflection *know* they're being bad. Would this give enough sense that they are doing something special.
It would make consumers lazy about contacting the API owner about things they need exposed.
It would be an arms race - we would want the `IgnoreIgnore...` attribute to *really* protect things.
People will still have expectations about dependencies even if it was "their own fault" by using this attribute.
## Conclusion
Too risky/fishy in too many ways.
# Roslyn 17310
There is no good language level solution right now. This is better addressed with an analyzer, which can know specifically about SpinLock (for instance).
In time, when readonly struct declarations are added, as well as maybe the ability to declare individual struct members as readonly, *then* maybe we could start warn.
## Conclusion
Not at the language level
# Roslyn 20450
We have sympathy. It feels like a corner that's cut. But it's quite expensive to implement, and has semantic dark corners (`List<>.First.Foo`).
## Conclusion
Not now.
# Roslyn 20015
When default-expressions are constant (according to the language) this is not interesting expressiveness - there's a literal you can use.
When they are *not* it gets a bit more interesting - you might want to check that your custom struct is zero-initialized. But you can do that with equality. Even in recursive scenarios, you can just `var`-pattern it and check in `when` or `&&`.
Additionally there is some concern about the target type being clear enough for the `default` expression.
## Conclusion
No.

View file

@ -0,0 +1,87 @@
# C# Language Design Notes for Nov 8, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
# Constructors
Currently the prototype doesn't look for `this(...)` constructor initializers. It should, and exempt constructors that do that.
We may consider a more nifty analysis later, at least for private constructors, but it doesn't seem high priority.
It's a warning per constructor. If it's on the implicit (default) constructor it goes on the class name. That's good.
## struct initialization
We don't warn for the default constructor on structs. It's not really actionable, we think. But this is worth revisiting later, probably in the context of Xtreme mode.
# Dotted names
Now work in the prototype, modulo a few bugs. (Not fully handling reassignment).
# Default expressions
Should `default(string)` be of type `string?` or should it yield a warning *in and of itself*, and be of the type `string` (the type it states).
This is a question that's related to whether `string s = null` as a local declaration yields a warning.
We're going to leave the current impl, which makes `default(string)` be a `string?`.
Similar with `(string)null`. It's type is `string?`.
# Should we track null state for nonnullable ref types?
Not now. Worth thinking about for later, as a stop gap.
# Feedback
Email feedback.
Wiki page on GitHub, aka.ms link to it from blog
# Inferred types for method type inference
Best type and method type inference, don't pick up inferred nullability, but only declared nullability
``` c#
void M(string? s)
{
if (s == null) return;
var a = new[] { s }; // string?[], should be string[]
}
```
There's a design question what is inferred when types differ only by nullability of type arguments
# Inferred nullability in hover tips
Currently shows declared nullability. Relatively big work item, won't be fixed in prototype. So we need to set expectations.
The squiggle is the bottom line. :-D
# Smaller things not yet done
Some warnings not given: That's alright, we'll give more in the future.
New constraints (`class?`, `object`): Not blocking
Variance: we'll deal with it when we get there.
# Unconstrained generics
Need to revisit to see if the weirdness we do is the right weirdness. Doesn't have to be consistent with the rest of the language.
# Other issues
- No switch in the prototype. Need to design the command line switch
- Annotations for TryGet etc.
- How to update BCL with annotations (automatically?)

View file

@ -0,0 +1,74 @@
# C# Language Design Notes for Nov 20, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
# Nullable feedback
Great feedback. All over the map in blog comments, great quality in email feedback.
# Recursive pattern matching
## Grammar
The grammar splits out to a couple of cases so that the right things are optional etc. directly in the grammar.
That separation is also there in the representation of the implementation, currently. An alternative is to have a single production and call it out in prose. There are advantages to having a separated representation, in that your code can make more assumptions about what's there or not.
## Names in deconstruction pattern
What is the utility? Current proposal it's only to guarantee that you get the thing you say. It does not allow reordering or leaving out elements.
This pattern should by and large work like deconstruction. But it's plausible to have names here, even if we don't in deconstruction; the argument would be a symmetry with constructors, which are sometimes used with names and optional arguments.
## Should the identifier be restricted from being a discard?
Since it can be left out completely? No, it's probably good to allow the discard. For refactoring etc.
## Matching via ITuple
It's "too likely" that the compiler would consider that a thing *may*
implement `ITuple`.
We'll restrict to static types `object`, `ITuple` and any type that derives from `ITuple` and has no deconstructors.
`dynamic` is treated just like `object`. We don't go looking for deconstructors dynamically.
## Syntactic ambiguity around parenthesized expression
Should we even have single-element deconstruction patterns?
Could require some other element to disambiguate, e.g. `(2) _`.
This would raise the cost of adding single-element tuples and deconstruction in the future, at least if they have a syntax *other* than parenthesized expressions (e.g., `(x,)`).
``` c#
switch(my1DPoint)
case 1DPoint(0):
...
case 1DPoint(var x):
...
```
Compromise position: Allow a single one only if there is a type in front. It gives the obvious symmetry with a single-element constructor, without restricting the design space for future single-element tuples or pattern grouping constructs.
## Cast ambiguity
Now went away. It's a cast, or an error.
## Short discard
Yes, allow `_` as a pattern in and of itself. It would not be allowed at the top level in an `is` expression. (That is allowed today, and designates the type `_`).
It actually means something different than `default` in a switch, because it gets an error if there are no cases left. That seems useful.
So: allow it everywhere except at the top level in an is-expression.
## Colon or is?

View file

@ -0,0 +1,33 @@
# C# Language Design Notes for Nov 27, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
We went over the feedback on the nullable reference types prototype, and discussed how to address the top issues that people had found using the feature on their own source code.
1. Interacting with existing, unannotated APIs
2. Accommodating alternative initialization patterns
3. Tracking nullable value types
4. Tracking dotted names
5. Special methods
6. Filtering out nulls
# Interacting with existing, unannotated APIs
# Accommodating alternative initialization patterns
# Tracking nullable value types
# Tracking dotted names
# Special methods
# Filtering out nulls

View file

@ -0,0 +1,80 @@
# C# Language Design Notes for Nov 29, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
# Match expression
Current proposal:
- Infix vs prefix `switch`
- `switch` vs `match`
- curlies or parens for body
- `case` or not
- arrows?
- discards or defaults?
- also need to remember when clauses
If it's too similar to switch statements, then it sets certain expectations. If it's too different, it's not utilizing existing intuition.
Should it be a verb/command? Not many expression keywords are that (`select` is an exception, though).
`case` is heavyweight, but helps visually separate the issues.
``` c#
state = (state, action) switch (
(DoorState.Closed, Action.Open) => DoorState.Opened,
(DoorState.Opened, Action.Close) => DoorState.Closed,
(DoorState.Closed, Action.Lock) => DoorState.Locked,
(DoorState.Locked, Action.Unlock) => DoorState.Closed,
_ => state);
state = match (state, action)
{
(DoorState.Closed, Action.Open) => DoorState.Opened,
(DoorState.Opened, Action.Close) => DoorState.Closed,
(DoorState.Closed, Action.Lock) => DoorState.Locked,
(DoorState.Locked, Action.Unlock) => DoorState.Closed,
_ => state
};
state = switch (state, action)
{
case (DoorState.Closed, Action.Open): DoorState.Opened
case (DoorState.Opened, Action.Close): DoorState.Closed
case (DoorState.Closed, Action.Lock): DoorState.Locked
case (DoorState.Locked, Action.Unlock): DoorState.Closed
case _: state
};
```
The last one is subject to ambiguity-like situations betyween expression and statement `switch`.
We should also consider nesting of match expressions.
No matter what syntax we choose, we'll get requests for doing more things in expressions. We can live with that.
Parens look too much like a list of *expressions*.
Arrows make it look like lambda expressions.
## Decisions
We agree that we will not use the keyword `default`. You can use `_`, and in the rare case where that's defined, you can use `var _`.
We like curly braces for the grouping.
The rest is up in the air. We'll stay with the first version for now in the prototype, other than the curly braces.
# Where and when can identifier appear?
# Syntax for property patterns
Not urgent
``` c#
```

View file

@ -0,0 +1,130 @@
# C# Language Design Notes for Dec 4, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
QOTD: "Days since working around the JIT: 0"
QOTD: "What's wrong with dangerous?"
QOTD: "If we disallowed `Dangerous` in method names, Dangerous Dave wouldn't compile!"
# Foreach for Span and ReadOnlySpan
`Span` and `ReadOnlySpan` implement the foreach pattern with `GetEnumerator()` etc., even though they don't implement the `IEnumerable<T>` interface. (Interesting aside, the `Current` property is ref-returning, which the compiler is quite happy with.)
But it's faster to iterate them like an array, by using the `Length` and the indexer. We want to allow that for spans as well. We would keep the enumerable around, which also allows write access to the elements of a `Span`.
Interesting to consider opening up for a new `Length`/indexer `foreach` pattern, that's opted into e.g. with an attribute.
## Conclusion
Definitely allow it as an optimization. The general case; not now, and maybe not ever. Better to limit it to compiler-known types, that are guaranteed to have equivalent semantics.
# In vs value parameter ambiguity
If you have overloads
``` c#
void M(in DateTime d) { }
void M(DateTime d) {}
M(myDT); / ambiguity
```
Options in tie breaker:
1. Choose based on LValue vs RValue
2. val preferred
3. Do nothing
How would you legitimately end up in this situation:
- Want to "change" to `in`, but must keep value for binary compat
- But now you have source break
We currently do 3. We should do 2. 1 is too arbitrary.
People might want to move existing calls to the `in` version, and we won't be helping them. But that's an analyzer.
Problem with operators. They can't use `in` when applied, so can't move away from val behavior. Oh well.
## Conclusion
Go with 2. Roll out as bug fix.
# Pattern-based fixed
Lots of types (especially new ones, like `Span`, `Memory` etc., but also `string`) would benefit from being `fixed`.
`Span<T>` currently has a `DangerousGetPinnableReference` method for this. We can make this a pattern.
It's a thin layer of syntactic sugar.
## Conclusion
Let's do it. Extension methods are allowed (as always when we add new patterns). Let's aim for 7.3, but not super high pri.
# this ref vs ref this
We allow `ref this` but not the other way around. That's wrong! We can't disallow `ref this`, but we should allow and prefer `this ref`.
## Conclusion
Fix it. Go out as a bug fix.
# Reachability, definite assignment and local functions
There's a bit of a mess around when captured variables in local functions are definitely assigned.
This is different from lambdas, because local functions take all calls into account.
The forthcoming ECMA spec changes the wording of these rules, so that they are not defined based on reachability.
Proposal:
- Make beginning of local functions always reachable. (And lambdas, which is implemented but not spec'ed).
- A captured local in a local function is considered definitely assigned if it's definitely assigned before every call of it.
- That last one can be vacuously true.
## Conclusion
We like this. It could give errors in a few cases that don't today (including the bug report).
Is this an acceptable breaking change? In order to get an error, you'd have to have:
- an unassigned local variable
- an uncalled local function that uses it
Both are unlikely, undesirable and easily fixed. We'll check with compat council.
# Equality operators on tuples
Needs to be defined by the language, in order to deal with long tuples, and to recursively apply `==` rather than `Equals`.
If somebody wrote their own `ValueTuple` *with* user defined equality, then this would break the use of that. That's not a supported scenario; you could get into the same kind of trouble with `Nullable<T>`.
For tuple literals, there is a tension between left-to-right evaluation and performance.
It seems that `t1 == t2` should mean the same as `t1 == (t2.Item1, t2.Item2)`. The upshot of the feature really is to deconstruct tuples (if they aren't already, by being tuple literals), then do point-wise comparison.
We will evaluate all operands left to right, recursively through tuple literals, and save to temps. We don't evaluate target-typed things, and we don't yet convert them.
Then we do point-wise `==` (or `!=`, in element position order, separated by `&&` (Or for `!=`, `||`). This may involve conversions. We are ok with those happening "late", and conditionally (won't happen if previous comparison failed), because conversions aren't usually expected to have side effects.
For dynamic we think we can allow it by just converting the result of, e.g. `d == e` (where `d` is dynamic) to `bool`.
Conversion from tuples matter, but not to tuples. If I have a `Foo` and a tuple, and `Foo` converts to tuple, then that won't help you.
## Conclusion
We think we have it, but will revisit during implementation.