diff --git a/standard/attributes.md b/standard/attributes.md index 061aa4430..64c591843 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -498,6 +498,7 @@ A number of attributes affect the language in some way. These attributes include - `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)), which is used to specify parameter for the cancellation token in an asynchronous iterator. - `System.Runtime.CompilerServices.ModuleInitializer` ([§23.5.9](attributes.md#2359-the-moduleinitializer-attribute)), which is used to mark a method as a module initializer. - `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` and `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, which are used to declare a custom interpolated string expression handler ([§23.5.9.1](attributes.md#23591-custom-interpolated-string-expression-handlers)) and to call one of its constructors, respectively. +- System.Diagnostics.CodeAnalysis.UnscopedRefAttribute (§UnscopedRefAttribute), which allows an otherwise implicitly scoped ref to be treated as not being scoped. 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)). @@ -927,19 +928,19 @@ The attributes in this subclause are used to provide additional information to s The code-analysis attributes are declared in namespace `System.Diagnostics.CodeAnalysis`. -**Attribute** | **Meaning** ------------------- | ------------------ -`AllowNull` ([§23.5.7.2](attributes.md#23572-the-allownull-attribute)) | A non-nullable argument may be null. -`DisallowNull` ([§23.5.7.3](attributes.md#23573-the-disallownull-attribute)) | A nullable argument should never be null. -`MaybeNull` ([§23.5.7.6](attributes.md#23576-the-maybenull-attribute)) | A non-nullable return value may be null. -`NotNull` ([§23.5.7.10](attributes.md#235710-the-notnull-attribute)) | A nullable return value will never be null. -`MaybeNullWhen` ([§23.5.7.7](attributes.md#23577-the-maybenullwhen-attribute)) | A non-nullable argument may be null when the method returns the specified `bool` value. -`NotNullWhen` ([§23.5.7.12](attributes.md#235712-the-notnullwhen-attribute)) | A nullable argument won't be null when the method returns the specified `bool` value. -`NotNullIfNotNull` ([§23.5.7.11](attributes.md#235711-the-notnullifnotnull-attribute)) | A return value isn't null if the argument for the specified parameter isn't null. -`MemberNotNull` ([§23.5.7.8](attributes.md#23578-the-membernotnull-attribute)) | The listed member won't be null when the method returns. -`MemberNotNullWhen` ([§23.5.7.9](attributes.md#23579-the-membernotnullwhen-attribute)) | The listed member won't be null when the method returns the specified `bool` value. -`DoesNotReturn` ([§23.5.7.4](attributes.md#23574-the-doesnotreturn-attribute)) | This method never returns. -`DoesNotReturnIf` ([§23.5.7.5](attributes.md#23575-the-doesnotreturnif-attribute)) | This method never returns if the associated `bool` parameter has the specified value. +| **Attribute** | **Meaning** | +| ------------------ | ------------------ | +| `AllowNull` ([§23.5.7.2](attributes.md#23572-the-allownull-attribute)) | A non-nullable argument may be null. | +| `DisallowNull` ([§23.5.7.3](attributes.md#23573-the-disallownull-attribute)) | A nullable argument should never be null. | +| `MaybeNull` ([§23.5.7.6](attributes.md#23576-the-maybenull-attribute)) | A non-nullable return value may be null. | +| `NotNull` ([§23.5.7.10](attributes.md#235710-the-notnull-attribute)) | A nullable return value will never be null. | +| `MaybeNullWhen` ([§23.5.7.7](attributes.md#23577-the-maybenullwhen-attribute)) | A non-nullable argument may be null when the method returns the specified `bool` value. | +| `NotNullWhen` ([§23.5.7.12](attributes.md#235712-the-notnullwhen-attribute)) | A nullable argument won't be null when the method returns the specified `bool` value. | +| `NotNullIfNotNull` ([§23.5.7.11](attributes.md#235711-the-notnullifnotnull-attribute)) | A return value isn't null if the argument for the specified parameter isn't null. | +| `MemberNotNull` ([§23.5.7.8](attributes.md#23578-the-membernotnull-attribute)) | The listed member won't be null when the method returns. | +| `MemberNotNullWhen` ([§23.5.7.9](attributes.md#23579-the-membernotnullwhen-attribute)) | The listed member won't be null when the method returns the specified `bool` value. | +| `DoesNotReturn` ([§23.5.7.4](attributes.md#23574-the-doesnotreturn-attribute)) | This method never returns. | +| `DoesNotReturnIf` ([§23.5.7.5](attributes.md#23575-the-doesnotreturnif-attribute)) | This method never returns if the associated `bool` parameter has the specified value. | The following subclauses in [§23.5.7](attributes.md#2357-code-analysis-attributes) are conditionally normative. @@ -1184,6 +1185,40 @@ Specifies that a nullable argument will not be `null` when the method returns th > > *end example* +### §UnscopedRefAttribute The UnscopedRef attribute + +There are several cases in which a ref is treated as being implicitly scoped (§scoped-modifier); that is, the ref is not allowed to escape a method. For example: + +- `this` for struct instance methods. +- ref parameters that refer to ref struct types. +- out parameters. + +This attribute is used in those situations where the ref should be allowed to escape. + +This attribute may can be applied to any `ref` and it changes the ref-safe-context to be one level wider than its default. For example: + +| UnscopedRef applied to | Original ref-safe-context | New ref-safe-context | +| --- | --- | --- | +| instance member | function-member | return-only | +| `in` / `ref` parameter | return-only | caller-context | +| `out` parameter | function-member | return-only | + +When applying this attribute to an instance method of a struct it modifies the implicit `this` parameter; that is, `this` acts as an unannotated `ref` of the same type. + +An instance method or property annotated with `[UnscopedRef]` has the ref-safe-context of `this` set to the *caller-context*. + +A member annotated with `[UnscopedRef]` may not implement an interface. + +It is an error to use `[UnscopedRef]` on + +- A member that is not declared on a `struct`. +- A `static` member, `init` member, or constructor on a `struct`. +- A parameter marked `scoped`. +- A parameter passed by value. +- A parameter passed by reference that is not implicitly scoped. + +See §scoped-modifier for more information. + ### 23.5.8 The EnumeratorCancellation attribute Specifies the parameter representing the `CancellationToken` for an asynchronous iterator ([§15.15](classes.md#1515-synchronous-and-asynchronous-iterators)). The argument for this parameter shall be combined with the argument passed to `IAsyncEnumerable.GetAsyncEnumerator(CancellationToken)`. This combined token shall be polled by `IAsyncEnumerator.MoveNextAsync()` ([§15.15.5.2](classes.md#151552-advance-the-enumerator)). The tokens shall be combined into a single token as if by `CancellationToken.CreateLinkedTokenSource` and its `Token` property. The combined token will be canceled if either of the two source tokens are canceled. The combined token is seen as the argument to the asynchronous iterator method ([§15.15](classes.md#1515-synchronous-and-asynchronous-iterators)) in the body of that method. diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 7900ea53d..676a3fa57 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -717,11 +717,11 @@ The following accessibility constraints exist: Methods, instance constructors, indexers, and operators are characterized by their ***signature***s: -- The signature of a method consists of the name of the method, the number of type parameters, and the type and parameter-passing mode of each of its parameters, considered in the order left to right. For these purposes, any type parameter of the method that occurs in the type of a parameter is identified not by its name, but by its ordinal position in the type parameter list of the method. The signature of a method specifically does not include the return type, parameter names, type parameter names, type parameter constraints, the `params` or `this` parameter modifiers, nor whether parameters are required or optional. +- The signature of a method consists of the name of the method, the number of type parameters, and the type and parameter-passing mode of each of its parameters, considered in the order left to right. For these purposes, any type parameter of the method that occurs in the type of a parameter is identified not by its name, but by its ordinal position in the type parameter list of the method. The signature of a method specifically does not include the return type, parameter names, type parameter names, type parameter constraints, the `params`, `scoped`, or `this` parameter modifiers, nor whether parameters are required or optional. - The signature of an instance constructor consists of the type and parameter-passing mode of each of its parameters, considered in the order left to right. The signature of an instance constructor specifically does not include the `params` modifier that may be specified for the right-most parameter, nor whether parameters are required or optional. -- The signature of an indexer consists of the type of each of its parameters, considered in the order left to right. The signature of an indexer specifically does not include the element type, nor does it include the `params` modifier that may be specified for the right-most parameter, nor whether parameters are required or optional. -- The signature of an operator consists of the name of the operator and the type of each of its parameters, considered in the order left to right. The signature of an operator specifically does not include the result type. -- The signature of a conversion operator consists of the source type and the target type. The implicit or explicit classification of a conversion operator is not part of the signature. +- The signature of an indexer consists of the type of each of its parameters, considered in the order left to right. The signature of an indexer specifically does not include the element type, or the `scoped` modifier, nor does it include the `params` modifier that may be specified for the right-most parameter, or the `scoped` modifier, nor whether parameters are required or optional. +- The signature of an operator consists of the name of the operator and the type of each of its parameters, considered in the order left to right. The signature of an operator specifically does not include the result type or the `scoped` modifier. +- The signature of a conversion operator consists of the source type and the target type. The implicit or explicit classification of a conversion operator is not part of the signature nor is the `scoped` modifier. - Two signatures of the same member kind (method, instance constructor, indexer or operator) are considered to be the *same signatures* if they have the same name, number of type parameters, number of parameters, and parameter-passing modes, and an identity conversion exists between the types of their corresponding parameters ([§10.2.2](conversions.md#1022-identity-conversion)). Signatures are the enabling mechanism for ***overloading*** of members in classes, structs, and interfaces: diff --git a/standard/classes.md b/standard/classes.md index 7b8551607..f8ac0d14c 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2270,8 +2270,9 @@ default_argument parameter_modifier : parameter_mode_modifier - | 'this' parameter_mode_modifier? - | parameter_mode_modifier? 'this' + | 'this' 'scoped'? parameter_mode_modifier? + | 'scoped'? parameter_mode_modifier? 'this' + | 'scoped' parameter_mode_modifier? ; parameter_mode_modifier @@ -2287,7 +2288,11 @@ parameter_array The parameter list consists of one or more comma-separated parameters of which only the last may be a *parameter_array*. -A *fixed_parameter* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)); an optional `in`, `out`, `ref`, or `this` modifier; a *type*; an *identifier*; and an optional *default_argument*. Each *fixed_parameter* declares a parameter of the given type with the given name. The `this` modifier designates the method as an extension method and is only allowed on the first parameter of a static method in a non-generic, non-nested static class. If the parameter is a `struct` type or a type parameter constrained to a `struct`, the `this` modifier may be combined with either the `ref` or `in` modifier, but not the `out` modifier. Extension methods are further described in [§15.6.10](classes.md#15610-extension-methods). A *fixed_parameter* with a *default_argument* is known as an ***optional parameter***, whereas a *fixed_parameter* without a *default_argument* is a ***required parameter***. A required parameter shall not appear after an optional parameter in a *parameter_list*. +A *fixed_parameter* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)); an optional `this` modifier; an optional `scoped` modifier; an optional `in`, `out`, `ref` modifier; a *type*; an *identifier*; and an optional *default_argument*. Each *fixed_parameter* declares a parameter of the given type with the given name. The `this` modifier designates the method as an extension method and is only allowed on the first parameter of a static method in a non-generic, non-nested static class. If the parameter is a `struct` type or a type parameter constrained to a `struct`, the `this` modifier may be combined with either the `ref` or `in` modifier, but not the `out` modifier. Extension methods are further described in [§15.6.10](classes.md#15610-extension-methods). A *fixed_parameter* with a *default_argument* is known as an ***optional parameter***, whereas a *fixed_parameter* without a *default_argument* is a ***required parameter***. A required parameter shall not appear after an optional parameter in a *parameter_list*. + +An output parameter implicitly has the `scoped` modifier. + +For a discussion of `scoped`, see §scoped-modifier. A parameter with a `ref`, `out` or `this` modifier cannot have a *default_argument*. An input parameter may have a *default_argument*. The *expression* in a *default_argument* shall be one of the following: @@ -2335,7 +2340,7 @@ The following kinds of parameters exist: - Reference parameters ([§15.6.2.3.3](classes.md#156233-reference-parameters)). - Parameter arrays ([§15.6.2.4](classes.md#15624-parameter-arrays)). -> *Note*: As described in [§7.6](basic-concepts.md#76-signatures-and-overloading), the `in`, `out`, and `ref` modifiers are part of a method’s signature, but the `params` modifier is not. *end note* +> *Note*: As described in [§7.6](basic-concepts.md#76-signatures-and-overloading), the `in`, `out`, and `ref` modifiers are part of a method’s signature, but the `params` and `scoped` modifiers are not. *end note* #### 15.6.2.2 Value parameters diff --git a/standard/expressions.md b/standard/expressions.md index fb4927576..4ae8b6006 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -567,9 +567,9 @@ argument_name argument_value : expression - | 'in' variable_reference - | 'ref' variable_reference - | 'out' variable_reference + | 'in' 'scoped'? variable_reference + | 'ref' 'scoped'? variable_reference + | 'out' 'scoped'? variable_reference ; ``` @@ -578,9 +578,9 @@ An *argument_list* consists of one or more *argument*s, separated by commas. Eac The *argument_value* can take one of the following forms: - An *expression*, indicating that the argument is passed as a value parameter or is transformed into an input parameter and then passed as that, as determined by ([§12.6.4.2](expressions.md#12642-applicable-function-member) and described in [§12.6.2.3](expressions.md#12623-run-time-evaluation-of-argument-lists). -- The keyword `in` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)). A variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before it can be passed as an input parameter. -- The keyword `ref` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)). A variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before it can be passed as a reference parameter. -- The keyword `out` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as an output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)). A variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following a function member invocation in which the variable is passed as an output parameter. +- The keyword `in` optionally followed by `scoped`, followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)). A variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before it can be passed as an input parameter. For a discussion of `scoped`, see §scoped-modifier. +- The keyword `ref` optionally followed by `scoped`, followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)). A variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before it can be passed as a reference parameter. For a discussion of `scoped`, see §scoped-modifier. +- The keyword `out` optionally followed by `scoped`, optionally followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as an output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)). A variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following a function member invocation in which the variable is passed as an output parameter. For a discussion of `scoped`, see §scoped-modifier. The form determines the ***parameter-passing mode*** of the argument: *value*, *input*, *reference*, or *output*, respectively. However, as mentioned above, an argument with value passing mode, might be transformed into one with input passing mode. @@ -2728,7 +2728,7 @@ member_initializer_list ; member_initializer - : initializer_target '=' initializer_value + : initializer_target '=' 'ref'? initializer_value ; initializer_target @@ -2737,7 +2737,7 @@ initializer_target ; initializer_value - : expression + : 'ref'? expression | object_or_collection_initializer ; ``` @@ -3518,6 +3518,10 @@ A *default_value_expression* is a constant expression ([§12.26](expressions.md# - 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. +A reference variable field `rv` of type `T` shall not have an explicit initializer of `default`. + +> *Note*: If one tries to initialize `rv` using `rv = default`, this does not set the reference variable to `null`. Instead, it attempts to set the value of the (possibly non-existent) referent to the default value for type `T`. *end note* + ### 12.8.22 Stack allocation A stack allocation expression allocates a block of memory from the execution stack. The ***execution stack*** is an area of memory where local variables are stored. The execution stack is not part of the managed heap. The memory used for local variable storage is automatically recovered when the current function returns. @@ -5295,7 +5299,7 @@ A declaration expression declares a local variable. ```ANTLR declaration_expression - : local_variable_type identifier + : 'scoped'? local_variable_type identifier ; local_variable_type @@ -5304,6 +5308,8 @@ local_variable_type ; ``` +'scoped' shall only be permitted with *local_variable_type* if *local_variable_type* is 'var' or a ref struct type, and *identifer* is not a discard. + The *simple_name* `_` is also considered a declaration expression if simple name lookup did not find an associated declaration ([§12.8.4](expressions.md#1284-simple-names)). When used as a declaration expression, `_` is called a *simple discard*. It is semantically equivalent to `var _`, but is permitted in more places. A declaration expression shall only occur in the following syntactic contexts: @@ -5466,7 +5472,7 @@ explicit_anonymous_function_parameter_list ; explicit_anonymous_function_parameter - : attributes? anonymous_function_parameter_modifier? type identifier + : attributes? 'scoped'? anonymous_function_parameter_modifier? type identifier ; anonymous_function_parameter_modifier @@ -5499,6 +5505,8 @@ anonymous_function_body If the modifier `static` is present, the anonymous function cannot capture state from the enclosing scope. +For a discussion of `scoped`, see §scoped-modifier. + A non-`static` local function or non-`static` anonymous function can capture state from an enclosing `static` anonymous function, but cannot capture state outside the enclosing static anonymous function. Removing the `static` modifier from an anonymous function in a valid program does not change the meaning of the program. @@ -5565,6 +5573,8 @@ A *block* body of an anonymous function is always reachable ([§13.2](statements > var concat = string ([DisallowNull] string a, [DisallowNull] string b) => a + b; > Func parse = [X][return: Y] ([Z] s) > => (s is not null) ? int.Parse(s) : null; +> var lambda = (in int p1, out bool p2, scoped ref float p3, +> scoped Span p4) => Mlam(in p1, out p2, ref p3, p4); > ``` > > *end example* @@ -5575,6 +5585,7 @@ The behavior of *lambda_expression*s and *anonymous_method_expression*s is the s - *lambda_expression*s permit parameter types to be omitted and inferred whereas *anonymous_method_expression*s require parameter types to be explicitly stated. - The body of a *lambda_expression* can be an expression or a block whereas the body of an *anonymous_method_expression* shall be a block. - Only *lambda_expression*s have conversions to compatible expression tree types ([§8.6](types.md#86-expression-tree-types)). +- Only *lambda_expression* parameters may contain 'scoped'. - Only *lambda_expression*s may have *attributes* and explicit return types. ### 12.22.2 Anonymous function signatures @@ -7158,7 +7169,9 @@ The `= ref` operator is known as the *ref assignment* operator. The left operand shall be an expression that binds to a reference variable ([§9.7](variables.md#97-reference-variables-and-returns)), a reference parameter (other than `this`), an output parameter, or an input parameter. The right operand shall be an expression that yields a *variable_reference* designating a value of the same type as the left operand. -It is a compile time error if the ref-safe-context ([§9.7.2](variables.md#972-ref-safe-contexts)) of the left operand is wider than the ref-safe-context of the right operand. +The ref-safe-context of the right operand shall be at least as wide as the ref-safe-context of the left operand, and the left operand shall have the same safe-context as the right operand (§9.7.2). + +> *Note*: This requirement exists because the lifetime of the value pointed to by a ref location is invariant. The indirection prevents one from allowing any kind of variance here, even to narrower lifetimes. *end note* The right operand shall be definitely assigned at the point of the ref assignment. diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 1f3a89cba..e7b2d9b79 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -629,7 +629,7 @@ contextual_keyword | 'group' | 'init' | 'into' | 'join' | 'let' | 'managed' | 'nameof' | 'nint' | 'not' | 'notnull' | 'nuint' | 'on' | 'or' | 'orderby' | 'partial' - | 'record' | 'remove' | 'select' | 'set' | 'Stdcall' + | 'record' | 'remove' | 'scoped' | 'select' | 'set' | 'Stdcall' | 'Thiscall' | 'unmanaged' | 'value' | 'var' | 'when' | 'where' | 'yield' ; diff --git a/standard/standard-library.md b/standard/standard-library.md index b5b242e65..24952cce0 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -375,6 +375,7 @@ namespace System.Runtime.CompilerServices public static class Unsafe { public static ref T NullRef(); + public static bool IsNullRef(ref T source); } } @@ -767,6 +768,15 @@ namespace System.Diagnostics.CodeAnalysis { public NotNullWhenAttribute(bool returnValue); } + + [System.AttributeUsage(System.AttributeTargets.Method + | System.AttributeTargets.Parameter + | System.AttributeTargets.Property, + AllowMultiple=false, Inherited=false)] + public sealed class UnscopedRefAttribute : Attribute + { + public UnscopedRefAttribute(); + } } namespace System.Linq.Expressions @@ -1433,6 +1443,7 @@ The following library types are referenced in this specification. The full names - `global::System.Diagnostics.CodeAnalysis.NotNullAttribute` - `global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute` - `global::System.Diagnostics.CodeAnalysis.NotNullWhenAttribute` +- `global::System.Diagnostics.CodeAnalysis.UnscopedRefAttribute` - `global::System.Linq.Expressions.Expression` - `global::System.Reflection.MemberInfo` - `global::System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` diff --git a/standard/statements.md b/standard/statements.md index bc26591d2..b414102c0 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -355,8 +355,8 @@ The ref-safe-context ([§9.7.2](variables.md#972-ref-safe-contexts)) of a ref lo ```ANTLR implicitly_typed_local_variable_declaration - : 'var' implicitly_typed_local_variable_declarator - | ref_kind 'var' ref_local_variable_declarator + : 'scoped'? 'var' implicitly_typed_local_variable_declarator + | 'scoped'? ref_kind 'var' ref_local_variable_declarator ; implicitly_typed_local_variable_declarator @@ -366,9 +366,11 @@ implicitly_typed_local_variable_declarator An *implicitly_typed_local_variable_declaration* introduces a single local variable, *identifier*. The *expression* or *variable_reference* shall have a compile-time type, `T`. The first alternative declares a variable with an initial value of *expression*; its type is `T?` when `T` is a non-nullable reference type, otherwise its type is `T`. The second alternative declares a ref variable with an initial value of `ref` *variable_reference*; its type is `ref T?` when `T` is a non-nullable reference type, otherwise its type is `ref T`. (*ref_kind* is described in [§15.6.1](classes.md#1561-general).) +For a discussion of `scoped`, see §scoped-modifier. + > *Example*: > -> +> > ```csharp > var i = 5; > var s = "Hello"; @@ -377,11 +379,12 @@ An *implicitly_typed_local_variable_declaration* introduces a single local varia > var orders = new Dictionary(); > ref var j = ref i; > ref readonly var k = ref i; +> scoped var r = new RS(ref i); // ref struct RS { ref int Field; ... } > ``` > > The implicitly typed local variable declarations above are precisely equivalent to the following explicitly typed declarations: > -> +> > ```csharp > int i = 5; > string s = "Hello"; @@ -390,17 +393,19 @@ An *implicitly_typed_local_variable_declaration* introduces a single local varia > Dictionary orders = new Dictionary(); > ref int j = ref i; > ref readonly int k = ref i; +> scoped RS r = new RS(ref i); // ref struct RS { ref int Field; ... } > ``` > > The following are incorrect implicitly typed local variable declarations: > -> +> > ```csharp > var x; // Error, no initializer to infer type from > var y = {1, 2, 3}; // Error, array initializer not permitted > var z = null; // Error, null does not have a type > var u = x => x + 1; // Error, anonymous functions do not have a type > var v = v++; // Error, initializer cannot refer to v itself +> scoped var i = 10; // Error, i must be a ref or ref struct > ``` > > *end example* @@ -427,7 +432,7 @@ Anonymous functions and method groups with anonymous function types may not be u ```ANTLR explicitly_typed_local_variable_declaration - : type explicitly_typed_local_variable_declarators + : 'scoped'? type explicitly_typed_local_variable_declarators ; explicitly_typed_local_variable_declarators @@ -449,11 +454,13 @@ An *explicitly_typed_local_variable_declaration* introduces one or more local va If a *local_variable_initializer* is present then its type shall be appropriate according to the rules of simple assignment ([§12.24.2](expressions.md#12242-simple-assignment)) or array initialization ([§17.7](arrays.md#177-array-initializers)) and its value is assigned as the initial value of the variable. +For a discussion of `scoped`, see §scoped-modifier. + #### 13.6.2.4 Explicitly typed ref local variable declarations ```ANTLR explicitly_typed_ref_local_variable_declaration - : ref_kind type ref_local_variable_declarators + : 'scoped'? ref_kind type ref_local_variable_declarators ; ref_local_variable_declarators @@ -465,12 +472,16 @@ ref_local_variable_declarator ; ``` +An *explicitly_typed_ref_local_variable_declaration* introduces one or more local ref variables with the specified `scoped` modifier and *type*. + The initializing *variable_reference* shall have type *type* and meet the same requirements as for a *ref assignment* ([§12.24.3](expressions.md#12243-ref-assignment)). If *ref_kind* is `ref readonly`, the *identifier*s being declared are references to variables that are treated as read-only. Otherwise, if *ref_kind* is `ref`, the *identifier*s being declared are references to variables that shall be writable. It is a compile-time error to declare a ref local variable, or a variable of a `ref struct` type, within a method declared with the *method_modifier* `async`, or within an iterator ([§15.15](classes.md#1515-synchronous-and-asynchronous-iterators)). +For a discussion of `scoped`, see §scoped-modifier. + ### 13.6.3 Local constant declarations A *local_constant_declaration* declares one or more local constants. @@ -1132,11 +1143,13 @@ The `foreach` statement enumerates the elements of a collection, executing an em ```ANTLR foreach_statement - : 'await'? 'foreach' '(' ref_kind? local_variable_type identifier + : 'await'? 'foreach' '(' 'scoped'? ref_kind? local_variable_type identifier 'in' expression ')' embedded_statement ; ``` +It is a compile-time error for `scoped` to be present in a *foreach_statement* unless a *ref_kind* is also present or the *local_variable_type* denotes a ref struct type. + The *local_variable_type* and *identifier* of a foreach statement declare the ***iteration variable*** of the statement. If the `var` identifier is given as the *local_variable_type*, and no type named `var` is in scope, the iteration variable is said to be an ***implicitly typed iteration variable***, and its type is taken to be the element type of the `foreach` statement, as specified below. It is a compile time error for both `await` and `ref_kind` to be present in a `foreach statement`. @@ -1145,6 +1158,8 @@ If the *foreach_statement* contains both or neither `ref` and `readonly`, the it The iteration variable corresponds to a local variable with a scope that extends over the embedded statement. During execution of a `foreach` statement, the iteration variable represents the collection element for which an iteration is currently being performed. If the iteration variable denotes a read-only variable, a compile-time error occurs if the embedded statement attempts to modify it (via assignment or the `++` and `--` operators) or pass it as a reference or output parameter. +For a discussion of `scoped`, see §scoped-modifier. + The compile-time processing of a `foreach` statement first determines the ***collection type*** (`C`), ***enumerator type*** (`E`) and ***iteration type*** (`T`, `ref T` or `ref readonly T`) of the expression. The determination is similar for the synchronous and asynchronous versions. Different interfaces with different methods and return types distinguish the synchronous and asynchronous versions. The general process proceeds as follows. Names within ‘«’ and ‘»’ are placeholders for the actual names for synchronous and asynchronous iterators. The types allowed for «GetEnumerator», «MoveNext», «IEnumerable»\, «IEnumerator»\, and any other distinctions are detailed in [§13.9.5.2](statements.md#13952-synchronous-foreach) for a synchronous `foreach` statement, and in [§13.9.5.3](statements.md#13953-await-foreach) for an asynchronous `foreach` statement. diff --git a/standard/structs.md b/standard/structs.md index ee4a3a2fb..d1e7bf258 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -146,7 +146,7 @@ The members of a struct consist of the members introduced by its *struct_member_ ```ANTLR struct_member_declaration : constant_declaration - | field_declaration + | struct_field_declaration | method_declaration | property_declaration | event_declaration @@ -161,7 +161,9 @@ struct_member_declaration *fixed_size_buffer_declaration* ([§24.8.2](unsafe-code.md#2482-fixed-size-buffer-declarations)) is only available in unsafe code ([§24](unsafe-code.md#24-unsafe-code)). -> *Note*: All kinds of *class_member_declaration*s except *finalizer_declaration* are also *struct_member_declaration*s. *end note* +> *Note*: A *struct_member_declaration* includes all *class_member_declaration* alternatives except *finalizer_declaration*, and adds *struct_field_declaration* which supports ref fields (§Ref-Fields). *end note* + +Fields in structs support capabilities not supported in classes. See §Ref-Fields for details. Except for the differences noted in [§16.5](structs.md#165-class-and-struct-differences), the descriptions of class members provided in [§15.3](classes.md#153-class-members) through [§15.12](classes.md#1512-static-constructors) apply to struct members as well. @@ -503,7 +505,7 @@ A variable of a struct type directly contains the data of the struct, whereas a With classes, it is possible for two variables to reference the same object, and thus possible for operations on one variable to affect the object referenced by the other variable. With structs, the variables each have their own copy of the data (except in the case of by-reference parameters), and it is not possible for operations on one to affect the other. Furthermore, except when explicitly nullable ([§8.3.12](types.md#8312-nullable-value-types)), it is not possible for values of a struct type to be `null`. -> *Note*: If a struct contains a field of reference type then the contents of the object referenced can be altered by other operations. However the value of the field itself, i.e., which object it references, cannot be changed through a mutation of a different struct value. *end note* +> *Note*: If a struct contains a field of reference type or that is a reference variable then the contents of the object referenced can be altered by other operations. However the value of the field itself, i.e., which object it references, cannot be changed through a mutation of a different struct value. *end note* @@ -558,7 +560,7 @@ When a property or indexer of a struct is the target of an assignment, the insta ### 16.5.5 Default values -As described in [§9.3](variables.md#93-default-values), several kinds of variables are automatically initialized to their default value when they are created. For variables of class types and other reference types, this default value is `null`. However, since structs are value types that cannot be `null`, the default value of a struct is the value produced by setting all value type fields to their default value and all reference type fields to `null`. +As described in [§9.3](variables.md#93-default-values), several kinds of variables are automatically initialized to their default value when they are created. For variables of class types and other reference types, as well as reference variable fields, this default value is `null`. However, since structs are value types that cannot be `null`, the default value of a struct is the value produced by setting all value type fields to their default value and all reference variable fields and reference type fields to `null`. > *Example*: Referring to the `Point` struct declared above, the example > @@ -699,9 +701,11 @@ Similarly, boxing never implicitly occurs when accessing a member on a constrain > > *end example* -### 16.5.8 Field initializers +### 16.5.8 Fields + +#### 16.5.8.1 Field initializers -As described in [§16.5.5](structs.md#1655-default-values), the default value of a struct consists of the value that results from setting all value type fields to their default value and all reference type fields to `null`. Static and instance fields of a struct are permitted to include variable initializers; however, in the case of an instance field initializer, at least one instance constructor shall also be declared, or for a record struct, a *delimited_parameter_list* shall be present. +As described in [§16.5.5](structs.md#1655-default-values), the default value of a struct consists of the value that results from setting all value type and reference variable fields to their default value and all reference type fields to `null`. Static and instance fields of a struct are permitted to include variable initializers; however, in the case of an instance field initializer, at least one instance constructor shall also be declared, or for a record struct, a *delimited_parameter_list* shall be present. > *Example*: > @@ -735,9 +739,62 @@ When a struct instance constructor has a `this()` constructor initializer that r A *field_declaration* declared directly inside a *struct_declaration* having the *struct_modifier* `readonly` shall have the *field_modifier* `readonly`. +#### §Ref-Fields Ref fields + +```ANTLR +struct_field_declaration + : attributes? field_modifier* ('readonly'? 'ref' 'readonly'?)? type + variable_declarators ';' + ; +``` + +*field_modifier* is described in [§15.5.1](classes.md#1551-general). + +A *struct_field_declaration* without `ref`, `readonly ref`, or `ref readonly` is as described in [§15.5](classes.md#155-fields). + +A `ref` or `readonly ref` field is a reference variable and shall only be declared in a `ref` struct. + +Consider the following ref struct declaration: + + + +```csharp +ref struct RwS +{ + public static int rwField = 100; + + public ref int rwRefToRwData = ref rwField; + public ref readonly int rwRefToRoData = ref rwField; + public readonly ref int roRefToRwData = ref rwField; + public readonly ref readonly int roRefToRoData = ref rwField; + public RwS() { /*…*/ } +} +``` + +`rwRefToRwData` is a writable reference variable, whose referent is seen as a writable `int`. `rwRefToRoData` is a writable reference variable, whose referent is seen as a read-only `int`. `roRefToRwData` is a read-only reference variable, whose referent is seen as a writable `int`. `roRefToRoData` is a read-only reference variable, whose referent is seen as a read-only `int`. The read/write field `rwField` can be written directly, and via the reference variables `rwRefToRwData` and `roRefToRwData`. + +A readonly reference variable may take on a value via an initializer, or via an assignment inside a constructor or an init-only setter. + +Consider the following readonly ref struct declaration: + + + +```csharp +readonly ref struct RoS +{ + public static int rwField = 200; + + public readonly ref int roRefToRwData = ref rwField; + public readonly ref readonly int roRefToRoData = ref rwField; + public RoS() { /*...*/ } +} +``` + +`roRefToRwData` is a read-only reference variable, whose referent is seen as a writable `int`. `roRefToRoData` is a read-only reference variable, whose referent is seen as a read-only `int`. The read/write field `rwField` can be written directly, and via the reference variable `roRefToRwData`. + ### 16.5.9 Constructors -A struct can declare instance constructors, with zero or more parameters. If a struct has no explicitly declared parameterless instance constructor, one is synthesized, with public accessibility, which always returns the value that results from setting all value type fields to their default value and all reference type fields to `null` ([§8.3.3](types.md#833-default-constructors)). In such a case, any instance field initializers are ignored when that constructor executes. +A struct can declare instance constructors, with zero or more parameters. If a struct has no explicitly declared parameterless instance constructor, one is synthesized, with public accessibility, which always returns the value that results from setting all value type fields to their default value, all reference variable fields to null references, and all reference type fields to `null` ([§8.3.3](types.md#833-default-constructors)). In such a case, any instance field initializers are ignored when that constructor executes. An explicitly declared parameterless instance constructor shall have public accessibility. @@ -913,17 +970,20 @@ For any non-default expression whose compile-time type is a ref struct has a saf The safe-context records which context a value may be copied into. Given an assignment from an expression `E1` with a safe-context `S1`, to an expression `E2` with safe-context `S2`, it is an error if `S2` is a wider context than `S1`. -There are three different safe-context values, the same as the ref-safe-context values defined for reference variables ([§9.7.2](variables.md#972-ref-safe-contexts)): **declaration-block**, **function-member**, and **caller-context**. The safe-context of an expression constrains its use as follows: +There are four different safe-context values, the same as the ref-safe-context values defined for reference variables ([§9.7.2](variables.md#972-ref-safe-contexts)): **declaration-block**, **function-member**, **return-only**, and **caller-context**. The safe-context of an expression constrains its use as follows: -- For a return statement `return e1`, the safe-context of `e1` shall be caller-context. +- For a return statement `return e1`, the safe-context of `e1` shall be at least return-only. - For an assignment `e1 = e2` the safe-context of `e2` shall be at least as wide a context as the safe-context of `e1`. - -For a method invocation if there is a `ref` or `out` argument of a `ref struct` type (including the receiver unless the type is `readonly`), with safe-context `S1`, then no argument (including the receiver) may have a narrower safe-context than `S1`. +- For an assignment to an `out` parameter, the safe-context of the right-hand side shall be at least return-only. #### 16.5.15.2 Parameter safe context A parameter of a ref struct type, including the `this` parameter of an instance method, has a safe-context of caller-context. +An `out` parameter of a ref struct type has a safe-context of return-only. + +A `this` parameter in a struct constructor has a safe-context of return-only. + #### 16.5.15.3 Local variable safe context A local variable of a ref struct type has a safe-context as follows: @@ -932,6 +992,8 @@ A local variable of a ref struct type has a safe-context as follows: - Otherwise if the variable’s declaration has an initializer then the variable’s safe-context is the same as the safe-context of that initializer. - Otherwise the variable is uninitialized at the point of declaration and has a safe-context of caller-context. +See [§9.7.2.1](variables.md#9721-general) and [§9.7.2.2](variables.md#9722-local-variable-ref-safe-context). + #### 16.5.15.4 Field safe context A reference to a field `e.F`, where the type of `F` is a ref struct type, has a safe-context that is the same as the safe-context of `e`. @@ -946,27 +1008,185 @@ For an operator that yields a value, such as `e1 + e2` or `c ? e1 : e2`, the saf #### 16.5.15.6 Method and property invocation -A value resulting from a method invocation `e1.M(e2, ...)` or property invocation `e.P` has safe-context of the smallest of the following contexts: +A value resulting from a method invocation `e1.M(e2, ...)` or property invocation `e.P`, where `M()` does not return ref-to-ref-struct, has safe-context of the smallest of the following contexts: -- caller-context. -- The safe-context of all argument expressions (including the receiver). +- The caller-context. +- When the return is a `ref struct`, the safe-context contributed by all argument expressions (including the receiver), excluding arguments corresponding to `scoped` parameters and excluding `out` arguments. +- When the return is a `ref struct`, the ref-safe-context contributed by all `ref` arguments, excluding those corresponding to `scoped ref` parameters and excluding `out` arguments. + +If `M()` does return ref-to-ref-struct, the safe-context is the same as the safe-context of all arguments which are ref-to-ref-struct. It is an error if there are multiple such arguments with different safe-contexts. + +For the purpose of these rules, a given argument `expr` passed to parameter `p`: + +1. If `p` is `scoped ref`, then `expr` does not contribute ref-safe-context. +2. If `p` is `scoped`, then `expr` does not contribute safe-context. +3. If `p` is `out`, then `expr` does not contribute ref-safe-context or safe-context. A property invocation (either `get` or `set`) is treated as a method invocation of the underlying method by the above rules. -#### 16.5.15.7 stackalloc +> *Example*: The following illustrates how `scoped` affects the safe-context of a method's return value: +> +> +> ```csharp +> ref struct RS +> { +> public ref int RefField; +> public RS(ref int i) { RefField = ref i; } +> } +> +> class C +> { +> static RS CreateAndCapture(ref int value) +> { +> // OK: ref-safe-context of `ref value` is caller-context, +> // safe-context contributed is caller-context. +> return new RS(ref value); +> } +> +> static RS CreateWithoutCapture(scoped ref int value) +> { +> // Error: `value` is scoped ref so it does not contribute +> // ref-safe-context. The constructor needs ref-safe-context +> // of caller-context but `value` only has function-member. +> return new RS(ref value); +> } +> } +> ``` +> +> *end example* -The result of a stackalloc expression has safe-context of function-member. +#### §method-arguments-must-match Method arguments must match -#### 16.5.15.8 Constructor invocations +For any method invocation `e.M(a1, a2, ... aN)`: -A `new` expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed. +1. Calculate the narrowest safe-context from: + - caller-context. + - The safe-context of all arguments. + - The ref-safe-context of all `ref` arguments whose corresponding parameters have a ref-safe-context of caller-context. + +2. All `ref` arguments of `ref struct` types shall be assignable by a value with that safe-context. In this rule, `ref` does **not** generalize to include `in` and `out`. + +For any method invocation `e.M(a1, a2, ... aN)`: -In addition the safe-context is the smallest of the safe-contexts of all arguments and operands of all object initializer expressions, recursively, if any initializer is present. +1. Calculate the narrowest safe-context from: + - caller-context. + - The safe-context of all arguments. + - The ref-safe-context of all `ref` arguments whose corresponding parameters are not `scoped`. -> *Note*: These rules rely on `Span` not having a constructor of the following form: +2. All `out` arguments of `ref struct` types shall be assignable by a value with that safe-context. + +The presence of `scoped` allows developers to reduce the friction this rule creates by marking parameters which are not returned as `scoped`. This removes their arguments from (1) in both cases above and provides greater flexibility to callers. + +> *Example*: The following illustrates how the method-arguments-must-match rule prevents a value with a narrower safe-context from being stored into a `ref` argument with a wider safe-context: > +> > ```csharp -> public Span(ref T p) +> ref struct R { } +> +> class C +> { +> static void F0(ref R a, scoped ref R b) { } +> +> static void F1(ref R x, scoped R y) +> { +> // Error: The narrowest safe-context is function-member (from `y`) +> // but `x` is a ref argument of a ref struct type whose +> // safe-context is caller-context. It must be assignable by +> // a value with that narrowest safe-context, which fails. +> F0(ref x, ref y); +> } +> } +> ``` +> +> *end example* + +#### §declaration-expression-safe-context Infer safe-context of declaration expressions + +The safe-context of a declaration variable from an `out` argument (`M(x, out var y)`) or deconstruction (`(var x, var y) = M()`) is the narrowest of the following: + +- caller-context. +- If the out variable is marked `scoped`, then declaration-block (i.e., function-member or narrower). +- If the out variable's type is a `ref struct`, consider all arguments to the containing invocation, including the receiver: + - The safe-context of any argument where its corresponding parameter is not `out` and has safe-context of return-only or wider. + - The ref-safe-context of any argument where its corresponding parameter has ref-safe-context of return-only or wider. + +> *Example*: The following illustrates how the safe-context of an `out` declaration variable is inferred from the other arguments to the invocation: +> +> +> ```csharp +> ref struct RS +> { +> public RS(ref int x) { } +> +> static void M0(RS input, out RS output) => output = input; +> +> static RS M1() +> { +> var i = 0; +> var rs1 = new RS(ref i); // safe-context of rs1 is function-member +> M0(rs1, out var rs2); // safe-context of rs2 is function-member +> return rs2; // Error: rs2 cannot escape function-member +> } +> +> static void M2(RS rs1) +> { +> M0(rs1, out scoped var rs2); // scoped forces safe-context to +> // declaration-block +> } +> } +> ``` +> +> In `M1`, the safe-context of `rs2` is the narrowest of *caller-context* and the safe-context of `rs1` (*function-member*), which is *function-member*. Therefore `rs2` cannot be returned. In `M2`, the `scoped` modifier forces the safe-context of `rs2` to *declaration-block*. +> +> *end example* + +#### §object-initializer-safe-context Object initializer safe context + +The safe-context of an object initializer expression is the narrowest of: + +1. The safe-context of the constructor invocation. +2. The safe-context and ref-safe-context of arguments to member initializer indexers that can escape to the receiver. +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: +> +> +> ```csharp +> using System; +> +> ref struct S +> { +> public Span Field; +> public S(ref int i) { } +> } +> +> class C +> { +> static S Example() +> { +> Span stackSpan = stackalloc int[42]; +> int i = 0; +> +> // safe-context is narrowest of: +> // constructor safe-context (caller-context) and +> // RHS of Field assignment (stackSpan: function-member) +> // = function-member +> var x = new S(ref i) { Field = stackSpan }; +> return x; // Error: x has safe-context of function-member +> } +> } > ``` > -> Such a constructor makes instances of `Span` used as fields indistinguishable from a `ref` field. The safety rules described in this document depend on `ref` fields not being a valid construct in C# or .NET. *end note* +> *end example* + +#### 16.5.15.7 stackalloc + +The result of a stackalloc expression has safe-context of function-member. + +#### 16.5.15.8 Constructor invocations + +A `new` expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed. + +In addition the safe-context is the smallest of the safe-contexts of all arguments and operands of all object initializer expressions, recursively, if any initializer is present. See §object-initializer-safe-context for details. diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index 9e624bc3b..4cadd819d 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -133,7 +133,7 @@ The type of the target of a pointer type is called the ***referent type*** of th A *pointer_type* may only be used in an *array_type* in an unsafe context ([§24.2](unsafe-code.md#242-unsafe-contexts)). A *non_array_type* is any type that is not itself an *array_type*. -Unlike references (values of reference types), pointers are not tracked by the garbage collector—the garbage collector has no knowledge of pointers and the data or static methods to which they point. For this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer shall be an *unmanaged_type*. Pointer types themselves are unmanaged types, so a pointer type may be used as the referent type for another pointer type. +Unlike references (values of reference types), pointers are not tracked by the garbage collector—the garbage collector has no knowledge of pointers and the data or static methods to which they point. For this reason the referent type of a pointer shall be an *unmanaged_type*. An implementation may permit a managed type as a referent type, in which case a warning is produced. Pointer types themselves are unmanaged types, so a pointer type may be used as the referent type for another pointer type. The intuitive rule for mixing pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references. @@ -708,7 +708,7 @@ addressof_expression *unary_expression* shall designate either a variable or a method group. The variable case is described immediately below. -Given an expression `E` which is of a type `T` and is classified as a fixed variable ([§24.4](unsafe-code.md#244-fixed-and-moveable-variables)), the construct `&E` computes the address of the variable given by `E`. The type of the result is `T*` and is classified as a value. A compile-time error occurs if `E` is not classified as a variable, if `E` is classified as a read-only local variable, or if `E` denotes a moveable variable. In the last case, a fixed statement ([§24.7](unsafe-code.md#247-the-fixed-statement)) can be used to temporarily “fix” the variable before obtaining its address. +Given an expression `E` which is of a type `T` and is classified as a fixed variable ([§24.4](unsafe-code.md#244-fixed-and-moveable-variables)), the construct `&E` computes the address of the variable given by `E`. The type of the result is `T*` and is classified as a value. If `T` is a managed type, a warning is produced. A compile-time error occurs if `E` is not classified as a variable, if `E` is classified as a read-only local variable, or if `E` denotes a moveable variable. In the last case, a fixed statement ([§24.7](unsafe-code.md#247-the-fixed-statement)) can be used to temporarily “fix” the variable before obtaining its address. > *Note*: As stated in [§12.8.7](expressions.md#1287-member-access), outside an instance constructor or static constructor for a struct or class that defines a `readonly` field, that field is considered a value, not a variable. As such, its address cannot be taken. Similarly, the address of a constant cannot be taken. *end note* @@ -891,8 +891,8 @@ Each *fixed_pointer_declarator* declares a local variable of the given *pointer_ It is an error to use a captured local variable ([§12.22.6.2](expressions.md#122262-captured-outer-variables)), value parameter, or parameter array in a *fixed_pointer_initializer*. A *fixed_pointer_initializer* can be one of the following: -- The token “`&`” followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)) to a moveable variable ([§24.4](unsafe-code.md#244-fixed-and-moveable-variables)) of an unmanaged type `T`, provided the type `T*` is implicitly convertible to the pointer type given in the `fixed` statement. In this case, the initializer computes the address of the given variable, and the variable is guaranteed to remain at a fixed address for the duration of the fixed statement. -- An expression of an *array_type* with elements of an unmanaged type `T`, provided the type `T*` is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the first element in the array, and the entire array is guaranteed to remain at a fixed address for the duration of the `fixed` statement. If the array expression is `null` or if the array has zero elements, the initializer computes an address equal to zero. +- The token “`&`” followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)) to a moveable variable ([§24.4](unsafe-code.md#244-fixed-and-moveable-variables)) of a type `T`, provided the type `T*` is implicitly convertible to the pointer type given in the `fixed` statement. In this case, the initializer computes the address of the given variable, and the variable is guaranteed to remain at a fixed address for the duration of the fixed statement. If `T` is a managed type, a warning is produced. +- An expression of an *array_type* with elements of a type `T`, provided the type `T*` is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the first element in the array, and the entire array is guaranteed to remain at a fixed address for the duration of the `fixed` statement. If the array expression is `null` or if the array has zero elements, the initializer computes an address equal to zero. If `T` is a managed type, a warning is produced. - An expression of type `string`, provided the type `char*` is implicitly convertible to the pointer type given in the `fixed` statement. In this case, the initializer computes the address of the first character in the string, and the entire string is guaranteed to remain at a fixed address for the duration of the `fixed` statement. The behavior of the `fixed` statement is implementation-defined if the string expression is `null`. - An expression of type other than *array_type* or `string`, provided there exists an accessible method or accessible extension method matching the signature `ref [readonly] T GetPinnableReference()`, where `T` is an *unmanaged_type*, and `T*` is implicitly convertible to the pointer type given in the `fixed` statement. In this case, the initializer computes the address of the returned variable, and that variable is guaranteed to remain at a fixed address for the duration of the `fixed` statement. A `GetPinnableReference()` method can be used by the `fixed` statement when overload resolution ([§12.6.4](expressions.md#1264-overload-resolution)) produces exactly one function member and that function member satisfies the preceding conditions. The `GetPinnableReference` method should return a reference to an address equal to zero, such as that returned from `System.Runtime.CompilerServices.Unsafe.NullRef()` when there is no data to pin. - A *simple_name* or *member_access* that references a fixed-size buffer member of a moveable variable, provided the type of the fixed-size buffer member is implicitly convertible to the pointer type given in the `fixed` statement. In this case, the initializer computes a pointer to the first element of the fixed-size buffer ([§24.8.3](unsafe-code.md#2483-fixed-size-buffers-in-expressions)), and the fixed-size buffer is guaranteed to remain at a fixed address for the duration of the `fixed` statement. diff --git a/standard/variables.md b/standard/variables.md index 750a43d3b..348a0442e 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -190,16 +190,18 @@ A discard is not initially assigned, so it is always an error to access its valu The following categories of variables are automatically initialized to their default values: - Static variables. -- Instance variables of class instances. +- Instance variables of class and struct instances. - Array elements. The default value of a variable depends on the type of the variable and is determined as follows: - For a variable of a *value_type*, the default value is the same as the value computed by the *value_type*’s default constructor ([§8.3.3](types.md#833-default-constructors)). -- For a variable of a *reference_type*, the default value is `null`. +- For a variable of a *reference_type* or a reference variable, the default value is `null`. > *Note*: Initialization to default values is typically done by having the memory manager or garbage collector initialize memory to all-bits-zero before it is allocated for use. For this reason, it is convenient to use all-bits-zero to represent the null reference. *end note* +> *Note*: To test if a ref variable has been assigned a referent, call `System.Runtime.CompilerServices.Unsafe.IsNullRef(ref fieldName)`. One cannot test a ref variable to see if it has been assigned a referent by using `fieldName == null`, as that tests the value of the (potentially non-existent) referent, not the reference itself. *end note* + ## 9.4 Definite assignment ### 9.4.1 General @@ -1180,7 +1182,7 @@ Reads and writes of the following data types shall be atomic: `bool`, `char`, `b ### 9.7.1 General -A ***reference variable*** is a variable that refers to another variable, called the referent ([§9.2.6](variables.md#926-reference-parameters)). A reference variable is a local variable declared with the `ref` modifier. +A ***reference variable*** is a variable that refers to another variable, called the referent ([§9.2.6](variables.md#926-reference-parameters)). A reference variable is a local variable or ref struct field declared with the `ref` modifier. A reference variable stores a *variable_reference* ([§9.5](variables.md#95-variable-references)) to its referent and not the value of its referent. When a reference variable is used where a value is required its referent’s value is returned; similarly when a reference variable is the target of an assignment it is the referent which is assigned to. The variable to which a reference variable refers, i.e. the stored *variable_reference* for its referent, can be changed using a ref assignment (`= ref`). @@ -1235,7 +1237,7 @@ For any variable, the ***ref-safe-context*** of that variable is the context whe > *Note*: A compiler determines the ref-safe-context through a static analysis of the program text. The ref-safe-context reflects the lifetime of a variable at runtime. *end note* -There are three ref-safe-contexts: +There are four ref-safe-contexts: - ***declaration-block***: The ref-safe-context of a *variable_reference* to a local variable ([§9.2.9.1](variables.md#9291-general)) is that local variable’s scope ([§13.6.2](statements.md#1362-local-variable-declarations)), including any nested *embedded-statement*s in that scope. @@ -1243,14 +1245,21 @@ There are three ref-safe-contexts: - ***function-member***: Within a function a *variable_reference* to any of the following has a ref-safe-context of function-member: - - Value parameters ([§15.6.2.2](classes.md#15622-value-parameters)) on a function member declaration, including the implicit `this` of class member functions; and - - The implicit reference (`ref`) parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)) `this` of a struct member function, along with its fields. + - Value parameters ([§15.6.2.2](classes.md#15622-value-parameters)) on a function member declaration, including the implicit `this` of class member functions; + - Output parameters ([§15.6.2.3.4](classes.md#156234-output-parameters)), which are implicitly `scoped ref`; and + - The implicit reference (`ref`) parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)) `this` of a struct member function, which is implicitly `scoped ref`, along with its fields. + + A *variable_reference* with ref-safe-context of function-member is a valid referent only if the reference variable is declared in the same function member. + +- ***return-only***: Within a function a *variable_reference* to any of the following has a ref-safe-context of return-only: - A *variable_reference* with ref-safe-context of function-member is a valid referent only if the reference variable is declared in the same function member. + - Reference parameters ([§9.2.6](variables.md#926-reference-parameters)) other than the implicit `this` of a struct member function and other than output parameters; and + - Input parameters ([§15.6.2.3.2](classes.md#156232-input-parameters)). -- ***caller-context***: Within a function a *variable_reference* to any of the following has a ref-safe-context of caller-context: - - Reference parameters ([§9.2.6](variables.md#926-reference-parameters)) other than the implicit `this` of a struct member function; - - Member fields and elements of such parameters; + A *variable_reference* with ref-safe-context of return-only can be the referent of a reference return. + +- ***caller-context***: Within a function a *variable_reference* to any of the following has a ref-safe-context of caller-context: + - Member fields and elements of reference or input parameters; - Member fields of parameters of class type; and - Elements of parameters of array type. @@ -1268,7 +1277,7 @@ These values form a nesting relationship from narrowest (declaration-block) to w > // ref safe context of arr[i] is "caller-context". > private int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; > -> // ref safe context is "caller-context" +> // ref safe context is "return-only" > public ref int M1(ref int r1) > { > return ref r1; // r1 is safe to ref return @@ -1335,6 +1344,8 @@ These values form a nesting relationship from narrowest (declaration-block) to w > > *end example.* +A reference variable that is a local variable or parameter can be scoped explicitly; see §scoped-modifier. + #### 9.7.2.2 Local variable ref safe context For a local variable `v`: @@ -1346,30 +1357,82 @@ For a local variable `v`: For a parameter `p`: -- If `p` is a reference or input parameter, its ref-safe-context is the caller-context. If `p` is an input parameter, it cannot be returned as a writable `ref` but can be returned as `ref readonly`. -- If `p` is an output parameter, its ref-safe-context is the caller-context. -- Otherwise, if `p` is the `this` parameter of a struct type, its ref-safe-context is the function-member. +- If `p` is a reference or input parameter, its ref-safe-context is return-only. If `p` is an input parameter, it cannot be returned as a writable `ref` but can be returned as `ref readonly`. +- If `p` is an output parameter, its ref-safe-context is function-member. An output parameter is implicitly `scoped ref`. +- Otherwise, if `p` is the `this` parameter of a struct type, its ref-safe-context is function-member. The `this` parameter of a struct instance method is implicitly `scoped ref`. - Otherwise, the parameter is a value parameter, and its ref-safe-context is the function-member. +When a parameter is annotated with `[UnscopedRef]` ([§UnscopedRefAttribute](attributes.md#unscopedrefattribute-the-unscopedref-attribute)), its ref-safe-context is widened by one level from its default: function-member becomes return-only, and return-only becomes caller-context. + +> *Example*: The following illustrates how the implicit `this` parameter of a struct instance method is `scoped ref` (ref-safe-context of *function-member*), and how `[UnscopedRef]` widens it to *return-only*, enabling ref returns of fields: +> +> +> ```csharp +> using System.Diagnostics.CodeAnalysis; +> +> struct S +> { +> private int _field; +> +> // Error: ref-safe-context of `this` is function-member, +> // so ref-safe-context of `_field` is also function-member, +> // which does not satisfy the return-only requirement. +> public ref int Bad() => ref _field; +> +> // OK: [UnscopedRef] widens `this` from function-member to +> // return-only, so `_field` also has ref-safe-context of +> // return-only, satisfying the ref return requirement. +> [UnscopedRef] +> public ref int Good() => ref _field; +> } +> ``` +> +> *end example* + #### 9.7.2.4 Field ref safe context For a variable designating a reference to a field, `e.F`: -- If `e` is of a reference type, its ref-safe-context is the caller-context. +- If `F` is a reference variable, its ref-safe-context is the safe-context of `e`. +- Else if `e` is of a reference type, its ref-safe-context is the caller-context. - Otherwise, if `e` is of a value type, its ref-safe-context is the same as the ref-safe-context of `e`. +As a result, a field that is a reference variable may be returned as a reference variable from a `ref struct` or `readonly ref struct`, but a non-reference variable field may not. + +> *Example*: +> +> +> ```csharp +> ref struct RS +> { +> ref int _refField; +> int _field; +> public ref int Prop1 => ref _refField; // OK +> public ref int Prop2 => ref _field; // Error +> } +> ``` +> +> *end example* + #### 9.7.2.5 Operators The conditional operator ([§12.21](expressions.md#1221-conditional-operator)), `c ? ref e1 : ref e2`, and reference assignment operator, `= ref e` ([§12.24.1](expressions.md#12241-general)) have reference variables as operands and yield a reference variable. For those operators, the ref-safe-context of the result is the narrowest context among the ref-safe-contexts of all `ref` operands. #### 9.7.2.6 Function invocation -For a variable `c` resulting from a ref-returning function invocation, its ref-safe-context is the narrowest of the following contexts: +For a variable `c` resulting from a ref-returning function invocation, `ref e1.M(e2, ...)`, where `M()` does not return ref-to-ref-struct, its ref-safe-context is the narrowest of the following contexts: - The caller-context. -- The ref-safe-context of all `ref`, `out`, and `in` argument expressions (excluding the receiver). -- For each input parameter, if there is a corresponding expression that is a variable and there exists an identity conversion between the type of the variable and the type of the parameter, the variable’s ref-safe-context, otherwise the nearest enclosing context. -- The safe-context ([§16.5.15](structs.md#16515-safe-context-constraint)) of all argument expressions (including the receiver). +- The safe-context ([§16.5.15](structs.md#16515-safe-context-constraint)) contributed by all argument expressions (including the receiver), excluding arguments corresponding to `scoped` parameters and excluding `out` arguments. +- The ref-safe-context contributed by all `ref` arguments, excluding those corresponding to `scoped ref` parameters and excluding `out` arguments. + +If `M()` does return ref-to-ref-struct, the ref-safe-context is the narrowest ref-safe-context contributed by all arguments which are ref-to-ref-struct. + +For the purpose of these rules, a given argument `expr` passed to parameter `p`: + +1. If `p` is `scoped ref`, then `expr` does not contribute ref-safe-context. +2. If `p` is `scoped`, then `expr` does not contribute safe-context. +3. If `p` is `out`, then `expr` does not contribute ref-safe-context or safe-context. > *Example*: the last bullet is necessary to handle code such as > @@ -1410,4 +1473,62 @@ A `new` expression that invokes a constructor obeys the same rules as a method i - Neither a reference parameter, nor an output parameter, nor an input parameter, nor a parameter of a `ref struct` type shall be an argument for an iterator method or an `async` method. - Neither a `ref` local, nor a local of a `ref struct` type shall be in context at the point of a `yield return` statement or an `await` expression. - For a ref reassignment `e1 = ref e2`, the ref-safe-context of `e2` shall be at least as wide a context as the *ref-safe-context* of `e1`. -- For a ref return statement `return ref e1`, the ref-safe-context of `e1` shall be the caller-context. +- For a ref return statement `return ref e1`, the ref-safe-context of `e1` shall be at least return-only. + +### §scoped-modifier The scoped modifier + +The contextual keyword `scoped` is used as a modifier to restrict the ref-safe-context ([§9.7.2](variables.md#972-ref-safe-contexts)) or safe-context ([§16.5.15](structs.md#16515-safe-context-constraint)) of a variable. The presence of this modifier requires that related code doesn’t extend the lifetime of the variable. + +`scoped` shall only be applied to reference variables (which includes non-value parameters) and to variables of a ref struct type. `scoped` shall not be applied to fields, array elements, or return types. + +Consider the following declarations and their safe contexts: + +| Local Variable | ref-safe-context | safe-context | +|---|---|---| +| `Span s` | *function-member* | *caller-context* | +| `scoped Span s` | *function-member* | *function-member* | +| `ref Span s` | *caller-context* | *caller-context* | +| `scoped ref Span s` | *function-member* | *caller-context* | + +In this relationship the *ref-safe-context* of a value can never be wider than the *safe-context*. + +### §parameter-scope-variance Parameter scope variance + +The `scoped` modifier (§scoped-modifier) and `[UnscopedRef]` attribute (§UnscopedRefAttribute) on parameters affect overriding, interface implementation, and `delegate` conversion. The signature for an override, interface implementation, or `delegate` conversion may: + +- Add `scoped` to a `ref` or `in` parameter. +- Add `scoped` to a parameter of a `ref struct` type. +- Remove `[UnscopedRef]` from an `out` parameter. +- Remove `[UnscopedRef]` from a `ref` parameter of a `ref struct` type. + +Any other difference with respect to `scoped` or `[UnscopedRef]` between the base and the overriding, implementing, or converting signature is a mismatch. + +The `scoped` modifier and `[UnscopedRef]` attribute do not affect hiding. + +Overloads shall not differ only on `scoped` or `[UnscopedRef]`. + +> *Example*: The following illustrates valid and invalid scope variance in overrides: +> +> +> ```csharp +> using System; +> +> class Base +> { +> public virtual void M(ref Span x) { } +> } +> +> class Derived : Base +> { +> // OK: adds scoped to a ref parameter +> public override void M(scoped ref Span x) { } +> } +> +> class C +> { +> void N(Span x) { } +> void N(scoped Span x) { } // Error: overloads differ only on scoped +> } +> ``` +> +> *end example* diff --git a/tools/example-templates/additional-files/RefStruct.cs b/tools/example-templates/additional-files/RefStruct.cs new file mode 100644 index 000000000..168251305 --- /dev/null +++ b/tools/example-templates/additional-files/RefStruct.cs @@ -0,0 +1,5 @@ +ref struct RS +{ + public ref int Field; + public RS(ref int i) { Field = ref i; } +}