From 42c9a389845e2ab36850434d69bce304f988105b Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 6 Mar 2026 10:20:05 -0500 Subject: [PATCH 1/5] Support unsigned right shift operator --- standard/lexical-structure.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 1f3a89cba..d43810053 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -1049,11 +1049,19 @@ right_shift right_shift_assignment : '>' '>=' ; + +unsigned_right_shift + : '>' '>' '>' + ; + +unsigned_right_shift_assignment + : '>' '>' '>=' + ; ``` -> *Note*: *right_shift* and *right_shift_assignment* are parser rules as they do not introduce a new token kind but represent a sequence of two tokens. The *operator_or_punctuator* rule exists for descriptive purposes only and is not used elsewhere in the grammar. *end note* +> *Note*: *right_shift* and *right_shift_assignment* are parser rules as they do not introduce a new token kind but represent a sequence of two tokens. Similarly, *unsigned_right_shift* and *unsigned_right_shift_assignment* are parser rules as they do not introduce a new token kind but represent a sequence of three tokens. The *operator_or_punctuator* rule exists for descriptive purposes only and is not used elsewhere in the grammar. *end note* -*right_shift* is made up of the two tokens `>` and `>`. Similarly, *right_shift_assignment* is made up of the two tokens `>` and `>=`. Unlike other productions in the syntactic grammar, no characters of any kind (not even whitespace) are allowed between the two tokens in each of these productions. These productions are treated specially in order to enable the correct handling of *type_parameter_lists* ([§15.2.3](classes.md#1523-type-parameters)). +*right_shift* is made up of the two tokens `>` and `>`, and *unsigned_right_shift* is made up of the three tokens `>`, `>`, and `>`. Similarly, *right_shift_assignment* is made up of the two tokens `>` and `>=`, and *unsigned_right_shift_assignment* is made up of the three tokens `>`, `>`, and `>=`. Unlike other productions in the syntactic grammar, no characters of any kind (not even whitespace) are allowed between the two tokens in each of these productions. These productions are treated specially in order to enable the correct handling of *type_parameter_lists* ([§15.2.3](classes.md#1523-type-parameters)). > *Note*: Prior to the addition of generics to C#, `>>` and `>>=` were both single tokens. However, the syntax for generics uses the `<` and `>` characters to delimit type parameters and type arguments. It is often desirable to use nested constructed types, such as `List>`. Rather than requiring the programmer to separate the `>` and `>` by a space, the definition of the two *operator_or_punctuator*s was changed. *end note* From f28c070ffbbd9ffd17479752d2bd86489925e73b Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 6 Mar 2026 10:22:32 -0500 Subject: [PATCH 2/5] support unsigned right shift operator --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index f86990a96..36f703360 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -663,7 +663,7 @@ The following rule applies to these kinds of expressions: literals ([§12.8.2](e #### 9.4.4.23 General rules for expressions with embedded expressions -The following rules apply to these kinds of expressions: parenthesized expressions ([§12.8.5](expressions.md#1285-parenthesized-expressions)), tuple expressions ([§12.8.6](expressions.md#1286-tuple-expressions)), element access expressions ([§12.8.12](expressions.md#12812-element-access)), base access expressions with indexing ([§12.8.15](expressions.md#12815-base-access)), increment and decrement expressions ([§12.8.16](expressions.md#12816-postfix-increment-and-decrement-operators), [§12.9.7](expressions.md#1297-prefix-increment-and-decrement-operators)), cast expressions ([§12.9.8](expressions.md#1298-cast-expressions)), unary `+`, `-`, `~`, `*` expressions, binary `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `is`, `as`, `&`, `|`, `^` expressions ([§12.13](expressions.md#1213-arithmetic-operators), [§12.14](expressions.md#1214-shift-operators), [§12.15](expressions.md#1215-relational-and-type-testing-operators), [§12.16](expressions.md#1216-logical-operators)), compound assignment expressions ([§12.24.4](expressions.md#12244-compound-assignment)), `checked` and `unchecked` expressions ([§12.8.20](expressions.md#12820-the-checked-and-unchecked-operators)), array and delegate creation expressions ([§12.8.17](expressions.md#12817-the-new-operator)) , and `await` expressions ([§12.9.9](expressions.md#1299-await-expressions)). +The following rules apply to these kinds of expressions: parenthesized expressions ([§12.8.5](expressions.md#1285-parenthesized-expressions)), tuple expressions ([§12.8.6](expressions.md#1286-tuple-expressions)), element access expressions ([§12.8.12](expressions.md#12812-element-access)), base access expressions with indexing ([§12.8.15](expressions.md#12815-base-access)), increment and decrement expressions ([§12.8.16](expressions.md#12816-postfix-increment-and-decrement-operators), [§12.9.7](expressions.md#1297-prefix-increment-and-decrement-operators)), cast expressions ([§12.9.8](expressions.md#1298-cast-expressions)), unary `+`, `-`, `~`, `*` expressions, binary `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `is`, `as`, `&`, `|`, `^` expressions ([§12.13](expressions.md#1213-arithmetic-operators), [§12.14](expressions.md#1214-shift-operators), [§12.15](expressions.md#1215-relational-and-type-testing-operators), [§12.16](expressions.md#1216-logical-operators)), compound assignment expressions ([§12.24.4](expressions.md#12244-compound-assignment)), `checked` and `unchecked` expressions ([§12.8.20](expressions.md#12820-the-checked-and-unchecked-operators)), array and delegate creation expressions ([§12.8.17](expressions.md#12817-the-new-operator)) , and `await` expressions ([§12.9.9](expressions.md#1299-await-expressions)). Each of these expressions has one or more subexpressions that are unconditionally evaluated in a fixed order. From 1985ea0ffa66170545db3e0dddfe5e299f682df1 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 6 Mar 2026 10:38:34 -0500 Subject: [PATCH 3/5] support unsigned right shift operator --- standard/expressions.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 82ce81a03..a049cb6ee 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -67,8 +67,8 @@ The following operations in C# are subject to binding: - Element access: `e[e₁,...,eᵥ]` - Object creation: new `C(e₁,...,eᵥ)` - Overloaded unary operators: `+`, `-`, `!` (logical negation only), `~`, `++`, `--`, `true`, `false` -- Overloaded binary operators: `+`, `-`, `*`, `/`, `%`, `&`, `&&`, `|`, `||`, `??`, `^`, `<<`, `>>`, `==`, `!=`, `>`, `<`, `>=`, `<=` -- Assignment operators: `=`, `= ref`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `??=` +- Overloaded binary operators: `+`, `-`, `*`, `/`, `%`, `&`, `&&`, `|`, `||`, `??`, `^`, `<<`, `>>`, `>>>` (static binding only), `==`, `!=`, `>`, `<`, `>=`, `<=` +- Assignment operators: `=`, `= ref`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `>>>=` (static binding only), `??=` - Implicit and explicit conversions When no dynamic expressions are involved, C# defaults to static binding, which means that the compile-time types of subexpressions are used in the selection process. However, when one of the subexpressions in the operations listed above is a dynamic expression, the operation is instead dynamically bound. @@ -160,7 +160,7 @@ The precedence of an operator is established by the definition of its associated > | [§12.12](expressions.md#1212-switch-expression) | Switch | `switch { … }` | > | [§12.13](expressions.md#1213-arithmetic-operators) | Multiplicative | `*` `/` `%` | > | [§12.13](expressions.md#1213-arithmetic-operators) | Additive | `+` `-` | -> | [§12.14](expressions.md#1214-shift-operators) | Shift | `<<` `>>` | +> | [§12.14](expressions.md#1214-shift-operators) | Shift | `<<` `>>` `>>>` | > | [§12.15](expressions.md#1215-relational-and-type-testing-operators) | Relational and type-testing | `<` `>` `<=` `>=` `is` `as` | > | [§12.15](expressions.md#1215-relational-and-type-testing-operators) | Equality | `==` `!=` | > | [§12.16](expressions.md#1216-logical-operators) | Logical AND | `&` | @@ -170,7 +170,7 @@ The precedence of an operator is established by the definition of its associated > | [§12.17](expressions.md#1217-conditional-logical-operators) | Conditional OR | `\|\|` | > | [§12.18](expressions.md#1218-the-null-coalescing-operator) and [§12.19](expressions.md#1219-the-throw-expression-operator) | Null coalescing and throw expression | `??` `throw x` | > | [§12.21](expressions.md#1221-conditional-operator) | Conditional | `?:` | -> | [§12.24](expressions.md#1224-assignment-operators) and [§12.22](expressions.md#1222-anonymous-function-expressions) | Assignment and lambda expression | `=` `= ref` `*=` `/=` `%=` `+=` `-=` `<<=` `>>=` `&=` `^=` `\|=` `=>` `??=` | +> | [§12.24](expressions.md#1224-assignment-operators) and [§12.22](expressions.md#1222-anonymous-function-expressions) | Assignment and lambda expression | `=` `= ref` `*=` `/=` `%=` `+=` `-=` `<<=` `>>=` `>>>=` `&=` `^=` `\|=` `=>` `??=` | > > *end note* @@ -201,7 +201,7 @@ Only the operators listed above can be overloaded. In particular, it is not poss The ***overloadable binary operator***s are: -> `+ - * / % & | ^ << >> == != > < <= >=` +> `+ - * / % & | ^ << >> >>> == != > < <= >=` Only the operators listed above can be overloaded. In particular, it is not possible to overload member access, method invocation, or the `..`, `=`, `&&`, `||`, `??`, `?:`, `=>`, `checked`, `unchecked`, `new`, `typeof`, `default`, `as`, and `is` operators. @@ -360,7 +360,7 @@ In both of the above cases, a cast expression can be used to explicitly convert A ***lifted operator*** permits predefined and user-defined operators that operate on a non-nullable value type to also be used with the nullable form of that type. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described in the following: - For the unary operators `+`, `++`, `-`, `--`, `!` (logical negation), `^`, and `~`, a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single `?` modifier to the operand and result types. The lifted operator produces a `null` value if the operand is `null`. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result. -- For the binary operators `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `..`, `<<`, and `>>`, a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single `?` modifier to each operand and result type. The lifted operator produces a `null` value if one or both operands are `null` (an exception being the `&` and `|` operators of the `bool?` type, as described in [§12.16.5](expressions.md#12165-nullable-boolean--and--operators)). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result. +- For the binary operators `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `..`, `<<`, `>>`, and `>>>`, a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single `?` modifier to each operand and result type. The lifted operator produces a `null` value if one or both operands are `null` (an exception being the `&` and `|` operators of the `bool?` type, as described in [§12.16.5](expressions.md#12165-nullable-boolean--and--operators)). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result. - For the equality operators `==` and `!=`, a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is `bool`. The lifted form is constructed by adding a single `?` modifier to each operand type. The lifted operator considers two `null` values equal, and a `null` value unequal to any non-`null` value. If both operands are non-`null`, the lifted operator unwraps the operands and applies the underlying operator to produce the `bool` result. - For the relational operators `<`, `>`, `<=`, and `>=`, a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is `bool`. The lifted form is constructed by adding a single `?` modifier to each operand type. The lifted operator produces the value `false` if one or both operands are `null`. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the `bool` result. @@ -4498,22 +4498,29 @@ Lifted ([§12.4.8](expressions.md#1248-lifted-operators)) forms of the unlifted ## 12.14 Shift operators -The `<<` and `>>` operators are used to perform bit-shifting operations. +The `<<`, `>>`, and `>>>` operators are used to perform bit-shifting operations. ```ANTLR shift_expression : additive_expression | shift_expression '<<' additive_expression | shift_expression right_shift additive_expression + | shift_expression unsigned_right_shift additive_expression ; ``` If an operand of a *shift_expression* has the compile-time type `dynamic`, then the expression is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)). In this case, the compile-time type of the expression is `dynamic`, and the resolution described below will take place at run-time using the run-time type of those operands that have the compile-time type `dynamic`. -For an operation of the form `x << count` or `x >> count`, binary operator overload resolution ([§12.4.5](expressions.md#1245-binary-operator-overload-resolution)) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator. +Dynamic binding uses values of type `enum System.Linq.Expressions.ExpressionType` to communicate binary operator kind to the runtime binder. As there is no enum member specifically representing an unsigned right shift operator, dynamic binding for `>>>` is not supported. + +For an operation of the form `x << count`, `x >> count`, or `X >>> count`, binary operator overload resolution ([§12.4.5](expressions.md#1245-binary-operator-overload-resolution)) is applied to select a specific operator implementation. The left operand is converted to type `T`, where `T` is the first of `int`, `uint`, `long`, and `ulong` that can fully represent all possible values of the operand. The operation is then performed using the precision of type `T`, and the type of the result is `T`. When declaring an overloaded shift operator, the type of the first operand shall always be the class or struct containing the operator declaration, and the type of the second operand shall always be `int`. +The *unsigned_right_shift* operator (`>>>`) shall not be present in an expression tree. + +> *Note*: The semantics of the predefined `>>>` operator on signed types cannot be accurately represented without adding conversions to an unsigned type and back. *end note* + The predefined shift operators are listed below. - Shift left: @@ -4535,6 +4542,12 @@ The predefined shift operators are listed below. uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count); + int operator >>>(int x, int count); + uint operator >>>(uint x, int count); + nint operator >>>(nint x, int count); + nuint operator >>>(nuint x, int count); + long operator >>>(long x, int count); + ulong operator >>>(ulong x, int count); ``` The `>>` operator shifts `x` right by a number of bits computed as described below. @@ -4543,6 +4556,8 @@ The predefined shift operators are listed below. When `x` is of type `uint`, `nuint`, or `ulong`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. + The `>>>` operator shifts `x` right by a number of bits computed as follows: The low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. + For the predefined operators, the number of bits to shift is computed as follows: - When the type of `x` is `int` or `uint`, the shift count is given by the low-order five bits of `count`. In other words, the shift count is computed from `count & 0x1F`. This also applies when the type of `x` is `nint` or `nuint`, and those types have the same size and representation as `int` and `uint`, respectively. @@ -6996,6 +7011,7 @@ assignment_operator : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '??=' | right_shift_assignment + | unsigned_right_shift_assignment ; ``` @@ -7306,7 +7322,7 @@ Only the following constructs are permitted in constant expressions: - `checked` and `unchecked` expressions. - `nameof` expressions. - The predefined `+`, `-`, `!` (logical negation) and `~` unary operators. -- The predefined `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `&`, `|`, `^`, `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, and `>=` binary operators. +- The predefined `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^`, `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, and `>=` binary operators. - The `?:` conditional operator. - The `!` null-forgiving operator ([§12.8.9](expressions.md#1289-null-forgiving-expressions)). - `sizeof` expressions, provided the unmanaged-type is one of the types specified in [§24.6.9](unsafe-code.md#2469-the-sizeof-operator) for which `sizeof` returns a constant value. From f4326a8ae20f643fc7210fdda14c08c06a14e257 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 6 Mar 2026 10:42:05 -0500 Subject: [PATCH 4/5] support unsigned right shift operator --- standard/classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 594d56fb5..abc96f5c4 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -4930,7 +4930,7 @@ binary_operator_declarator overloadable_binary_operator : '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<' - | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<=' + | right_shift | unsigned_right_shift | '==' | '!=' | '>' | '<' | '>=' | '<=' ; conversion_operator_declarator @@ -5025,7 +5025,7 @@ The `true` and `false` unary operators require pair-wise declaration. A compile- The following rules apply to binary operator declarations, where `T` denotes the instance type of the class or struct that contains the operator declaration: - A binary non-shift operator shall take two parameters, at least one of which shall have type `T` or `T?`, and can return any type. -- A binary `<<` or `>>` operator ([§12.14](expressions.md#1214-shift-operators)) shall take two parameters, the first of which shall have type `T` or `T?` and the second of which shall have type `int` or `int?`, and can return any type. +- A binary `<<`, `>>`, or `>>>` operator ([§12.14](expressions.md#1214-shift-operators)) shall take two parameters, the first of which shall have type `T` or `T?` and the second of which shall have type `int` or `int?`, and can return any type. The signature of a binary operator consists of the operator token (`+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `<<`, `>>`, `==`, `!=`, `>`, `<`, `>=`, or `<=`) and the types of the two parameters. The return type and the names of the parameters are not part of a binary operator’s signature. From e619ed778d335f91c3980760a694681e8d8739f0 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 6 Mar 2026 10:44:16 -0500 Subject: [PATCH 5/5] support unsigned right shift operator --- standard/documentation-comments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/documentation-comments.md b/standard/documentation-comments.md index a6dbfbde9..1ce4db128 100644 --- a/standard/documentation-comments.md +++ b/standard/documentation-comments.md @@ -984,7 +984,7 @@ IDs: "M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)" ``` -The complete set of binary operator function names used is as follows: `op_Addition`, `op_Subtraction`, `op_Multiply`, `op_Division`, `op_Modulus`, `op_BitwiseAnd`, `op_BitwiseOr`, `op_ExclusiveOr`, `op_LeftShift`, `op_RightShift`, `op_Equality`, `op_Inequality`, `op_LessThan`, `op_LessThanOrEqual`, `op_GreaterThan`, and `op_GreaterThanOrEqual`. +The complete set of binary operator function names used is as follows: `op_Addition`, `op_Subtraction`, `op_Multiply`, `op_Division`, `op_Modulus`, `op_BitwiseAnd`, `op_BitwiseOr`, `op_ExclusiveOr`, `op_LeftShift`, `op_RightShift`, `op_UnsignedRightShift`, `op_Equality`, `op_Inequality`, `op_LessThan`, `op_LessThanOrEqual`, `op_GreaterThan`, and `op_GreaterThanOrEqual`. **Conversion operators** have a trailing “`~`” followed by the return type. When either the source or destination of a conversion operator is a generic type, the “`<`” and “`">`” characters are replaced by the “`{`” and “`}`” characters, respectively.