[Version 9.0] Feature support for native sized integers (nint, and nuint)#1457
[Version 9.0] Feature support for native sized integers (nint, and nuint)#1457BillWagner wants to merge 26 commits intodraft-v9from
nint, and nuint)#1457Conversation
nint, and nuint)nint, and nuint)
|
Can someone please check if changes should be made to §12.6.4.7. |
eda247c to
746fdf2
Compare
|
Retaining draft status for this meeting. If there's time, I'd like to have everyone get a first look at this before the January meeting. There's an interesting time discussion: The changes to the feature will remove quite a bit of language from the standard. Once the keywords Do we want full fidelity in both versions? If not, how much hand-waving for temporary C# 9 behavior do we allow? |
standard/types.md
Outdated
| > | ||
| > *end note*. | ||
|
|
||
| Although `nint` and `nuint` shall be represented by the types `System.IntPtr` and `System.UIntPtr`, respectively, `nint` and `nuint` are *not* aliases for those types. As such, not all members of the corresponding `System` types are defined for `nint` and `nuint`. Instead, the compiler shall make available additional conversions, unary operators (§12.9) and arithmetic operators (§12.12) for the types `System.IntPtr` and `System.UIntPtr` when used in the context of native integer types. |
There was a problem hiding this comment.
We've said that there's an identity conversion between nint and IntPtr - but do we say elsewhere that IntPtr must be used to represent nint values? It may well be somewhere, and I've missed it...
There was a problem hiding this comment.
(Line 365 of this file states it, effectively - but given that that is after this paragraph, presumably it's somewhere else as well...)
There was a problem hiding this comment.
(Not sure what "shall be represented" even means in the language spec...)
There was a problem hiding this comment.
I think we might be able to remove a lot of the distinction, but more work required for us to be sure. What I think we might be seeing here are two different things:
- The language adding contextual keywords in C#9, and moving to keywords in C#11
- An implementation changing how it provides the language features in its C#11 release compared to how it provided them in its C#9 & C#10 releases.
If this is correct then (1) is for the Standard and (2) is not.
There was a problem hiding this comment.
That implementation change likely has impact on the standard.
Until there was runtime support for checked and unchecked user defined operators, System.IntPtr and System.UIntPtr couldn't support the correct behavior for numeric types. We need to discuss how we want to handle that requirement.
standard/types.md
Outdated
| public void* ToPointer(); | ||
| ``` | ||
|
|
||
| The remaining members of `System.IntPtr` and `System.UIntPtr` are implicitly included in `nint` and `nuint`. These are: |
There was a problem hiding this comment.
"These are" sounds like we're prohibiting the introduction of other members, which we've previously said would be okay for an implementation to do. Let's discuss what we want to require here.
There was a problem hiding this comment.
I removed the list. I think it's valid just to say "everything else"
|
Next step: @BillWagner to see whether it would be possible to spec this in a way that looks forward to C# 11 more, defining them as aliases but specifying the exceptions in one place (e.g. no bitwise shift on IntPtr), and then removing that section for C# 11. |
I favor full fidelity. I'm not sure we can call them aliases of the same type and place exceptions on them; wouldn't that effectively be making them separate types? Something we didn't discuss in the meeting is that the C# 11 feature is only available on some runtimes. At the top of the C# 11 spec:
So it seems that even C# 11 still maintains What will the C# spec do in 11 then? Keep the feature "bimodal" forever, or assume that C# 11 requires .NET 7? Update: I've raised the same question, but a more time-pressing version of it, at #1532 |
| ## 17.4 Array element access | ||
| Array elements are accessed using the *array access* variant of *element_access* expressions ([§12.8.12.2](expressions.md#128122-array-access)) of the form `A[I₁, I₂, ..., Iₓ]`, where `A` is an expression of an array type and each `Iₑ` is an expression of type `int`, `uint`, `long`, `ulong`, or can be implicitly converted to one or more of these types. The result of an array access is a variable reference ([§9.5](variables.md#95-variable-references)) to the array element selected by the indices. | ||
| Array elements are accessed using the *array access* variant of *element_access* expressions ([§12.8.12.2](expressions.md#128122-array-access)) of the form `A[I₁, I₂, ..., Iₓ]`, where `A` is an expression of an array type and each `Iₑ` is an expression of type `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, or can be implicitly converted to one or more of these types. The result of an array access is a variable reference ([§9.5](variables.md#95-variable-references)) to the array element selected by the indices. |
There was a problem hiding this comment.
What was the reason to have listed int and uint in the first place? They fall under "can be implicitly converted to long/ulong."
There was a problem hiding this comment.
Completeness for one. And, to make it clear that in those cases, no conversion is required.
standard/conversions.md
Outdated
| Conversion from `A` to `Nullable<B>` is: | ||
|
|
||
| - an implicit nullable conversion if there is an identity conversion or implicit conversion from `A` to `B`; | ||
| - an explicit nullable conversion if there is an explicit conversion from `A` to `B`; | ||
| - otherwise, invalid. |
There was a problem hiding this comment.
Instead of adding this new section and the new letters A and B, what if we took this section above:
- An implicit or explicit conversion from `S?` to `T?`
- An implicit or explicit conversion from `S` to `T?`
- An explicit conversion from `S?` to `T`.And augmented it thus:
- A conversion from `S?` to `T?`, implicit if the conversion from `S` to `T` is implicit, otherwise explicit
- A conversion from `S` to `T?`, implicit if the conversion from `S` to `T` is implicit, otherwise explicit
- A conversion from `S?` to `T`, always explicitI think "invalid" is implied; when no conversion exists from S to T, we will not have gotten anywhere in this section.
There was a problem hiding this comment.
I agree in principle with @jnm2 above but the wording of his suggestion does not read well this non-american as it is missing small words 😉. If there is consensus that 749-751 are not clear then I’ll offer instead:
- A matching implicit or explicit conversion from `S?` to `T?`
- A matching implicit or explicit conversion from `S` to `T?`
- An explicit conversion from `S?` to `T`.Either way the added lines are redundant:
| Conversion from `A` to `Nullable<B>` is: | |
| - an implicit nullable conversion if there is an identity conversion or implicit conversion from `A` to `B`; | |
| - an explicit nullable conversion if there is an explicit conversion from `A` to `B`; | |
| - otherwise, invalid. |
standard/expressions.md
Outdated
| **This subclause is informative.** | ||
|
|
||
| Unary numeric promotion occurs for the operands of the predefined `+`, `-`, and `~` unary operators. Unary numeric promotion simply consists of converting operands of type `sbyte`, `byte`, `short`, `ushort`, or `char` to type `int`. Additionally, for the unary – operator, unary numeric promotion converts operands of type `uint` to type `long`. | ||
| Unary numeric promotion occurs for the operands of the predefined `+`, `–`, and `~` unary operators. Unary numeric promotion simply consists of converting operands of type `sbyte`, `byte`, `short`, `ushort`, or `char` to type `int`. Additionally, for the unary – operator, unary numeric promotion converts operands of type `uint` or `nint` to type `long`. |
There was a problem hiding this comment.
Is the en dash used when referring to the operator, despite the fact that the syntax for the operator must use a hyphen?
There was a problem hiding this comment.
No, that must have snuck in in another PR.
| A constant expression shall either have the value `null` or one of the following types: | ||
| A constant expression may be either a value type or a reference type. If a constant expression has a value type, that type shall be one of the following: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,` or any enumeration type. If a constant expression has a reference type, the expression shall: | ||
|
|
||
| - have a value of type `string`; | ||
| - have a value of `null`; or | ||
| - be a default value expression ([§12.8.21](expressions.md#12821-default-value-expressions)) of reference type. |
There was a problem hiding this comment.
This change no longer permits a constant expression of null of a nullable value type, which would make the following code invalid:
class C
{
void M(int? x = null) { }
}https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15621-general:
An input parameter may have a default_argument. The expression in a default_argument shall be one of the following:
- a constant_expression
- an expression of the form
new S()whereSis a value type- an expression of the form
default(S)whereSis a value type
standard/expressions.md
Outdated
|
|
||
| If any `nint`/`nuint` values are not representable as `Int32`/`UInt32`, or the run-time evaluation of the whole expression would throw an exception, then a compile-time error shall be produced. | ||
|
|
||
| > *Note*: These rules mean that an expression involving native integers which is superficially valid as a *constant_expression* may only be valid as an *expression* evaluated at runtime using the full implementation-defined native integer precision. *end note* |
There was a problem hiding this comment.
Added in the next commit. (Also changed note to an example)
| - treat all values of type `nuint` as `System.UInt32`; | ||
| - and otherwise use the same evaluation rules as for run-time non-constant expressions. | ||
|
|
||
| If any `nint`/`nuint` values are not representable as `Int32`/`UInt32`, or the run-time evaluation of the whole expression would throw an exception, then a compile-time error shall be produced. |
There was a problem hiding this comment.
Aren't all nint/nuint values within a constant expression representable as Int32/UInt32, given this line above?
A constant_expression of type
nintshall have a value in the range [int.MinValue,int.MaxValue]. A constant_expression of typenuintshall have a value in the range [uint.MinValue,uint.MaxValue].
There was a problem hiding this comment.
This is saying two different things, I think: First that any value on the RHS of a constant expression must be in valid range. The second is saying that the compiler must evaluate the full expression at compile time and store the value. If the result of that compile time evaluation is out of range, the compiler emits an error.
d9fd9bd to
6b62227
Compare
|
(Quoting as this is coming some way down the conversation)
See my reply above, I think this might be a question not of “fidelity” but of implementation-specific material sneaking into this PR. We are certainly not going to be saying in the Standard that C#11 requires .NET 7 as C# does not require any version of .NET at all. |
Nigel-Ecma
left a comment
There was a problem hiding this comment.
A lot of changes seem to be required, but what they are will require some work. See the one comment under this review.
standard/types.md
Outdated
| Although `nint` and `nuint` shall be represented by the types `System.IntPtr` and `System.UIntPtr`, respectively, `nint` and `nuint` are *not* aliases for those types. As such, not all members of the corresponding `System` types are defined for `nint` and `nuint`. Instead, the compiler shall make available additional conversions, unary operators (§12.9) and arithmetic operators (§12.12) for the types `System.IntPtr` and `System.UIntPtr` when used in the context of native integer types. | ||
|
|
||
| While the language provides operations and conversions for `nint` and `nuint` that are appropriate for integer types, those operations and conversions are not available on the `System` type counterparts. For example, |
There was a problem hiding this comment.
These few lines give rise to a few questions:
Although
nintandnuintshall be represented by the typesSystem.IntPtrandSystem.UIntPtr, respectively,nintandnuintare not aliases for those types. As such, not all members of the correspondingSystemtypes are defined fornintandnuint.
This seems to say there are members defined on IntPtr & UIntPtr that are explicitly not accessible via objects/values of type nint & nuint.
So is the deviation of nint & nuint from the other numeric types the restriction that not all members of the System.X type used for them may be accessed whether those members?
Remember under the conformance rules an implementation may provide types with additional members which are then accessible, are these native types avoiding that by not being “aliases“?
Instead, the compiler shall make available additional conversions, unary operators (§12.9) and arithmetic operators (§12.12) for the types
System.IntPtrandSystem.UIntPtrwhen used in the context of native integer types.
First, and obviously, the “compiler” shall do not such thing – the language defines the operators, e.g. §12.10.5 (v7):
The predefined addition operators are listed below. For numeric and enumeration types, the predefined addition operators compute the sum of the two operands.
Where in the Standard does it ever state that these predefined operators are members of the “aliased” System.X type?
What difference is being asserted here that the “compiler” is making available that the language doesn’t for the other numeric types?
From a language perspective there are very few required members of any of numeric types. Take a look at System.Int and in §C no members are listed, look at the adopted spec from the CLI and it contains: 2 fields (min & max values), 2 compare methods, 2 equality methods, 1 hash code method, 4 parsing methods, and 4 conversions to string. What isn’t there is more notable than what is, e.g. not a single operator.
While the language provides operations and conversions for
nintandnuintthat are appropriate for integer types, those operations and conversions are not available on theSystemtype counterparts.
So how does that differ from System.Int above, it also has no operators…
Now today .NET’s System.Int does, for example, implement IAdditionOperators<TSelf,TOther,TResult> which provides and addition operator but it is not in the C# Standard and use Roslyn to add to integers and it does not call it – nothing has changed, the language defines the operators, the implementation implements them using the features of its target architecture (for Roslyn compiling C#9 the CLR’s add for both int and nint).
So what is different about nint and nuint that needs to go into the Standard (v9 & v11)? At present this PR has no native int operators listed in §12.10.5 (v7) which seems rather key, but does have stuff about the compiler making additional things available which seems entirely irrelevant.
Looking at the source material linked at the top of the PR it is clear it is heavily implementation specific, going as far as listing CLR instructions.
All the implementation specific stuff picked up from the source material needs to be elided, and the core of the C# languages changes found in what is left – and once that is done I suspect the differences between C#9 & C#11 will be much reduced.
Nigel-Ecma
left a comment
There was a problem hiding this comment.
This is hopefully getting close. It is pleasing to see that the differences between being aliases of & represented by are really minimal so the step to C#11 will be small.
standard/conversions.md
Outdated
| Conversion from `A` to `Nullable<B>` is: | ||
|
|
||
| - an implicit nullable conversion if there is an identity conversion or implicit conversion from `A` to `B`; | ||
| - an explicit nullable conversion if there is an explicit conversion from `A` to `B`; | ||
| - otherwise, invalid. |
There was a problem hiding this comment.
I agree in principle with @jnm2 above but the wording of his suggestion does not read well this non-american as it is missing small words 😉. If there is consensus that 749-751 are not clear then I’ll offer instead:
- A matching implicit or explicit conversion from `S?` to `T?`
- A matching implicit or explicit conversion from `S` to `T?`
- An explicit conversion from `S?` to `T`.Either way the added lines are redundant:
| Conversion from `A` to `Nullable<B>` is: | |
| - an implicit nullable conversion if there is an identity conversion or implicit conversion from `A` to `B`; | |
| - an explicit nullable conversion if there is an explicit conversion from `A` to `B`; | |
| - otherwise, invalid. |
standard/enums.md
Outdated
| Each enum type has a corresponding integral type called the ***underlying type*** of the enum type. This underlying type shall be able to represent all the enumerator values defined in the enumeration. If the *enum_base* is present, it explicitly declares the underlying type. The underlying type shall be one of the *integral types* ([§8.3.6](types.md#836-integral-types)) other than `char`. The underlying type may be specified either by an `integral_type` ([§8.3.5](types.md#835-simple-types)), or an `integral_type_name`. The `integral_type_name` is resolved in the same way as `type_name` ([§7.8.1](basic-concepts.md#781-general)), including taking any using directives ([§14.5](namespaces.md#145-using-directives)) into account. | ||
| Each enum type has a corresponding integral type called the ***underlying type*** of the enum type. This underlying type shall be able to represent all the enumerator values defined in the enumeration. If the *enum_base* is present, it explicitly declares the underlying type. The underlying type shall be one of the *integral types* ([§8.3.6](types.md#836-integral-types)) other than `nint`, `nuint`, and `char`. The underlying type may be specified either by an `integral_type` ([§8.3.5](types.md#835-simple-types)), or an `integral_type_name`. The `integral_type_name` is resolved in the same way as `type_name` ([§7.8.1](basic-concepts.md#781-general)), including taking any using directives ([§14.5](namespaces.md#145-using-directives)) into account. | ||
|
|
||
| > *Note*: The `char` type cannot be used as an underlying type, either by keyword or via an `integral_type_name`. *end note* |
There was a problem hiding this comment.
Should this Note also now cover nint & nuint? I think the note borderline as it the text:
The underlying type shall be one of the integral types (§8.3.6) other than
nint,nuint, andchar. The underlying type may be specified either by anintegral_type(§8.3.5), or anintegral_type_name.
seems fairly clear, so dropping rather than updating would also be fine.
There was a problem hiding this comment.
removing the note in the next commit.
| - If either operand is of type `decimal`, the other operand is converted to type `decimal`, or a binding-time error occurs if the other operand is of type `float` or `double`. | ||
| - Otherwise, if either operand is of type `double`, the other operand is converted to type `double`. | ||
| - Otherwise, if either operand is of type `float`, the other operand is converted to type `float`. | ||
| - Otherwise, if either operand is of type `ulong`, the other operand is converted to type `ulong`, or a binding-time error occurs if the other operand is of `type sbyte`, `short`, `int`, or `long`. | ||
| - Otherwise, if either operand is of type `ulong`, the other operand is converted to type `ulong`, or a binding-time error occurs if the other operand is of type `sbyte`, `short`, `int`, `nint`, or `long`. | ||
| - Otherwise, if either operand is of type `nuint`, the other operand is converted to type `nuint`, or a binding-time error occurs if the other operand is of type `sbyte`, `short`, `int`, `nint`, or `long`. |
There was a problem hiding this comment.
@jnm2: I think it can probably be simplified along the lines you suggest and avoid the lists. Maybe use letters as typical, e.g. let the two operands have types S & T. If S and T are the same type no promotion is required. If S implicitly converts to T then the first arg is promoted, if T to S then the second. Does the case if S & T are both implicitly convertible to R cover the case on line 316?
I’ll leave the wordsmithing to you (or others) 🙂
Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers Add support for native-sized integers fix example name correct link fix md formatting fix formatting fix md formatting made minor tweaks Add 2 new types make minor tweak Add native types to pointer indexing Update standard/expressions.md Co-authored-by: Kalle Olavi Niemitalo <kon@iki.fi> Update standard/expressions.md Co-authored-by: Kalle Olavi Niemitalo <kon@iki.fi> Add support for native-sized integers fix missing commit
- #1060 (comment): Take Nigel's suggestion, with minor wordsmithing. - #1060 (comment) No change in this PR. Addressed with Rex's work on Annex C. - For #1060 (comment): Use Nigel's suggestion. - For #1060 (comment): Took Nigel's suggestion. - #1060 (comment) No change. Deferred to when we address #729
Proofread and edit pass.
Co-authored-by: Joseph Musser <me@jnm2.com>
Rework the language in types with respect to `nint` and `nuint`. Focus on the language semantics, not the implementation based on `System.IntPtr` and `System.UIntPtr`.
Add text to specify the predefined operators for the `nint` and `nuint` types.
Co-authored-by: Joseph Musser <me@jnm2.com> Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
cedda43 to
1b6c859
Compare
Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
1. Arithmetic operator code blocks (all add nint/nuint between uint/ulong and long/ulong lines):
- added nint operator *(nint x, nint y); and nuint operator *(nuint x, nuint y);
- added nint/nuint division operators
- added nint/nuint remainder operators
- added nint/nuint addition operators
- added nint/nuint subtraction operators (also fixed missing closing paren on ulong line)
2. Shift operator code blocks:
- added nint operator <<(nint x, int count); and nuint operator <<(nuint x, int count);
- added nint operator >>(nint x, int count); and nuint operator >>(nuint x, int count);
3. Unary minus operator:
- added nint operator –(nint x);
- updated to mention nint: "(−2³¹ for int, the corresponding value for nint, or −2⁶³ for long)"
1. Removed or \nint`from the unary numeric promotion rule in §12.4.7.2 Since there is now a predefinednint operator -(nint x), nintshould not be promoted tolong`. 1. Add the S₁ is nint and S₂ is nuint or ulong entry in the better conversion target list
Changed from "A constant expression may be either a value type or a reference type" (with null only under the reference type branch) to "A constant expression shall have the value null, or be of a value type or a reference type." This hoists null to the top level so it's valid regardless of target type — covering nullable value type defaults like void M(int? x = null). Removed the now-redundant null bullet from the reference type list.
1. Fixed missing opening backtick on `System.IntPtr`. 2. Fixed grammar from "In context if type resolution (§7.8.1) on either of these names succeeds then that name shall be recognised" to "If type resolution (§7.8.1) on either of these names succeeds, that name shall be recognised". 3. Added "native signed precision, or native unsigned precision" to the operator precision list, and added a note clarifying that native precision means 32-bit or 64-bit depending on platform and that nint/nuint operators use native precision rather than being promoted.
This fixes an old bulk change.
I wrote the bulk of this spec some years ago (based on the V9 MS proposal), and was happy with it. Then several years later, when I started looking at V11 features, I discovered that this topic was revisited (see V11 MS proposal). And I got confused as to which things should go in the V9 standard and which in the V11. Hopefully, I have it right now as far as V9 goes, but anyone reviewing this PR would do well to at least be conversant with how things will be changed in V11, to make sure the V9 spec is complete/correct. [Outside GitHub I have a Word document showing the expected changes for V11; ping me to get a copy (which Bill and Mads also have).] Rex
The commits from #1060 were squashed into one commit in this PR.
There are several comments on #1060 that need to be addressed: