commit
55f74b7374
2 changed files with 346 additions and 0 deletions
151
meetings/2018/LDM-2018-04-25.md
Normal file
151
meetings/2018/LDM-2018-04-25.md
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
# C# Language Design Notes for Apr 25, 2018
|
||||||
|
|
||||||
|
## Agenda
|
||||||
|
|
||||||
|
|
||||||
|
# Warn on nullable ref type argument when T has `class` constraint
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
static void F<T>(T t) where T : class { t.ToString(); /* no warning */ }
|
||||||
|
F(maybeNull); // warning: string? does not satisfy 'class' constraint
|
||||||
|
F<string?>(string.Empty); // warning: string? does not satisfy 'class' constraint
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a real danger, and we should be warning here.
|
||||||
|
|
||||||
|
Note that in both cases the warning is about constraint checking: even in the second line we infer `string?` and it warns on constraint check.
|
||||||
|
|
||||||
|
We should probably have the error message say that this is because of nullability.
|
||||||
|
|
||||||
|
|
||||||
|
# Warn on nullable reference type argument when T has non-nullable reference type or interface type constraint
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
static void F<T>(T t) where T : IDisposable { }
|
||||||
|
F(maybeNull); // warning: Stream? does not satisfy 'IDisposable' constraint
|
||||||
|
```
|
||||||
|
Yes, warn for same reasons.
|
||||||
|
|
||||||
|
We also want to warn (or error) on inconsistent nullability in constraints. Specifically if one constraint is known to be non-nullable and another is known to be nullable.
|
||||||
|
|
||||||
|
What about
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
class C<T, U, V> where T: class? where U : T, IDisposable where V : T, IDisposable?
|
||||||
|
{
|
||||||
|
// Fine
|
||||||
|
}
|
||||||
|
class D<T, U, V> where T: class where U : T, IDisposable where V : T, IDisposable?
|
||||||
|
{
|
||||||
|
// Warn on V
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
... where T : Node<T>?
|
||||||
|
... where T : Node<T?> // both currently allowed, but not where T : Node<T?>?
|
||||||
|
... where T : class, INode<T?>
|
||||||
|
... where T : class?, INode<T>?
|
||||||
|
```
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
static void F<T>(T t) where T : IFoo<string?> { }
|
||||||
|
IFoo<string> foo;
|
||||||
|
F(foo); // warning, unless IFoo is covariant
|
||||||
|
```
|
||||||
|
|
||||||
|
Another kind of clash:
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
interface IBar : IFoo<string> {}
|
||||||
|
static void F<T>(T t) where T : IFoo<string?>, IBar { }
|
||||||
|
```
|
||||||
|
|
||||||
|
We want to warn on this: maybe find shared base interfaces between the two constraints, and check if they have consistent nullability.
|
||||||
|
|
||||||
|
Other, slightly more complex example, same conclusion:
|
||||||
|
``` c#
|
||||||
|
interface IBar : IFoo<string?> {}
|
||||||
|
static void F<T, U>(T t) where T : IFoo<U>, IBar where U : class{ }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Allow nullable reference types and interface types as constraints
|
||||||
|
|
||||||
|
`where T : Person?, IDisposable?`
|
||||||
|
|
||||||
|
Yes
|
||||||
|
|
||||||
|
# Allow `class?` constraint
|
||||||
|
|
||||||
|
Yes
|
||||||
|
|
||||||
|
How do we express in metadata?
|
||||||
|
|
||||||
|
Let's put top-level nullability as an attribute on the type parameter itself, rather than on the constraints. We can noodle more on this later.
|
||||||
|
|
||||||
|
# Allow `object` constraint
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
static void F<T>(T t) where T : object { }
|
||||||
|
F(maybeNull); // warning: constraint violated
|
||||||
|
```
|
||||||
|
|
||||||
|
## Need to talk about relationship to value types soon
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
int? i = null;
|
||||||
|
object o = i;
|
||||||
|
o.ToString();
|
||||||
|
```
|
||||||
|
|
||||||
|
# Allow `object?` explicitly?
|
||||||
|
|
||||||
|
It's the "most general" constraint that is implied by unconstrained generics. Today we disallow `object` because of that.
|
||||||
|
|
||||||
|
We don't know that this is a terribly useful restriction today, but we'll keep doing it (now with `object?`) unless we get evidence to the contrary.
|
||||||
|
|
||||||
|
|
||||||
|
# Warn on dereference of T when T is unconstrained?
|
||||||
|
|
||||||
|
Yes, if a type parameter `T` can be instantiated with a nullable reference type, then we should track null state and warn on unguarded dereference.
|
||||||
|
|
||||||
|
The warning when occurring on unconstrained generics might suggest using the `object` constraint.
|
||||||
|
|
||||||
|
|
||||||
|
# Warn on assignment to `object` of unconstrained `T`?
|
||||||
|
|
||||||
|
Yes. This can be a `W` (cosmetic) warning when the `object` variable is a local.
|
||||||
|
|
||||||
|
|
||||||
|
# default(T) with unconstrained T
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
T M<T>()
|
||||||
|
{
|
||||||
|
T t = default(T); // W warning
|
||||||
|
return default(T); // safety warning
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is something completely safe. I don't want it to warn!:
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
T M<T>()
|
||||||
|
{
|
||||||
|
var t = default(T);
|
||||||
|
if (something) t = somethingelse;
|
||||||
|
if (t != null) WriteLine(t.ToString());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This could be an argument for `T?` that is not about special methods. Or it is an argument against having the W warnings at all.
|
||||||
|
|
||||||
|
Let's keep this example around and revisit. But for now, let's consider `default(T) to be potentially null, and therefore warn on its unguarded use.
|
||||||
|
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
|
||||||
|
We would like to deal with methods with special null semantics, such as string.IsNullOrEmpty, TryGetValue, Assert.NotNull, Object.Equals, Object.ReferenceEquals.
|
||||||
|
|
||||||
|
We'll come back to this later.
|
195
meetings/2018/LDM-2018-04-30.md
Normal file
195
meetings/2018/LDM-2018-04-30.md
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
# C# Language Design Notes for Apr 30, 2018
|
||||||
|
|
||||||
|
|
||||||
|
# Switch expressions
|
||||||
|
|
||||||
|
Current syntax:
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
e switch
|
||||||
|
{
|
||||||
|
1 => "one",
|
||||||
|
2 => "two",
|
||||||
|
var x when x > 2 => "too many",
|
||||||
|
_ => "too few"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
No default: use `_`.
|
||||||
|
|
||||||
|
If the compiler thinks there are cases you don't handle, it'll warn. If you actually don't handle a case we throw an exception (NRE for prototype, something else in the long run).
|
||||||
|
|
||||||
|
Also discussed:
|
||||||
|
|
||||||
|
- use `match` instead of `switch`
|
||||||
|
- keyword first, followed by parens, like `switch` statement
|
||||||
|
- or without parens
|
||||||
|
- optional implicit default at the end?
|
||||||
|
|
||||||
|
The current thing is nice for error recovery.
|
||||||
|
|
||||||
|
The lack of statements inside may be a frustration, but that's orthogonal. Let's leave it for now.
|
||||||
|
|
||||||
|
## Exploration
|
||||||
|
|
||||||
|
Is there a way to generalize the syntax from the conditional (used to be ternary!) operator?
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
e ?
|
||||||
|
true => e1 ? x : y :
|
||||||
|
false => e2
|
||||||
|
|
||||||
|
e
|
||||||
|
? true => e1
|
||||||
|
: false => e2
|
||||||
|
|
||||||
|
// Interpret ? as following the tested expression, and : as a separator of test/result pairs
|
||||||
|
e
|
||||||
|
? 1 => "one"
|
||||||
|
: 2 => "two"
|
||||||
|
: var x when x > 2 => "too many"
|
||||||
|
: _ => "too few"
|
||||||
|
|
||||||
|
// Interpret ? as introducing test/result pairs, and : as introducing the fallback result
|
||||||
|
e
|
||||||
|
? 1 => "one"
|
||||||
|
? 2 => "two"
|
||||||
|
? var x when x > 2 => "too many"
|
||||||
|
: "too few"
|
||||||
|
|
||||||
|
|
||||||
|
e
|
||||||
|
? 1 -> "one"
|
||||||
|
? 2 -> "two"
|
||||||
|
? var x when x > 2 -> "too many"
|
||||||
|
: "too few"
|
||||||
|
|
||||||
|
|
||||||
|
// Compromise - terser
|
||||||
|
e ? { 1: "one", 2: "two", var x when x > 2: "two many", "too few" }
|
||||||
|
|
||||||
|
// Formatted
|
||||||
|
e ?
|
||||||
|
{
|
||||||
|
1: "one",
|
||||||
|
2: "two",
|
||||||
|
var x when x > 2: "two many",
|
||||||
|
"too few"
|
||||||
|
}
|
||||||
|
|
||||||
|
e switch
|
||||||
|
{
|
||||||
|
1: "one",
|
||||||
|
2: "two",
|
||||||
|
var x when x > 2: "two many",
|
||||||
|
"too few"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of these in context of a var
|
||||||
|
var x = e
|
||||||
|
? 1 -> "one"
|
||||||
|
: 2 -> "two"
|
||||||
|
: var x when x > 2 -> "too many"
|
||||||
|
: _ -> "too few";
|
||||||
|
|
||||||
|
var x = e ?
|
||||||
|
{
|
||||||
|
1: "one",
|
||||||
|
2: "two",
|
||||||
|
var x when x > 2: "two many",
|
||||||
|
"too few"
|
||||||
|
};
|
||||||
|
|
||||||
|
var x = e switch
|
||||||
|
{
|
||||||
|
1: "one",
|
||||||
|
2: "two",
|
||||||
|
var x when x > 2: "two many",
|
||||||
|
"too few"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Some of these as one-liners in a method call
|
||||||
|
|
||||||
|
M(x switch { null => 0, _ => x.Length }); // 1
|
||||||
|
M(x switch { null: 0, x.Length }); // 2
|
||||||
|
|
||||||
|
M(x ? null -> 0 : _ -> x.Length); // 3
|
||||||
|
M(x ? { null: 0, x.Length }); // 4
|
||||||
|
|
||||||
|
M(x ? null -> 0 : x.Length); // 5
|
||||||
|
M(x ? { null -> 0, x.Length }); // 6
|
||||||
|
```
|
||||||
|
|
||||||
|
Argument against 1:
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
strings.Select(x => x switch { null => 0, _ => x.Length }); // Lots of => with different meaning
|
||||||
|
```
|
||||||
|
|
||||||
|
Argument against `->`: Has meaning in unsafe code
|
||||||
|
|
||||||
|
Argument against `:` as used in 4: Clashes with other uses of `:`. But
|
||||||
|
|
||||||
|
Where input is an expression rather than a variable:
|
||||||
|
``` c#
|
||||||
|
M(e switch { null => 0, var x => x.Length }); // 1 - 0
|
||||||
|
M(e switch { null: 0, var x: x.Length }); // 2 - 13 - 6
|
||||||
|
|
||||||
|
M(e ? null -> 0 : var x -> x.Length); // 3 - 1
|
||||||
|
M(e ? { null: 0, var x: x.Length }); // 4 - 6 - 3
|
||||||
|
|
||||||
|
M(e ? { null -> 0, var x -> x.Length }); // 6 - 0
|
||||||
|
```
|
||||||
|
|
||||||
|
We might want to allow the last thing to be a default value without pattern, but not in the prototype.
|
||||||
|
|
||||||
|
So the prototype will have version 2.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Conditional operators and switch expressions on bool are semantically equivalent.
|
||||||
|
|
||||||
|
The fact that you know the number of colons today means you can have fewer parentheses than you would get away with here.
|
||||||
|
|
||||||
|
The `=>` is probably not right in a `?:` style syntax. It would have to be something else that more clearly signals pattern/result pairs.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Property pattern
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
if (e is { Name: "Mads", Employer: { ID: string id } }) { WriteLine(id); } // 1 - Current
|
||||||
|
if (e is { Name = "Mads", Employer = { ID = string id } }) { WriteLine(id); } // 2
|
||||||
|
if (e is { Name == "Mads", Employer == { ID == string id } }) { WriteLine(id); } // 3
|
||||||
|
if (e is { Name is "Mads", Employer is { ID is string id } }) { WriteLine(id); } // 4
|
||||||
|
```
|
||||||
|
|
||||||
|
1 is what we have implemented, but clashes a little with what we just decided for switch expressions.
|
||||||
|
2 mirrors object initializers the most
|
||||||
|
3 implies equality, but clashes in meaning with `==` elsewhere
|
||||||
|
4 emphasizes `is` as a means for applying patterns
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
var result = person switch
|
||||||
|
{
|
||||||
|
{ Name: "Mads", Employer: { ID: string id } }: id,
|
||||||
|
(_, id: var pid){ Name: "Matt" }: pid,
|
||||||
|
_: null
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
There's a clash but maybe it doesn't feel too bad. The second pattern with three different meanings of `:` is certainly not common.
|
||||||
|
|
||||||
|
``` c#
|
||||||
|
var result = person switch
|
||||||
|
{
|
||||||
|
{ Name is "Mads", Employer is { ID is string id } }: id,
|
||||||
|
(_, var pid){ Name is "Matt" }: pid,
|
||||||
|
_: null
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Decision: stay with `:` for prototype, remains open question though!
|
Loading…
Reference in a new issue