csharplang/meetings/2020/LDM-2020-03-25.md
2020-03-25 17:46:24 -07:00

4.5 KiB

C# Language Design Meeting for March 25, 2020

Agenda

  1. Questions around the new nint type

  2. Target-typed new

Discussion

Issue #3259

LangVersion

THe question is what the behavior of the compiler should be when seeing an nint type in langversion C# 8. Our convention is that the compiler never preserves old behavior for older language versions. For instance, we do not preserve the code for older code generation strategies and switch to that with the language version flag. Instead, langversion is meant to be guard rails, providing diagnostics when features are used that aren't available in older versions of the language.

There are a few options we could take.

  1. Make an exception for nint, allowing them to be seen and compiled like an IntPtr in langversion C# 8.

  2. Make a wider divergence between nint and IntPtr. Adding a modreq to the emitted IntPtr type would make them effectively unusable by older language versions and other languages.

  3. Preserve the behavior, as long as no new semantics are used. For instance, using the arithmetic operators on nint and on IntPtr have different semantics. It would be an error to use any of these operators in older language versions.

Conclusion

We think (3) is the best balance.

IntPtr and nint operators

We have two proposals:

  1. Remove built-in identity conversions between native integers and underlying types and add explicit conversions.

  2. Remove nint operators when using the IntPtr type

Conclusion

(1) is a little too harsh. Let's do (2).

Behavior of constant folding

The concern is platform dependence.

In the following example

const nint m = int.MaxValue;
const nint u1 = unchecked(m + 1);
      nint u2 = unchecked(m + 1);

if the machine is 32-bit, then the result overflows. If the machine is 64-bit, it does not.

While it's possible in the existing language to produce constant-folded values which are undefined, we don't think that behavior is desirable for nint.

The main contention is what to do in a checked context if we know the value will overflow 32-bits. We could either produce an error, saying that this will overflow on some platforms, or produce a warning and push the calculation to runtime, warning that the calculation may overflow at runtime (and produce an exception).

Conclusion

Whenever we can safely produce a constant value under 32-bits, we do constant folding. Otherwise, the result is non-constant, and under checked, the code produces a warning and the result is non-constant.

Interfaces on nint?

Should interfaces on IntPtr and nint match? Or should nint only accept a certain set of compiler-validated interfaces on IntPtr?

Conclusion

We trust that interfaces will only be added to IntPtr with recognition that those interfaces also affect nint. We'll make all interfaces on IntPtr available on nint, with IntPtr occurrences substituted for nint.

Target-typed new

https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md

Clarification about library evolution: if a user uses new(), adding a constructor to a type can produce an ambiguity. Similarly, if a method is called with new() that can produce an ambiguity if more overloads of that method is added. This is analogous with null or default, which can convert to many different types and can produce ambiguity.

The spec currently specifies that there are a list of types where target-typed new is allowed. To simplify, we propose that we specify that target-typed new should produce a fully-typed new and the legality of that expression is defined elsewhere. This does make new() work on enums, which is currently proposed as illegal because it may be confusing. However, new Enum() is legal today, so we think that it should be allowed for target-typed new simply because of consistency.

There's some debate on what it should do for nullable value types. On the one hand, the rule "new() is just shorthand for writing out the type on the left," implies that the result should be null. On the other hand, the nullable lifting rules would imply that the base type of the target should be the underlying type, not the nullable type. Overall, we think that newing the underlying type makes the most sense, both because it's the most useful (we already have a shorthand for null) and because it's likely what the user intended.

For dynamic, we will not permit it simply because new dynamic() is also illegal.

Final thought: many thanks to @alrz for the great contribution!