143 lines
5.7 KiB
Markdown
143 lines
5.7 KiB
Markdown
# C# Language Design Notes for Jan 3, 2018
|
|
|
|
Quote of the Day: "What about `CallerPoliticalAffiliationAttribute`?"
|
|
|
|
|
|
## Agenda
|
|
|
|
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
|
|
|
|
|
|
# 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
|
|
|
|
Let `x` be in scope in the whole constructor, including the body.
|
|
|
|
|
|
# Scoping of expression variables in field initializer
|
|
|
|
Where should an expression variable `x` introduced in a field initializer be in scope?
|
|
|
|
``` c#
|
|
class C
|
|
{
|
|
int i = M(out int x)
|
|
.N(x), // here?
|
|
y = M(x); // here?
|
|
int j = x; // here?
|
|
int X => x; // here?
|
|
}
|
|
```
|
|
|
|
A number of options:
|
|
|
|
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)
|
|
|
|
There's something tempting about 4, especially considering the relatively loose scope rules we chose for expression variables inside statement lists.
|
|
|
|
``` c#
|
|
void M()
|
|
{
|
|
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?
|
|
}
|
|
```
|
|
|
|
Also, if we ever do primary constructors, and want to allow statements interspersed with member declarations, we'll probably wish we did 4.
|
|
|
|
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 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.
|
|
|
|
The script dialect will have to stay consistent, so declaration variables must be lifted to fields, and be visible in subsequent statements.
|
|
|
|
|
|
# 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
|
|
```
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
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
|
|
|
|
[csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287)
|
|
|
|
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:
|
|
|
|
1. Take a binary breaking change (that's probably what XUnit would do)
|
|
2. Use different API names (won't be picked up automatically by old code)
|
|
3. Change in ref assemblies only (might for `Debug.Assert`)
|
|
|
|
In summary, folks have a decent slate of options.
|
|
|
|
## Conclusion
|
|
|
|
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 [csharplang/issues/87](https://github.com/dotnet/csharplang/issues/87).
|
|
|
|
|
|
# Constraints
|
|
|
|
[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.
|
|
|
|
[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.
|
|
|
|
|