Clean up Jan design notes

This commit is contained in:
Mads Torgersen 2018-03-20 17:38:03 -07:00
parent 74feb87849
commit 9cb067bb79
6 changed files with 241 additions and 226 deletions

View file

@ -1,6 +1,7 @@
# C# Language Design Notes for Jan 3, 2018
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
Quote of the Day: "What about `CallerPoliticalAffiliationAttribute`?"
## Agenda
@ -8,115 +9,110 @@
2. Scoping of expression variables in field initializer
3. Scoping of expression variables in query clauses
4. Caller argument expression attribute
5. New constraints
5. Other caller attributes
6. New constraints
# Scoping of expression variables in constructor initializer
Should an expression variable `x` introduced in a `this` or `base` initializer be available in the constructor body?
``` c#
C(int i) : base(out var x) { ... x ... }
```
It feels natural and occasionally useful that `x` would be in scope in the body.
Any reason not to allow it? Not at the language level. There's a design question as to how it's represented in `IOperation`, but that's not a language question.
## Conclusion
Whole body scope.
Let `x` be in scope in the whole constructor, including the body.
# Scoping of expression variables in field initializer
``` c#
void M(int x)
Where should an expression variable `x` introduced in a field initializer be in scope?
``` c#
class C
{
int y = N(out var z);
{
z
}
}
class C(int x)
{
int y = N(out var z);
{
z
}
int w = z;
}
int i = M(out int x)
.N(x), // here?
y = M(x); // here?
int j = x; // here?
int X => x; // here?
}
```
Options:
A number of options:
1. In scope only in the expression
2. In scope in the whole field declaration
3. In scope throughout the class body (in other initializers, methods, etc)
1. Only in the same initializer expression
2. In the whole field declaration, including initializers for subsequent fields
3. In all subsequent initializers in the class declaration
4. In the whole class body, including function members (so it would effectively become a private field if necessary)
For 3, can it be used before declared? (Locals cannot) Is it visible in member declarations, like method bodies? (Sure seems in scope). What about across partial classes?
There's something tempting about 4, especially considering the relatively loose scope rules we chose for expression variables inside statement lists.
``` c#
class Point(int x, int y)
void M()
{
int length = (sq = x*x + y*y; Sqrt(sq));
int i = N(out int x);
int M() => x; // in scope here
}
class C
{
int i = N(out int x);
int M() => x; // why not here?
}
```
Instead you can put it in the body of the constructor.
Also, if we ever do primary constructors, and want to allow statements interspersed with member declarations, we'll probably wish we did 4.
1 or 2 pretty much rule out a future where primary constructor statements are interspersed between member declarations.
However, there are also difficult questions with 4: can it be used before it is declared? Is it visible across partial classes? These are all questions that could be answered, but we are also a little uneasy making it so easy to "accidentally" declare an extra private field, in case some function members close over it.
That latter concern could be alleviated by reducing to option 3, but now the scope is weird, and most of the usefulness is gone. May as well reduce to 2 or 1 then.
## Conclusion
We'll go with 1.
We'll go with option 1. If you want to use expression variables to carry information between initializers, you'll instead have to do do your initialization in the constructor body.
Script will have to stay consistent, so declaration variables are lifted to fields, and are visible in subsequent statements.
The script dialect will have to stay consistent, so declaration variables must be lifted to fields, and be visible in subsequent statements.
``` c#
int x, y;
Point { (x, y) = M(); }
(int x, int y) = M();
```
``` c#
int l;
Length { var (x, y) = M(); l = Sqrt(x*x + y*y); }
var (x, y) = M();
int l = Sqrt(x*x + y*y);
```
# Scoping of expression variables in query clauses
What should be the scope of an expression variable `x` that occurs in a query clause?
It seems desirable at first that the variables would carry over from clause to clause:
``` c#
from x in e
where M(x, out var y)
select y
from x in e
let (m, y) = (M(x, out var y), y)
select y;
```
To fully do what the users expect (example 1) we would need to do much more semantic analysis of the query, before we lower. It is very out of touch with the syntactic flavor of queries today.
However, to do this we would need to perform much more semantic analysis of the query than we do today, before we lower. It feels out of touch with the syntactic flavor of queries today.
This does not seem worthwhile, though it probably makes sense.
Instead, a more reasonable approach may be to make it easier to carry multiple range variables forward from a `let` (and maybe `from`) clause, using deconstruction syntax:
``` c#
from x in e
let (m, z) = (M(x, out var y), y)
select z;
```
## Conclusion
So we should allow expression variables in queries, but keep them scoped to the individual query clauses. In other words, they aren't range variables. They are normal variables scoped by the lambdas that we translate into.
Separately it will be useful in `let` (and maybe `from`) clauses.
We should allow expression variables in queries, but keep them scoped to the individual query clauses. In other words, they aren't range variables. They are normal variables scoped by the lambdas that we translate into.
# Caller argument expression attribute
# Caller expression attribute
[csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287)
The proposal would make it a bit hard for existing APIs: they have existing APIs that would match better than the new ones.
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
There are solutions to that for API owners:
@ -130,18 +126,18 @@ In summary, folks have a decent slate of options.
Fine to accept this one.
# Other caller attributes
We are not comfortable with those just yet. We'd need to work on the details. For now we are suspicious of having a record of a lot of different information, and of the number of proposed ones in #87.
QOTD: "What about `CallerPoliticalAffiliation`?".
We are not comfortable with those just yet. We'd need to work on the details. For now we are suspicious of having a record of a lot of different information, and of the number of proposed ones in [csharplang/issues/87](https://github.com/dotnet/csharplang/issues/87).
# Constraints
Propose to allow the `Enum` and `Delegate` types as constraints. No special meaning, just allow them. That's a starting point. Adding syntax `enum` and `delegate` could be discussed later.
[csharplang/issues/103](https://github.com/dotnet/csharplang/issues/103) and [csharplang/issues/104](https://github.com/dotnet/csharplang/issues/104) propose to allow the `Enum` and `Delegate` types as constraints. No special meaning, just stop disallowing them. That's a starting point. Adding keywords `enum` and `delegate` could be discussed later.
Propose a contextual keyword `unmanaged`; represent it by putting a `ModReq` in the constraint of the parameter. It implies the `struct` constraint.
[csharplang/issues/187](https://github.com/dotnet/csharplang/issues/187) proposes a contextual keyword `unmanaged`; we would represent it by putting a `ModReq` in the constraint of the parameter. It implies the `struct` constraint.
Reference assemblies are a problem: some tools produce reference assemblies that remove private fields from structs, which already leads to semantic problems in the compiler (definite assignment, allowing pointers to them), and now would lead to more. That's fundamentally not a language problem though.
Reference assemblies are a problem: some tools produce reference assemblies that remove private fields from structs, which already leads to semantic problems in the compiler (definite assignment, allowing pointers to them), and now would lead to more.

View file

@ -1,22 +1,15 @@
# C# Language Design Notes for Jan 10, 2018
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
# Range
1. Ranges and endpoint types
A proposed position.
## Endpoint types
# Ranges and endpoint types
Wouldn't it be nice to have a range type that work on any comparable? Possibly, but we're not eager to solve this right now.
We need to believe that our future selves can extend the language with some form of target typing, for instance. There's a burden on them to be able to do that without a compat break (silent semantic change), e.g.: There's a type in the future that has conversions so that it would work one way in 7.3 and a different way in the future.
We need to believe that our future selves can extend the language with some form of target typing, for instance. There's a burden on them (us) to be able to do that without a compat break (silent semantic change), e.g.: There's a type in the future that has conversions so that it would work one way in 7.3 and a different way in the future.
We want `..(int, int)` and `..(long, long)`. We want to treat those as usual operators, so they have to allow user defined conversions of the end points. We could consider blocking off normal conversions of the operands, but that's extremely distasteful.
We probably want `..(int, int)` and `..(long, long)`. We want to treat those as usual operators, so they have to allow user defined conversions of the end points. We could consider blocking off normal conversions of the operands, but that's extremely distasteful.
So decision: There's `Range` (for ints) and `LongRange`. The `..` operators are built-in. There's an implicit conversion one way, and an explicit the other, also both built-in.
## Exclusive
Exclusive isn't really the same for int ranges (where it is notational but has same).
We probably want inclusive, but we may also want exclusive. Next time.
So first tentative decision: There's `Range` (for ints) and `LongRange`. The `..` operators are built-in. There's an implicit conversion one way, and an explicit the other, also both built-in.

View file

@ -1,66 +1,72 @@
# C# Language Design Notes for Jan 18, 2018
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
We discussed the range operator in C# and the underlying types for it.
1. Scope of the feature
2. Range types
3. Language questions
3. Type name
4. Open-ended ranges
5. Empty ranges
6. Enumerability
7. Language questions
# Scope of the feature
The scenario we are eager to address right now relates to indexing and slicing, where the elements of the range are contiguous indices into some data structure.
The scenario we are eager to address right now relates to indexing and slicing, where the elements of the range are contiguous integral indices into some data structure.
We want to design the language feature and API so that we address this scenario in the best possible way, without cutting off future support for range syntax in other scenarios, such as math, inclusion tests, etc., which require more generality.
# Range types
We've talked about having a couple of types, say `Range` and `LongRange` representing general-purposes ranges over ints and longs, respectively.
We've talked about having a couple of types, say `Range` and `LongRange` representing general-purpose ranges over ints and longs, respectively.
However a `Range` type that ranges over all ints, might have a length that is greater than what an `int` can contain! We could represent the `Length` property with a `uint`, but then it doesn't interoperate well with all the `int`-based types and logic in the language and framework.
However a `Range` type that ranges over all ints might have a length that is greater than what an `int` can contain! We could represent the `Length` property with a `uint`, but then it doesn't interoperate well with all the `int`-based types and logic in the language and framework.
And when we think about the core scenario, this pain would all be for naught: for indexing purposes, indices are always non-negative `int`s, and ranges of them would therefore never be longer than `int.MaxValue`.
It therefore seems that we should build a `Range` type specifically for indexing purposes, and have language support *only* for that, but in a way that we can generalize later. Specifically we can support conversion of `x..y` expressions only to `Range` for now, but open up for user-defined `..` operators later.
## Name
## Conclusion
Design just an indexing-oriented `Range` type for now.
# Type name
Should the indexing-specific range type be called `IndexRange` or just `Range`? On the one hand it is a bit dangerous to use the good name for a special-purpose range type. On the other it is going to be *very* common, probably much more so than any other future range type. It is possible that we will someday have a `Range<T>` type that represents a more abstract mathematical notion of ranges. It will then be a bit odd for `Range` and `Range<T>` to be only loosely related to each other.
### Conclusion
## Conclusion
We can live with that risk. We think it is ok for this type to be called `Range`.
## Open-ended ranges
# Open-ended ranges
Should we allow open-ended range values, as represented by missing endpoints in the language syntax: `x..`, `..y` and `..`? It seems a significant loss of expressiveness if we don't. Scenarios for open-at-one-end are common, and scenarios for open-at-both-ends include multi-dimensional slicing, where we want to keep "all of" a given dimension: `tensor[.., ..1, ..]`.
Should we allow open-ended range values, as represented by missing endpoints in the language syntax: `x..`, `..y` and `..`? It seems a significant loss of expressiveness if we don't. Scenarios for open-at-one-end are common, and scenarios for open-at-both-ends include multi-dimensional slicing, where we want a concise way to keep "all of" a given dimension: `tensor[.., ..1, ..]`.
Open-ended ranges are semantically different from just having `0` and `int.MaxValue` at the ends, because indexing/slicing APIs will require range arguments to be "within range" of the target data structure. Where `m..int.MaxValue` would therefore often yield an exception, an open-ended `n..` would just mean "from `n` to the end, wherever that is", and always be legal. We can't just represent open-ended ranges as closed ones with the target data structure's min and max index substituted in, because range values can exist independently of a given target data structure.
Open-ended ranges are semantically different from just having `0` and `int.MaxValue` at the ends, because indexing/slicing APIs will require range arguments to be "within range" of the target data structure. Where `m..int.MaxValue` would therefore often yield an exception, an open-ended `n..` would just mean "from `n` to the end, wherever that is", and be legal as long as `n` is within range. Also, we can't just represent open-ended ranges as closed ones with the target data structure's min and max index substituted in, because range values can exist independently of a given target data structure.
Internally, the `Range` type can e.g. use negative ints to represent open ends, so as not to waste space. This representation would not be visible from the outside though.
Can we use nullable int in the constructor, to represent endpoints that may not be there? Then `null` means open in that end. We could have an `(int, int)` constructor overload for efficiency. Proposal for this to be written offline.
### Conclusion
## Conclusion
Yes, support open-ended ranges.
## Empty ranges
# Empty ranges
Does the `Range` type represent empty ranges? In that case, does the start point matter or not, or are all empty ranges the same? They might behave differently when you slice, depending of whether the start point is in range in the target data structure. (If not, they will throw).
Can the `Range` type represent empty ranges? In that case, does the start point matter or not, or are all empty ranges the same? They might behave differently when you slice, depending of whether the start point is in range in the target data structure. (If not, they will throw).
Can you index an empty array with an empty range? Yes. As long as it's in range; otherwise it throws. This is consistent with slices, spans and strings.
### Conclusion
## Conclusion
It's important to support empty ranges. The start point should matter even for empty ranges, in that indexing/slicing would throw if the start point of an empty range wasn't in range of the target data structure.
## Enumerability
# Enumerability
`Range` should implement `IEnumerable<T>`, and needs to support the enumerable pattern of having a struct enumerator type. This is extra bulk in the type, but that's just how we do things.
@ -69,7 +75,7 @@ Enumeration on open ranges should probably throw.
# Language questions
Given this there are a number of questions on the language support, which we gathered here, but did not yet answer.
Given this there are a couple of questions on the language support, which we gathered here, but did not yet answer.
## Should range expressions have `Range` as a natural type, or should they just convert to it?
@ -79,11 +85,11 @@ However, this locks in `Range` as the "primary" range type for the language fore
Also, the `foreach` support based on this natural type wouldn't be very expressive: It wouldn't allow starting at negative numbers, or going backwards.
## Other syntaxes
## Other notations
We're fairly certain we want an inclusive `start..end` syntax. Presumably, open-ended ranges are expressed by leaving out one or the other endpoint.
But should there be other syntaxes? In particular, it seems that a syntax for giving an index and a *count* would handle a common scenario; possibly *more* common. All current APIs are like that, and many (most?) use cases would take a given number of elements at a time.
But should there be other notations? In particular, it seems that a syntax for giving an index and a *count* would handle a common scenario; possibly *more* common. All current APIs are like that, and many (most?) use cases would take a given number of elements at a time.
It's not obvious what such a syntax should be, though. `start:count` and `start::count` both would have some ambiguity with existing syntax.

View file

@ -1,116 +1,99 @@
# C# Language Design Notes for Jan 22, 2018
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
# Range
We discussed the range operator in C# and the underlying types for it.
1. Inclusive or exclusive?
2. Natural type of range expressions
3. Start/length notation
# Inclusive or exclusive?
``` c#
var numbers = ints[from..to];
```
Should this mean inclusive?
Should this mean inclusive or exclusive of the element at `to`?
Not many languages have *only* inclusive ranges. Apart from F#, they tend to either have exclusive ranges or both (at the upper end; the lower is always inclusive).
Not many languages have *only* inclusive ranges. Apart from F#, they tend to either have exclusive ranges or have notations for both (at the upper end; the lower is always inclusive).
Python has exclusive, though a lot of people seem to be confused about it.
Python uses exclusive ranges. A lot of people seem to be confused about it; it doesn't immediately gel with their intuition. The obvious advantage of course is that the collection's `Length` is directly allowed at the end:
```
var s = a[0..a.Length];
```
Do we read it out loud as "zero *to* a dot length"? If so, then the intuition really does suggest inclusive.
In foreach loops over ranges, if you use constant end points, again intuition seems to suggest inclusiveness:
```
foreach (var x in 1..100) { ... }
foreach (var x in 0..100) { ... }
```
You don't use constants in practice, though, except as a zero lower bound. In F# it is often a bit of a pain for do a for loop for array iteration, for instance.
You don't often use constants in practice, though, except as a zero lower bound. In fact, in F# it is often a bit of a pain to write a `for` loop for array iteration, for instance, because you need to subtract one at the end to avoid overrunning.
Exclusive is "in the middle" between inclusive and start/length.
Let's look at these four scenarios:
1. Create a range, `incl`, that goes from `start` *through* `end`
2. Create a range, `excl`, that goes from `start` *up to* `end` (but not including)
3. Create a range, `rel`, that goes from `start` and has `length` elements
4. Create an empty range, `emp`, that starts at 0.
This is what they will look like, given exclusive semantics, inclusive semantics, and a notation for `start:length` ranges:
``` c#
// The three scenarios with exclusive
// Exclusive x..y
Range incl = start..end+1;
Range excl = start..end;
Range rel = start..start+length;
Range emp = 0..0;
// The three scenarios with inclusive
// Inclusive x..y
Range incl = start..end;
Range excl = start..end-1;
Range rel = start..start+length-1;
Range emp = 0..-1;
// The three scenarios with start/length
// Relative x:l
Range incl = start:end-start+1;
Range excl = start:end-start;
Range rel = start:length;
Range emp = 0:0;
```
[1/22/2018 10:27 AM] Miguel de Icaza:
From the TensorFlow (504), NUmPy (1,063) - both numeric heavy- and Pythons core libraries (2,094). Only one use of “end-range plus one”, ie, used in exlusive way
[1/22/2018 10:31 AM] Miguel de Icaza:
More data for you: in F# compiler source code, out of 1,034 uses of ranges, 51 use the idiom limit-1
[1/22/2018 10:32 AM] Miguel de Icaza:
Fixed: 64 out of 1,034
[1/22/2018 10:39 AM] Miguel de Icaza:
Go source code, out of 5865, 11 need “+1”
One idea is that when we start talking about sets of numbers (not enumerations) we should just have a different syntax. That's in pattern matching/containment checking, etc.
The one that seems to have the least amount of computational gymnastics across the scenarios is the exclusive option. Also, the inclusive notation for an empty range is severely unappetizing! It is of course entirely possible that we have more than one syntax.
One idea is that when we start talking about scenarios outside of indexing, we simply have a different syntax; maybe one that is built in to language constructs for containment and iteration:
``` c#
x is in 3 to 5;
x is in 3--5;
x is in 3 !.. 5
bool b = x is in 3 to 5;
foreach (var x in 0 to 100) { ... }
```
Tensors are really just multidimensional arrays, so similar arguments apply.
## Conclusion
Let us go with `..` means exclusive. Since we've chosen to focus on the indexing/slicing scenario, this seems the right thing to do:
* It allows `a.Length` as an endpoint without adding/subtracting 1.
* It lets the end of one range be the beginning of the next without overlap
* It avoids ugly empty ranges of the form `x..x-1`
Let us go with `..` means exclusive.
## Open ended
Is represented as such in the type. Syntax:
``` c#
Range openL = ..5;
Range openR = 3..;
Range openLR = ..;
```
``` c#
r1 = ..12;
r2 = 3..;
var a = new int[5];
a[r2]; // fine
a[r1 * r2]; // fail;
Range x = new Range(r1.Start, r2.End);
Range x = r1.EndAt(r2);
```
Would like not to write conditional logic to manipulate and combine ranges.
## Natural type
# Natural type of range expressions
If we allow `Range` to be the natural type of range expressions, the following will work:
``` c#
var r = 4..6; // infer Range
```
If we do this, `Range` will be forever tied to it as the preferential type.
On the other hand, if we do this, `Range` will be forever tied to range expressions as the preferential type.
We're good with that.
## Conclusion
We're good with that.
## Start/length
# Start/length notation
Can technically be added later, but if we want to do it we should try to do it now.
We may want an additional notation for specifying start and length. It can technically be added later, but if we want to do it we should try to do it now.

View file

@ -1,54 +1,50 @@
# C# Language Design Notes
# C# Language Design Notes for Jan 24, 2018
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
## Agenda
1. Ref reassignment
2. New constraints
3. Target typed stackalloc initializers
4. Deconstruct as ref extension method
# Ref reassignment
https://github.com/agocke/roslyn/blob/4de95445af874269e74f9b022d83c89d85ec9669/docs/features/ref-reassignment.md
Proposal [here](https://github.com/agocke/roslyn/blob/4de95445af874269e74f9b022d83c89d85ec9669/docs/features/ref-reassignment.md).
What do we gain by making it an expression form? Might be hard to understand. Should we have it only as a statement form?
C# 7.0 added ref locals, but did not allow them to be reassigned with other refs. In C# 7.3 we are looking to allow that, and have a handle on the rules that need to be in place to make it safe.
## Ref assignment expressions
The proposal adds a new ref-assignment expression of the form `r = ref v`. This makes `r` point to the storage location that `v` points to. The result of the expression is the variable designating that storage location. The variable can e.g. be evaluated or assigned to:
``` c#
M(ref (x = ref b ? ref v1 : ref v2)); // or
M(ref x = ref b ? ref v1 : ref v2);
// as opposed to
x = ref b ? ref v1 : ref v2;
M(ref x)
// Linked list walking
while ((x = ref x.Next) != null)
{
}
while ((l = ref l.Next) != null) ... // linked list walk
```
Against: might be confusing.
For: regularity. People are already aware that assignment-as-an-expression shouldn't be overused.
The linked list walk example shows why it is beneficial to have it be an expression form just like regular assignment, rather than just a statement form. Of course, just like with regular assignment, using it as an expression is easily overused; we think there is enough cultural awareness around this.
There should be a principle that expressions never start with `ref`. Therefore, `ref` in an expression is always part of an enclosing construct. This principle will help people (a little) reasoning about these expressions.
Into the future we need to maintain a principle that expressions never start with `ref`. Therefore, `ref` in front of an expression is always part of an enclosing construct. This principle will help people (a little) when reasoning about these expressions.
Linked list walk is convincing. Make them expressions.
## Lifetimes
``` c#
void M(in object o)
The compiler tracks lifetimes of variables, in order to make sure that a variable is not referenced by something that will outlast it. In C# 7.0 a ref local is simply created with the lifetime of the variable that it is initialized with.
string s = ...;
In a ref assignment expression, the compiler maintains lifetime safety by requiring that the lifetime of the right-hand side is at least as long as the lifetime of the left-hand side.
M(in s); // allowed? covariance? No. Invariant, like out and ref.
```
## Uninitialized ref locals
Lifetime of RHS must be at least as long as lifetime of LHS.
With ref reassignment it now becomes meaningful to allow ref (and ref readonly) locals to be left uninitialized at declaration. In that case what should be their lifetime? We can discuss that later.
What's the lifetime of an uninitialized ref local? Discuss later.
## Ref locals in looping constructs
Foreach and for variables allow ref in syntax. In the `for` loop, this needs reassignment.
Iteration variables declared in `foreach` and `for` loops can now be `ref`. In the case of `for` loops this only makes sense because ref reassignment is allowed.
Foreach iteration variables can never be reassigned in the body, and that is also the case for ref iteration variables: they cannot be ref reassigned.
# Constraints
# New constraints
Let's finalize the new forms of constraints.
``` c#
void M<T1, T2, T3>()
@ -62,71 +58,58 @@ void M<T1, T2, T3>()
## Unmanaged
It should be called `unmanaged`, not some other word like `blittable`. F#, the runtime and the C# spec all use the term "unmanaged".
It should be called `unmanaged`, not some other word like `blittable`. F#, the runtime and the C# spec all use the term "unmanaged" for types that you can take a pointer to.
It can't be a keyword (would be breaking) and not even a contextual keyword (in the sense that there's a syntactic context that determines it). Instead it needs to be semantically recognized.
It can't be a keyword (that would be breaking) and not even a contextual keyword (in the sense that there's a syntactic context that determines it). Instead it needs to be semantically recognized, like `var`.
Just like var, it can be escaped to make clear that you are using it as an identifier. If it is in scope, there's no way to get at the constraint meaning of it.
Just like `var`, it can be escaped to make clear that you are using it as an identifier. If a type of that name is in scope, there's no way to get at the constraint meaning of it. So just like `var`, a devious person can declare an `unmanaged` type to prevent people from using the unmanaged constraint.
## Delegate
It is useful to allow Delegate as a constraint, as it has several methods on it. It doesn't guarantee that the type argument would be a delegate type; it could be `Delegate` itself, or `MulticastDelegate`, etc.
It is useful to allow `System.Delegate` as a constraint, as it has several methods on it. It doesn't guarantee that the type argument would be a delegate type; it could be `Delegate` itself, or `MulticastDelegate`, etc.
Let's just unblock `System.Delegate` and `MulticastDelegate` from being a constraint, and give it no special meaning. This is useful and *reduces* complexity in the language. (Let's assume the same for VB as well).
Let's just unblock `Delegate` and `MulticastDelegate` from being a constraint, and give it no special meaning. This is useful and *reduces* complexity in the language.
## Enums
## Enum
Same for enum. Let's not make a special feature for `Enum + struct`, but just stop disallowing `Enum`.
Same for enum. We could imagine a special `enum` constraint that means `Enum + struct`, but instead let's just stop disallowing `System.Enum`. People should be allowed to manually write
``` c#
where T : struct, System.Enum
where T : struct, Enum
```
We need a special rule to allow them together, since otherwise only interfaces are allowed to combine with the `struct` constraint.
We a special rule to allow `struct` and `Enum` together, since otherwise only interfaces are allowed to combine with the `struct` constraint.
## Arrays
## Other non-sealed reference types?
`System.Array` is then the only non-sealed reference type that is prevented from being a constraint. Ideally we should remove this restriction as well, but we don't have a scenario, haven't been asked for it, and don't want to bother making sure it'll be alright.
Also System.ValueType
There are a few other non-sealed reference types that are prevented from being used as constraints, e.g. `Array` and `ValueType`. While it is tempting to unblock them all, now that we're at it, let's not rock that boat until we have useful scenarios for it.
# Stackalloc initializers
# Target typed stackalloc initializers
``` c#
var x = new int[] { 1, 2, 3 }; // allowed today
var z = stackalloc int[5]; // z is int*;
Span<int> zs = stackalloc int[5]; // target typed
var y = stackalloc int[] { 1, 2, 3 }; // should be allowed? y is int*
var x = new int[] { 1, 2, 3 }; // allowed today
var z = stackalloc int[5]; // z is int* for back compat
Span<int> zs = stackalloc int[5]; // target typed
var y = stackalloc int[] { 1, 2, 3 }; // should be allowed? y is int*
Span<int> ys = stackalloc int[] { 1, 2, 3 }; // should be allowed? y is Span<int>
```
The `int*` is for back compat. In contexts other than this the natural type is `Span<T>`.
The `int*` is for back compat. In contexts other than this the natural type of `stackalloc` is `Span<T>`. That's a bit inconsistent, and there are probably other ways to skin the cat, but they would probably add more syntax and not reduce the inconsistency - just push it around. So we're good with this.
There would be other ways to skin the cat, but would probably add more syntax and not reduce the inconsistency - just push it around.
Should we allow the type to be inferred from the initializer, just as with array initializers? Yes.
Should we allow the element type to be inferred from the initializer, just as we do with array initializers? Yes.
# Deconstruct
# Deconstruct as ref extension method
``` c#
public static void Deconstruct(this int i, out int x, out int y);
public static void Deconstruct(this in int i, out int x, out int y);
public static void Deconstruct(this ref int i, out int x, out int y);
public static void Deconstruct(this int i, out int x, out int y); // 1
public static void Deconstruct(this in int i, out int x, out int y); // 2
public static void Deconstruct(this ref int i, out int x, out int y); // 3
```
Currently 1 and 2 are allowed as deconstructors, 3 is not.
Currently 1 and 2 are eligible as deconstructors, whereas 3 is not. you could argue that it is an arbitrary and strangely specific limitation. On the other hand, 3 is probably never desirable, as a deconstructor should not wish to mutate its receiver!
While 3 is probably not desirable, you could argue that it is an arbitrary limitation, that is strangely specific.
Fixing this does not seem to add any value. Let's keep it the way it is.
You will only be able to deconstruct lvalues! So be it.
# Native ints
Won't make 7.3
# Discard ambiguity

54
meetings/2018/README.md Normal file
View file

@ -0,0 +1,54 @@
# C# Language Design Notes for 2018
Overview of meetings and agendas for 2018
## Jan 3, 2018
[C# Language Design Notes for Jan 3, 2018](LDM-2018-01-03.md)
1. Scoping of expression variables in constructor initializer
2. Scoping of expression variables in field initializer
3. Scoping of expression variables in query clauses
4. Caller argument expression attribute
5. Other caller attributes
6. New constraints
## Jan 10, 2018
[C# Language Design Notes for Jan 10, 2018](LDM-2018-01-10.md)
1. Ranges and endpoint types
## Jan 18, 2018
[C# Language Design Notes for Jan 18, 2018](LDM-2018-01-18.md)
We discussed the range operator in C# and the underlying types for it.
1. Scope of the feature
2. Range types
3. Type name
4. Open-ended ranges
5. Empty ranges
6. Enumerability
7. Language questions
## Jan 22, 2018
[C# Language Design Notes for Jan 22, 2018](LDM-2018-01-22.md)
We continued to discuss the range operator in C# and the underlying types for it.
1. Inclusive or exclusive?
2. Natural type of range expressions
3. Start/length notation
## Jan 24, 2018
[C# Language Design Notes for Jan 24, 2018](LDM-2018-01-24.md)
1. Ref reassignment
2. New constraints
3. Target typed stackalloc initializers
4. Deconstruct as ref extension method