meetings/2020/LDM-2020-06-24.md
static Member Variance"It feels a little bit like we're playing code golf here"
https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516
There are a few open questions from a previous LDM 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:
func_ptr_calling_convention
: 'managed'
| 'unmanaged' ('[' func_ptr_callkind ']')?
func_ptr_callkind
: 'CallConvCdecl'
| 'CallConvStdcall'
| 'CallConvThiscall'
| 'CallConvFastcall'
| identifier (',' identifier)*
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.
Given the specificness required here, we will use special name lookup.
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:
CallConv onto the identifier. No casemapping is performed.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.
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.
We ended the previous meeting 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.
We will be moving forward with !! as the syntax for parameter null checking:
public void M(Chitty chitty!!)
static Member Variancehttps://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:
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 retcon 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.
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.
In a previous LDM 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:
field contextual keyword that allows the user to refer to the backing storage of
the property in the getter/setter of that property.
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.field isn't treated as a keyword when it
can bind to an existing name.Lazy<T> to initialize itself on first access, for example.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.
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.