From 7bef2b7828519e811ffece613d9e1fb9ae004cd6 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:14:07 -0400 Subject: [PATCH 01/11] Add support for native-sized integers --- standard/lexical-structure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 6f718ff1f..e7fd07a70 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -593,7 +593,7 @@ contextual_keyword : 'add' | 'alias' | 'ascending' | 'async' | 'await' | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' | 'get' | 'global' | 'group' | 'into' | 'join' - | 'let' | 'nameof' | 'on' | 'orderby' | 'partial' + | 'let' | 'nameof' | 'nint' | 'nuint' | 'on' | 'orderby' | 'partial' | 'remove' | 'select' | 'set' | 'unmanaged' | 'value' | 'var' | 'when' | 'where' | 'yield' ; @@ -647,7 +647,7 @@ The type of a *boolean_literal* is `bool`. #### 6.4.5.3 Integer literals -Integer literals are used to write values of types `int`, `uint`, `long`, and `ulong`. Integer literals have three possible forms: decimal, hexadecimal, and binary. +Integer literals are used to write values of types `int`, `uint`, `long`, and `ulong`. (There is no way to write values of type `nint` and `nuint`. Instead, implicit or explicit casts of other integral constant values may be used.) Integer literals have three possible forms: decimal, hexadecimal, and binary. ```ANTLR Integer_Literal From 5c118cf0f1cf45db26b4153f9e9be43f131c9684 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:24:19 -0400 Subject: [PATCH 02/11] Add support for native-sized integers --- standard/types.md | 79 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/standard/types.md b/standard/types.md index 917bab6fa..28fc922dd 100644 --- a/standard/types.md +++ b/standard/types.md @@ -137,7 +137,7 @@ Delegate types are described in [§20](delegates.md#20-delegates). ### 8.3.1 General -A value type is either a struct type or an enumeration type. C# provides a set of predefined struct types called the ***simple types***. The simple types are identified through keywords. +A value type is either a struct type or an enumeration type. C# provides a set of predefined struct types called the ***simple types***. The simple types are identified through keywords and contextual keywords. ```ANTLR value_type @@ -174,6 +174,8 @@ integral_type | 'ushort' | 'int' | 'uint' + | 'nint' + | 'nuint' | 'long' | 'ulong' | 'char' @@ -216,7 +218,7 @@ Note that `System.ValueType` is not itself a *value_type*. Rather, it is a *clas All value types implicitly declare a public parameterless instance constructor called the ***default constructor***. The default constructor returns a zero-initialized instance known as the ***default value*** for the value type: - For all *simple_type*s, the default value is the value produced by a bit pattern of all zeros: - - For `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, and `ulong`, the default value is `0`. + - For `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, and `ulong`, the default value is `0`. - For `char`, the default value is `'\x0000'`. - For `float`, the default value is `0.0f`. - For `double`, the default value is `0.0d`. @@ -257,7 +259,7 @@ A struct type is a value type that can declare constants, fields, methods, prope ### 8.3.5 Simple types -C# provides a set of predefined `struct` types called the simple types. The simple types are identified through keywords, but these keywords are simply aliases for predefined `struct` types in the `System` namespace, as described in the table below. +Except for `nint` and `nuint`, the simple types are aliases for predefined `struct` types in the `System` namespace, as described in the table below. **Keyword** | **Aliased type** ----------- | ------------------ @@ -267,6 +269,8 @@ C# provides a set of predefined `struct` types called the simple types. The simp `ushort` | `System.UInt16` `int` | `System.Int32` `uint` | `System.UInt32` + `nint` | none; see below + `nuint` | none; see below `long` | `System.Int64` `ulong` | `System.UInt64` `char` | `System.Char` @@ -275,7 +279,7 @@ C# provides a set of predefined `struct` types called the simple types. The simp `bool` | `System.Boolean` `decimal` | `System.Decimal` -Because a simple type aliases a struct type, every simple type has members. +Every simple type has members. Each simple type that is an alias for a predefined struct type, has that struct type’s members. > *Example*: `int` has the members declared in `System.Int32` and the members inherited from `System.Object`, and the following statements are permitted: > @@ -299,9 +303,70 @@ Because a simple type aliases a struct type, every simple type has members. > > *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 and operations for the types `System.IntPtr` and `System.UIntPtr`, as native integer types. + +Consider the following: + + +```csharp +nint a1 = 1; // OK +System.IntPtr a2 = 1; // Error: no implicit conversion +``` + +While the implementation 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. Similarly, + + +```csharp +M((nint)1); + +static void M(dynamic d) +{ + var v = d >> 2; // RuntimeBinderException: '>>' cannot be applied to operands + // of type System.IntPtr/System.UIntPtr and int +} +``` + +The only constructor for `nint` or `nuint` is the parameter-less constructor. + +The following members of `System.IntPtr` and `System.UIntPtr` are explicitly excluded from `nint` or `nuint`: + +```csharp +// constructors +// arithmetic operators +// implicit and explicit conversions +public static readonly IntPtr Zero; // use 0 instead +public static int Size { get; } // use sizeof() instead +public static IntPtr Add(IntPtr pointer, int offset); +public static IntPtr Subtract(IntPtr pointer, int offset); +public int ToInt32(); +public long ToInt64(); +public void* ToPointer(); +``` + +The remaining members of `System.IntPtr` and `System.UIntPtr` are implicitly included in `nint` and `nuint`. These are: + +```csharp +public override bool Equals(object obj); +public override int GetHashCode(); +public override string ToString(); +public string ToString(string format); +``` + +Interfaces implemented by `System.IntPtr` and `System.UIntPtr` are implicitly included in `nint` and `nuint`, with occurrences of the underlying types replaced by the corresponding native integer types. For example, if `IntPtr` implements `ISerializable, IEquatable, and IComparable`, then `nint` implements `ISerializable, IEquatable, and IComparable`. + +`nint` and `System.IntPtr`, and `nuint` and `System.UIntPtr`, are considered equivalent for overriding, hiding, and implementing, however. + +Overloads cannot differ by `nint` and `System.IntPtr`, and `nuint` and `System.UIntPtr`, alone. However, overrides and implementations may differ by `nint` and `System.IntPtr`, or `nuint` and `System.UIntPtr`, alone. + +Methods hide other methods that differ by `nint` and `System.IntPtr`, or `nuint` and `System.UIntPtr`, alone. + +`typeof(nint)` is `typeof(System.IntPtr)`, and `typeof(nuint)` is `typeof(System.UIntPtr)`. + +Due to the implementation-defined nature of native integers ([§8.3.6]( types.md#836-integral-types)), constant folding operations on `nint` and `nuint` operands shall be evaluated as if they were `System.Int32` and `System.UInt32`, respectively. If the operation results in a constant value representable in 32-bits, constant folding may be performed at compile-time. Otherwise, the operation is executed at runtime and is not considered a constant. + ### 8.3.6 Integral types -C# supports nine integral types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, and `char`. The integral types have the following sizes and ranges of values: +C# supports the following integral types, with the sizes and value ranges, as shown: - The `sbyte` type represents signed 8-bit integers with values from `-128` to `127`, inclusive. - The `byte` type represents unsigned 8-bit integers with values from `0` to `255`, inclusive. @@ -309,6 +374,8 @@ C# supports nine integral types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uin - The `ushort` type represents unsigned 16-bit integers with values from `0` to `65535`, inclusive. - The `int` type represents signed 32-bit integers with values from `-2147483648` to `2147483647`, inclusive. - The `uint` type represents unsigned 32-bit integers with values from `0` to `4294967295`, inclusive. +- The `nint` type represents a ***native signed integer*** whose size and value range are implementation-defined, but which shall be either that of `int` or `long`. +- The `nuint` type represents a ***native unsigned integer*** whose size and value range are implementation-defined, but which shall be either that of `uint` or `ulong`. The size of a native unsigned integer shall be the same as that of a native signed integer. - The `long` type represents signed 64-bit integers with values from `-9223372036854775808` to `9223372036854775807`, inclusive. - The `ulong` type represents unsigned 64-bit integers with values from `0` to `18446744073709551615`, inclusive. - The `char` type represents unsigned 16-bit integers with values from `0` to `65535`, inclusive. The set of possible values for the `char` type corresponds to the Unicode character set. @@ -698,6 +765,6 @@ unmanaged_type An *unmanaged_type* is any type that isn’t a *reference_type*, a *type_parameter*, or a constructed type, and contains no instance fields whose type is not an *unmanaged_type*. In other words, an *unmanaged_type* is one of the following: -- `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`. +- `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`. - Any *enum_type*. - Any user-defined *struct_type* that is not a constructed type and contains instance fields of *unmanaged_type*s only. From 8715ce5e6970a4b5b96c9a90ac4d39cd46b8857d Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:26:36 -0400 Subject: [PATCH 03/11] Add support for native-sized integers --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index f088735e5..2999ba637 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -997,7 +997,7 @@ variable_reference ## 9.6 Atomicity of variable references -Reads and writes of the following data types shall be atomic: `bool`, `char`, `byte`, `sbyte`, `short`, `ushort`, `uint`, `int`, `float`, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list shall also be atomic. Reads and writes of other types, including `long`, `ulong`, `double`, and `decimal`, as well as user-defined types, need not be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement. +Reads and writes of the following data types shall be atomic: `bool`, `char`, `byte`, `sbyte`, `short`, `ushort`, `uint`, `int`, `nint`, `nuint`, `float`, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list shall also be atomic. Reads and writes of other types, including `long`, `ulong`, `double`, and `decimal`, as well as user-defined types, need not be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement. ## 9.7 Reference variables and returns From a570b700d51ea1b1c39a576d7ef3cee2e73fa9d2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:36:50 -0400 Subject: [PATCH 04/11] Add support for native-sized integers --- standard/conversions.md | 63 ++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 0ed9932f7..3bfdfa09f 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -87,22 +87,28 @@ In some cases there is an identity conversion between types that are not exactly In most cases, an identity conversion has no effect at runtime. However, since floating point operations may be performed at higher precision than prescribed by their type ([§8.3.7](types.md#837-floating-point-types)), assignment of their results may result in a loss of precision, and explicit casts are guaranteed to reduce precision to what is prescribed by the type ([§12.9.7](expressions.md#1297-cast-expressions)). +There is an identity conversion between `nint` and `System.IntPtr`, and between `nuint` and `System.UIntPtr`. + +For the compound types array, nullable type, constructed type, and tuple, there is an identity conversion between native integers ([§8.3.6]( types.md#836-integral-types)) and their underlying types. + ### 10.2.3 Implicit numeric conversions The implicit numeric conversions are: -- From `sbyte` to `short`, `int`, `long`, `float`, `double`, or `decimal`. -- From `byte` to `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, or `decimal`. -- From `short` to `int`, `long`, `float`, `double`, or `decimal`. -- From `ushort` to `int`, `uint`, `long`, `ulong`, `float`, `double`, or `decimal`. -- From `int` to `long`, `float`, `double`, or `decimal`. -- From `uint` to `long`, `ulong`, `float`, `double`, or `decimal`. +- From `sbyte` to `short`, `int`, `nint`, `long`, `float`, `double`, or `decimal`. +- From `byte` to `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `float`, `double`, or `decimal`. +- From `short` to `int`, `nint`, `long`, `float`, `double`, or `decimal`. +- From `ushort` to `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `float`, `double`, or `decimal`. +- From `int` to `nint`, `long`, `float`, `double`, or `decimal`. +- From `uint` to `long`, `nuint`, `ulong`, `float`, `double`, or `decimal`. +- From `nint` to `long`, `float`, `double`, or `decimal`. +- From `nuint` to `ulong`, `float`, `double`, or `decimal`. - From `long` to `float`, `double`, or `decimal`. - From `ulong` to `float`, `double`, or `decimal`. -- From `char` to `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, or `decimal`. +- From `char` to `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `float`, `double`, or `decimal`. - From `float` to `double`. -Conversions from `int`, `uint`, `long` or `ulong` to `float` and from `long` or `ulong` to `double` may cause a loss of precision, but will never cause a loss of magnitude. The other implicit numeric conversions never lose any information. +Conversions from `int`, `uint`, `nint`, `nuint`, `long` or `ulong` to `float` and from `nint`, `nuint`, `long` or `ulong` to `double` may cause a loss of precision, but will never cause a loss of magnitude. The other implicit numeric conversions never lose any information. There are no predefined implicit conversions to the `char` type, so values of the other integral types do not automatically convert to the `char` type. @@ -382,18 +388,20 @@ The explicit conversions that are not implicit conversions are conversions that The explicit numeric conversions are the conversions from a *numeric_type* to another *numeric_type* for which an implicit numeric conversion ([§10.2.3](conversions.md#1023-implicit-numeric-conversions)) does not already exist: -- From `sbyte` to `byte`, `ushort`, `uint`, `ulong`, or `char`. +- From `sbyte` to `byte`, `ushort`, `uint`, `ulong`, `nuint`, or `char`. - From `byte` to `sbyte` or `char`. -- From `short` to `sbyte`, `byte`, `ushort`, `uint`, `ulong`, or `char`. +- From `short` to `sbyte`, `byte`, `ushort`, `uint`, `nuint`, `ulong`, or `char`. - From `ushort` to `sbyte`, `byte`, `short`, or `char`. -- From `int` to `sbyte`, `byte`, `short`, `ushort`, `uint`, `ulong`, or `char`. -- From `uint` to `sbyte`, `byte`, `short`, `ushort`, `int`, or `char`. -- From `long` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `ulong`, or `char`. -- From `ulong` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, or `char`. +- From `int` to `sbyte`, `byte`, `short`, `ushort`, `uint`, `nuint`, `ulong`, or `char`. +- From `uint` to `sbyte`, `byte`, `short`, `ushort`, `int`, `nint`, or `char`. +- From `nint` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nuint`, `long`, `ulong`, or `char`. +- From `nuint` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, or `char`. +- From `long` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `ulong`, or `char`. +- From `ulong` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, or `char`. - From `char` to `sbyte`, `byte`, or `short`. -- From `float` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, or `decimal`. -- From `double` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, or `decimal`. -- From `decimal` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, or `double`. +- From `float` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, or `decimal`. +- From `double` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, or `decimal`. +- From `decimal` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, or `double`. Because the explicit conversions include all implicit and explicit numeric conversions, it is always possible to convert from any *numeric_type* to any other *numeric_type* using a cast expression ([§12.9.7](expressions.md#1297-cast-expressions)). @@ -427,8 +435,8 @@ The explicit numeric conversions possibly lose information or possibly cause exc The explicit enumeration conversions are: -- From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, or `decimal` to any *enum_type*. -- From any *enum_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, or `decimal`. +- From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, or `decimal` to any *enum_type*. +- From any *enum_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, or `decimal`. - From any *enum_type* to any other *enum_type*. An explicit enumeration conversion between two types is processed by treating any participating *enum_type* as the underlying type of that *enum_type*, and then performing an implicit or explicit numeric conversion between the resulting types. @@ -758,6 +766,23 @@ Evaluation of a nullable conversion based on an underlying conversion from `S` - If the nullable conversion is from `S` to `T?`, the conversion is evaluated as the underlying conversion from `S` to `T` followed by a wrapping from `T` to `T?`. - If the nullable conversion is from `S?` to `T`, the conversion is evaluated as an unwrapping from `S?` to `S` followed by the underlying conversion from `S` to `T`. +Conversion from `A` to `Nullable` 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. + +Conversion from `Nullable` to `B` is: + +- an explicit nullable conversion if there is an identity conversion or implicit or explicit numeric conversion from `A` to `B`; +- otherwise, invalid. + +Conversion from `Nullable` to `Nullable` is: + +- an identity conversion if there is an identity conversion from `A` to `B`; +- an explicit nullable conversion if there is an implicit or explicit numeric conversion from `A` to `B`; +- otherwise, invalid. + ### 10.6.2 Lifted conversions Given a user-defined conversion operator that converts from a non-nullable value type `S` to a non-nullable value type `T`, a ***lifted conversion operator*** exists that converts from `S?` to `T?`. This lifted conversion operator performs an unwrapping from `S?` to `S` followed by the user-defined conversion from `S` to `T` followed by a wrapping from `T` to `T?`, except that a null valued `S?` converts directly to a null valued `T?`. A lifted conversion operator has the same implicit or explicit classification as its underlying user-defined conversion operator. From 5dcf66a58a5c7b197b9152f9f03e9633def1d7c7 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:53:58 -0400 Subject: [PATCH 05/11] Add support for native-sized integers --- standard/expressions.md | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index d2c0df118..17bab10df 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -289,13 +289,15 @@ When overload resolution rules ([§12.6.4](expressions.md#1264-overload-resoluti > *Example*: For the operation `b * s`, where `b` is a `byte` and `s` is a `short`, overload resolution selects `operator *(int, int)` as the best operator. Thus, the effect is that `b` and `s` are converted to `int`, and the type of the result is `int`. Likewise, for the operation `i * d`, where `i` is an `int` and `d` is a `double`, `overload` resolution selects `operator *(double, double)` as the best operator. *end example* +There are no predefined operators for dealing with native integer ([§8.3.6]( types.md#836-integral-types)). Instead, `nint` and `nuint` values shall be promoted to `long` and `ulong`, respectively, and the resulting corresponding predefined operators used instead. + **End of informative text.** #### 12.4.7.2 Unary numeric promotions **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`. **End of informative text.** @@ -308,10 +310,12 @@ Binary numeric promotion occurs for the operands of the predefined `+`, `–`, ` - 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`. - Otherwise, if either operand is of type `long`, the other operand is converted to type `long`. -- Otherwise, if either operand is of type `uint` and the other operand is of type `sbyte`, `short`, or `int`, both operands are converted to type `long`. +- Otherwise, if either operand is of type `uint` and the other operand is of type `sbyte`, `short`, `nint`, or `int`, both operands are converted to type `long`. - Otherwise, if either operand is of type `uint`, the other operand is converted to type `uint`. +- Otherwise, if either operand is of type `nint`, the other operand is converted to type `nint`. - Otherwise, both operands are converted to type `int`. > *Note*: The first rule disallows any operations that mix the `decimal` type with the `double` and `float` types. The rule follows from the fact that there are no implicit conversions between the `decimal` type and the `double` and `float` types. *end note* @@ -2083,7 +2087,7 @@ If the *primary_no_array_creation_expression* of an *element_access* is a value #### 12.8.11.2 Array access -For an array access, the *primary_no_array_creation_expression* of the *element_access* shall be a value of an *array_type*. Furthermore, the *argument_list* of an array access is not allowed to contain named arguments. The number of expressions in the *argument_list* shall be the same as the rank of the *array_type*, and each expression shall be of type `int`, `uint`, `long`, or `ulong,` or shall be implicitly convertible to one or more of these types. +For an array access, the *primary_no_array_creation_expression* of the *element_access* shall be a value of an *array_type*. Furthermore, the *argument_list* of an array access is not allowed to contain named arguments. The number of expressions in the *argument_list* shall be the same as the rank of the *array_type*, and each expression shall be of type `int`, `uint`, `nint`, `nuint`, `long`, or `ulong,` or shall be implicitly convertible to one or more of these types. The result of evaluating an array access is a variable of the element type of the array, namely the array element selected by the value(s) of the expression(s) in the *argument_list*. @@ -2329,6 +2333,8 @@ The run-time processing of an *object_creation_expression* of the form new `T(A) - An instance of type `T` is created by allocating a temporary local variable. Since an instance constructor of a *struct_type* is required to definitely assign a value to each field of the instance being created, no initialization of the temporary variable is necessary. - The instance constructor is invoked according to the rules of function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)). A reference to the newly allocated instance is automatically passed to the instance constructor and the instance can be accessed from within that constructor as this. +`new nint()` is equivalent to `(nint)0`, and `new nuint()` is equivalent to `(nuint)0`. + #### 12.8.16.3 Object initializers An ***object initializer*** specifies values for zero or more fields, properties, or indexed elements of an object. @@ -3117,7 +3123,7 @@ A *default_value_expression* is a constant expression ([§12.23](expressions.md# - a reference type - a type parameter that is known to be a reference type ([§8.2](types.md#82-reference-types)); -- one of the following value types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,`; or +- one of the following value types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,`; or - any enumeration type. ### 12.8.21 Stack allocation @@ -3367,7 +3373,9 @@ For an operation of the form `–x`, unary operator overload resolution ([§12. The result is computed by subtracting `X` from zero. If the value of `X` is the smallest representable value of the operand type (−2³¹ for `int` or −2⁶³ for `long`), then the mathematical negation of `X` is not representable within the operand type. If this occurs within a `checked` context, a `System.OverflowException` is thrown; if it occurs within an `unchecked` context, the result is the value of the operand and the overflow is not reported. If the operand of the negation operator is of type `uint`, it is converted to type `long`, and the type of the result is `long`. An exception is the rule that permits the `int` value `−2147483648` (−2³¹) to be written as a decimal integer literal ([§6.4.5.3](lexical-structure.md#6453-integer-literals)). - + + If the operand of the negation operator is of type `nuint`, a compile-time error occurs. + If the operand of the negation operator is of type `ulong`, a compile-time error occurs. An exception is the rule that permits the `long` value `−9223372036854775808` (−2⁶³) to be written as a decimal integer literal ([§6.4.5.3](lexical-structure.md#6453-integer-literals)) - Floating-point negation: @@ -3643,7 +3651,7 @@ The predefined division operators are listed below. The operators all compute th The division rounds the result towards zero. Thus the absolute value of the result is the largest possible integer that is less than or equal to the absolute value of the quotient of the two operands. The result is zero or positive when the two operands have the same sign and zero or negative when the two operands have opposite signs. - If the left operand is the smallest representable `int` or `long` value and the right operand is `–1`, an overflow occurs. In a `checked` context, this causes a `System.ArithmeticException` (or a subclass thereof) to be thrown. In an `unchecked` context, it is implementation-defined as to whether a `System.ArithmeticException` (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand. + If the left operand is the smallest representable `int`, `nint`, or `long` value and the right operand is `–1`, an overflow occurs. In a `checked` context, this causes a `System.ArithmeticException` (or a subclass thereof) to be thrown. In an `unchecked` context, it is implementation-defined as to whether a `System.ArithmeticException` (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand. - Floating-point division: ```csharp @@ -3692,7 +3700,7 @@ The predefined remainder operators are listed below. The operators all compute t The result of `x % y` is the value produced by `x – (x / y) * y`. If `y` is zero, a `System.DivideByZeroException` is thrown. - If the left operand is the smallest `int` or `long` value and the right operand is `–1`, a `System.OverflowException` is thrown if and only if `x / y` would throw an exception. + If the left operand is the smallest `int`, `nint`, or `long` value and the right operand is `–1`, a `System.OverflowException` is thrown if and only if `x / y` would throw an exception. - Floating-point remainder: ```csharp @@ -3981,14 +3989,14 @@ The predefined shift operators are listed below. The `>>` operator shifts `x` right by a number of bits computed as described below. - When `x` is of type `int` or `long`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if `x` is non-negative and set to one if `x` is negative. + When `x` is of type `int`, `nint`, or `long`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if `x` is non-negative and set to one if `x` is negative. - When `x` is of type `uint` or `ulong`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. + When `x` is of type `uint`, `nuint`, or `ulong`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. For the predefined operators, the number of bits to shift is computed as follows: -- When the type of `x` is `int` or `uint`, the shift count is given by the low-order five bits of `count`. In other words, the shift count is computed from `count & 0x1F`. -- When the type of `x` is `long` or `ulong`, the shift count is given by the low-order six bits of `count`. In other words, the shift count is computed from `count & 0x3F`. +- When the type of `x` is `int` or `uint`, the shift count is given by the low-order five bits of `count`. In other words, the shift count is computed from `count & 0x1F`. This also applies when the type of `x` is `nint` or `nuint`, and those types have the same size and representation as `int` and `uint`, respectively. +- When the type of `x` is `long` or `ulong`, the shift count is given by the low-order six bits of `count`. In other words, the shift count is computed from `count & 0x3F`. This also applies when the type of `x` is `nint` or `nuint`, and those types have the same size and representation as `long` and `ulong`, respectively. If the resulting shift count is zero, the shift operators simply return the value of `x`. @@ -6609,7 +6617,9 @@ constant_expression ; ``` -A constant expression may be either a value type or a reference type. If a constant expression is a value type, it must be one of the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,` or any enumeration type. If a constant expression is a reference type, it must be the `string` type, a default value expression ([§12.8.20](expressions.md#12820-default-value-expressions)) for some reference type, or the value of the expression must be `null`. +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, that type shall be `string`, a default value expression ([§12.8.20](expressions.md#12820-default-value-expressions)) for some reference type, or the value of the expression shall be `null`. + +A *constant_expression* of type `nint` shall have a value in the range [`int.MinValue`,`int.MaxValue`]. A *constant_expression* of type `nuint` shall have a value in the range [`uint.MinValue`,`uint.MaxValue`]. Only the following constructs are permitted in constant expressions: @@ -6671,7 +6681,7 @@ Constant expressions are required in the contexts listed below and this is indic - Attributes ([§22](attributes.md#22-attributes)) - In a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) -An implicit constant expression conversion ([§10.2.11](conversions.md#10211-implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the constant expression is within the range of the destination type. +An implicit constant expression conversion ([§10.2.11](conversions.md#10211-implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, `nint`, `nuint`, or `ulong`, provided the value of the constant expression is within the range of the destination type. ## 12.24 Boolean expressions From 9aa573bc99e4728f635b5e37f2a6a6b10232ff20 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:56:15 -0400 Subject: [PATCH 06/11] Add support for native-sized integers --- standard/statements.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index a8fc58a60..220f849e5 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -731,8 +731,8 @@ A *switch_statement* consists of the keyword `switch`, followed by a parenthesiz The ***governing type*** of a `switch` statement is established by the switch expression. -- If the type of the switch expression is `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `bool`, `string`, or an *enum_type*, or if it is the nullable value type corresponding to one of these types, then that is the governing type of the `switch` statement. -- Otherwise, if exactly one user-defined implicit conversion exists from the type of the switch expression to one of the following possible governing types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `string`, or, a nullable value type corresponding to one of those types, then the converted type is the governing type of the `switch` statement. +- If the type of the switch expression is `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `bool`, `string`, or an *enum_type*, or if it is the nullable value type corresponding to one of these types, then that is the governing type of the `switch` statement. +- Otherwise, if exactly one user-defined implicit conversion exists from the type of the switch expression to one of the following possible governing types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `string`, or, a nullable value type corresponding to one of those types, then the converted type is the governing type of the `switch` statement. - Otherwise, the governing type of the `switch` statement is the type of the switch expression. It is an error if no such type exists. There can be at most one `default` label in a `switch` statement. From 6b7fd16a76273ba94ef602a8920b65989b056136 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 15:58:42 -0400 Subject: [PATCH 07/11] Add support for native-sized integers --- standard/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 6d81a34ae..dad76d92c 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -1404,7 +1404,7 @@ A *constant_declaration* may include a set of *attributes* ([§22](attributes.md The *type* of a *constant_declaration* specifies the type of the members introduced by the declaration. The type is followed by a list of *constant_declarator*s ([§13.6.3](statements.md#1363-local-constant-declarations)), each of which introduces a new member. A *constant_declarator* consists of an *identifier* that names the member, followed by an “`=`” token, followed by a *constant_expression* ([§12.23](expressions.md#1223-constant-expressions)) that gives the value of the member. -The *type* specified in a constant declaration shall be `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`, `string`, an *enum_type*, or a *reference_type*. Each *constant_expression* shall yield a value of the target type or of a type that can be converted to the target type by an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). +The *type* specified in a constant declaration shall be `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`, `string`, an *enum_type*, or a *reference_type*. Each *constant_expression* shall yield a value of the target type or of a type that can be converted to the target type by an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). The *type* of a constant shall be at least as accessible as the constant itself ([§7.5.5](basic-concepts.md#755-accessibility-constraints)). @@ -1638,7 +1638,7 @@ These restrictions ensure that all threads will observe volatile writes performe - A *reference_type*. - A *type_parameter* that is known to be a reference type ([§15.2.5](classes.md#1525-type-parameter-constraints)). -- The type `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `char`, `float`, `bool`, `System.IntPtr`, or `System.UIntPtr`. +- The type `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `char`, `float`, `bool`, `System.IntPtr`, or `System.UIntPtr`. - An *enum_type* having an *enum_base* type of `byte`, `sbyte`, `short`, `ushort`, `int`, or `uint`. > *Example*: The example From 4abc062c2d82fd3daf00470812df0766adc741ec Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 16:00:11 -0400 Subject: [PATCH 08/11] Add support for native-sized integers --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index 7d10142b1..84d5f468e 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -112,7 +112,7 @@ Elements of arrays created by *array_creation_expression*s are always initialize ## 17.4 Array element access -Array elements are accessed using *element_access* expressions ([§12.8.11.2](expressions.md#128112-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 element access is a variable, namely the array element selected by the indices. +Array elements are accessed using *element_access* expressions ([§12.8.11.2](expressions.md#128112-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`, `nint`, `nuint`, `ulong`, or can be implicitly converted to one or more of these types. The result of an array element access is a variable, namely the array element selected by the indices. The elements of an array can be enumerated using a `foreach` statement ([§13.9.5](statements.md#1395-the-foreach-statement)). From a645d5096d050082b877829690d1e47216b5dfd2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 16:01:46 -0400 Subject: [PATCH 09/11] Add support for native-sized integers --- standard/enums.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/enums.md b/standard/enums.md index 4439edb88..20ad5a067 100644 --- a/standard/enums.md +++ b/standard/enums.md @@ -44,7 +44,7 @@ enum_body ; ``` -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* From 4f1470e83dfa9d618775351fedd0976cd02ae2fc Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 16:05:40 -0400 Subject: [PATCH 10/11] Add support for native-sized integers --- standard/unsafe-code.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 15a5406eb..f7d72761b 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -281,8 +281,8 @@ In an unsafe context, the set of available implicit conversions ([§10.2](conver Additionally, in an unsafe context, the set of available explicit conversions ([§10.3](conversions.md#103-explicit-conversions)) is extended to include the following explicit pointer conversions: - From any *pointer_type* to any other *pointer_type*. -- From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, or `ulong` to any *pointer_type*. -- From any *pointer_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, or `ulong`. +- From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, or `ulong` to any *pointer_type*. +- From any *pointer_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, or `ulong`. Finally, in an unsafe context, the set of standard implicit conversions ([§10.4.2](conversions.md#1042-standard-implicit-conversions)) includes the following pointer conversions: @@ -614,6 +614,8 @@ T* operator –(T* x, ulong y); long operator –(T* x, T* y); ``` +There are no predefined operators for pointer addition or subtraction with native integer ([§8.3.6]( types.md#836-integral-types)) offsets. Instead, `nint` and `nuint` values shall be promoted to `long` and `ulong`, respectively, with pointer arithmetic using the predefined operators for those types. + Given an expression `P` of a pointer type `T*` and an expression `N` of type `int`, `uint`, `long`, or `ulong`, the expressions `P + N` and `N + P` compute the pointer value of type `T*` that results from adding `N * sizeof(T)` to the address given by `P`. Likewise, the expression `P – N` computes the pointer value of type `T*` that results from subtracting `N * sizeof(T)` from the address given by `P`. Given two expressions, `P` and `Q`, of a pointer type `T*`, the expression `P – Q` computes the difference between the addresses given by `P` and `Q` and then divides that difference by `sizeof(T)`. The type of the result is always `long`. In effect, `P - Q` is computed as `((long)(P) - (long)(Q)) / sizeof(T)`. @@ -947,7 +949,7 @@ A fixed-size buffer declaration may include a set of attributes ([§22](attribut A fixed-size buffer declaration is not permitted to include the `static` modifier. -The buffer element type of a fixed-size buffer declaration specifies the element type of the buffer(s) introduced by the declaration. The buffer element type shall be one of the predefined types `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, or `bool`. +The buffer element type of a fixed-size buffer declaration specifies the element type of the buffer(s) introduced by the declaration. The buffer element type shall be one of the predefined types `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, or `bool`. The buffer element type is followed by a list of fixed-size buffer declarators, each of which introduces a new member. A fixed-size buffer declarator consists of an identifier that names the member, followed by a constant expression enclosed in `[` and `]` tokens. The constant expression denotes the number of elements in the member introduced by that fixed-size buffer declarator. The type of the constant expression shall be implicitly convertible to type `int`, and the value shall be a non-zero positive integer. From e83994517180ed2b9798cd404f0dd7d5f8615312 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 17 Mar 2024 16:30:19 -0400 Subject: [PATCH 11/11] fix example name --- standard/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index 28fc922dd..a5622ef39 100644 --- a/standard/types.md +++ b/standard/types.md @@ -307,7 +307,7 @@ Although `nint` and `nuint` shall be represented by the types `System.IntPtr` an Consider the following: - + ```csharp nint a1 = 1; // OK System.IntPtr a2 = 1; // Error: no implicit conversion