# 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 ```C# 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 `new`ing 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!