Add design notes

This commit is contained in:
Mads Torgersen 2017-07-05 12:43:48 -07:00
parent 739cf48434
commit e63f863440
3 changed files with 242 additions and 8 deletions

View file

@ -0,0 +1,118 @@
# C# Language Design Notes for Jun 27, 2017
*Quotes of the day:*
> "Let's support most operators, but not equal and not-equal"
> "We'll rename the next version to C# l'eight"
## Agenda
1. User-defined operators in interfaces
2. return/break/continue as expressions
# User-defined operators in interfaces
## Conversion operators
Let's not allow conversion operators in interfaces, since they are not allowed *on* interfaces today, even when defined elsewhere.
## Equality operators
There'd be no way to override `Equals` and `GetHashCode`, so you couldn't do `==` and `!=` in a recommended way. That's an argument for disallowing. What if we *did* allow `Equals` and `GetHashCode` to be overridden in interfaces? That would be a major undertaking, for what seems like modest gains.
However, if we allow `<=` and `>=` you can get the effect of `==` anyway, through `&`. This may or may not be an argument for disallowing *more* - or maybe all - operators.
Some options:
1. Drop operators across the board
2. Let's not allow `==` and `!=` and see what feedback we get
3. Let's allow `==` and `!=` and see what people use it for
### Conclusion
Let's stick with option 2 for now. We'll see from prototype use whether this is an intolerable limitation, but we believe it is a decent place to land.
## Is the remainder worthwhile?
Conversion and equality are the most useful operators, in that they apply to any domain, whereas most of the other operators are for more specific, often math-related, purposes. If we don't allow them, is it worth allowing the rest?
### Conclusion
Let's assume that it is. Those specific domains are likely still important to express in interfaces.
## Lookup rules for operators
We only even look for operator implementations in interfaces if one of the operands has a type that is an interface or a type parameter with a non-empty effective base interface list.
### Unary operators
The applicable operators from classes/structs shadow those in interfaces. This matters for constrained type parameters: the effective base class can shadow operators from effective base interfaces.
### Binary operators
Here there could be operators declared in interfaces that are more specific than ones implemented in classes.
We should look at operators from classes first, in order to avoid breaking changes. Only if there are no applicable user-defined operators from classes will we look in interfaces. If there aren't any there either, we go to built-ins.
We hypothesize that there's no case (outside of equality and conversion) where user-defined operators in interfaces shadow predefined operators, but we may be wrong. So that needs to be investigated a bit.
## Shadowing within interfaces
If we find an applicable candidate in an interface, that candidate shadows all applicable operators in base interfaces: we stop looking. This is a bit more complicated than in classes, where it's linear, but we can express it right.
# return/break/continue as expressions
It's been proposed to allow `return`, `break` and `continue` as expressions, the same way we now allow `throw` expressions.
There's a bigger discussion of whether we should seek to make C# more expression-oriented, adding a "value" (or at least a classification) for each statement kind, and allowing all or most statements in an expression context. However, this specific proposal does not need to be part of that discussion: we already have `throw` expressions, and the potential value of adding these other *jump-statement*s can be evaluated in that narrower context.
There are clearly scenarios that can be expressed more concisely with this feature, but the really compelling ones involve the use of `??` on nullable value types (and presumably on nullable reference types in the future). It provides an elegant way to get at the underlying type without having to more explicitly unpack it:
``` c#
int z = x + y ?? return null; // where x and y are of type int?
//as opposed to something like
if (x is null || y is null) return null;
int z = (x + y).Value; // explicit unpacking
// or
int? nz = x + y;
if (nz is null) return null;
int z = nz ?? 0; // dummy unpacking
// or using patterns
if (!(x + y is int z)) return null;
```
The scenarios involving the ternary conditional operator seem less compelling, as they are more easily replaced by a "bouncer" if-statement:
``` c#
accumulator += (y != 0) ? x/y : continue;
// as opposed to
if (y == 0) continue;
accumulator += x/y;
```
Yes, it's more concise, but is it more clear?
`break` and `continue` differ from `throw` in that the latter just gets out of there, whereas these affect definite assignment more subtly and locally.
`return` feels more like `throw`, in that it leaves the whole call context. However, unlike `throw` it is normal control flow.
There's something unfortunate about calls like these:
``` c#
M(Foo1(), Foo2() ?? break, int.TryParse(s, out int x) ? x * 10 : continue);
```
You start computing arguments and then you decide not to do the call. Wouldn't it have been better (at least more efficient) to decide before starting to compute the arguments? Maybe, but that also leads to more ceremony and less locality of the code.
## Conclusion
We are somewhat compelled by the `??` examples. They really do read better than using patterns for nullable things.
Let's have someone champion it, and keep it on the radar.

View file

@ -0,0 +1,86 @@
# C# Language Design Notes for Jun 28, 2017
## Agenda
1. Tuple name round-tripping between C# 6.0 and C# 7.0
2. Deconstruction without `ValueTuple`
3. Non-trailing named arguments
# Tuple name round-tripping between C# 6.0 and C# 7.0
There are a few unintended breaking changes with tuples that came out through real use cases.
Say there's an interface `IUtil`:
``` c#
public interface IUtil
{
void M((int a, int b) x);
}
```
I want to implement this interface with code that works both in C# 6.0, but it turns out I can't! In C# 7.0 we force you to use the same tuple element names when you implement an interface member, but of course you can't do that (and aren't being forced to) in C# 6.0, where your only option is to use `ValueTuple<...>` directly.
Maybe using the `TupleName` attribute directly in C# 6.0 would help? It is hard to use, but it turns out VS 2015 happily inserts it for you to match the attribute found on the interface member. Little does it know that this attribute is about to become compiler-reserved in C# 7.0, and if you try to compile the C# 6.0 compliant implementation with the attribute in C# 7.0, it still fails, now complaining that you cannot use that attribute in source code!
There are no less than two breaking changes here, so we'll address them one at a time:
## Issue 1: C# 6.0 has to implement it without names, but C# 7.0 requires names
This will not do. We need to relax the rules in C# 7.0 to allow for a C# 6.0 implementation to continue compiling.
A couple of reasonable options:
- a tuple type *without* names can always be given when one *with* names is required (but one with different names cannot)
- you are permitted to omit required names only if you use the `ValueTuple<...>` syntax, not with tuple syntax
The latter is attractive in that it pushes things to a corner, but it does break the fact that `ValueTuple<int, string>` is exactly equivalent to `(int, string)` in all scenarios.
It also has a bit of a problem in establishing whether the tuple syntax *was* used:
``` c#
itf I {
void M((int a, int b) x);
}
cls Base {
public void M((int, int) y);
}
// Separate assembly
cls Derived, I {} // how does it know which syntax Base used?
```
### Conclusion
Let's go with a strict version of the former option: If a member has *any* tuple element names in it, and is required to match another member (by overriding or implementing), then *all* required tuple element names have to be matched. Only a member declaration with *no* tuple element names in it can override or implement a member in which tuple element names are found, without matching those names.
## Issue 2: VS 2015 Implement Interface will explicitly spit the attributes, but C# 7.0 doesn't allow them
This would have been a problem with `dynamic` too, but we never really heard of it. Maybe there was less usage, more of a gap between framework version, or the impact was less because we didn't have round tripping back then. For whatever reason, this never seemed to be a problem before.
Options:
- Keep the attribute usage an error
- Allow it but ignore it
- possibly with a warning when tuple syntax is used
### Conclusion
Keep it an error, willing to take it up again if necessary. We are unsure that this is really a problem.
For the future we want to get better at marking attributes as compiler-only in a well-known way, so that they won't be put in source code even by older compilers. One option is to do that using `Obsolete`; then no compiler will allow them in source, but will be happy to detect or emit them in metadata.
# Deconstruction without ValueTuple
Deconstructing assignments are expressions, and their value is a tuple. This is natural from a language perspective, even though the result value of assignments is rarely used, and we expect this goes for deconstructing assignments as well.
Unfortunately, even in the common case where the assignment occurs as an expression statement (so the resulting tuple is discarded), the compiler currently still requires the associated `ValueTuple<...>` type to be present. That means you have the hassle of importing `ValueTuple` even when you never use a tuple - if you happen to make use of deconstruction.
## Conclusion
We'll rewrite the compiler to be more lazy about `ValueTuple<...>`, requiring it only when it actually needs it for code gen.
# Non-trailing named arguments
It looks like this is making it into C# 7.2. The basic idea is that we allow named arguments *that are in their place, positionally* to be followed by positional arguments. This is mainly for the self-documenting convenience of calling out the meaning of a positional argument where it is non-obvious in the calling context. However, it can be useful for disambiguation of overloads also.

View file

@ -2,20 +2,20 @@
Overview of meetings and agendas for 2017
## Jan 10, 2017
## Jan 10, 2017
[C# Language Design Notes for Jan 10, 2017](LDM-2017-01-10.md)
1. Discriminated unions via "closed" types
## Jan 11, 2017
## Jan 11, 2017
[C# Language Design Notes for Jan 11, 2017](LDM-2017-01-11.md)
1. Language aspects of [compiler intrinsics](https://github.com/dotnet/roslyn/issues/11475)
## Jan 17, 2017
## Jan 17, 2017
[C# Language Design Notes for Jan 17, 2017](LDM-2017-01-17.md)
1. Constant pattern semantics: which equality exactly?
@ -23,13 +23,12 @@ Overview of meetings and agendas for 2017
## Jan 18, 2017
[C# Language Design Notes for Jan 18, 2017](LDM-2017-01-18.md)
1. Async streams (visit from Oren Novotny)
## Feb 21, 2017
## Feb 21, 2017
[C# Language Design Notes for Feb 21, 2017](LDM-2017-02-21.md)
We triaged some of the [championed features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22), to give them a tentative milestone and ensure they had a champion.
@ -49,25 +48,27 @@ As part of this we revisited potential 7.1 features and pushed several out.
11. Declarations in embedded statements *(No)*
12. Field targeted attributes on auto-properties *(C# 7.1)*
## Feb 22, 2017
## Feb 22, 2017
[C# Language Design Notes for Feb 22, 2017](LDM-2017-02-22.md)
We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://github.com/dotnet/csharplang/issues/38).
## Feb 28, 2017
## Feb 28, 2017
[C# Language Design Notes for Feb 28, 2017](LDM-2017-02-28.md)
1. Conditional operator over refs (*Yes, but no decision on syntax*)
2. Async Main (*Allow Task-returning Main methods*)
## Mar 1, 2017
[C# Language Design Notes for Mar 1, 2017](LDM-2017-03-01.md)
1. Shapes and extensions (*exploration*)
2. Conditional refs (*original design adopted*)
## Mar 7, 2017
[C# Language Design Notes for Mar 7, 2017](LDM-2017-03-07.md)
@ -77,6 +78,7 @@ We continued to flesh out the designs for features currently considered for C# 7
2. Field target on auto-properties (*yes*)
3. private protected (*yes, if things work as expected*)
## Mar 8, 2017
[C# Language Design Notes for Mar 8, 2017](LDM-2017-03-08.md)
@ -90,6 +92,7 @@ We looked at default interface member implementations.
6. Binary compatibility
7. Other semantic challenges
## Mar 15, 2017
[C# Language Design Notes for Mar 8, 2017](LDM-2017-03-15.md)
@ -104,6 +107,7 @@ Triage of championed features
7. Deconstruction in lambda parameters
8. Private protected
## Mar 21, 2017
[C# Language Design Notes for Mar 21, 2017](LDM-2017-03-21.md)
@ -123,6 +127,7 @@ Discussion of default interface member implementations, based on [this guided to
12. Accessibility levels
13. Existing programs
## Mar 28, 2017
[C# Language Design Notes for Mar 28, 2017](LDM-2017-03-28.md)
@ -131,13 +136,14 @@ Design some remaining 7.1 features
1. Fix pattern matching restriction with generics
2. Better best common type
## Mar 29, 2017
## Mar 29, 2017
[C# Language Design Notes for Mar 29, 2017](LDM-2017-03-29.md)
1. Nullable scenarios
2. `Span<T>` safety
## Apr 5, 2017
[C# Language Design Notes for Apr 5, 2017](LDM-2017-04-05.md)
@ -145,11 +151,13 @@ Design some remaining 7.1 features
2. Inferred tuple element names
3. Tuple element names in generic constraints
## Apr 11, 2017
[C# Language Design Notes for Apr 11, 2017](LDM-2017-04-11.md)
1. Runtime behavior of ambiguous default implementation
## Apr 18, 2017
[C# Language Design Notes for Apr 18, 2017](LDM-2017-04-18.md)
@ -162,6 +170,7 @@ Design some remaining 7.1 features
7. Not quite implementing a member
8. asynchronous `Main`
## Apr 19, 2017
[C# Language Design Notes for Apr 19, 2017](LDM-2017-04-19.md)
@ -170,6 +179,7 @@ Design some remaining 7.1 features
3. Structs and default implementations
4. Base invocation
## May 16, 2017
[C# Language Design Notes for May 16, 2017](LDM-2017-05-16.md)
@ -178,6 +188,7 @@ Design some remaining 7.1 features
3. GitHub procedure around new design notes and proposals
4. Triage of championed features
## May 17, 2017
[C# Language Design Notes for May 17, 2017](LDM-2017-05-17.md)
@ -190,11 +201,13 @@ More questions about default interface member implementations
5. Does an override introduce a member?
6. Parameter names
## May 26, 2017
[C# Language Design Notes for May 26, 2017](LDM-2017-05-26.md)
1. Native ints
## May 31, 2017
[C# Language Design Notes for May 31, 2017](LDM-2017-05-31.md)
@ -203,12 +216,14 @@ More questions about default interface member implementations
3. Extension methods with ref this and generics
4. Default in operators
## Jun 13, 2017
[C# Language Design Notes for Jun 13, 2017](LDM-2017-06-13.md)
1. Native-size ints
2. Native-size floats
## Jun 14, 2017
[C# Language Design Notes for Jun 14, 2017](LDM-2017-06-14.md)
@ -219,3 +234,18 @@ Several issues related to default implementations of interface members
3. Member declaration syntax revisited
4. Base calls
## Jun 27, 2017
[C# Language Design Notes for Jun 27, 2017](LDM-2017-06-27.md)
1. User-defined operators in interfaces
2. return/break/continue as expressions
## Jun 28, 2017
[C# Language Design Notes for Jun 28, 2017](LDM-2017-06-28.md)
1. Tuple name round-tripping between C# 6.0 and C# 7.0
2. Deconstruction without `ValueTuple`
3. Non-trailing named arguments