From c3e6aede927ca84ffc21ef4f39aba20f3206b0f9 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 13:51:59 -0400 Subject: [PATCH 01/32] support ref fields and scoped --- standard/lexical-structure.md | 44 +++++++++++------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 707b3a5b0..988b162bf 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -78,13 +78,14 @@ These productions occur in contexts where a value can occur in an expression, an If a sequence of tokens can be parsed, in context, as one of the disambiguated productions including an optional *type_argument_list* ([§8.4.2](types.md#842-type-arguments)), then the token immediately following the closing `>` token shall be examined and if it is: -- one of `( ) ] } : ; , . ? == != | ^ && || & [`; or +- one of `( ) ] } : ; , . ? == != | ^ && || & [ =>`; or - one of the relational operators `< <= >= is as`; or - a contextual query keyword appearing inside a query expression. +- In certain contexts, *identifier* is treated as a disambiguating token. Those contexts are where the sequence of tokens being disambiguated is immediately preceded by one of the keywords `is`, `case` or `out`, or arises while parsing the first element of a tuple literal (in which case the tokens are preceded by `(` or `:` and the identifier is followed by a `,`) or a subsequent element of a tuple literal. then the *type_argument_list* shall be retained as part of the disambiguated production and any other possible parse of the sequence of tokens discarded. Otherwise, the tokens parsed as a *type_argument_list* shall not be considered to be part of the disambiguated production, even if there is no other possible parse of those tokens. -> *Note*: These disambiguation rules shall not be applied when parsing other productions even if they similarly end in “`identifier type_argument_list?`”; such productions shall be parsed as normal. Examples include: *namespace_or_type_name* ([§7.7](basic-concepts.md#77-namespace-and-type-names)); *named_entity* ([§12.8.23](expressions.md#12823-the-nameof-operator)); *null_conditional_projection_initializer* ([§12.8.8](expressions.md#1288-null-conditional-member-access)); and *qualified_alias_member* ([§14.9.1](namespaces.md#1491-general)). *end note* +> *Note*: These disambiguation rules shall not be applied when parsing other productions even if they similarly end in “`identifier type_argument_list?`”; such productions shall be parsed as normal. Examples include: *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)); *named_entity* ([§12.8.23](expressions.md#12823-the-nameof-operator)); *null_conditional_projection_initializer* ([§12.8.8](expressions.md#1288-null-conditional-member-access)); and *qualified_alias_member* ([§14.9.1](namespaces.md#1491-general)). *end note* @@ -134,24 +135,6 @@ then the *type_argument_list* shall be retained as part of the disambiguated pro > > *end example* -When recognising a *relational_expression* ([§12.15.1](expressions.md#12151-general)) if both the “*relational_expression* `is` *type*” and “*relational_expression* `is` *pattern*” alternatives are applicable, and *type* resolves to an accessible type, then the “*relational_expression* `is` *type*” alternative shall be chosen. - -To differentiate a collection initializer ([§12.8.17.2.3](expressions.md#1281723-collection-initializers)) with an element assignment, from a collection initializer with a lambda expression, the parser shall look ahead. Consider the following: - -```csharp -var y = new C { [A] = x }; // OK: y[A] = x -var z = new C { [A] x => x }; // OK: z[0] = [A] x => x -``` - -The parser shall treat `?[` as the start of a *null_conditional_element_access* ([[§12.8.13](expressions.md#12813-null-conditional-element-access)): - -```csharp -x = b ? [A]; // OK -y = b ? [A] () => { } : z; // error -``` - -To differentiate a method call `T()` from a lambda expression `T () => e`, the parser shall look ahead. - ## 6.3 Lexical analysis ### 6.3.1 General @@ -377,7 +360,7 @@ token ### 6.4.2 Unicode character escape sequences -A Unicode character escape sequence represents a Unicode code point. Unicode character escape sequences are processed in identifiers ([§6.4.3](lexical-structure.md#643-identifiers)), character literals ([§6.4.5.5](lexical-structure.md#6455-character-literals)), regular string literals ([§6.4.5.6](lexical-structure.md#6456-string-literals)), and interpolated regular string expressions ([§12.8.3](expressions.md#1283-interpolated-string-expressions)). A Unicode character escape sequence is not processed in any other location (for example, to form an operator, punctuator, keyword or contextual keyword). +A Unicode character escape sequence represents a Unicode code point. Unicode character escape sequences are processed in identifiers ([§6.4.3](lexical-structure.md#643-identifiers)), character literals ([§6.4.5.5](lexical-structure.md#6455-character-literals)), regular string literals ([§6.4.5.6](lexical-structure.md#6456-string-literals)), and interpolated regular string expressions ([§12.8.3](expressions.md#1283-interpolated-string-expressions)). A Unicode character escape sequence is not processed in any other location (for example, to form an operator, punctuator, or keyword). ```ANTLR fragment Unicode_Escape_Sequence @@ -622,14 +605,15 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has ```ANTLR contextual_keyword - : 'add' | 'alias' | 'ascending' | 'async' | 'await' - | 'by' | 'Cdecl' | 'descending' | 'dynamic' | 'equals' - | 'Fastcall' | 'from' | 'get' | 'global' | 'group' - | 'init' | 'into' | 'join' | 'let' | 'managed' - | 'nameof' | 'nint' | 'notnull' | 'nuint' | 'on' - | 'orderby' | 'partial' | 'record' | 'remove' | 'select' - | 'set' | 'Stdcall' | 'Thiscall' | 'unmanaged' | 'value' - | 'var' | 'when' | 'where' | 'yield' + : 'add' | 'alias' | 'and' | 'ascending' | 'async' + | 'await' | 'by' | 'Cdecl' | 'descending'| 'dynamic' + | 'equals' | 'Fastcall' | 'from' | 'get' | 'global' + | 'group' | 'init' | 'into' | 'join' | 'let' + | 'managed' | 'nameof' | 'nint' | 'not' | 'notnull' + | 'nuint' | 'on' | 'or' | 'orderby' | 'partial' + | 'record' | 'remove' | 'scoped' | 'select' | 'set' | 'Stdcall' + | 'Thiscall' | 'unmanaged' | 'value' | 'var' | 'when' + | 'where' | 'yield' ; ``` @@ -1522,7 +1506,7 @@ fragment PP_Line_Indicator | Decimal_Digit+ | DEFAULT | 'hidden' - | PP_Start_Line_Character PP_Whitespace? '-' PP_Whitespace? PP_End_Line_Character + | PP_Start_Line_Character PP_Whitespace? '-' PP_Whitespace? PP_End_Line_Character PP_Whitespace (PP_Character_Offset PP_Whitespace)? PP_Compilation_Unit_Name ; From e8dad0c74882af0dbef4743c7c4c61686d9a12d5 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 13:55:39 -0400 Subject: [PATCH 02/32] 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 79c65bcdc..578d37214 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -624,11 +624,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 b0b231200576a604e38270a7a2ecb1ef1f4f1ca3 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:05:23 -0400 Subject: [PATCH 03/32] 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 6efcd3543..e9997d418 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -190,16 +190,20 @@ A discard introduced by a declaration expression is not initially assigned, so i 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 @@ -1184,7 +1188,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`). @@ -1339,6 +1343,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`: @@ -1359,9 +1365,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. @@ -1415,3 +1439,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 c406830049766dc24db44a67e2a5f19aff764055 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:15:48 -0400 Subject: [PATCH 04/32] support ref fields and scoped --- standard/expressions.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 8bef27ddf..85e74056f 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -568,7 +568,7 @@ argument_value | 'in' variable_reference | 'ref' variable_reference | 'out' declaration_expression - | 'out' variable_reference + | 'out' 'scoped'? variable_reference ; ``` @@ -579,8 +579,8 @@ 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` followed by a *declaration_expression* ([§12.20](expressions.md#1220-declaration-expressions)), indicating that a new local variable is declared, and then passed as an output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)). The newly-declared variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following the function member invocation. +- The keyword `out`, optionally followed by `scoped`, 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 keyword `out`, optionally followed by `scoped`, followed by a *declaration_expression* ([§12.20](expressions.md#1220-declaration-expressions)), indicating that a new local variable is declared, and then passed as an output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)). The newly-declared variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following the function member invocation. For a discussion of `scoped`, see §scoped-modifier. The form determines the ***parameter-passing mode*** of the argument: *value*, *input*, *reference*, or *output*, respectively (where both forms using the `out` keyword use the output passing mode). However, as mentioned above, an argument with value passing mode, might be transformed into one with input passing mode. @@ -2714,7 +2714,7 @@ member_initializer_list ; member_initializer - : initializer_target '=' initializer_value + : initializer_target '=' 'ref'? initializer_value ; initializer_target @@ -2723,7 +2723,7 @@ initializer_target ; initializer_value - : expression + : 'ref'? expression | object_or_collection_initializer ; ``` @@ -3506,6 +3506,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. @@ -5321,7 +5325,7 @@ A declaration expression declares a local variable. ```ANTLR declaration_expression - : local_variable_type identifier + : 'scoped'? local_variable_type identifier ; local_variable_type @@ -5330,6 +5334,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 only occurs in the following syntactic contexts: @@ -5492,7 +5498,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 @@ -5525,6 +5531,8 @@ anonymous_function_body If the modifier `static` is present, the anonymous function cannot capture state from the enclosing scope. A `static` anonymous function may reference `static` members, type parameters, and constant definitions 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. @@ -5597,6 +5605,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* @@ -5607,6 +5618,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. The contextual keyword `var` shall not be used as an explicit return type in a *lambda_expression*. @@ -7363,6 +7375,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 767339e23d5d2af1325d135da8db8576188c0259 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:26:41 -0400 Subject: [PATCH 05/32] support ref fields and scoped --- standard/statements.md | 69 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index b78880054..7344d03a5 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -289,7 +289,7 @@ declaration_statement ; ``` -Except for a *local_using_declaration*, the declared names are introduced into the nearest enclosing declaration space ([§7.2](basic-concepts.md#72-declarations)). A *local_using_declaration* introduces a new declaration space and scope that extends from the declaration to the end of the enclosing block, as specified in [§13.14.2](statements.md#13142-using-declaration). +Except for a *local_using_declaration*, the declared names are introduced into the nearest enclosing declaration space ([§7.3](basic-concepts.md#73-declarations)). A *local_using_declaration* introduces a new declaration space and scope that extends from the declaration to the end of the enclosing block, as specified in [§13.14.2](statements.md#13142-using-declaration). ### 13.6.2 Local variable declarations @@ -340,7 +340,7 @@ If there are multiple declarators in a declaration then they are processed, incl The value of a local variable is obtained in an expression using a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)). A local variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) at each location where its value is obtained. Each local variable introduced by a *local_variable_declaration* is *initially unassigned* ([§9.4.3](variables.md#943-initially-unassigned-variables)). If a declarator has an initializing expression then the introduced local variable is classified as *assigned* at the end of the declarator ([§9.4.4.5](variables.md#9445-declaration-statements)). -The scope of a local variable introduced by a *local_variable_declaration* is defined as follows ([§7.6](basic-concepts.md#76-scopes)): +The scope of a local variable introduced by a *local_variable_declaration* is defined as follows ([§7.7](basic-concepts.md#77-scopes)): - If the declaration occurs as a *for_initializer* then the scope is the *for_initializer*, *for_condition*, *for_iterator*, and *embedded_statement* ([§13.9.4](statements.md#1394-the-for-statement)); - If the declaration occurs as a *resource_acquisition* then the scope is the outermost block of the semantically equivalent expansion of the *using_statement* ([§13.14](statements.md#1314-the-using-statement)); @@ -354,8 +354,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 @@ -365,9 +365,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"; @@ -376,11 +378,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"; @@ -389,17 +392,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, no natural 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* @@ -426,7 +431,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 @@ -448,11 +453,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 @@ -464,12 +471,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.4](expressions.md#12244-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. @@ -596,7 +607,7 @@ It is a compile-time error for the body of the local function to contain a `goto > *Note*: the above rules for `this` and `goto` mirror the rules for anonymous functions in [§12.22.3](expressions.md#12223-anonymous-function-bodies). *end note* -A local function may be called from a lexical point prior to its declaration. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.6](basic-concepts.md#76-scopes)). +A local function may be called from a lexical point prior to its declaration. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.7](basic-concepts.md#77-scopes)). It is a compile-time error for a local function to declare a parameter, type parameter or local variable with the same name as one declared in any enclosing local variable declaration space. @@ -1139,6 +1150,9 @@ foreach_statement | // asynchronous foreach 'await' 'foreach' '(' local_variable_type identifier 'in' expression ')' embedded_statement + | 'await'? 'foreach' '(' ('scoped'? ref_kind) local_variable_type identifier + 'in' expression ')' + embedded_statement | // deconstructing foreach 'await'? 'foreach' '(' deconstructor 'in' expression ')' embedded_statement @@ -1155,6 +1169,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-asynchronous-foreach) for an asynchronous `foreach` statement. @@ -1259,7 +1275,7 @@ is semantically equivalent to: } ``` -The variable `e` is not visible or accessible to the expression `x` or the embedded statement or any other source code of the program. The reference variable `v` is read-write in the embedded statement, but `v` shall not be ref-reassigned ([§12.24.4](expressions.md#12244-ref-assignment)). If there is not an identity conversion ([§10.2.2](conversions.md#1022-identity-conversion)) from `T` (the iteration type) to `V` (the *local_variable_type* in the `foreach` statement), an error is produced and no further steps are taken. +The variable `e` is not visible or accessible to the expression `x` or the embedded statement or any other source code of the program. The reference variable `v` is read-write in the embedded statement, but `v` shall not be ref-reassigned ([§12.24.3](expressions.md#12243-deconstructing-assignment)). If there is not an identity conversion ([§10.2.2](conversions.md#1022-identity-conversion)) from `T` (the iteration type) to `V` (the *local_variable_type* in the `foreach` statement), an error is produced and no further steps are taken. A `foreach` statement of the form `foreach (ref readonly V v in x) «embedded_statement»` has a similar equivalent form, but the reference variable `v` is `ref readonly` in the embedded statement, and therefore cannot be ref-reassigned or reassigned. @@ -1479,8 +1495,6 @@ A deconstructing foreach replaces the declaration and initialisation of a single All variables assigned to by the *deconstructor* must be declared within the *deconstructor*, it is a compile time error for any *deconstructor_element* to be a *variable_reference*. -> *Note*: Consequently, a deconstructing `foreach` can discard ([§9.2.9.2](variables.md#9292-discards)) every component, as in `foreach ((_, _) in e)`, and therefore declare no iteration variables. This differs from an ordinary single-variable `foreach` statement, whose iteration variable is introduced by a declaration; a bare `_` in `foreach (_ in e)` is not such a declaration. The equivalent “values are not needed” intent can be written as `foreach (var _ in e)`. *end note* - A foreach statement of the form: ```csharp @@ -1507,11 +1521,11 @@ is semantically equivalent to: } ``` -This follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the declaration and initialisation of a single iteration variable with a *deconstructing_assignment* in which the *deconstructor* contains the iteration-variable declarations: each *declaration_expression* other than a discard declares one iteration variable, and each discard ([§9.2.9.2](variables.md#9292-discards)) declares none: +This follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables: - `C` and `E` are determined as for synchronous foreach -- `e` is not visible or accessible anywhere in the program except as indicated in the above code -- the variables declared within the «deconstructor» are read-only to the «embedded_statement» +- `e` is not visible or accessible anywhere in the program accept as indicated in the above code +- the variables declared by the «deconstructor» are read-only to the «embedded_statement» - the code in the `finally` block is determined as for synchronous foreach An `await foreach` statement of the form: @@ -1540,25 +1554,12 @@ is semantically equivalent to: } ``` -This follows the behavior of asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), differing by replacing the declaration and initialisation of a single iteration variable with a *deconstructing_assignment* in which the *deconstructor* contains the iteration-variable declarations: each *declaration_expression* other than a discard declares one iteration variable, and each discard ([§9.2.9.2](variables.md#9292-discards)) declares none: +This follows the behavior of asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables: -- `enumerator` is not visible or accessible anywhere in the program except as indicated in the above code -- the variables declared within the «deconstructor» are read-only to the «embedded_statement» +- `enumerator` is not visible or accessible anywhere in the program accept as indicated in the above code +- the variables declared by the «deconstructor» are read-only to the «embedded_statement» - the code in the `finally` block is determined as for asynchronous foreach -> *Example*: A deconstructing foreach uses discards as placeholders for elements that are not needed. Here each element of the collection is a tuple whose second element is discarded: -> -> -> ```csharp -> var points = new List<(int X, int Y)> { (1, 2), (3, 4) }; -> foreach (var (x, _) in points) -> { -> Console.WriteLine(x); -> } -> ``` -> -> *end example* - ## 13.10 Jump statements ### 13.10.1 General @@ -1848,7 +1849,7 @@ A *try_statement* consists of the keyword `try` followed by a *block*, then zero In an *exception_specifier* the *type*, or its effective base class if it is a *type_parameter*, shall be `System.Exception` or a type that derives from it. -When a `catch` clause specifies both a *class_type* and an *identifier*, an ***exception variable*** of the given name and type is declared. The exception variable is introduced into the declaration space of the *specific_catch_clause* ([§7.2](basic-concepts.md#72-declarations)). During execution of the *exception_filter* and `catch` block, the exception variable represents the exception currently being handled. For purposes of definite assignment checking, the exception variable is considered definitely assigned in its entire scope. +When a `catch` clause specifies both a *class_type* and an *identifier*, an ***exception variable*** of the given name and type is declared. The exception variable is introduced into the declaration space of the *specific_catch_clause* ([§7.3](basic-concepts.md#73-declarations)). During execution of the *exception_filter* and `catch` block, the exception variable represents the exception currently being handled. For purposes of definite assignment checking, the exception variable is considered definitely assigned in its entire scope. Unless a `catch` clause includes an exception variable name, it is impossible to access the exception object in the filter and `catch` block. @@ -2286,7 +2287,7 @@ await using («local_variable_type» «local_variable_declarators») } ``` -The lifetime of the variables declared in a *non_ref_local_variable_declaration* extends to the end of the scope in which they are declared. Those variables are then disposed in the reverse order in which they are declared. The variables declared by a *local_using_declaration*, together with the trailing *statement_list*, form a new declaration space and scope ([§7.2](basic-concepts.md#72-declarations), [§13.3.1](statements.md#1331-general)), equivalent to the block introduced by the corresponding rewrite to a *using_statement* shown above. +The lifetime of the variables declared in a *non_ref_local_variable_declaration* extends to the end of the scope in which they are declared. Those variables are then disposed in the reverse order in which they are declared. The variables declared by a *local_using_declaration*, together with the trailing *statement_list*, form a new declaration space and scope ([§7.3](basic-concepts.md#73-declarations), [§13.3.1](statements.md#1331-general)), equivalent to the block introduced by the corresponding rewrite to a *using_statement* shown above. ```csharp From ea57c589215c68d9aff6e4214683df003b70f461 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:31:06 -0400 Subject: [PATCH 06/32] 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 12e4484cf..5f3b4250d 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2253,8 +2253,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 @@ -2270,7 +2271,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: @@ -2318,7 +2323,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.5](basic-concepts.md#75-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.5](basic-concepts.md#75-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 b0cec26b59cc0439fbf0174d8565dd518f21f64a Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:38:48 -0400 Subject: [PATCH 07/32] 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 c7b4b8385..8331026c6 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -141,7 +141,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 @@ -156,7 +156,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.6](structs.md#166-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. @@ -540,7 +540,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* @@ -595,7 +595,7 @@ When a property or indexer of a struct is the target of an assignment, the insta ### 16.6.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 > @@ -736,9 +736,11 @@ Similarly, boxing never implicitly occurs when accessing a member on a constrain > > *end example* -### 16.6.8 Field initializers +### §fields Fields -As described in [§16.6.5](structs.md#1665-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. +#### §field-initializers Field initializers + +As described in [§16.6.5](structs.md#1665-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*: > @@ -772,6 +774,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.6.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. @@ -969,6 +1023,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.6.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 a9ec531fe4638c4856f2c281568df101e506e5d9 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:46:43 -0400 Subject: [PATCH 08/32] 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 150df7494..8e162199f 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -504,6 +504,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.ModuleInitializerAttribute` ([§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)). @@ -1228,6 +1229,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 b541bfc896d0fdbdff31f7ef9509734110f282f6 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 14:54:56 -0400 Subject: [PATCH 09/32] 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 8ee30c3df..6a31459b4 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 @@ -1473,6 +1483,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 038c1204c3cb69a92c9442813a9d6d31a3826c1f Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:09:28 -0400 Subject: [PATCH 10/32] 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 e9997d418..5f352d456 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1448,11 +1448,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 0cf8ef6c84101080d875566dd50c9f20abda9cbf Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:12:55 -0400 Subject: [PATCH 11/32] 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 5f352d456..a5e5d822e 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1455,4 +1455,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 2c03b57fff24c61828ef0d979c131f4cb32c6410 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:15:00 -0400 Subject: [PATCH 12/32] fix md formatting --- standard/expressions.md | 1 - 1 file changed, 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index 85e74056f..835080da8 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -5607,7 +5607,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 1687ae480c37aa78c5411c700c89dd6ae1fe71ba Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:16:38 -0400 Subject: [PATCH 13/32] 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 835080da8..5a1295b05 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3506,7 +3506,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 468fb6cb36c4b20698d03f281af69038ab1124dd Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:30:44 -0400 Subject: [PATCH 14/32] 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 8e162199f..f0d1641c9 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -972,19 +972,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** ------------------- | ------------------ -`AllowNullAttribute` ([§23.5.7.2](attributes.md#23572-the-allownull-attribute)) | A non-nullable argument may be null. -`DisallowNullAttribute` ([§23.5.7.3](attributes.md#23573-the-disallownull-attribute)) | A nullable argument should never be null. -`MaybeNullAttribute` ([§23.5.7.6](attributes.md#23576-the-maybenull-attribute)) | A non-nullable return value may be null. -`NotNullAttribute` ([§23.5.7.10](attributes.md#235710-the-notnull-attribute)) | A nullable return value will never be null. -`MaybeNullWhenAttribute` ([§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. -`NotNullWhenAttribute` ([§23.5.7.12](attributes.md#235712-the-notnullwhen-attribute)) | A nullable argument will not be null when the method returns the specified `bool` value. -`NotNullIfNotNullAttribute` ([§23.5.7.11](attributes.md#235711-the-notnullifnotnull-attribute)) | A return value is not null if the argument for the specified parameter is not null. -`MemberNotNullAttribute` ([§23.5.7.8](attributes.md#23578-the-membernotnull-attribute)) | The listed member will not be null when the method returns. -`MemberNotNullWhenAttribute` ([§23.5.7.9](attributes.md#23579-the-membernotnullwhen-attribute)) | The listed member will not be null when the method returns the specified `bool` value. -`DoesNotReturnAttribute` ([§23.5.7.4](attributes.md#23574-the-doesnotreturn-attribute)) | This method never returns. -`DoesNotReturnIfAttribute` ([§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** | +| --- | --- | +| `AllowNullAttribute` ([§23.5.7.2](attributes.md#23572-the-allownull-attribute)) | A non-nullable argument may be null. | +| `DisallowNullAttribute` ([§23.5.7.3](attributes.md#23573-the-disallownull-attribute)) | A nullable argument should never be null. | +| `MaybeNullAttribute` ([§23.5.7.6](attributes.md#23576-the-maybenull-attribute)) | A non-nullable return value may be null. | +| `NotNullAttribute` ([§23.5.7.10](attributes.md#235710-the-notnull-attribute)) | A nullable return value will never be null. | +| `MaybeNullWhenAttribute` ([§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. | +| `NotNullWhenAttribute` ([§23.5.7.12](attributes.md#235712-the-notnullwhen-attribute)) | A nullable argument will not be null when the method returns the specified `bool` value. | +| `NotNullIfNotNullAttribute` ([§23.5.7.11](attributes.md#235711-the-notnullifnotnull-attribute)) | A return value is not null if the argument for the specified parameter is not null. | +| `MemberNotNullAttribute` ([§23.5.7.8](attributes.md#23578-the-membernotnull-attribute)) | The listed member will not be null when the method returns. | +| `MemberNotNullWhenAttribute` ([§23.5.7.9](attributes.md#23579-the-membernotnullwhen-attribute)) | The listed member will not be null when the method returns the specified `bool` value. | +| `DoesNotReturnAttribute` ([§23.5.7.4](attributes.md#23574-the-doesnotreturn-attribute)) | This method never returns. | +| `DoesNotReturnIfAttribute` ([§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 1a02793e0e414af2df6c49282cfda1a07cf797bb Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sat, 28 Mar 2026 16:32:17 -0400 Subject: [PATCH 15/32] 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 f0d1641c9..ec2bf9efb 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1255,11 +1255,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 bf0e3390ec40e1393826d6dcba1d716ad43fe4cd Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 15:57:52 -0400 Subject: [PATCH 16/32] 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 8331026c6..8f041b1a3 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1004,17 +1004,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.6.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.6.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 a5e5d822e..2c2cc283f 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1243,7 +1243,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. @@ -1251,14 +1251,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. @@ -1276,7 +1283,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 @@ -1356,11 +1363,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`: @@ -1438,7 +1447,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 42bae2d1b624f556fe1e88786bbd6ddd01be46b6 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:05:13 -0400 Subject: [PATCH 17/32] 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 8f041b1a3..0d3712d4d 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1042,10 +1042,19 @@ For an operator that yields a value, such as `e1 + e2` or `c ? e1 : e2`, the saf #### 16.6.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 2c2cc283f..1c02806f7 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1401,12 +1401,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.6.15](structs.md#16615-safe-context-constraint)) of all argument expressions (including the receiver). +- The safe-context ([§16.6.15](structs.md#16615-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 dc9c060a0fa13e0e1a3ebab6520e4b95342548f8 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:12:40 -0400 Subject: [PATCH 18/32] 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 0d3712d4d..945d9b4d8 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1058,6 +1058,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.6.15.7 stackalloc The result of a stackalloc expression has safe-context of function-member. @@ -1066,12 +1108,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 0aef9bfb55531faaf8d6dc099d8eaf564ab659e4 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:19:22 -0400 Subject: [PATCH 19/32] 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 | 9 ++++----- standard/statements.md | 13 ++++++++----- standard/structs.md | 5 +++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 5a1295b05..1e18edc06 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -565,9 +565,8 @@ argument_name argument_value : expression - | 'in' variable_reference - | 'ref' variable_reference - | 'out' declaration_expression + | 'in' 'scoped'? variable_reference + | 'ref' 'scoped'? variable_reference | 'out' 'scoped'? variable_reference ; ``` @@ -577,8 +576,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`, 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 keyword `out`, optionally followed by `scoped`, followed by a *declaration_expression* ([§12.20](expressions.md#1220-declaration-expressions)), indicating that a new local variable is declared, and then passed as an output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)). The newly-declared variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following the function member invocation. For a discussion of `scoped`, see §scoped-modifier. diff --git a/standard/statements.md b/standard/statements.md index 7344d03a5..7593648a9 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -1145,13 +1145,10 @@ The `foreach` statement enumerates the elements of a collection, executing an em ```ANTLR foreach_statement : // synchronous foreach - 'foreach' '(' ref_kind? local_variable_type identifier 'in' expression ')' + 'foreach' '(' 'scoped'? ref_kind? local_variable_type identifier 'in' expression ')' embedded_statement | // asynchronous foreach - 'await' 'foreach' '(' local_variable_type identifier 'in' expression ')' - embedded_statement - | 'await'? 'foreach' '(' ('scoped'? ref_kind) local_variable_type identifier - 'in' expression ')' + 'await' 'foreach' '(' 'scoped'? ref_kind? local_variable_type identifier 'in' expression ')' embedded_statement | // deconstructing foreach 'await'? 'foreach' '(' deconstructor 'in' expression ')' @@ -1161,6 +1158,12 @@ foreach_statement There are three forms of the *foreach_statement*: *synchronous*, *asynchronous* and *deconstructing*; corresponding to the three alternatives of the above grammar. +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. + +It is a compile-time error for an *asynchronous* *foreach_statement* to omit the `scoped` modifier if *ref_kind* is 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. + The deconstructing foreach supports both synchronous and asynchronous forms and is described in [§13.9.5.4](statements.md#13954-deconstructing-foreach). 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. diff --git a/standard/structs.md b/standard/structs.md index 945d9b4d8..1b47349c3 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -778,13 +778,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 777aca7d9942af9f24df9b4d406f5e3d9f4e6cd5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:23:51 -0400 Subject: [PATCH 20/32] 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 5160a762d..b9e6acc02 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 115006ff75d8652c918819dd77c91709f261f803 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:30:14 -0400 Subject: [PATCH 21/32] 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 ec2bf9efb..165a5dfeb 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1231,7 +1231,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 1c02806f7..2ff6e553d 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1472,3 +1472,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 f17987133bdf07aaeb2885fc1915a2bd0d334a1f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 3 Apr 2026 16:39:03 -0400 Subject: [PATCH 22/32] 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 | 6 +++++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 1e18edc06..c9fa8d7b4 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3505,7 +3505,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* @@ -7371,9 +7371,7 @@ The operator `= ref` is called the ***ref assignment operator***. The expressio 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, an input parameter, or a discard. When the left operand is a discard, the right operand is evaluated but no variable reference is stored. Otherwise, the right operand shall be an expression that yields a *variable_reference* ([§9.5](variables.md#95-variable-references)) 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 7593648a9..db06b490b 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -378,7 +378,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: @@ -392,7 +392,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 1b47349c3..6657d955c 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -156,6 +156,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.6](structs.md#166-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. @@ -829,7 +831,7 @@ readonly ref struct RoS ### 16.6.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 2ff6e553d..7d5eb78b6 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 @@ -1350,7 +1348,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 @@ -1458,9 +1456,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 index 8a1de6823..168251305 100644 --- a/tools/example-templates/additional-files/RefStruct.cs +++ b/tools/example-templates/additional-files/RefStruct.cs @@ -1 +1,5 @@ -public ref struct RS {} +ref struct RS +{ + public ref int Field; + public RS(ref int i) { Field = ref i; } +} From 9921153d8a7789c37ba9a738a49c23da653b74bc Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 6 Apr 2026 10:14:48 -0400 Subject: [PATCH 23/32] 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 6657d955c..94a4bf2e4 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1061,6 +1061,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)`: @@ -1083,6 +1114,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: @@ -1093,6 +1147,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 403bc066a44ac72fd6dc8b276b99391f7e6f471b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 6 Apr 2026 10:24:24 -0400 Subject: [PATCH 24/32] 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 94a4bf2e4..5f42a4390 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1187,6 +1187,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.6.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 7d5eb78b6..3ae96c720 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1368,6 +1368,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`: @@ -1485,3 +1510,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* From ab98f2f4d9dde29497efc89ca35ca13905384551 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 7 Apr 2026 07:03:14 -0400 Subject: [PATCH 25/32] fix md formatting --- standard/variables.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/standard/variables.md b/standard/variables.md index 3ae96c720..94f4f78b7 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -199,7 +199,9 @@ The default value of a variable depends on the type of the variable and is deter - For a variable of a *reference_type* or a reference variable, the default value is `null`. > *Note*: Initialization to default values is typically done by having the memory manager or garbage collector initialize memory to all-bits-zero before it is allocated for use. For this reason, it is convenient to use all-bits-zero to represent the null reference. *end note* + + > *Note*: To test if a ref variable has been assigned a referent, call `System.Runtime.CompilerServices.Unsafe.IsNullRef(ref fieldName)`. One cannot test a ref variable to see if it has been assigned a referent by using `fieldName == null`, as that tests the value of the (potentially non-existent) referent, not the reference itself. *end note* ## 9.4 Definite assignment From d63377c1f09b74d3250f795b7563ad66202616f9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 4 May 2026 15:50:33 -0400 Subject: [PATCH 26/32] Review and update ref fields Add attribite, update examples, add a few prose areas. --- standard/attributes.md | 4 ++++ standard/classes.md | 10 ++++++++- standard/structs.md | 7 ++++++- standard/variables.md | 47 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 165a5dfeb..89b219fc2 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1263,6 +1263,10 @@ It is an error to use `[UnscopedRef]` on See §scoped-modifier for more information. +### §ScopedRefAttribute The ScopedRef attribute + +The name `System.Runtime.CompilerServices.ScopedRefAttribute` is reserved for compiler use. The compiler emits this attribute on a parameter when the parameter's `scoped` annotation differs from its default state, in order to encode the `scoped` modifier (§scoped-modifier) in metadata. This attribute is not permitted in source. + ### 23.5.8 The EnumeratorCancellation attribute Specifies the parameter representing the `CancellationToken` for an asynchronous iterator ([§15.15](classes.md#1515-synchronous-and-asynchronous-iterators)). The argument for this parameter shall be combined with the argument passed to `IAsyncEnumerable.GetAsyncEnumerator(CancellationToken)`. This combined token shall be polled by `IAsyncEnumerator.MoveNextAsync()` ([§15.15.5.2](classes.md#151552-advance-the-enumerator)). The tokens shall be combined into a single token as if by `CancellationToken.CreateLinkedTokenSource` and its `Token` property. The combined token will be canceled if either of the two source tokens are canceled. The combined token is seen as the argument to the asynchronous iterator method ([§15.15](classes.md#1515-synchronous-and-asynchronous-iterators)) in the body of that method. diff --git a/standard/classes.md b/standard/classes.md index 5f3b4250d..8d940b7c8 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -1698,6 +1698,8 @@ The value of a field is obtained in an expression using a *simple_name* ([§12.8 A field declaration that declares multiple fields is equivalent to multiple declarations of single fields with the same attributes, modifiers, and type. +> *Note*: Inside a `ref struct`, a field may also be declared as a reference variable; see §Ref-Fields. *end note* + > *Example*: > > @@ -2357,6 +2359,8 @@ It is a compile-time error to modify the value of an input parameter. > *Note*: The primary purpose of input parameters is for efficiency. When the type of a method parameter is a large struct (in terms of memory requirements), it is useful to be able to avoid copying the whole value of the argument when calling the method. Input parameters allow methods to refer to existing values in memory, while providing protection against unwanted changes to those values. *end note* +An input parameter may carry the `scoped` modifier (§scoped-modifier) or the `[UnscopedRef]` attribute (§UnscopedRefAttribute). + ##### 15.6.2.3.3 Reference parameters A parameter declared with a `ref` modifier is a ***reference parameter***. For definite-assignment rules, see [§9.2.6](variables.md#926-reference-parameters). @@ -2420,7 +2424,9 @@ A parameter declared with a `ref` modifier is a ***reference parameter***. For d > > *end example* -For a `struct` type, within an instance method, instance accessor ([§12.2.1](expressions.md#1221-general)), or instance constructor with a constructor initializer, the `this` keyword behaves exactly as a reference parameter of the struct type ([§12.8.14](expressions.md#12814-this-access)). +For a `struct` type, within an instance method, instance accessor ([§12.2.1](expressions.md#1221-general)), or instance constructor with a constructor initializer, the `this` keyword behaves exactly as a reference parameter of the struct type ([§12.8.14](expressions.md#12814-this-access)). The `this` parameter of a struct instance method is implicitly `scoped ref` (§scoped-modifier). + +A reference parameter may carry the `scoped` modifier (§scoped-modifier) or the `[UnscopedRef]` attribute (§UnscopedRefAttribute). ##### 15.6.2.3.4 Output parameters @@ -2428,6 +2434,8 @@ A parameter declared with an `out` modifier is an ***output parameter***. For de A method declared as an optional partial method ([§15.6.9.2](classes.md#15692-optional-partial-methods)) shall not have output parameters. +An output parameter is implicitly `scoped` (§scoped-modifier); the `[UnscopedRef]` attribute (§UnscopedRefAttribute) may be applied to widen its ref-safe-context. + > *Note*: Output parameters are typically used in methods that produce multiple return values. *end note* diff --git a/standard/structs.md b/standard/structs.md index 5f42a4390..1dfb6f6a2 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -789,7 +789,12 @@ struct_field_declaration 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. +A `ref` or `readonly ref` field is a reference variable and is subject to the following constraints: + +- It shall only be declared in a `ref struct`. +- It shall not be declared `static`, `volatile`, or `const`. +- Its type shall not itself be a `ref struct` type. +- In a `readonly ref struct`, every `ref` field shall be declared `readonly ref` (it may additionally be declared `readonly ref readonly`). Consider the following ref struct declaration: diff --git a/standard/variables.md b/standard/variables.md index 94f4f78b7..9f4cf634b 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1498,6 +1498,53 @@ Consider the following declarations and their safe contexts: In this relationship the *ref-safe-context* of a value can never be wider than the *safe-context*. +> *Example*: The following illustrates how `scoped` restricts the lifetime of a local and prevents it from escaping its enclosing function: +> +> +> ```csharp +> using System; +> +> class C +> { +> static Span Bad() +> { +> // Without `scoped`, the safe-context of `s` would be caller-context +> // because the right-hand side has safe-context of caller-context. +> // The `scoped` modifier forces the safe-context to function-member, +> // so `s` cannot be returned. +> scoped Span s = default; +> return s; // Error: s has safe-context of function-member +> } +> } +> ``` +> +> *end example* + +> *Example*: The following illustrates how `scoped ref` on a parameter prevents the parameter from being captured by a constructed `ref struct` value that the method returns: +> +> +> ```csharp +> ref struct RS +> { +> public ref int RefField; +> public RS(ref int i) { RefField = ref i; } +> } +> +> class C +> { +> // `scoped ref` means `value` does not contribute ref-safe-context +> // to the return value. The `RS` constructor requires a ref argument +> // with ref-safe-context of caller-context, but `value` only contributes +> // function-member, so the call is rejected. +> static RS CreateWithoutCapture(scoped ref int value) +> => new RS(ref value); +> } +> ``` +> +> *end example* + +In summary, two `ref` locations are implicitly `scoped`: the `this` parameter of a struct instance method, and every `out` parameter. See §9.7.2.3. + ### §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: From 2f6313cc989a58998c3a4754383cff2e02ce45dc Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Tue, 15 Sep 2026 10:51:55 -0400 Subject: [PATCH 27/32] add Attribute suffix --- standard/attributes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 89b219fc2..bbd638ce2 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1249,11 +1249,11 @@ This attribute may can be applied to any `ref` and it changes the ref-safe-conte 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*. +An instance method or property annotated with `UnscopedRefAttribute` has the ref-safe-context of `this` set to the *caller-context*. -A member annotated with `[UnscopedRef]` may not implement an interface. +A member annotated with `UnscopedRefAttribute` may not implement an interface. -It is an error to use `[UnscopedRef]` on +It is an error to use `UnscopedRefAttribute` on - A member that is not declared on a `struct`. - A `static` member, `init` member, or constructor on a `struct`. From d29eb3f4e7313f209b6197f2badcad4a1b7ae35b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 18 Sep 2026 13:49:21 -0400 Subject: [PATCH 28/32] Fix markdown lint issues in ref fields text Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64c533dc-05e9-40c5-98b6-e1d7d50f36bd --- standard/attributes.md | 10 +++++----- standard/structs.md | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index bbd638ce2..58c79701a 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1241,11 +1241,11 @@ This attribute is used in those situations where the ref should be allowed to es 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 | +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. diff --git a/standard/structs.md b/standard/structs.md index 1dfb6f6a2..69c3d639b 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1191,7 +1191,9 @@ The safe-context of an object initializer expression is the narrowest of: 3. The safe-context of the RHS of assignments in member initializers to non-readonly setters, or the ref-safe-context in the case of ref assignment. > *Note*: Another way of modeling this is to consider any argument to a member initializer that can be assigned to the receiver as being an argument to the constructor. *end note* + + > *Example*: The following illustrates how an object initializer narrows the safe-context of the resulting value: > > From 5fa97372cf8b3a5564073af9cbb9a13e67275b1a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 18 Sep 2026 13:58:04 -0400 Subject: [PATCH 29/32] Restore consistent attributes table style Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 64c533dc-05e9-40c5-98b6-e1d7d50f36bd --- standard/attributes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 58c79701a..bbd638ce2 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1241,11 +1241,11 @@ This attribute is used in those situations where the ref should be allowed to es 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 +| 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. From 2e5b01dd7686cc3245be94afdca21feea900205f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 18 Sep 2026 16:58:08 -0400 Subject: [PATCH 30/32] Update safe-context cross-reference --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index 9f4cf634b..0aca26fbe 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1483,7 +1483,7 @@ 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 requires 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.6.15](structs.md#16615-safe-context-constraint)) of a variable. The presence of this modifier requires that related code doesn’t extend the lifetime of the variable. `scoped` shall only be applied to reference variables (which includes non-value parameters) and to variables of a ref struct type. `scoped` shall not be applied to fields, array elements, or return types. From 98140427092de254dabde17d735de3208410c152 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 18 Sep 2026 17:13:25 -0400 Subject: [PATCH 31/32] Fix scoped-reference blockquote lint --- standard/classes.md | 2 ++ standard/variables.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/standard/classes.md b/standard/classes.md index 8d940b7c8..9d7bfc77a 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -1700,6 +1700,8 @@ A field declaration that declares multiple fields is equivalent to multiple decl > *Note*: Inside a `ref struct`, a field may also be declared as a reference variable; see §Ref-Fields. *end note* + + > *Example*: > > diff --git a/standard/variables.md b/standard/variables.md index 0aca26fbe..53c423fe0 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1520,6 +1520,8 @@ In this relationship the *ref-safe-context* of a value can never be wider than t > > *end example* + + > *Example*: The following illustrates how `scoped ref` on a parameter prevents the parameter from being captured by a constructed `ref struct` value that the method returns: > > From a66f61cae1c71a6ea0d77bfbdac8441d9aa8d627 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 21 Sep 2026 15:00:40 -0400 Subject: [PATCH 32/32] Fix ref-fields validation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bce5e82a-89fd-4655-bc08-6d6ba97f0cc6 --- standard/expressions.md | 2 +- standard/lexical-structure.md | 4 ++-- standard/statements.md | 20 ++++++++++---------- standard/structs.md | 4 ++-- standard/variables.md | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index c9fa8d7b4..147ee5ee0 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -5604,7 +5604,7 @@ 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, +> var lambda = (in int p1, out bool p2, scoped ref float p3, > scoped Span p4) => Mlam(in p1, out p2, ref p3, p4); > ``` > diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 988b162bf..f9e54ed1b 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -85,7 +85,7 @@ If a sequence of tokens can be parsed, in context, as one of the disambiguated p then the *type_argument_list* shall be retained as part of the disambiguated production and any other possible parse of the sequence of tokens discarded. Otherwise, the tokens parsed as a *type_argument_list* shall not be considered to be part of the disambiguated production, even if there is no other possible parse of those tokens. -> *Note*: These disambiguation rules shall not be applied when parsing other productions even if they similarly end in “`identifier type_argument_list?`”; such productions shall be parsed as normal. Examples include: *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)); *named_entity* ([§12.8.23](expressions.md#12823-the-nameof-operator)); *null_conditional_projection_initializer* ([§12.8.8](expressions.md#1288-null-conditional-member-access)); and *qualified_alias_member* ([§14.9.1](namespaces.md#1491-general)). *end note* +> *Note*: These disambiguation rules shall not be applied when parsing other productions even if they similarly end in “`identifier type_argument_list?`”; such productions shall be parsed as normal. Examples include: *namespace_or_type_name* ([§7.7](basic-concepts.md#77-namespace-and-type-names)); *named_entity* ([§12.8.23](expressions.md#12823-the-nameof-operator)); *null_conditional_projection_initializer* ([§12.8.8](expressions.md#1288-null-conditional-member-access)); and *qualified_alias_member* ([§14.9.1](namespaces.md#1491-general)). *end note* @@ -1506,7 +1506,7 @@ fragment PP_Line_Indicator | Decimal_Digit+ | DEFAULT | 'hidden' - | PP_Start_Line_Character PP_Whitespace? '-' PP_Whitespace? PP_End_Line_Character + | PP_Start_Line_Character PP_Whitespace? '-' PP_Whitespace? PP_End_Line_Character PP_Whitespace (PP_Character_Offset PP_Whitespace)? PP_Compilation_Unit_Name ; diff --git a/standard/statements.md b/standard/statements.md index db06b490b..4e917ce62 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -289,7 +289,7 @@ declaration_statement ; ``` -Except for a *local_using_declaration*, the declared names are introduced into the nearest enclosing declaration space ([§7.3](basic-concepts.md#73-declarations)). A *local_using_declaration* introduces a new declaration space and scope that extends from the declaration to the end of the enclosing block, as specified in [§13.14.2](statements.md#13142-using-declaration). +Except for a *local_using_declaration*, the declared names are introduced into the nearest enclosing declaration space ([§7.2](basic-concepts.md#72-declarations)). A *local_using_declaration* introduces a new declaration space and scope that extends from the declaration to the end of the enclosing block, as specified in [§13.14.2](statements.md#13142-using-declaration). ### 13.6.2 Local variable declarations @@ -340,7 +340,7 @@ If there are multiple declarators in a declaration then they are processed, incl The value of a local variable is obtained in an expression using a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)). A local variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) at each location where its value is obtained. Each local variable introduced by a *local_variable_declaration* is *initially unassigned* ([§9.4.3](variables.md#943-initially-unassigned-variables)). If a declarator has an initializing expression then the introduced local variable is classified as *assigned* at the end of the declarator ([§9.4.4.5](variables.md#9445-declaration-statements)). -The scope of a local variable introduced by a *local_variable_declaration* is defined as follows ([§7.7](basic-concepts.md#77-scopes)): +The scope of a local variable introduced by a *local_variable_declaration* is defined as follows ([§7.6](basic-concepts.md#76-scopes)): - If the declaration occurs as a *for_initializer* then the scope is the *for_initializer*, *for_condition*, *for_iterator*, and *embedded_statement* ([§13.9.4](statements.md#1394-the-for-statement)); - If the declaration occurs as a *resource_acquisition* then the scope is the outermost block of the semantically equivalent expansion of the *using_statement* ([§13.14](statements.md#1314-the-using-statement)); @@ -369,7 +369,7 @@ For a discussion of `scoped`, see §scoped-modifier. > *Example*: > -> +> > ```csharp > var i = 5; > var s = "Hello"; @@ -378,12 +378,12 @@ 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 i); // ref struct RS { ref int Field; ... } +> scoped var r = new RS(ref i); // ref struct RS { ref int Field; ... } > ``` > > The implicitly typed local variable declarations above are precisely equivalent to the following explicitly typed declarations: > -> +> > ```csharp > int i = 5; > string s = "Hello"; @@ -392,7 +392,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 i); // ref struct RS { ref int Field; ... } +> scoped RS r = new RS(ref i); // ref struct RS { ref int Field; ... } > ``` > > The following are incorrect implicitly typed local variable declarations: @@ -404,7 +404,7 @@ For a discussion of `scoped`, see §scoped-modifier. > var z = null; // Error, null does not have a type > var u = x => x + 1; // Error, no natural type > var v = v++; // Error, initializer cannot refer to v itself -> scoped var i = 10; // Error, i must be a ref or ref struct +> scoped var i = 10; // Error, i must be a ref or ref struct > ``` > > *end example* @@ -607,7 +607,7 @@ It is a compile-time error for the body of the local function to contain a `goto > *Note*: the above rules for `this` and `goto` mirror the rules for anonymous functions in [§12.22.3](expressions.md#12223-anonymous-function-bodies). *end note* -A local function may be called from a lexical point prior to its declaration. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.7](basic-concepts.md#77-scopes)). +A local function may be called from a lexical point prior to its declaration. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.6](basic-concepts.md#76-scopes)). It is a compile-time error for a local function to declare a parameter, type parameter or local variable with the same name as one declared in any enclosing local variable declaration space. @@ -1852,7 +1852,7 @@ A *try_statement* consists of the keyword `try` followed by a *block*, then zero In an *exception_specifier* the *type*, or its effective base class if it is a *type_parameter*, shall be `System.Exception` or a type that derives from it. -When a `catch` clause specifies both a *class_type* and an *identifier*, an ***exception variable*** of the given name and type is declared. The exception variable is introduced into the declaration space of the *specific_catch_clause* ([§7.3](basic-concepts.md#73-declarations)). During execution of the *exception_filter* and `catch` block, the exception variable represents the exception currently being handled. For purposes of definite assignment checking, the exception variable is considered definitely assigned in its entire scope. +When a `catch` clause specifies both a *class_type* and an *identifier*, an ***exception variable*** of the given name and type is declared. The exception variable is introduced into the declaration space of the *specific_catch_clause* ([§7.2](basic-concepts.md#72-declarations)). During execution of the *exception_filter* and `catch` block, the exception variable represents the exception currently being handled. For purposes of definite assignment checking, the exception variable is considered definitely assigned in its entire scope. Unless a `catch` clause includes an exception variable name, it is impossible to access the exception object in the filter and `catch` block. @@ -2290,7 +2290,7 @@ await using («local_variable_type» «local_variable_declarators») } ``` -The lifetime of the variables declared in a *non_ref_local_variable_declaration* extends to the end of the scope in which they are declared. Those variables are then disposed in the reverse order in which they are declared. The variables declared by a *local_using_declaration*, together with the trailing *statement_list*, form a new declaration space and scope ([§7.3](basic-concepts.md#73-declarations), [§13.3.1](statements.md#1331-general)), equivalent to the block introduced by the corresponding rewrite to a *using_statement* shown above. +The lifetime of the variables declared in a *non_ref_local_variable_declaration* extends to the end of the scope in which they are declared. Those variables are then disposed in the reverse order in which they are declared. The variables declared by a *local_using_declaration*, together with the trailing *statement_list*, form a new declaration space and scope ([§7.2](basic-concepts.md#72-declarations), [§13.3.1](statements.md#1331-general)), equivalent to the block introduced by the corresponding rewrite to a *using_statement* shown above. ```csharp diff --git a/standard/structs.md b/standard/structs.md index 69c3d639b..8c8b8785b 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -1068,7 +1068,7 @@ A property invocation (either `get` or `set`) is treated as a method invocation > *Example*: The following illustrates how `scoped` affects the safe-context of a method's return value: > -> +> > ```csharp > ref struct RS > { @@ -1121,7 +1121,7 @@ The presence of `scoped` allows developers to reduce the friction this rule crea > *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 { } > diff --git a/standard/variables.md b/standard/variables.md index 53c423fe0..9e2d1936c 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -1524,7 +1524,7 @@ In this relationship the *ref-safe-context* of a value can never be wider than t > *Example*: The following illustrates how `scoped ref` on a parameter prevents the parameter from being captured by a constructed `ref struct` value that the method returns: > -> +> > ```csharp > ref struct RS > {