csharplang/meetings/2017/LDM-2017-12-04.md
2017-12-05 17:06:17 -08:00

4.9 KiB

C# Language Design Notes for Dec 4, 2017

Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!

QOTD: "Days since working around the JIT: 0" QOTD: "What's wrong with dangerous?" QOTD: "If we disallowed Dangerous in method names, Dangerous Dave wouldn't compile!"

Foreach for Span and ReadOnlySpan

Span and ReadOnlySpan implement the foreach pattern with GetEnumerator() etc., even though they don't implement the IEnumerable<T> interface. (Interesting aside, the Current property is ref-returning, which the compiler is quite happy with.)

But it's faster to iterate them like an array, by using the Length and the indexer. We want to allow that for spans as well. We would keep the enumerable around, which also allows write access to the elements of a Span.

Interesting to consider opening up for a new Length/indexer foreach pattern, that's opted into e.g. with an attribute.

Conclusion

Definitely allow it as an optimization. The general case; not now, and maybe not ever. Better to limit it to compiler-known types, that are guaranteed to have equivalent semantics.

In vs value parameter ambiguity

If you have overloads

        void M(in DateTime d) { }
        void M(DateTime d) {}
        
        M(myDT); / ambiguity

Options in tie breaker:

  1. Choose based on LValue vs RValue
  2. val preferred
  3. Do nothing

How would you legitimately end up in this situation:

  • Want to "change" to in, but must keep value for binary compat
  • But now you have source break

We currently do 3. We should do 2. 1 is too arbitrary.

People might want to move existing calls to the in version, and we won't be helping them. But that's an analyzer.

Problem with operators. They can't use in when applied, so can't move away from val behavior. Oh well.

Conclusion

Go with 2. Roll out as bug fix.

Pattern-based fixed

Lots of types (especially new ones, like Span, Memory etc., but also string) would benefit from being fixed.

Span<T> currently has a DangerousGetPinnableReference method for this. We can make this a pattern.

It's a thin layer of syntactic sugar.

Conclusion

Let's do it. Extension methods are allowed (as always when we add new patterns). Let's aim for 7.3, but not super high pri.

this ref vs ref this

We allow ref this but not the other way around. That's wrong! We can't disallow ref this, but we should allow and prefer this ref.

Conclusion

Fix it. Go out as a bug fix.

Reachability, definite assignment and local functions

There's a bit of a mess around when captured variables in local functions are definitely assigned.

This is different from lambdas, because local functions take all calls into account.

The forthcoming ECMA spec changes the wording of these rules, so that they are not defined based on reachability.

Proposal:

  • Make beginning of local functions always reachable. (And lambdas, which is implemented but not spec'ed).
  • A captured local in a local function is considered definitely assigned if it's definitely assigned before every call of it.
  • That last one can be vacuously true.

Conclusion

We like this. It could give errors in a few cases that don't today (including the bug report).

Is this an acceptable breaking change? In order to get an error, you'd have to have:

  • an unassigned local variable
  • an uncalled local function that uses it

Both are unlikely, undesirable and easily fixed. We'll check with compat council.

Equality operators on tuples

Needs to be defined by the language, in order to deal with long tuples, and to recursively apply == rather than Equals.

If somebody wrote their own ValueTuple with user defined equality, then this would break the use of that. That's not a supported scenario; you could get into the same kind of trouble with Nullable<T>.

For tuple literals, there is a tension between left-to-right evaluation and performance.

It seems that t1 == t2 should mean the same as t1 == (t2.Item1, t2.Item2). The upshot of the feature really is to deconstruct tuples (if they aren't already, by being tuple literals), then do point-wise comparison.

We will evaluate all operands left to right, recursively through tuple literals, and save to temps. We don't evaluate target-typed things, and we don't yet convert them.

Then we do point-wise == (or !=, in element position order, separated by && (Or for !=, ||). This may involve conversions. We are ok with those happening "late", and conditionally (won't happen if previous comparison failed), because conversions aren't usually expected to have side effects.

For dynamic we think we can allow it by just converting the result of, e.g. d == e (where d is dynamic) to bool.

Conversion from tuples matter, but not to tuples. If I have a Foo and a tuple, and Foo converts to tuple, then that won't help you.

Conclusion

We think we have it, but will revisit during implementation.