2020-03-25 20:04:00 +01:00
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
2020-03-26 01:46:24 +01:00
|
|
|
Final thought: many thanks to @alrz for the great contribution!
|