188 lines
10 KiB
Markdown
188 lines
10 KiB
Markdown
# C# Language Design Meeting for June 24th, 2020
|
|
|
|
## Agenda
|
|
|
|
1. [Confirming Function Pointer Decisions](#Confirming-Function-Pointer-Decisions)
|
|
1. [Parameter Null Checking](#Parameter-Null-Checking)
|
|
1. [Interface `static` Member Variance](#Interface-`static`-Member-Variance)
|
|
1. [Property Enhancements](#Property-Enhancements)
|
|
|
|
## Quote of the Day
|
|
|
|
"It feels a little bit like we're playing code golf here"
|
|
|
|
## Discussion
|
|
|
|
### Confirming Function Pointer Decisions
|
|
|
|
https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516
|
|
|
|
There are a few open questions from a previous [LDM](LDM-2020-04-01.md) and a followup email chain
|
|
that need to be confirmed before they can be implemented. These questions center around calling
|
|
convention type lookup and how identifiers need to be written in source. The grammar we had roughly
|
|
proposed after the previous meeting is:
|
|
|
|
```antlr
|
|
func_ptr_calling_convention
|
|
: 'managed'
|
|
| 'unmanaged' ('[' func_ptr_callkind ']')?
|
|
|
|
func_ptr_callkind
|
|
: 'CallConvCdecl'
|
|
| 'CallConvStdcall'
|
|
| 'CallConvThiscall'
|
|
| 'CallConvFastcall'
|
|
| identifier (',' identifier)*
|
|
```
|
|
|
|
##### Calling Convention Lookup
|
|
|
|
When attempting to bind the `identifier` used in an unmanaged calling convention, should this follow
|
|
standard lookup rules, such that the type must be in scope at the current location, or is using a
|
|
form of special lookup that disregards the types in scope at the current location? The types valid
|
|
in this location are a very specific set: they must come from the `System.Runtime.CompilerServices`
|
|
namespace, and the types must have been defined in the same assembly that defines `System.Object`,
|
|
regardless of the binding strategy used here, so it's really a question of whether the user has to
|
|
include this namespace in their current scope, adding a bunch of types that they are generally not
|
|
advised to use directly, and whether they can get an error because they defined their own calling
|
|
convention.
|
|
|
|
##### Conclusion
|
|
|
|
Given the specificness required here, we will use special name lookup.
|
|
|
|
##### Required identifiers
|
|
|
|
The previous LDM did not specify the required syntax for the identifiers quite explicitly enough for
|
|
implementation, and specified that identifiers should be lowercase while also having upper case
|
|
identifiers in some later examples. The following rules are proposed as the steps the compiler will
|
|
take to match the identifier to a type:
|
|
|
|
1. Prepend `CallConv` onto the identifier. No casemapping is performed.
|
|
2. Perform special name lookup with that typename in the `System.Runtime.CompilerServices` namespace
|
|
only considering types that are defined in the core library of the program (the library that defines
|
|
`System.Object` and has no dependencies itself).
|
|
|
|
We also reconsidered the decision from the previous LDM on using lowercase mapping for the identifier
|
|
names. There is convention for this in other languages: C/C++, for example, use `__cdecl` or similar
|
|
as their calling convention specifiers, and given that this feature will be used for native interop
|
|
with libraries doing this it would be nice to have some parity. However, this would introduce several
|
|
issues with name lookup: existing special name lookup allows us to modify the `identifier` specified
|
|
in source, but it does not allow us to modify the names of the types we're matching against, which
|
|
we would need to do here. There is certainly an algorithm that could be specified here, but we overall
|
|
felt that this was too complicated for what was a split aesthetic preference among members.
|
|
|
|
##### Conclusion
|
|
|
|
The proposed rules are accepted. As a consquence, the identifier specified in source cannot start
|
|
with `CallConv` in the name, unless the runtime were to add a type like `CallConvCallConv`.
|
|
|
|
### Parameter Null Checking
|
|
|
|
We ended the [previous meeting](LDM-2020-06-17.md) on this with two broad camps: support for the `!!`
|
|
syntax, and support for some kind of keyword. Email discussion over the remainder of the week and
|
|
polling showed that a clear majority supported the `!!` syntax.
|
|
|
|
#### Conclusion
|
|
|
|
We will be moving forward with `!!` as the syntax for parameter null checking:
|
|
|
|
```cs
|
|
public void M(Chitty chitty!!)
|
|
```
|
|
|
|
### Interface `static` Member Variance
|
|
|
|
https://github.com/dotnet/csharplang/issues/3275
|
|
|
|
We considered variance in `static` interface members. Today, for co/contravariant type parameters
|
|
used in these members, they must follow the full standard rules of variance, leading to some
|
|
inconsistency with the way that `static` fields are treated vs `static` properties or methods:
|
|
|
|
```cs
|
|
public interface I<out T>
|
|
{
|
|
static Task<T> F = Task.FromResult(default(T)); // No problem
|
|
static Task<T> P => Task.FromResult(default(T)); //CS1961
|
|
static Task<T> M() => Task.FromResult(default(T)); //CS1961
|
|
static event EventHandler<T> E; // CS1961
|
|
}
|
|
```
|
|
|
|
Because these members are `static` and non-virtual, there aren't any safety issues here: you can't
|
|
derive a looser/more restricted member in some fashion by subtyping the interface and overriding
|
|
the member. We also considered whether this could potentially interfere with some of the other
|
|
enhancements we hope to make regarding roles, type classes, and extensions. These should all be
|
|
fine: we won't be able to retconn the existing static members to be virtual-by-default for interfaces,
|
|
as that would end up being a breaking change on multiple levels, even without changing the variance
|
|
behavior here.
|
|
|
|
We also considered whether this change could be considered a bug fix on top of C# 8, meaning that
|
|
users would not have to opt into C# 9 in order to see this behavior. While the change is small and
|
|
likely very rarely needed, we would still prefer to avoid breaking downlevel compilers.
|
|
|
|
#### Conclusion
|
|
|
|
We will allow `static`, non-virtual members in interfaces to treat type parameters as invariant,
|
|
regardless of their declared variance, and will ship this change in C# 9.
|
|
|
|
### Property Enhancements
|
|
|
|
In a [previous LDM](#LDM-2020-04-01.md) we started to look at various enhancements we could make
|
|
to properties in response to customer feedback. Broadly, we feel that these can be addressed by
|
|
one or more of the following ideas:
|
|
|
|
1. Introduce a `field` contextual keyword that allows the user to refer to the backing storage of
|
|
the property in the getter/setter of that property.
|
|
* In this proposal, we can consider all properties today as having this `field` keyword. As
|
|
an optimization, if the user does not refer to the backing field in the property body, we
|
|
elide emitting of the field, which happens to be the behavior of all full properties today.
|
|
* Of the proposals, this allows the most brevity for simple scenarios, allowing some lazily-fetched
|
|
properties to be one-liners.
|
|
* This proposal does not move the cliff far: if you need to have a type differing from the
|
|
type of the property, or multiple fields in a single property, then you must fall back to a full
|
|
property and expose the backing field to the entire class.
|
|
* There are also questions about the nullability of these backing properties: must they be
|
|
initialized? Or should we provide a way to declare them as nullable, despite the property
|
|
itself not being nullable?
|
|
* There was also concern that this is adding conceptual overhead for not enough gain: education
|
|
would be needed on when backing fields are elided and when they are not, complicated by the
|
|
additional backcompat overhead of ensuring that `field` isn't treated as a keyword when it
|
|
can bind to an existing name.
|
|
2. Allow field declarations in properties.
|
|
* Conveniently for this proposal, a scoping set of braces for properties already exists.
|
|
* This addresses the issue of properties backed by a different storage type: if you have a
|
|
property that uses a `Lazy<T>` to initialize itself on first access, for example.
|
|
* This also allows users to declare multiple backing fields, if they want to lock access
|
|
to the property or combine multiple pieces of information into a single type in the public
|
|
surface area.
|
|
* In terms of user education, this is the simplest proposal. Since a set of braces already
|
|
exists, the education is just "There's a new scope you can put fields in."
|
|
3. Introduce a delegated property pattern into the language.
|
|
* There have been a few proposals for this, such as #2657. Other languages have also adopted
|
|
this type of feature, including Kotlin and Swift.
|
|
* This is by far the most complex of the proposals, adding new patterns to the language and
|
|
requiring declaration of a whole new type in order to remove one or possibly 2 fields from
|
|
a general class scope.
|
|
* By that same token, however, this is the most broad of the proposals, allowing users to
|
|
write reusable property implementations that could be abstracted out.
|
|
|
|
The LDM broadly viewed these proposals as increasing in scope: the `field` keyword allows the most
|
|
brief syntax, but forces users off the cliff back to full class-scoped fields immediately if their
|
|
use case is not having a single backing field of the same type. Meanwhile, property-scoped fields
|
|
don't allow for and encourage creating reusable helpers, like delegated properties would.
|
|
|
|
We also recognize that regardless of what decisions we make today, we're not done in this space.
|
|
None of these proposals are mutually exclusive, and we can "turn the crank" by introducing one, and
|
|
then adding more in a future release. There is interest among multiple LDM members in adding some
|
|
form of reusable delegated properties or property wrappers, and adding one of either the `field`
|
|
keyword or property-scoped fields does not preclude adding the other in a later release. Further,
|
|
all of these proposals are early enough that we still have a bunch of design space to work through
|
|
with them, while designing ahead enough to ensure that we don't add a wart on the language that we
|
|
will regret in the future.
|
|
|
|
#### Conclusion
|
|
|
|
A majority of the LDM members would like to start by exploring the property-scoped locals space.
|
|
We'll start by expanding that proposal with intent to include in C# 10, but will keep the other
|
|
proposals in mind as we do so.
|