diff --git a/standard/attributes.md b/standard/attributes.md index a64dda4e7..7b8996a54 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -500,6 +500,7 @@ A number of attributes affect the language in some way. These attributes include - `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` and `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, which are used to declare a custom interpolated string expression handler ([§23.5.10.1](attributes.md#235101-custom-interpolated-string-expression-handlers)) and to call one of its constructors, respectively. - System.Diagnostics.CodeAnalysis.UnscopedRefAttribute ([§23.5.8](attributes.md#2358-the-unscopedref-attribute)), which allows an otherwise implicitly scoped ref to be treated as not being scoped. - `System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute` ([§23.5.11.1](attributes.md#235111-the-setsrequiredmembers-attribute)) and `System.Runtime.CompilerServices.RequiredMemberAttribute` ([§23.5.11.2](attributes.md#235112-the-requiredmember-attribute)), which are used in required-member contexts ([§15.7.1](classes.md#1571-general)). +- `System.Runtime.CompilerServices.InlineArrayAttribute` (§InlineArrayAttribute), which marks a struct type as an inline array type (§InlineArray). The Nullable static analysis attributes ([§23.5.7](attributes.md#2357-code-analysis-attributes)) can improve the correctness of warnings generated for nullabilities and null states ([§8.9.5](types.md#895-nullabilities-and-null-states)). @@ -1523,6 +1524,10 @@ This attribute indicates that the constructor it decorates sets all required mem This attribute indicates that the current type has one or more required members ([§15.7.1](classes.md#1571-general)), or that a specific member of that type is required. However, it is an error for this attribute to be used explicitly. Instead, the presence of the modifier `required` results in the type or member being treated as if it were decorated with this attribute. +### §InlineArrayAttribute The InlineArray attribute + +The attribute `InlineArray` is used to identify a non-record struct as an inline array type. For further information and examples of its use, see §InlineArray. + ## 23.6 Attributes for interoperation For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name. diff --git a/standard/conversions.md b/standard/conversions.md index 0144c757c..07d05bf0c 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -443,6 +443,16 @@ Although an implicit conversion to `object` is permitted, a warning shall be iss > > *end example* +### §ImplicitInlineArrayConversions Implicit inline array conversions + +The implicit inline array (§InlineArray) conversions are: + +- From an expression designating a writable inline array with element type `T` to `System.Span` +- From an expression designating a writable inline array with element type `T` to `System.ReadonlySpan` +- From an expression designating a readonly inline array with element type `T` to `System.ReadonlySpan` + +The conversion of an inline array to a `System.Span` or `System.ReadonlySpan` ignores any declared operators in the inline array type that might otherwise appear to be applicable. See §InlineArray for more information. + ## 10.3 Explicit conversions ### 10.3.1 General @@ -681,6 +691,7 @@ The following implicit conversions are classified as standard implicit conversio - Boxing conversions ([§10.2.9](conversions.md#1029-boxing-conversions)) - Implicit constant expression conversions ([§10.2.11](conversions.md#10211-implicit-constant-expression-conversions)) - Implicit conversions involving type parameters ([§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters)) +- Implicit inline array conversions (§ImplicitInlineArrayConversions) The standard implicit conversions specifically exclude user-defined implicit conversions. diff --git a/standard/expressions.md b/standard/expressions.md index 0ae84f22b..ea03d6c25 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2443,7 +2443,7 @@ The *primary_expression* of an *element_access* shall not be an *array_creation_ An *element_access* is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)) if at least one of the following holds: - The *primary_expression* has compile-time type `dynamic`. -- At least one expression of the *argument_list* has compile-time type `dynamic`. +- At least one expression of the *argument_list* has compile-time type `dynamic`, and the *primary_no_array_creation_expression* does not have an inline array type (§InlineArray) or there is more than one *argument* in the *argument_list*. In this case the compile-time type of the *element_access* depends on the compile-time type of its *primary_expression*: if it has an array type then the compile-time type is the element type of that array type; otherwise the compile-time type is `dynamic` and the *element_access* is classified as a value of type `dynamic`. The rules below to determine the meaning of the *element_access* are then applied at run-time, using the run-time type instead of the compile-time type of those of the *primary_expression* and *argument_list* expressions which have the compile-time type `dynamic`. If the *primary_expression* does not have compile-time type `dynamic`, then the element access undergoes a limited compile-time check as described in [§12.6.5](expressions.md#1265-compile-time-checking-of-dynamic-member-invocation). @@ -2459,10 +2459,12 @@ In this case the compile-time type of the *element_access* depends on the compil > > *end example* -If the *primary_expression* of an *element_access* is: +If the *primary_expression* of an *element_access* is a value of an *array_type*, the *element_access* is an array access ([§12.8.12.2](expressions.md#128122-array-access)). Otherwise, if the *primary_no_array_creation_expression* of an *element_access* is a variable or value of an inline array type and the *argument_list* consists of a single argument, the *element_access* is an inline array element access (§InlineArrayElementAccess). Otherwise, the *primary_no_array_creation_expression* shall be a variable or value of a class, struct, or interface type that has one or more indexer members, in which case the *element_access* is an indexer access ([§12.8.12.4](expressions.md#128124-indexer-access)). #### 12.8.12.2 Array access +For access to elements in an inline array (§InlineArray) see §InlineArrayElementAccess. + For an array access the *argument_list* shall not contain named arguments or by-reference arguments ([§15.6.2.3](classes.md#15623-by-reference-parameters)). The number of expressions in the *argument_list* shall be the same as the rank of the *array_type*, and each expression shall be: @@ -2496,6 +2498,157 @@ The run-time processing of an array access of the form `P[A]`, where `P` is a *p - 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 variable reference of the array element given by the index expressions is computed, and this becomes the result of the array access. +#### §InlineArrayElementAccess Inline array element access + +For access to an element in an inline array (§InlineArray), the *primary_no_array_creation_expression* of the *element_access* shall designate an inline array. Furthermore, the *argument_list* shall contain a single *argument*, which is not a named argument ([§12.6.2.1](expressions.md#12621-general)). That *argument* shall be of type `int`, or be implicitly convertible to type `int`, `System.Index`, or `System.Range`. + +It is a compile-time error if *argument* is a constant expression whose value results in an index outside the bounds of the inline array. If at runtime the value of *argument* results in an index outside the bounds of the inline array, a `System.IndexOutOfRangeException` is thrown. + +**When *argument*’s type is `int`** + +If *primary_no_array_creation_expression* is a writable variable, the result of evaluating an inline array element access is a writable variable equivalent to invoking the indexer + +```csharp +public ref T this[int index] { get; } +``` + +with *argument* as the index, on an instance of `System.Span` that was created from the inline array designated by *primary_no_array_creation_expression*. For the purpose of ref-safety analysis, the safe-context ([§9.7.2.1](variables.md#9721-general)) of the access is equivalent to that for an invocation of a method with the signature + +```csharp +static ref T GetItem(ref «InlineArrayType» array) +``` + +The resulting variable is considered movable if and only if *primary_no_array_creation_expression* is movable. + +If *primary_no_array_creation_expression* is a readonly variable, the result of evaluating an inline array element access is a readonly variable equivalent to invoking the indexer + +```csharp +public ref readonly T this[int index] { get; } +``` + +with *argument* as the index, on an instance of `System.ReadOnlySpan` that was created from the inline array designated by *primary_no_array_creation_expression*. For the purpose of ref-safety analysis, the safe-context of the access is equivalent to that for an invocation of a method with the signature + +```csharp +static ref readonly T GetItem(in «InlineArrayType» array) +``` + +The resulting variable is considered movable if and only if *primary_no_array_creation_expression* is movable. + +If *primary_no_array_creation_expression* is a value, the result of evaluating an inline array element access is a value equivalent to invoking the indexer + +```csharp +public ref readonly T this[int index] { get; } +``` + +with *argument* as the index, on an instance of `System.ReadOnlySpan` that was created from the inline array designated by *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the safe-context of the access are equivalent to that for an invocation of a method with the signature + +```csharp +static T GetItem(«InlineArrayType» array) +``` + +> *Example*: +> +> +> ```csharp +> [System.Runtime.CompilerServices.InlineArray(10)] +> public struct Buffer10 +> { +> private T _element0; +> } +> public class Program +> { +> void M1(Buffer10 x) +> { +> ref int a = ref x[0]; // OK +> } +> void M2(in Buffer10 x) +> { +> ref readonly int a = ref x[0]; // OK +> ref int b = ref x[0]; // Error; x is readonly +> } +> Buffer10 GetBuffer() => default; +> void M3() +> { +> int a = GetBuffer()[0]; // OK +> ref readonly int b = ref GetBuffer()[0];// Error, rhs is a value +> ref int c = ref GetBuffer()[0]; // Error; rhs is a value +> } +> } +> ``` +> +> *end example* + +**When *argument*’s type is implicitly convertible to `int`** + +The value of *argument* is converted to `int` and the element access is interpreted as described when *argument*’s type is `int` + +**When *argument*’s type is implicitly convertible to `System.Index`** + +*argument* is converted to `System.Index` and then to an `int`-based index value indicating the element position relative to the start of the inline array. Then, the element access is interpreted as described when *argument*’s type is `int`. + +Using an index of `System.Index` to access an element in a non-inline array is described in [§12.8.12.2](expressions.md#128122-array-access). However, note carefully that that process is *not* used when an inline array is indexed using a `System.Index`. Specifically, an inline array element access ignores any declared indexers in the inline array type. See §InlineArray for more information. + +**When *argument*’s type is implicitly convertible to `System.Range`** + +If *primary_no_array_creation_expression* is a writable variable, the result of evaluating an inline array element access is a variable equivalent to invoking the method + +```csharp +public Span Slice (int start, int length) +``` + +passing the `int` equivalents of the Range’s start and end Indexes, respectively, on an instance of `System.Span` that was created from the inline array designated by *primary_no_array_creation_expression*. For the purpose of ref-safety analysis, the safe-context ([§9.7.2.1](variables.md#9721-general)) of the access is equivalent to that for an invocation of a method with the signature + +```csharp +static System.Span GetSlice(ref «InlineArrayType» array) +``` + +If *primary_no_array_creation_expression* is a readonly variable, the result of evaluating an inline array element access is a value equivalent to invoking the method + +```csharp +public ReadOnlySpan Slice (int start, int length) +``` + +passing the `int` equivalents of the Range’s start and end Indexes, respectively, on an instance of `System.ReadOnlySpan` that was created from the inline array designated by *primary_no_array_creation_expression*. For the purpose of ref-safety analysis, the safe-context of the access is equivalent to that for an invocation of a method with the signature + +```csharp +static System.ReadOnlySpan GetSlice(in «InlineArrayType» array) +``` + +Using an index of `System.Range` to access an element in a non-inline array is described in [§12.8.12.2](expressions.md#128122-array-access). However, note carefully that that process is *not* used when an inline array is indexed using a `System.Range`. Specifically, an inline array element access ignores any declared Slice methods in the inline array type. See §InlineArray for more information. + +If *primary_no_array_creation_expression* is a value, an error is reported. + +> *Example*: +> +> +> +> ```csharp +> [System.Runtime.CompilerServices.InlineArray(10)] +> public struct Buffer10 +> { +> private T _element0; +> } +> public class Program +> { +> void M1(Buffer10 x) +> { +> System.Span a = x[..]; // OK +> } +> void M2(in Buffer10 x) +> { +> System.ReadOnlySpan a = x[..]; // OK +> System.Span b = x[..]; // Error; no implicit conversion +> } +> Buffer10 GetBuffer() => default; +> void M3() +> { +> _ = GetBuffer()[..]; // Error; rhs is a value +> } +> } +> ``` +> +> *end example* + #### 12.8.12.3 String access For a string access the *argument_list* of the *element_access* shall contain a single unnamed value argument ([§15.6.2.2](classes.md#15622-value-parameters)) which shall be: diff --git a/standard/standard-library.md b/standard/standard-library.md index b12154453..2f8eddd66 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -892,6 +892,14 @@ namespace System.Runtime.CompilerServices void UnsafeOnCompleted(Action continuation); } + [AttributeUsage(AttributeTargets.Struct, AllowMultiple=false)] + public sealed class InlineArrayAttribute : Attribute + { + public InlineArrayAttribute(int length); + + public int Length { get; } + } + public interface INotifyCompletion { void OnCompleted(Action continuation); @@ -1492,6 +1500,7 @@ The following library types are referenced in this specification. The full names - `global::System.Runtime.CompilerServices.FormattableStringFactory` - `global::System.Runtime.CompilerServices.ICriticalNotifyCompletion` - `global::System.Runtime.CompilerServices.IndexerNameAttribute` +- `global::System.Runtime.CompilerServices.InlineArrayAttribute` - `global::System.Runtime.CompilerServices.INotifyCompletion` - `global::System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute` - `global::System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` diff --git a/standard/structs.md b/standard/structs.md index d60c82349..0ce765740 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -461,6 +461,95 @@ constructor declaration. The body of the method assigns each parameter of the De If the instance members accessed in the body do not include a property with a non-`readonly` `get` accessor, then the synthesized `Deconstruct` method is `readonly`. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or is static. +## §InlineArray Inline arrays + +A struct type decorated with the attribute `System.Runtime.CompilerServices.InlineArrayAttribute` (§InlineArrayAttribute) is an ***inline array type***, which is a managed type. An instance of that type is an ***inline array***, a structure that contains a contiguous block of a given number of elements of the same type, and nothing else. It’s the safe-code equivalent of unsafe-code’s fixed-size buffer ([§24.8](unsafe-code.md#248-fixed-size-buffers)). + +With some limitations (see later below), an inline array can be used like an array ([§17](arrays.md#17-arrays)). + +Consider the following: + + +```csharp +var buffer = new Buffer(); + +for (int i = 0; i < 5; ++i) +{ + buffer[i] = i; +} + +foreach (var i in buffer) +{ + Console.WriteLine(i); +} + +Console.WriteLine($"buffer[^2] = >{buffer[^2]}<"); +Console.WriteLine($"buffer[1..^0][0] = >{buffer[1..^0][0]}<"); + +[System.Runtime.CompilerServices.InlineArray(5)] +public struct Buffer +{ + private int _element0; + // other, optional, non-instance-field members +} +``` + +which produces this output: + +```console +0 +1 +2 +3 +4 +buffer[^2] = >3< +buffer[1..^0][0] = >1< +``` + +The attribute constructor requires a positive-valued `int` argument that indicates the number of elements in each inline array created from this type, in this case, five. The struct type shall declare exactly one instance field, whose type becomes that of the inline array’s elements. The accessibility of the instance field has no impact on the program’s ability to access the inline array via its struct-instance name, in this case, `buffer`. + +There are a number of restrictions on the instance field’s declaration: + +- It shall not contain the *field_modifier*s `readonly`, `ref`, `required`, or `volatile`. +- It shall not be declared as a *fixed_size_buffer_declaration* ([§24.8](unsafe-code.md#248-fixed-size-buffers)). +- *type* shall be a type valid as a type argument. + +`buffer` is an instance of `Buffer`, and contains an array of five `int` with element `buffer[0]` occupying the same memory as `buffer._element0`. However, unlike an array type, which has a public `Length` property, `buffer` is not derived from `System.Array`, and has no such member. + +> *Note*: One could add a public `Length` property to `Buffer` (which returns `sizeof(Buffer)/sizeof(int)`, but that property would have to be an unsafe member. Alternatively, one could perform an implicit conversion to `System.Span` or `System.ReadOnlySpan` and use their `Length` property. *end note* + +An inline array is a collection; as such, it can be iterated over by a `foreach` statement, as shown. + +The elements of the inline array can be accessed for read or write via subscripting (§InlineArrayElementAccess). + +A list pattern ([§11.2.11](patterns.md#11211-list-pattern)) shall not be used in the context of an inline array. + +An inline array type is a valid constructible collection target type for a collection literal. + +Any indexers or `Slice` methods declared for an inline array type that have signatures matching those defined for non-inline array processing, shall not be used during inline array element access. +> *Example*: Consider the following: +> +> +```csharp +> var buffer = new Buffer(); +> int x = buffer[2]; // element access +> +> [System.Runtime.CompilerServices.InlineArray(5)] +> public struct Buffer +> { +> private int _element0; +> public int this[int index] // NOT USED for element access +> { +> get +> { +> … +> } +> } +> } +> ``` +> +> Even though the struct declares an indexer taking an `int` argument, that indexer is not used by element access `buffer[2]`. *end example* + ## 16.5 Class and struct differences ### 16.5.1 General @@ -1152,7 +1241,9 @@ The safe-context of an object initializer expression is the narrowest of: 3. The safe-context of the RHS of assignments in member initializers to non-readonly setters, or the ref-safe-context in the case of ref assignment. > *Note*: Another way of modeling this is to consider any argument to a member initializer that can be assigned to the receiver as being an argument to the constructor. *end note* + + > *Example*: The following illustrates how an object initializer narrows the safe-context of the resulting value: > >