csharplang/meetings/2022/LDM-2022-06-06.md

6.0 KiB

C# Language Design Meeting for June 6, 2022

Agenda

  1. Open issues for ref fields
  2. Open issues for static virtual members
  3. Concatenation of Utf8 literals

Discussion

Open issues for ref fields

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

We discussed the questions out of order because of the way we perceived them to influence each other.

Question 5: scoped differences in overrides and interface implementations

When overrides or implementations differ from their base in terms of scoped what should we do? We seem to have the following options:

  1. Do nothing: no restrictions for 'scoped' in override/implementation (unsafe)
  2. Disallow use of 'scoped' in overrides/interfaces
  3. Disallow differences in 'scoped' in override/implementation
  4. Allow adding 'scoped', disallow dropping 'scoped' in override/implementation

Option 1 and 2 do not seem helpful. Option 3 is simple, allows expressing reasonable things and is safe.Option 4 is a bit more involved, but is more expressive too, and seems more consistent with nullability etc.

Conclusion

We lean to 4, but do not have important scenarios. We're fine if we only make it to 3 now and potentially do 4 later, which will be compatible. We'd need to spend the time to assure ourselves that 4 is safe.

Question 6. scoped differences in delegate conversions

Similar question and similar options, but for conversion of lambdas and method groups to delegate types.

Conclusion

This should do the same as in the previous question. Even for explicit conversion we should not break scoped safety. There are unsafe APIs for breaking the rules if people really want to.

Inferred delegate types will preserve scoped from their lambda or method group.

Question 4. Constructor that does not set ref field

Should we report a diagnostic on a constructor that doesn't initialize a ref field? It will lead to a null ref exception later.

On the one hand we have a set of rules for non-nullable reference types that we could probably adapt to ref fields. On the other hand this is an advanced feature set and this may not be worth it. Also we can't do anything about default values of the ref struct, which are likely to be common.

The runtime is planning to do work so that null ref dereference behavior is well defined, as no matter what, safe code can now create ref fields with nulls in them. The runtime will ensure that all locals with references will be zeroed even if SkipLocalsInit is specified.

Conclusion

No diagnostics - it's not worth it, wouldn't save every case anyway, and this is an expert feature.

Question 3. Ref assignment in object initializers

Constructors allow ref parameters to be assigned to ref fields. Should object initializers also allow ref assignment?

Conclusion

Yes. There's no reason not to, it's just a little bit of syntax and it's obvious what it does.

Open issues for static virtual members

https://github.com/dotnet/csharplang/blob/main/proposals/static-abstracts-in-interfaces.md#unresolved-questions

Adjusting the type argument rule

Should we allow interfaces to be type arguments if they have virtual but not abstract static members - i.e. if all virtual static members have a default implementation?

If we don't allow this, adding a static virtual with a default implementation to an existing interface that has no static virtuals will break anyone currently passing that interface as a type argument. This breaks the principle that new interface members with default implementations don't break existing code.

Conclusion

Let's loosen the rule to preserve the value of default implementations, assuming that we can loosen the corresponding rule in the runtime.

Virtual equality and conversion operators

Should virtual equality and conversion operators be allowed to have default implementations, and to provide implementations in overrides? If so are there any restrictions?

Currently interfaces are not allowed to declare non-virtual equality and conversion operators. The reason is that such operators too easily lead to confusing or conflicting behavior on the interfaces. However, virtual operators do not apply to the interfaces themselves but only to type parameters constrained by them, so they do not seem to have these same problems.

Nevertheless, an interface IThing with a declaration of the form

public interface IThing
{
    static virtual bool operator ==(IThing i1, IThing i2) => ...
    ...
}

would be quite confusing, especially since it would not apply to instances of the interface itself:

var result = i1 == i2; // Reference equality

On the other hand, a declaration that has "self-type" operands would probably be a lot harder to misunderstand in that way:

public interface IThing<TSelf> where TSelf : IThing<TSelf>
{
    static virtual bool operator ==(TSelf i1, TSelf i2) => ...
    ...
}

Conversions are already not allowed to have interface operands directly.

Conclusion

Both types of operand declarations should be allowed to have default implementations.

We will limit equality operators so that one of the operands cannot be the interface itself and has to be a self-constrained type parameter. The other can be anything (including an interface).

Concatenation of Utf8 literals

https://github.com/dotnet/roslyn/issues/60896#issuecomment-1129362977

The motivating scenario is line breaking source code containing very large Utf8 string literals.

Should we wait for a + operator for Utf8 strings (ReadOnlySpan<byte>) in general? Is such a thing even desirable? After all it would be expensive at runtime, and concatenation might not generally be the obvious semantics for + on spans - even on spans of bytes. However, on literals its meaning is obvious.

Conclusion

Let's do it. Let's make sure people get good error messages when they try to + non-literals.