From e3c8cc2303a456d6e95df647a6369eb608866418 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 13:51:59 -0400 Subject: [PATCH 01/24] support ref fields and scoped --- standard/lexical-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' ; From 420710f66b958f56685c8b60aa3c91bf1e95ec17 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 13:55:39 -0400 Subject: [PATCH 02/24] support ref fields and scoped --- standard/basic-concepts.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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: From 83a2eced52d6bcbafc768f1025adf7c096626cc7 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:05:23 -0400 Subject: [PATCH 03/24] support ref fields and scoped --- standard/variables.md | 49 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 750a43d3b..f878b07a3 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -190,16 +190,20 @@ 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* +To test if a ref variable has been assigned a referent, call `System.Runtime.CompilerServices.Unsafe.IsNullRef(ref fieldName)`. + +> *Note*: 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 +1184,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`). @@ -1335,6 +1339,8 @@ These values form a nesting relationship from narrowest (declaration-block) to w > > *end example.* +A reference variable can be scoped explicitly; see §scoped-modifier. + #### 9.7.2.2 Local variable ref safe context For a local variable `v`: @@ -1355,9 +1361,27 @@ For a parameter `p`: 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. @@ -1411,3 +1435,20 @@ A `new` expression that invokes a constructor obeys the same rules as a method i - 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. + +### §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 asserts 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. + +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*. From 6e75dd473179b7c748c3474218bfbbe3e8dc933d Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:15:48 -0400 Subject: [PATCH 04/24] support ref fields and scoped --- standard/expressions.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index fb4927576..d4f2397b2 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -569,7 +569,7 @@ argument_value : expression | 'in' variable_reference | 'ref' variable_reference - | 'out' variable_reference + | 'out' 'scoped'? variable_reference ; ``` @@ -580,7 +580,7 @@ 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 `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`, may 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,9 @@ 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 +5586,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 @@ -7160,6 +7172,10 @@ The left operand shall be an expression that binds to a reference variable ([§9 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 left operand shall have the same safe-context as the right operand. + +> *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. When the left operand binds to an output parameter, it is an error if that output parameter has not been definitely assigned at the beginning of the ref assignment operator. From ab7335485200748fe8be7543c514f6b3ccc5cf27 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:26:41 -0400 Subject: [PATCH 05/24] support ref fields and scoped --- standard/statements.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index bc26591d2..39da6632e 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 struct RS {} > ``` > > 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 struct RS {} > ``` > > 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,7 +1143,7 @@ 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 ; ``` @@ -1145,6 +1156,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. From 23ab11a0df8de046e610c02f5fff725a5d9575a5 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:31:06 -0400 Subject: [PATCH 06/24] support ref fields and scoped --- standard/classes.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 From 32fcc405437e37a58e47a24229ad9cc9722f74c2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:38:48 -0400 Subject: [PATCH 07/24] support ref fields and scoped --- standard/structs.md | 68 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index ee4a3a2fb..70991bb14 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,7 @@ 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* +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 +503,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 +558,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 +699,11 @@ Similarly, boxing never implicitly occurs when accessing a member on a constrain > > *end example* -### 16.5.8 Field initializers +### 16.5.8 Fields -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. +#### 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 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,6 +737,58 @@ 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? ('readonly'? 'ref')? field_modifier* type variable_declarators ';' + ; +``` + +*field_modifier* is described in [§15.5.1](classes.md#1551-general). + +A *struct_field_declaration* without `ref` or `readonly ref` 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. @@ -932,6 +986,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`. From 769ac121f807059c4cf939e801639ef6b39ed848 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:46:43 -0400 Subject: [PATCH 08/24] support ref fields and scoped --- standard/attributes.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/standard/attributes.md b/standard/attributes.md index 061aa4430..f95e8d6fe 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)). @@ -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; 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. From 042a782fd2d5f42f9d0377cc5085173fcaa4ff5f Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:54:56 -0400 Subject: [PATCH 09/24] support ref fields and scoped --- standard/standard-library.md | 11 +++++++++++ 1 file changed, 11 insertions(+) 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` From 0ee20b149ad8449215ab27c062f1fad17dbd5ba8 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:09:28 -0400 Subject: [PATCH 10/24] fix md formatting --- standard/variables.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index f878b07a3..8b31dfc74 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1444,11 +1444,11 @@ The contextual keyword `scoped` is used as a modifier to restrict the ref-safe-c Consider the following declarations and their safe contexts: -| Local Variable | ref-safe-context | safe-context | +| 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* | +| `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*. From 5db4998762fd0c4b16f9de5d267b20345c903ce2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:12:55 -0400 Subject: [PATCH 11/24] fix md formatting --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index 8b31dfc74..41af5de55 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1451,4 +1451,4 @@ Consider the following declarations and their safe contexts: | `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*. +In this relationship the *ref-safe-context* of a value can never be wider than the *safe-context*. From 5e556848b78c1fa9a7dec61554db3453a06ad3bd Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:15:00 -0400 Subject: [PATCH 12/24] fix md formatting --- standard/expressions.md | 1 - 1 file changed, 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index d4f2397b2..b4cd6a16d 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -5575,7 +5575,6 @@ A *block* body of an anonymous function is always reachable ([§13.2](statements > => (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* From fe94dd0dbfa22f021d5c5adf044cfc469441c192 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:16:38 -0400 Subject: [PATCH 13/24] fix md formatting --- standard/expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index b4cd6a16d..4a9ca9f84 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3518,7 +3518,7 @@ 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`, may not have an explicit initializer of `default`. +A reference variable field `rv` of type `T`, may 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* From acea867a29ab5ad0bfc76ed01bbbf91637cdc18b Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:30:44 -0400 Subject: [PATCH 14/24] fix md formatting --- standard/attributes.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index f95e8d6fe..ac719f805 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -928,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. From c1a4a208afe51f0ba3017cf6b8c8fff62107a59f Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:32:17 -0400 Subject: [PATCH 15/24] fix md formatting --- standard/attributes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index ac719f805..1b6e95be2 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1211,11 +1211,11 @@ 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. +- 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. From 32d6077fea02e23697a9388a2bbad98780e1eaec Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 15:57:52 -0400 Subject: [PATCH 16/24] Introduces the 4th safe-context value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | File | Change | |------|--------| | variables.md §9.7.2.1 | Add *return-only* as 4th ref-safe-context. "three" → "four". Restructure bullets: function-member includes `out` (implicitly scoped) + struct `this` (implicitly scoped); return-only covers `ref`/`in`; caller-context covers fields/elements. | | variables.md §9.7.2.3 | ref/in → *return-only*; out → *function-member*; struct this → *function-member*. Add `[UnscopedRef]` widening rule. | | variables.md §9.7.2.9 | Ref return rule: "shall be the caller-context" → "shall be at least *return-only*". | | structs.md §16.5.15.1 | Add *return-only* as 4th safe-context. Return rule → "at least return-only". Remove old MAMM paragraph. | | structs.md §16.5.15.2 | Add: out of ref struct → *return-only*; this in struct constructor → *return-only*. | --- standard/structs.md | 11 +++++++---- standard/variables.md | 33 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index 70991bb14..eb6057aeb 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -967,17 +967,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: diff --git a/standard/variables.md b/standard/variables.md index 41af5de55..3ea7c0801 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1239,7 +1239,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. @@ -1247,14 +1247,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. + 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. -- ***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; +- ***return-only***: Within a function a *variable_reference* to any of the following has a ref-safe-context of return-only: + + - 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)). + + 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. @@ -1272,7 +1279,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 @@ -1352,11 +1359,13 @@ 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. + #### 9.7.2.4 Field ref safe context For a variable designating a reference to a field, `e.F`: @@ -1434,7 +1443,7 @@ 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 From 4db244c4fc13105c4a70747fb832c62212be5c95 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:05:13 -0400 Subject: [PATCH 17/24] Method invocation rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | File | Change | |------|--------| | variables.md §9.7.2.6 | Replace function invocation rules: scoped ref → no ref-safe-context contribution; scoped → no safe-context contribution; out → neither. Add ref-to-ref-struct case. | | structs.md §16.5.15.6 | Parallel update: same scoped exclusions, same ref-to-ref-struct return case. | --- standard/structs.md | 15 ++++++++++++--- standard/variables.md | 15 +++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index eb6057aeb..fa21e5674 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1005,10 +1005,19 @@ 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. diff --git a/standard/variables.md b/standard/variables.md index 3ea7c0801..69d2e7b5f 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1397,12 +1397,19 @@ The conditional operator ([§12.21](expressions.md#1221-conditional-operator)), #### 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 > From 489d6520162c36be6be3fde75ac38073c55898a1 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:12:40 -0400 Subject: [PATCH 18/24] MAMM, declaration expressions, object initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New constraint subsections in §16.5.15. | File | Change | |------|--------| | structs.md (new §method-arguments-must-match) | Two checks: (1) ref args of ref struct must be assignable by narrowest safe-context of non-scoped inputs; (2) same for out args of ref struct. Replaces the old MAMM paragraph removed in B5. | | structs.md (new §declaration-expression-safe-context) | Infer safe-context of out declaration variables: narrowest of caller-context, scoped → declaration-block, and contributed safe/ref-safe contexts of non-out arguments. | | structs.md (new subsection) | Object initializer safe-context: narrowest of constructor safe-context, member-initializer argument escapes, and RHS of assignments/ref-assignments. | --- standard/structs.md | 52 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index fa21e5674..b3569a880 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1021,6 +1021,48 @@ For the purpose of these rules, a given argument `expr` passed to parameter `p`: A property invocation (either `get` or `set`) is treated as a method invocation of the underlying method by the above rules. +#### §method-arguments-must-match Method arguments must match + +For any method invocation `e.M(a1, a2, ... aN)`: + +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)`: + +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`. + +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. + +#### §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. + +#### §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* + #### 16.5.15.7 stackalloc The result of a stackalloc expression has safe-context of function-member. @@ -1029,12 +1071,4 @@ The result of a stackalloc expression has safe-context of function-member. 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. - -> *Note*: These rules rely on `Span` not having a constructor of the following form: -> -> ```csharp -> public Span(ref T p) -> ``` -> -> 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* +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. From 5bea6e44f650301fdbe32321499e77253bc02edb Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:19:22 -0400 Subject: [PATCH 19/24] Grammar corrections. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | File | Change | |------|--------| | statements.md foreach_statement | `('scoped'? ref_kind)` → `'scoped'? ref_kind?`. Add semantic restriction: scoped requires ref_kind or ref struct type. | | structs.md struct_field_declaration | `field_modifier*` before ref group; add `'readonly'?` after `'ref'` for `ref readonly`. | | expressions.md argument_value | Add `'scoped'?` to `'in'` and `'ref'` alternatives. Update descriptive text ~L581–583. | --- standard/expressions.md | 8 ++++---- standard/statements.md | 4 +++- standard/structs.md | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 4a9ca9f84..cdf7d2e0c 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -567,8 +567,8 @@ argument_name argument_value : expression - | 'in' variable_reference - | 'ref' variable_reference + | 'in' 'scoped'? variable_reference + | 'ref' 'scoped'? variable_reference | 'out' 'scoped'? variable_reference ; ``` @@ -578,8 +578,8 @@ 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 `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. diff --git a/standard/statements.md b/standard/statements.md index 39da6632e..01fbd9a26 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -1143,11 +1143,13 @@ The `foreach` statement enumerates the elements of a collection, executing an em ```ANTLR foreach_statement - : 'await'? 'foreach' '(' ('scoped'? 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`. diff --git a/standard/structs.md b/standard/structs.md index b3569a880..94e483fc1 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -741,13 +741,14 @@ A *field_declaration* declared directly inside a *struct_declaration* having the ```ANTLR struct_field_declaration - : attributes? ('readonly'? 'ref')? field_modifier* type variable_declarators ';' + : 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` or `readonly ref` is as described in [§15.5](classes.md#155-fields). +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. From 4e5569b2f5346b5b5d901448361924c028513799 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:23:51 -0400 Subject: [PATCH 20/24] unsafe context changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit |------|--------| | unsafe-code.md §24.3.1 | Permit managed referent types with a warning. | | unsafe-code.md §24.6.5 | Relax address-of to accept managed-type operands with a warning. | | unsafe-code.md §24.7 | Relax fixed statement initializers to accept managed types with a warning. | --- standard/unsafe-code.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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. From 4172df24452aaf17a8aee9232aed91c3350d25a9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:30:14 -0400 Subject: [PATCH 21/24] Parameter scope variance + UnscopedRef xref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | File | Change | |------|--------| | variables.md (new §parameter-scope-variance) | Overrides/implementations/delegate conversions may: add scoped to ref/in, add scoped to ref struct param, remove UnscopedRef from out/ref-of-ref-struct. Other differences = mismatch. No effect on hiding; overloads shall not differ only on scoped/UnscopedRef. | | attributes.md §UnscopedRefAttribute | Add "(§scoped-modifier)" xref to opening paragraph. | --- standard/attributes.md | 2 +- standard/variables.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 1b6e95be2..64c591843 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1187,7 +1187,7 @@ Specifies that a nullable argument will not be `null` when the method returns th ### §UnscopedRefAttribute The UnscopedRef attribute -There are several cases in which a ref is treated as being implicitly scoped; that is, the ref is not allowed to escape a method. For example: +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. diff --git a/standard/variables.md b/standard/variables.md index 69d2e7b5f..588b3332d 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1468,3 +1468,18 @@ Consider the following declarations and their safe contexts: | `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]`. From 7638352a549166f5cbade9f6c738f0856738004d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:39:03 -0400 Subject: [PATCH 22/24] Editorial fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | File | Change | |------|--------| | variables.md ~L1342 | Qualify scoped applicability: "local variable or parameter" (not fields). | | variables.md ~L1443 | Add normative restriction: scoped not on fields, array elements, return types. | | variables.md ~L203 | Make IsNullRef paragraph informative (wrap in note). | | variables.md ~L1441 | "asserts" → "requires". | | expressions.md ~L3521 | "may not" → "shall not" for ref field default. | | statements.md ~L382 | Better RS examples: `new RS(ref orders)` to show why scoped matters. | | expressions.md ~L7172 | Delete old ref-safe-context rule (subsumed); consolidate into two-rule formulation with §9.7.2 xref. | | structs.md ~L164 | Restore note: struct members = class members minus finalizer, plus ref fields. | | structs.md ~L795 | Constructor text: add "and all reference variable fields to null references". | --- standard/expressions.md | 6 ++---- standard/statements.md | 4 ++-- standard/structs.md | 4 +++- standard/variables.md | 10 ++++------ tools/example-templates/additional-files/RefStruct.cs | 5 +++++ 5 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 tools/example-templates/additional-files/RefStruct.cs diff --git a/standard/expressions.md b/standard/expressions.md index cdf7d2e0c..4ae8b6006 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3518,7 +3518,7 @@ 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`, may not have an explicit initializer of `default`. +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* @@ -7169,9 +7169,7 @@ 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 left operand shall have the same safe-context as 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* diff --git a/standard/statements.md b/standard/statements.md index 01fbd9a26..b414102c0 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -379,7 +379,7 @@ For a discussion of `scoped`, see §scoped-modifier. > var orders = new Dictionary(); > ref var j = ref i; > ref readonly var k = ref i; -> scoped var r = new RS(); // ref struct RS {} +> 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: @@ -393,7 +393,7 @@ For a discussion of `scoped`, see §scoped-modifier. > Dictionary orders = new Dictionary(); > ref int j = ref i; > ref readonly int k = ref i; -> scoped RS r = new RS(); // ref struct RS {} +> scoped RS r = new RS(ref i); // ref struct RS { ref int Field; ... } > ``` > > The following are incorrect implicitly typed local variable declarations: diff --git a/standard/structs.md b/standard/structs.md index 94e483fc1..ca9dab2de 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -161,6 +161,8 @@ 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*: 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. @@ -792,7 +794,7 @@ readonly ref struct RoS ### 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. diff --git a/standard/variables.md b/standard/variables.md index 588b3332d..51f2efa4d 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -200,9 +200,7 @@ The default value of a variable depends on the type of the variable and is deter > *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* -To test if a ref variable has been assigned a referent, call `System.Runtime.CompilerServices.Unsafe.IsNullRef(ref fieldName)`. - -> *Note*: 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* +> *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 @@ -1346,7 +1344,7 @@ These values form a nesting relationship from narrowest (declaration-block) to w > > *end example.* -A reference variable can be scoped explicitly; see §scoped-modifier. +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 @@ -1454,9 +1452,9 @@ A `new` expression that invokes a constructor obeys the same rules as a method i ### §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 asserts that related code doesn’t extend the lifetime of the variable. +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 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: 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; } +} From 08bba1a5982f6cb78dfb07731925ad6dd92895d6 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 6 Apr 2026 10:14:48 -0400 Subject: [PATCH 23/24] Add examples for complicated examples Add three examples from the speclet for the most complicated rules for `scoped` ref. --- standard/structs.md | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/standard/structs.md b/standard/structs.md index ca9dab2de..56456b5fc 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1024,6 +1024,37 @@ For the purpose of these rules, a given argument `expr` passed to parameter `p`: A property invocation (either `get` or `set`) is treated as a method invocation of the underlying method by the above rules. +> *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* + #### §method-arguments-must-match Method arguments must match For any method invocation `e.M(a1, a2, ... aN)`: @@ -1046,6 +1077,29 @@ For any method invocation `e.M(a1, a2, ... aN)`: 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 +> 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: @@ -1056,6 +1110,36 @@ The safe-context of a declaration variable from an `out` argument (`M(x, out var - 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: From f6513441634cb1b989c22e402dd23a82d9b1e6b5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 6 Apr 2026 10:24:24 -0400 Subject: [PATCH 24/24] Add additional examples. Add three more examples to illustrate the ref safety rules for scoped ref. These highlight the potential issues with an unscoped ref. --- standard/structs.md | 31 ++++++++++++++++++++++++++ standard/variables.md | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/standard/structs.md b/standard/structs.md index 56456b5fc..d1e7bf258 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1150,6 +1150,37 @@ The safe-context of an object initializer expression is the narrowest of: > *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 +> } +> } +> ``` +> +> *end example* + #### 16.5.15.7 stackalloc The result of a stackalloc expression has safe-context of function-member. diff --git a/standard/variables.md b/standard/variables.md index 51f2efa4d..348a0442e 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1364,6 +1364,31 @@ For a parameter `p`: 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`: @@ -1481,3 +1506,29 @@ Any other difference with respect to `scoped` or `[UnscopedRef]` between the bas 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*