
# C# Language Design Meeting for July 6th, 2020

## Agenda

1. [Repeated attributes on `partial` members](#repeated-attributes-on-partial-members)
2. [`sealed` on `data` members](#sealed-on-data-members)
3. [Required properties](#required-properties)

## Quote of the Day

"Is there a rubber stamp icon I can use here?"

## Discussion

### Repeated attributes on `partial` members

https://github.com/dotnet/csharplang/pull/3646

Today, when we encounter attribute definitions among partial member definitions, we merge these attributes, applying attributes multiple
times if there are duplicates across the definitions. However, if there are duplicated attributes that do not allow multiples, this merging
results in an error that might not be resolvable by the user. For example, a source generator might copy over the nullable-related
attributes from the stub side to the implementation side. This is further exacerbated by the new partial method model: previously, the
primary generator of partial stubs was the code generator itself. WPF or other code generators would generate the partial stub, which the
user could then implement to actually create the implementation. These generators generally wouldn't add any attributes, and the user could
add whatever attributes they wanted. However, the model is flipped for the newer source generators. Users will put attributes on the stub in
order to hint to the generator how to actually implement the method, so either the generator will have never copy attributes over (possibly
complicating implementation), or it will have to be smart about only copying over attributes that matter. It would further hurt debugability
as well; presumably tooling will want to show the actual implementation of the method when debugging, and it's likely that the tooling won't
want to try and compute the merged set of attributes from the stub and the implementation to show in debugging.

What emerged from this discussion is a clear divide in how members of the LDT view the stub and the implementation of a partial member: some
members view the stub as a hint that something like this method exists, and the implementation provides the final source of truth. This group
would expect that, if we were designing again, all attributes would need to be copied to the implementation and attributes on the stub would
effectively be ignored. The other segment of the LDT viewed partial methods in exactly the opposite way: the stub is the source of truth, and
the implementation had better conform to the stub. This reflects these two worlds of previous source generators vs current generators: for the
previous uses of partial, the user would actually be creating the implementation, so that's the source of truth. For the new uses, the user is
creating the stubs, so that's the source of truth.

We also briefly considered a few ways of enabling the attribute that keys the generator to be removed from the compiled binary, so that it
does not bloat the metadata. However, we feel that that's a broader feature that's been on the backlog for a while, source-only attributes. We
don't see anything in this feature conflicting with source-only attributes. We also don't see anything in this feature conflicting with
future expansions to partial members, such as partial properties.

#### Conclusion

The proposal is accepted. For the two open questions:

1. We will deduplicate across partial types as well as partial methods if `AllowMultiple` is false. This is considered lower priority if a
feature needs to be cut from the C# 9 timeframe.
2. We don't have a good use-case for enabling `AllowMultiple = true` attributes to opt into deduplication. If we encounter scenarios where
this is needed, we can take it up again at that point.

### `sealed` on `data` members

In a [previous LDM](LDM-2020-06-22.md#data-properties), we allowed an explicit set of attributes on `data` members, but did not include
`sealed` in that list, despite allowing `new`, `override`, `virtual`, and `abstract`. `sealed` feels like it's missing, as it's also
pertaining to inheritance.

#### Conclusion

`sealed` will be allowed on `data` properties.

### Required properties

https://github.com/dotnet/csharplang/issues/3630

In C# 9, we'll be shipping a set of improvements to nominal construction scenarios. These will allow creation of immutable objects via
object initializers, which has some advantages over positional object creation, such as not requiring exponential constructor growth
over object hierarchies and the ability to add a property to a base class without breaking all derived types. However, we're still
missing one major feature that positional constructors and methods have: requiredness. In C# today, there is no way to express that a
property must be set by a consumer, rather than by the class creator. In fact, there is no requirement that all fields of a class type
need to be initialized during object creation: any that aren't initialized are defaulted today. The nullable feature will issue warnings
for initialized fields inside the declaration of a class, but there is no way to indicate to the feature that this field must be initialized
by the consumer of the class. This goes further than just the newly added `init`: mutable properties should be able to participate in this
feature as well. In order for staged initialization to feel like a true first-class citizen in the language, we need to support requiredness
in the contract of creating a class via the feature.

The LDM has seemingly universal support of making improvements here. In particular, the proposed concept of "initialization debt" resonated
strongly with members. It allows for a general purpose mechanism that seems like it will extend natural to differing initialization modes.
We categorized the two extreme ends of object initialization, both of which can easily be present in a single nominal record: Nothing is
initialized in the constructor (the default constructor), and everything is initialized in the constructor (the copy constructor). The next
question is how are these initialization contracts created: there's some tension here with the initial goal of nominal construction.

Fundamentally, initialization contracts can be derived in one of two ways: implicitly, or explicitly. Implicit contracts are attractive at
first glance: they require little typing, and importantly they're not brittle to the addition of new properties on a base class, which was
an important goal of nominal creation in the first place. However, they also have some serious downsides: In C# today, public contracts for
consumers are always explicit. We don't have inferred field types or public-facing parameter/return types on methods/properties. This means
that any changes to the public contract of an object are obvious when reviewing changes to that type. Implicit contracts change that. It
would be very possible for a manually-implemented copy constructor on a derived type to miss adding a copy when a property is added to its
base type, and suddenly all uses of `with` on that type are now broken.

We further observe that this shouldn't just be limited to auto-properties: a non-autoprop should be able to be marked as required, and then
any fields that the initer or setter for that property initializes can be considered part of the "constructor body" for the purposes of
determining if a field has been initialized. Fields should be able to be required as well. This could extend well to structs: currently,
struct constructors are required to set all fields. If they can instead mark a field or property as required then the constructor wouldn't
have to initialize it all.

One way of implementing initialization debt would be to tie it to nullable: it already warns about lack of initialization for not null
fields when the feature is turned on. We're still in the nullable adoption phase where we have more leeway on changing warnings, so we
could actually change the warning to warn on _all_ fields, nullable or not. This would effectively be an expansion of definite assignment:
locals must be assigned a value before use, even if that value is the default. By extending that requirement to all fields in a class, we
could essentially make all fields required when nullable is enabled, regardless of their type. Then, C# 10 could add a feature to enable
skipping the initialization of some members based on whether the consumer must initialize them. This is also not really a breaking change
for structs: they're already required to initialize all fields in the constructor. However, it would be a breaking change for classes, and
we worry it would be a significantly painful one, especially with no ability to ship another preview before C# 9 releases. `= null!;` is
already a significant pain point in the community, and this would only make it worse.

We came up with a few different ways to mark a property as being required:
* A keyword, as in the initial proposal, on individual properties.
* Assigning some invalid value to the field/property. This could work well as a way to be explicit about what fields a particular
constructor would require, but does leave the issue about inherited brittleness.
* Attributes or other syntax on constructor bodies to indicate required properties.

We like the idea of some indication on a property itself dictating the requiredness. This puts all important parts of a declaration together,
enhancing readability. We think this can be combined with a defaulting mechanism: the property sets whether it is required, and then a
constructor can have a set of levers to override individual properties. These levers could go in multiple ways: a copy constructor could
say that it initializes _all_ members, without having to name individual members, whereas a constructor that initializes one or two specific
members could say it only initializes those specific members, and inherits the property defaults from their declarations. There's still
open questions in this proposal, but it's a promising first start.

#### Conclusion

We have unified consensus that in order for staged initialization to truly feel first-class in the language, we need a solution to this issue,
but we don't have anything concrete enough yet to make real decisions. A small group will move forward with brainstorming and come back to
LDM with a fully-fleshed-out proposal for consideration.
