diff --git a/standard/classes.md b/standard/classes.md index 2caf2184d..16e2f9bde 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -3299,6 +3299,8 @@ In a *ref_property_body* an expression body consisting of `=>` followed by `ref` When a property declaration includes an `extern` modifier, the property is said to be an ***external property***. Because an external property declaration provides no actual implementation, each of the *accessor_body*s in its *accessor_declarations* shall be a semicolon. +A type is ***countable*** if it has an instance property named `Length` or `Count` with an accessible `get` accessor ([§15.7.3]( classes.md#1573-accessors)) and a return type of `int`. + ### 15.7.2 Static and instance properties When a property declaration includes a `static` modifier, the property is said to be a ***static property***. When no `static` modifier is present, the property is said to be an ***instance property***. @@ -5542,3 +5544,129 @@ When the body of the async function terminates, the return task is moved out of If the return type of the async function is `void`, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread’s ***synchronization context***. The exact definition of synchronization context is implementation-dependent, but is a representation of “where” the current thread is running. The synchronization context is notified when evaluation of a `void`-returning async function commences, completes successfully, or causes an uncaught exception to be thrown. This allows the context to keep track of how many `void`-returning async functions are running under it, and to decide how to propagate exceptions coming out of them. + +## §indexable-sequence Indexable sequences + +### §indexable-sequence-general General + +An ***indexable sequence*** is an ordered set of zero or more elements having the same type. Any given element can be accessed via an index, and a contiguous subset of elements—referred to as a ***slice***—can be denoted via a range. + +An index is represented by a read-only variable of the value type `System.Index`. A range is represented by a read-only variable of value type `System.Range`, which contains a start and end index. A slice of an array is represented by a (possibly empty) array. The representation of a slice of a user-defined type is determined by the implementer of that type. + +For an indexable sequence of length *N*, elements can be accessed using indexes 0 through *N-1*, which are relative to the start. Elements can also be accessed relative to the end via the `^` index-from-end operator (§index-from-end-operator). `^0` denotes the (non-existent) element just beyond the end. + +A slice can be obtained using the `..` range operator (§range-operator). A range of the form `s..e` starts at element `s` and ends with the element immediately prior to element `e`. + +All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access). + +An object of type `string` is an indexable sequence. + +A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type. + +### §indexable-sequence-support-for-index Providing support for Index + +#### §indexable-sequence-expl-support-for-index Explicit Index support + +A type having an instance indexer taking a single argument of type `System.Index`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access). + +> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Individual bits are accessed for read/write via an `int` indexer. Adding an `Index` indexer that simply interprets the `Index` argument as an `int`, is simple: +> +> +> ```csharp +> partial class BitArray +> { +> public bool this[Index idx] +> { +> get +> { +> return this[idx.GetOffset(Length)]; // use the [int] indexer +> } +> set +> { +> this[idx.GetOffset(Length)] = value; // use the [int] indexer +> } +> } +> } +> ``` +> +> *end example* + +#### §indexable-sequence-impl-support-for-index Implicit Index support + +An implementation shall behave as if it provides a non-virtual instance indexer member with a single parameter of type `System.Index` for any type that meets the following criteria: + +- The type is countable ([§15.7.1](classes.md#1571-general)). +- The type has an accessible instance indexer taking an argument of type `int` as its only argument. +- The type does not have an accessible instance indexer taking a `System.Index` as its only argument, or as its first argument with the remaining arguments being optional. + +The provided instance indexer shall have the same get and set members with matching accessibility as the `int` indexer. + +The provided instance indexer shall take the given `System.Index` and use that to call the instance indexer taking an `int`. If both the `Length` and `Count` properties exist and are accessible, `Length` is used. + +> *Note*: See §indexable-sequence-expl-support-for-index for an example of an explicitly provided `Index` indexer. If that were not defined, its equivalent would be provided by the implementation. *end note* + +### §indexable-sequence-support-for-range Providing support for Range + +#### §indexable-sequence-expl-support-for-range Explicit Range support + +A type having an instance indexer taking a single argument of type `System.Range`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access). + +> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Adding a `Range` indexer that returns a `BitArray` representing the bit slice designated by the Range, is simple: +> +> +> ```csharp +> partial class BitArray +> { +> public BitArray this[Range range] // note the return type +> { +> get +> { +> int startIdx = range.Start.GetOffset(Length); +> int endIdx = range.End.GetOffset(Length); +> int rangeLength = endIdx - startIdx; +> BitArray newBitArray = new BitArray(rangeLength); +> for (int i = startIdx; i < endIdx; ++i) +> { +> newBitArray[i - startIdx] = this[i]; +> } +> return newBitArray; +> } +> } +> } +> ``` +> +> *end example* + +#### §indexable-sequence-impl-support-for-range Implicit Range support + +An implementation shall behave as if it provides a non-virtual instance indexer member with a single parameter of type `System.Range` for any type that meets the following criteria: + +- The type is countable ([§15.7.1](classes.md#1571-general)). +- The type has an accessible instance method named `Slice` taking two arguments of type `int` as the only arguments. For type `string`, the method `Substring` is used instead of `Slice`. + > *Note*: As specified in [§12.8.11.2](expressions.md#128112-array-access), for array access, the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` is used instead of `Slice`. *end note* +- The type does not have an accessible instance indexer taking a `System.Range` as its only argument, or as its first argument with the remaining arguments being optional. + +The provided instance indexer shall have the same accessibility and return type, including `ref` if present, as `Slice`. + +When the type is indexed with a `System.Range`, the provided instance indexer shall take the given range and pass its start index and length as `int`s to `Slice` (or in the case of `string`, to method `Substring`). + +> *Note*: See §indexable-sequence-expl-support-for-range for an example of an explicitly provided `Range` indexer. If that were not defined, its equivalent would be provided by the implementation, except that the provided indexer would call `Slice` to create and copy the slice. For type `BitArray`, `Slice` might be defined, as follows: +> +> +> ```csharp +> partial class BitArray +> { +> public BitArray Slice(int startIdx, int rangeLength) +> { +> int endIdx = startIdx + rangeLength; +> BitArray newBitArray = new BitArray(rangeLength); +> for (int i = startIdx; i < endIdx; ++i) +> { +> newBitArray[i - startIdx] = this[i]; +> } +> return newBitArray; +> } +> } +> ``` +> +> *end note* diff --git a/standard/expressions.md b/standard/expressions.md index cf3fcd4ea..5cfd085b8 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -148,7 +148,8 @@ The precedence of an operator is established by the definition of its associated > | **Subclause** | **Category** | **Operators** | > | ----------------- | ------------------------------- | -------------------------------------------------------| > | [§12.8](expressions.md#128-primary-expressions) | Primary | `x.y` `x?.y` `f(x)` `a[x]` `a?[x]` `x++` `x--` `x!` `new` `typeof` `default` `checked` `unchecked` `delegate` `stackalloc` | -> | [§12.9](expressions.md#129-unary-operators) | Unary | `+` `-` `!x` `~` `++x` `--x` `(T)x` `await x` | +> | §range-operator | Range | `..` | +> | [§12.9](expressions.md#129-unary-operators) | Unary | `+` `-` `!` `~` `^` `++x` `--x` `(T)x` `await x` | > | [§12.10](expressions.md#1210-arithmetic-operators) | Multiplicative | `*` `/` `%` | > | [§12.10](expressions.md#1210-arithmetic-operators) | Additive | `+` `-` | > | [§12.11](expressions.md#1211-shift-operators) | Shift | `<<` `>>` | @@ -348,8 +349,8 @@ In both of the above cases, a cast expression can be used to explicitly convert ***Lifted operators*** permit predefined and user-defined operators that operate on non-nullable value types to also be used with nullable forms of those types. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described in the following: -- For the unary operators `+`, `++`, `-`, `--`, `!`(logical negation), and `~`, a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single `?` modifier to the operand and result types. The lifted operator produces a `null` value if the operand is `null`. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result. -- For the binary operators `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `<<`, and `>>`, a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single `?` modifier to each operand and result type. The lifted operator produces a `null` value if one or both operands are `null` (an exception being the `&` and `|` operators of the `bool?` type, as described in [§12.13.5](expressions.md#12135-nullable-boolean--and--operators)). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result. +- For the unary operators `+`, `++`, `-`, `--`, `!`(logical negation), `~`, and `^`, a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single `?` modifier to the operand and result types. The lifted operator produces a `null` value if the operand is `null`. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result. +- For the binary operators `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `<<`, `>>`, and `..`, a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single `?` modifier to each operand and result type. The lifted operator produces a `null` value if one or both operands are `null` (an exception being the `&` and `|` operators of the `bool?` type, as described in [§12.13.5](expressions.md#12135-nullable-boolean--and--operators)). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result. - For the equality operators `==` and `!=`, a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is `bool`. The lifted form is constructed by adding a single `?` modifier to each operand type. The lifted operator considers two `null` values equal, and a `null` value unequal to any non-`null` value. If both operands are non-`null`, the lifted operator unwraps the operands and applies the underlying operator to produce the `bool` result. - For the relational operators `<`, `>`, `<=`, and `>=`, a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is `bool`. The lifted form is constructed by adding a single `?` modifier to each operand type. The lifted operator produces the value `false` if one or both operands are `null`. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the `bool` result. @@ -2227,17 +2228,103 @@ If the *primary_no_array_creation_expression* of an *element_access* is a value #### 12.8.12.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 shall not 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. -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*. +For single-dimension array access the argument expression can also be of type `System.Index` or `System.Range`, or implicitly convertible to one of these. + +See ([§12.8.17.3](expressions.md#128173-object-initializers) for a constraint on using `System.Index` in the context of an *initializer_target*. + +The result of evaluating an array access that does not involve `System.Range` 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*. + +The result of evaluating an array access that involves `System.Range` is a slice (§indexable-sequence-general) of the array being accessed, as selected by the value(s) of the expression(s) in the *argument_list*. The resulting slice has the same type as the array being accessed, both at compile time and runtime. The run-time processing of an array access of the form `P[A]`, where `P` is a *primary_no_array_creation_expression* of an *array_type* and `A` is an *argument_list*, consists of the following steps: - `P` is evaluated. If this evaluation causes an exception, no further steps are executed. -- The index expressions of the *argument_list* are evaluated in order, from left to right. Following evaluation of each index expression, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) to one of the following types is performed: `int`, `uint`, `long`, `ulong`. The first type in this list for which an implicit conversion exists is chosen. For instance, if the index expression is of type `short` then an implicit conversion to `int` is performed, since implicit conversions from `short` to `int` and from `short` to `long` are possible. If evaluation of an index expression or the subsequent implicit conversion causes an exception, then no further index expressions are evaluated and no further steps are executed. -- The value of `P` is checked to be valid. If the value of `P` is `null`, a `System.NullReferenceException` is thrown and no further steps are executed. -- The value of each expression in the *argument_list* is checked against the actual bounds of each dimension of the array instance referenced by `P`. If one or more values are out of range, a `System.IndexOutOfRangeException` is thrown and no further steps are executed. -- The location of the array element given by the index expression(s) is computed, and this location becomes the result of the array access. +- The index expressions of the *argument_list* are evaluated in order, from left to right. Following evaluation of each index expression, for expressions not having type `System.Index` or `System.Range`, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) to one of the following types is performed: `int`, `uint`, `long`, `ulong`. The first type in this list for which an implicit conversion exists is chosen. For instance, if the index expression is of type `short` then an implicit conversion to `int` is performed, since implicit conversions from `short` to `int` and from `short` to `long` are possible. For an index expression having type `System.Index`, the Index is transformed into the corresponding `int` index using `System.Index.GetOffset`, which calculates the offset from the start of the collection using the specified collection length. + +- For an index expression not having type `System.Range`: + - If evaluation of an index expression, the subsequent implicit conversion, or Index transformation causes an exception, then no further index expressions are evaluated, and no further steps are executed. + - The value of `P` is checked to be valid. If the value of `P` is `null`, a `System.NullReferenceException` is thrown and no further steps are executed. + - The value of each expression in the *argument_list* is checked against the actual bounds of each dimension of the array instance referenced by `P`. If one or more values are out of range, a `System.IndexOutOfRangeException` is thrown and no further steps are executed. + - The location of the array element given by the index expression(s) is computed, and this location becomes the result of the array access. +- Else, for an index expression having type `System.Range`: + - The value of `P` is checked to be valid. If the value of `P` is `null`, a `System.NullReferenceException` is thrown and no further steps are executed. + - The Range is transformed into the corresponding slice using `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray`. If this transformation causes an exception, no further steps are executed. + - The result of the transformation becomes the result of the array access. + +> *Example*: Given the following one-dimensional array and Index: +> +> +> ```csharp +> string[] words = new string[] { "red", "green", "blue" }; +> Index idx = 1; +> Console.WriteLine(words[idx]); // green +> Console.WriteLine(words[^0]); // IndexOutOfRangeException +> ``` +> +> `words[idx]` is transformed by the implementation to `words[idx.GetOffset(words.Length)]`. Similarly, `words[^0]` is transformed by the implementation to `words[(^0).GetOffset(words.Length)]`, which results in an exception. +> +> Given the following jagged array and Indexes: +> +> +> ```csharp +> int[][] values = new int[][] { new int[] { 10, 9 }, new int[] { 6, 12, 17 }}; +> Index idx1 = 1; +> Index idx2 = ^1; +> Console.WriteLine(values[idx1][idx2]); // 17 (values[1][2]) +> ``` +> +> `values[idx1][idx2]` is transformed by the implementation to +> +> ```csharp +> values[idx1.GetOffset(values.Length)][idx2.GetOffset(values[idx1].Length)] +> ``` +> +> Given the following multidimensional array and Indexes: +> +> +> ```csharp +> int[,] values2D = {{10, 5, 7, 1}, {34, 13, 6, 2}}; +> Index idx3 = 1; +> Index idx4 = ^1; +> Console.WriteLine(values2D[idx3, idx4]); // won't compile! +> ``` +> +> as there is no implicit conversion from `System.Index` to `int`, what one might like to express simply as `values2D[idx3, idx4]` must instead be written explicitly as +> +> ```csharp +> values2D[idx3.GetOffset(values2D.GetUpperBound(0) + 1), idx4.GetOffset(values2D.GetUpperBound(1) + 1)] +> +> ``` +> +> Given the following one-dimensional array: +> +> +> ```csharp +> string[] seasons = new string[] { "Summer", "Autumn", "Winter", "Spring" }; +> string[] names = seasons[0..2]; // slice containing "Summer" and "Autumn" +> ``` +> +> `seasons[0..2]` is transformed by the implementation to +> +> +> ```csharp +> System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray(seasons, 0..2) +> ``` +> +> which returns a `string[]` slice. +> +> Given the following jagged array: +> +> +> ```csharp +> int[][] values = new int[][] { new int[] { 10, 9, 5 }, +> new int[] { 6, 12, 17, 32 }, new int[] { 28, 42 } }; +> Console.WriteLine(values[1..3][^1][..2][^1]); // 42 +> ``` +> +> all the Range and Index expressions in `values[1..3][^1][..2][^1]` are transformed by the implementation, resulting in `values[2][1]`, which is 42. *end example* #### 12.8.12.3 Indexer access @@ -2256,6 +2343,8 @@ The binding-time processing of an indexer access of the form `P[A]`, where `P` i Depending on the context in which it is used, an indexer access causes invocation of either the get accessor or the set accessor of the indexer. If the indexer access is the target of an assignment, the set accessor is invoked to assign a new value ([§12.21.2](expressions.md#12212-simple-assignment)). In all other cases, the get accessor is invoked to obtain the current value ([§12.2.2](expressions.md#1222-values-of-expressions)). +> *Note*: An implementation is required to provide an instance indexer member with a single parameter of type `Index` for any type that meets the criteria specified in §indexable-sequence-impl-support-for-index. An implementation is required to provide an instance indexer member with a single parameter of type `Range` for any type that meets the criteria specified in §indexable-sequence-impl-support-for-range. *end note* + ### 12.8.13 Null Conditional Element Access A *null_conditional_element_access* consists of a *primary_no_array_creation_expression* followed by the two tokens “`?`” and “`[`”, followed by an *argument_list*, followed by a “`]`” token, followed by zero or more *dependent_access*es any of which can be preceded by a *null_forgiving_operator*. @@ -2506,7 +2595,7 @@ An object initializer consists of a sequence of member initializers, enclosed by > *Note*: While an object initializer is not permitted to set the same field or property more than once, there are no such restrictions for indexers. An object initializer may contain multiple initializer targets referring to indexers, and may even use the same indexer arguments multiple times. *end note* -Each *initializer_target* is followed by an equals sign and either an expression, an object initializer or a collection initializer. It is not possible for expressions within the object initializer to refer to the newly created object it is initializing. +Each *initializer_target* is followed by an equals sign and either an expression, an object initializer or a collection initializer. It is not possible for expressions within the object initializer to refer to the newly created object it is initializing. It is a compile-time error for *initializer_target* to have an *argument_list* with an *argument* of type `System.Index`. A member initializer that specifies an expression after the equals sign is processed in the same way as an assignment ([§12.21.2](expressions.md#12212-simple-assignment)) to the target. @@ -3444,7 +3533,7 @@ An *anonymous_method_expression* is one of two ways of defining an anonymous fun ### 12.9.1 General -The `+`, `-`, `!` (logical negation [§12.9.4](expressions.md#1294-logical-negation-operator) only), `~`, `++`, `--`, cast, and `await` operators are called the unary operators. +The `+`, `-`, `!` (logical negation [§12.9.4](expressions.md#1294-logical-negation-operator) only), `~`, `^`, `++`, `--`, cast, and `await` operators are called the unary operators. > *Note*: The postfix null-forgiving operator ([§12.8.9](expressions.md#1289-null-forgiving-expressions)), `!`, due to its compile-time and non-overloadable only nature, is excluded from the above list. *end note* @@ -3455,6 +3544,7 @@ unary_expression | '-' unary_expression | logical_negation_operator unary_expression | '~' unary_expression + | '^' unary_expression | pre_increment_expression | pre_decrement_expression | cast_expression @@ -3557,6 +3647,47 @@ The result of evaluating `~x`, where `X` is an expression of an enumeration ty Lifted ([§12.4.8](expressions.md#1248-lifted-operators)) forms of the unlifted predefined bitwise complement operators defined above are also predefined. +### §index-from-end-operator Index-from-end operator + +This operator provides a succinct syntax for denoting the position of an element in an indexable sequence (§indexable-sequence) relative to the end of that sequence. + +For an operation of the form `^x`, unary operator overload resolution ([§12.4.4](expressions.md#1244-unary-operator-overload-resolution)) is applied to select a specific operator implementation. The operand is converted to the parameter type of the selected operator, and the type of the result is the return type of the operator. Only one predefined index-from-end operator exists: + +> +```csharp +System.Index operator ^(int fromEnd); +``` + +For this operator, an object of (the immutable struct) type `System.Index` is returned that denotes element number `fromEnd` from the end of any indexable sequence. `^n` is shorthand for `new System.Index(n, fromEnd: true)`. + +If after implicit conversion to `int` the operand has a negative value, an exception of type `System.ArgumentOutOfRangeException` is thrown. + +Lifted ([§12.4.8](expressions.md#1248-lifted-operators)) forms of the unlifted predefined index-from-end operator defined above are also predefined. + +> *Example*: The following example uses array and string indexable sequences: +> +> +> ```csharp +> string[] words = new string[] { "red", "green", "blue" }; +> string str; +> str = words[^1]; // OK: "blue" +> str = words[^3]; // OK: "red" +> //str = words[^0]; // refers to the (non-existent) element beyond the end +> +> Index idx = ^0; // OK; no attempt made to access any non-existent element +> int i = -1; +> idx = ^(ushort)i; // OK; ^65535 (0xFFFF) +> //idx = ^(short)i; // System.ArgumentOutOfRangeException +> +> string s = "Hello!"; +> int? iN = 5; +> Index? idx4 = ^iN; // OK: non-null, ^5 +> char c = s[^idx4.Value.Value]; // OK: "e" +> ``` +> +> `^idx4.Value.Value` is the `int` position from the end of the sequence designated by the `Index` wrapped in the `Index?` designated by `idx4`. (`System.Nullable`and `System.Index` both have public read-only properties called `Value`.) +> *end example* + ### 12.9.6 Prefix increment and decrement operators ```ANTLR @@ -3688,6 +3819,55 @@ At run-time, the expression `await t` is evaluated as follows: An awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` should cause the delegate `r` to be invoked at most once. Otherwise, the behavior of the enclosing async function is undefined. +## §range-operator Range operator + +This operator provides a succinct syntax for specifying a (possibly empty) element range suitable for use in denoting a slice of an indexable sequence (§indexable-sequence). + +```ANTLR +range_expression + : unary_expression + | range_expression? '..' range_expression? + ; +``` + +For an operation of the form `s .. e`, binary operator overload resolution ([§12.4.5](expressions.md#1245-binary-operator-overload-resolution)) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator. *range_expression* shall have type `System.Index` or a type that can be converted implicitly to that type. Only one predefined range operator exists: + +> +```csharp +System.Range operator ..(System.Index start = 0, System.Index end = ^0); +``` + +The left and right operands denote, respectively, a start and end Index. For this operator, an object of (the immutable struct) type `System.Range` is returned that contains those Indexes. If the left operand is omitted, an Index of `0` is used. If the right operand is omitted, an Index of `^0` is used. As such, + +- `s .. e` is transformed by the implementation to `new System.Range(s, e)`. +- `s ..` is transformed by the implementation to `new System.Range(s, ^0)`. +- `.. e` is transformed by the implementation to `new System.Range(0, e)`. +- `..` is transformed by the implementation to `new System.Range(0, ^0)`. + +> *Note*: While a Range can be created with a start Index greater than the end Index, any attempt to use that Range to denote a slice from an indexable sequence will result in `System.ArgumentOutOfRangeException`. *end note* + +Lifted ([§12.4.8](expressions.md#1248-lifted-operators)) forms of the unlifted predefined range operator defined above are also predefined. + +> *Example*: The following example uses array and string indexable sequences: +> +> +> ```csharp +> string[] seasons = new string[] { "Summer", "Autumn", "Winter", "Spring" }; +> string[] slice; +> slice = seasons[1..3]; // string[2] "Autumn", "Winter" +> slice = seasons[^2..^1]; // string[1] "Winter" +> slice = seasons[2..]; // string[2] "Winter", "Spring" +> slice = seasons[1..1]; // string[0] +> +> string s1 = "Hello!"; +> Index? startN = 1; +> Index? endN = ^2; +> Range? r = startN .. endN; +> string s2 = s1[r.Value]; // "ell" +> ``` +> +> *end example* + ## 12.10 Arithmetic operators ### 12.10.1 General @@ -3696,10 +3876,10 @@ The `*`, `/`, `%`, `+`, and `-` operators are called the arithmetic operators. ```ANTLR multiplicative_expression - : unary_expression - | multiplicative_expression '*' unary_expression - | multiplicative_expression '/' unary_expression - | multiplicative_expression '%' unary_expression + : range_expression + | multiplicative_expression '*' range_expression + | multiplicative_expression '/' range_expression + | multiplicative_expression '%' range_expression ; additive_expression diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 49c0a1008..b71012dc3 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1004,7 +1004,7 @@ operator_or_punctuator | '+' | '-' | ASTERISK | SLASH | '%' | '&' | '|' | '^' | '!' | '~' | '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||' | '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%=' - | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>' + | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>' | '..' ; right_shift diff --git a/standard/standard-library.md b/standard/standard-library.md index 5aa2dace7..d3f91f8d2 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -379,11 +379,25 @@ The following types, including the members listed, shall be defined in a conform A conforming implementation may provide `Task.GetAwaiter()` and `Task.GetAwaiter()` as extension methods. +> Note to TG2 reviewers: Required vs. Optional library type members: We need to indicate which members of `Index` and `Range` are required and which are optional. + ```csharp namespace System { public class FormattableString : IFormattable { } + public readonly struct Index : IEquatable + { + public Index(int value, bool fromEnd = false); + public int Value { get; } + public bool Equals(Index other); + public override bool Equals(object? value); + public override int GetHashCode(); + public int GetOffset(int length); + public override string ToString(); + public static implicit operator Index(int value); + } + public class OperationCanceledException : Exception { public OperationCanceledException(); @@ -391,6 +405,18 @@ namespace System public OperationCanceledException(string? message, Exception? innerException); } + public struct Range : IEquatable + { + public Range (Index start, Index end); + public Index End { get; } + public Index Start { get; } + public override bool Equals(object? value); + public bool Equal (Range other); + public override int GetHashCode(); + public (int,int) GetOffsetAndLength(int length); + public override string ToString(); + } + public readonly ref struct ReadOnlySpan { public int Length { get; } @@ -618,7 +644,12 @@ namespace System.Runtime.CompilerServices void OnCompleted(Action continuation); } - public readonly struct TaskAwaiter : ICriticalNotifyCompletion, + public static class RuntimeHelpers + { + public static T[] GetSubArray(T[] array, System.Range range); + } + + public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } @@ -1077,6 +1108,7 @@ The following library types are referenced in this specification. The full names - `global::System.GC` - `global::System.IDisposable` - `global::System.IFormattable` +- `global::System.Index` - `global::System.IndexOutOfRangeException` - `global::System.Int16` - `global::System.Int32` @@ -1093,6 +1125,7 @@ The following library types are referenced in this specification. The full names - `global::System.OutOfMemoryException` - `global::System.OverflowException` - `global::System.ReadOnlySpan` +- `global::System.Range` - `global::System.SByte` - `global::System.Single` - `global::System.Span` @@ -1144,6 +1177,7 @@ The following library types are referenced in this specification. The full names - `global::System.Runtime.CompilerServices.ICriticalNotifyCompletion` - `global::System.Runtime.CompilerServices.IndexerNameAttribute` - `global::System.Runtime.CompilerServices.INotifyCompletion` +- `global::System.Runtime.CompilerServices.RuntimeHelpers` - `global::System.Runtime.CompilerServices.TaskAwaiter` - `global::System.Runtime.CompilerServices.TaskAwaiter` - `global::System.Runtime.CompilerServices.ValueTaskAwaiter` diff --git a/tools/example-templates/additional-files/BitArrayPartial1.cs b/tools/example-templates/additional-files/BitArrayPartial1.cs index a3cb12fee..c82b8ddfd 100644 --- a/tools/example-templates/additional-files/BitArrayPartial1.cs +++ b/tools/example-templates/additional-files/BitArrayPartial1.cs @@ -1,66 +1,66 @@ -class Test -{ - static void Main() - { - BitArray ba1 = new BitArray(100); - ba1[0] = true; - ba1[^98] = true; - ba1[99] = true; - Console.WriteLine("ba1[0] = {0}", ba1[0]); // True - Console.WriteLine("ba1[98] = {0}", ba1[98]); // False - Console.WriteLine("ba1[Index 0] = {0}", ba1[new Index(0)]); // True - Console.WriteLine("ba1[^1] = {0}", ba1[^1]); // True - } -} - -partial class BitArray -{ - int[] bits; - int length; - - public BitArray(int length) - { - if (length < 0) - { - throw new ArgumentException(); - } - else if (length == 0) - { - bits = new int[1]; - } - else - { - bits = new int[((length - 1) >> 5) + 1]; - } - this.length = length; - } - - public int Length => length; - - public bool this[int index] - { - get - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - return (bits[index >> 5] & 1 << index) != 0; - } - set - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - if (value) - { - bits[index >> 5] |= 1 << index; - } - else - { - bits[index >> 5] &= ~(1 << index); - } - } - } -} \ No newline at end of file +class Test +{ + static void Main() + { + BitArray ba1 = new BitArray(100); + ba1[0] = true; + ba1[^98] = true; + ba1[99] = true; + Console.WriteLine("ba1[0] = {0}", ba1[0]); // True + Console.WriteLine("ba1[98] = {0}", ba1[98]); // False + Console.WriteLine("ba1[Index 0] = {0}", ba1[new Index(0)]); // True + Console.WriteLine("ba1[^1] = {0}", ba1[^1]); // True + } +} + +partial class BitArray +{ + int[] bits; + int length; + + public BitArray(int length) + { + if (length < 0) + { + throw new ArgumentException(); + } + else if (length == 0) + { + bits = new int[1]; + } + else + { + bits = new int[((length - 1) >> 5) + 1]; + } + this.length = length; + } + + public int Length => length; + + public bool this[int index] + { + get + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + return (bits[index >> 5] & 1 << index) != 0; + } + set + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + if (value) + { + bits[index >> 5] |= 1 << index; + } + else + { + bits[index >> 5] &= ~(1 << index); + } + } + } +} diff --git a/tools/example-templates/additional-files/BitArrayPartial2.cs b/tools/example-templates/additional-files/BitArrayPartial2.cs index a09537b36..1e634efee 100644 --- a/tools/example-templates/additional-files/BitArrayPartial2.cs +++ b/tools/example-templates/additional-files/BitArrayPartial2.cs @@ -1,115 +1,115 @@ -class Test -{ - static void Main() - { - BitArray ba = new BitArray(5); - ba[0] = true; - ba[3] = true; - ba[^1] = true; - Console.WriteLine("ba = >{0}<", ba); - - Range[] testRange = { -// all elements - /*0..5,*/ 0.., ..5, //.., 0..^0, ..^0, ^5..5, ^5.., ^5..^0, - -// trailing part -// 1..5, 1.., 1..^0, ^4..5, ^4.., ^4..^0, - -// leading part -// 0..4, ..4, ^5..4, ^5..^1, - -// middle part: -// 1..4, ^4..4, ^4..^1, 1..^1, -// 2..4, ^3..4, ^3..^1, 2..^1, - 3..4, ^2..4, //^2..^1, 3..^1, - -// empty range - 0..0//, 1..1, 2..2, 3..3, 4..4, 5..5 - }; - - foreach (Range r in testRange) - { - Console.WriteLine($"BitArray is >{ba[r]}<"); - } - } -} - -partial class BitArray -{ - int[] bits; - int length; - - public BitArray(int length) - { - if (length < 0) - { - throw new ArgumentException(); - } - else if (length == 0) - { - bits = new int[1]; - } - else - { - bits = new int[((length - 1) >> 5) + 1]; - } - this.length = length; - } - - public int Length => length; - - public bool this[int index] - { - get - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - return (bits[index >> 5] & 1 << index) != 0; - } - set - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - if (value) - { - bits[index >> 5] |= 1 << index; - } - else - { - bits[index >> 5] &= ~(1 << index); - } - } - } - public override string ToString() - { - string retstr = ""; - int bitsWord; - int upBound = bits.GetUpperBound(0); - int bitCounter = Length; - - if (Length == 0) - { - return retstr; - } - - for (int i = 0; i <= upBound; ++i) - { - bitsWord = bits[i]; - for (int j = 0; j < 32; ++j) - { - if (bitCounter-- == 0) - { - break; - } - retstr += ((bitsWord & 1) == 1) ? "1" : "0"; - bitsWord >>= 1; - } - } - - return retstr; - } -} \ No newline at end of file +class Test +{ + static void Main() + { + BitArray ba = new BitArray(5); + ba[0] = true; + ba[3] = true; + ba[^1] = true; + Console.WriteLine("ba = >{0}<", ba); + + Range[] testRange = { +// all elements + /*0..5,*/ 0.., ..5, //.., 0..^0, ..^0, ^5..5, ^5.., ^5..^0, + +// trailing part +// 1..5, 1.., 1..^0, ^4..5, ^4.., ^4..^0, + +// leading part +// 0..4, ..4, ^5..4, ^5..^1, + +// middle part: +// 1..4, ^4..4, ^4..^1, 1..^1, +// 2..4, ^3..4, ^3..^1, 2..^1, + 3..4, ^2..4, //^2..^1, 3..^1, + +// empty range + 0..0//, 1..1, 2..2, 3..3, 4..4, 5..5 + }; + + foreach (Range r in testRange) + { + Console.WriteLine($"BitArray is >{ba[r]}<"); + } + } +} + +partial class BitArray +{ + int[] bits; + int length; + + public BitArray(int length) + { + if (length < 0) + { + throw new ArgumentException(); + } + else if (length == 0) + { + bits = new int[1]; + } + else + { + bits = new int[((length - 1) >> 5) + 1]; + } + this.length = length; + } + + public int Length => length; + + public bool this[int index] + { + get + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + return (bits[index >> 5] & 1 << index) != 0; + } + set + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + if (value) + { + bits[index >> 5] |= 1 << index; + } + else + { + bits[index >> 5] &= ~(1 << index); + } + } + } + public override string ToString() + { + string retstr = ""; + int bitsWord; + int upBound = bits.GetUpperBound(0); + int bitCounter = Length; + + if (Length == 0) + { + return retstr; + } + + for (int i = 0; i <= upBound; ++i) + { + bitsWord = bits[i]; + for (int j = 0; j < 32; ++j) + { + if (bitCounter-- == 0) + { + break; + } + retstr += ((bitsWord & 1) == 1) ? "1" : "0"; + bitsWord >>= 1; + } + } + + return retstr; + } +} diff --git a/tools/example-templates/additional-files/BitArrayPartial3.cs b/tools/example-templates/additional-files/BitArrayPartial3.cs index f26cdf5b6..95c2e09c3 100644 --- a/tools/example-templates/additional-files/BitArrayPartial3.cs +++ b/tools/example-templates/additional-files/BitArrayPartial3.cs @@ -1,116 +1,116 @@ -class Test -{ - static void Main() - { - BitArray ba = new BitArray(5); - ba[0] = true; - ba[3] = true; - ba[^1] = true; - Console.WriteLine("ba = >{0}<", ba); - - Range[] testRange = { -// all elements - /*0..5,*/ 0.., ..5, //.., 0..^0, ..^0, ^5..5, ^5.., ^5..^0, - -// trailing part -// 1..5, 1.., 1..^0, ^4..5, ^4.., ^4..^0, - -// leading part -// 0..4, ..4, ^5..4, ^5..^1, - -// middle part: -// 1..4, ^4..4, ^4..^1, 1..^1, -// 2..4, ^3..4, ^3..^1, 2..^1, - 3..4, ^2..4, //^2..^1, 3..^1, - -// empty range - 0..0//, 1..1, 2..2, 3..3, 4..4, 5..5 - }; - - foreach (Range r in testRange) - { - Console.WriteLine($"BitArray is >{ba[r]}<"); - } - } -} - -partial class BitArray -{ - int[] bits; - int length; - - public BitArray(int length) - { - if (length < 0) - { - throw new ArgumentException(); - } - else if (length == 0) - { - bits = new int[1]; - } - else - { - bits = new int[((length - 1) >> 5) + 1]; - } - this.length = length; - } - - public int Length => length; - - public override string ToString() - { - string retstr = ""; - int bitsWord; - int upBound = bits.GetUpperBound(0); - int bitCounter = Length; - - if (Length == 0) - { - return retstr; - } - - for (int i = 0; i <= upBound; ++i) - { - bitsWord = bits[i]; - for (int j = 0; j < 32; ++j) - { - if (bitCounter-- == 0) - { - break; - } - retstr += ((bitsWord & 1) == 1) ? "1" : "0"; - bitsWord >>= 1; - } - } - - return retstr; - } - - public bool this[int index] - { - get - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - return (bits[index >> 5] & 1 << index) != 0; - } - set - { - if (index < 0 || index >= length) - { - throw new IndexOutOfRangeException(); - } - if (value) - { - bits[index >> 5] |= 1 << index; - } - else - { - bits[index >> 5] &= ~(1 << index); - } - } - } -} \ No newline at end of file +class Test +{ + static void Main() + { + BitArray ba = new BitArray(5); + ba[0] = true; + ba[3] = true; + ba[^1] = true; + Console.WriteLine("ba = >{0}<", ba); + + Range[] testRange = { +// all elements + /*0..5,*/ 0.., ..5, //.., 0..^0, ..^0, ^5..5, ^5.., ^5..^0, + +// trailing part +// 1..5, 1.., 1..^0, ^4..5, ^4.., ^4..^0, + +// leading part +// 0..4, ..4, ^5..4, ^5..^1, + +// middle part: +// 1..4, ^4..4, ^4..^1, 1..^1, +// 2..4, ^3..4, ^3..^1, 2..^1, + 3..4, ^2..4, //^2..^1, 3..^1, + +// empty range + 0..0//, 1..1, 2..2, 3..3, 4..4, 5..5 + }; + + foreach (Range r in testRange) + { + Console.WriteLine($"BitArray is >{ba[r]}<"); + } + } +} + +partial class BitArray +{ + int[] bits; + int length; + + public BitArray(int length) + { + if (length < 0) + { + throw new ArgumentException(); + } + else if (length == 0) + { + bits = new int[1]; + } + else + { + bits = new int[((length - 1) >> 5) + 1]; + } + this.length = length; + } + + public int Length => length; + + public override string ToString() + { + string retstr = ""; + int bitsWord; + int upBound = bits.GetUpperBound(0); + int bitCounter = Length; + + if (Length == 0) + { + return retstr; + } + + for (int i = 0; i <= upBound; ++i) + { + bitsWord = bits[i]; + for (int j = 0; j < 32; ++j) + { + if (bitCounter-- == 0) + { + break; + } + retstr += ((bitsWord & 1) == 1) ? "1" : "0"; + bitsWord >>= 1; + } + } + + return retstr; + } + + public bool this[int index] + { + get + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + return (bits[index >> 5] & 1 << index) != 0; + } + set + { + if (index < 0 || index >= length) + { + throw new IndexOutOfRangeException(); + } + if (value) + { + bits[index >> 5] |= 1 << index; + } + else + { + bits[index >> 5] &= ~(1 << index); + } + } + } +}