From 47597386f562ba3c6d6cb8b285be31ff5070cc9f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 19 May 2026 11:55:35 -0400 Subject: [PATCH 1/3] Dealing with var, parts 1 and 2 1. A *var_pattern* can't be used if an identifier `var` is in scope. 2. If `var` is a type in scope, it can't be used as a *declaration_pattern*. However, other "spellings" of `var` are allowed as a type. --- standard/patterns.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index 3979195ed..898a05654 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -94,6 +94,10 @@ When recognising a *simple_designation* if both the *discard_designation* and *s > *Note*: ANTLR makes the specified choice automatically due to the ordering of the alternatives of *simple_designation*. *end note* +The *type* of a *declaration_pattern* cannot be the *identifier* `var`. + +> *Note*: In such a case the input may be recognisable as a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)). The exclusion applies only to the *identifier* `var`; the verbatim identifier `@var` and Unicode-escaped equivalents are distinct identifiers and remain valid as the *type* of a *declaration_pattern* when they name a type in scope. *end note* + It is a compile-time error if the *type* is a nullable value type ([§8.3.12](types.md#8312-nullable-value-types)) or a nullable reference type ([§8.9.3](types.md#893-nullable-reference-types)). The runtime type of the value is tested against the *type* in the pattern using the same rules specified in the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)). If the test succeeds, the pattern *matches* that value. @@ -186,6 +190,8 @@ A *var_pattern* *matches* every value. That is, a pattern-matching operation wit A *var_pattern* is *applicable to* every type. +A *var_pattern* cannot be used when a type named `var` is in scope. + ```ANTLR var_pattern : 'var' designation @@ -204,8 +210,6 @@ designations Given a pattern input value ([§11.1](patterns.md#111-general)) *e*, if *designation* is *discard_designation*, it denotes a discard ([§9.2.9.2](variables.md#9292-discards)), and the value of *e* is not bound to anything. (Although a declared variable with that name may be in scope at that point, that named variable is not seen in this context.) Otherwise, if *designation* is *single_variable_designation*, at runtime the value of *e* is bound to a newly introduced local variable ([§9.2.9](variables.md#929-local-variables)) of that name whose type is the static type of *e*, and the pattern input value is assigned to that local variable. -It is an error if the name `var` would bind to a type where a *var_pattern* is used. - If *designation* is a *tuple_designation*, the pattern is equivalent to a *positional_pattern* ([§11.2.5](patterns.md#1125-positional-pattern)) of the form `(var` *designation*, … `)` where the *designation*s are those found within the *tuple_designation*. For example, the pattern `var (x, (y, z))` is equivalent to `(var x, (var y, var z))`. ### 11.2.5 Positional pattern From f099a549c0985c64e011df94e33c9f8cb5af518c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 21 May 2026 11:55:48 -0400 Subject: [PATCH 2/3] Address Initial feedback I knew this looked to easy. First, moved "Constant Pattern" before "Declaration Pattern". Now, *declaration_pattern* and *var_pattern* are adjacent. That should make it easier to determine if we want to combine them. The flow seems reasonable. Next, handle `var` in general. I moved these rules into the "general" section, because they apply across all patterns. If `var` is an identifier, it binds to that element. If `var` is a constant, it's a constant pattern. If it's a type, it's a type pattern. If it's a variable, it's an invalid pattern. (Similarly if its a method group or namespace). Finally, add a note about how tuple literals are parsed as different patterns depending on their form. --- standard/patterns.md | 118 ++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 47 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index 898a05654..34ac0be7d 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -70,6 +70,61 @@ Each pattern form defines the set of values for which the pattern *matches* the The order of evaluation of operations and side effects during pattern-matching (calls to `Deconstruct`, property accesses, and invocations of members of `System.Runtime.CompilerServices.ITuple`) is not specified. +The *identifier* `var` appearing in any *pattern* is bound using the normal identifier-resolution rules. The *identifier* `var` may appear in patterns only where the entity to which it resolves is valid in that position. When no declaration of the *identifier* `var` is in scope, the form `var` *designation* is recognised as a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)); when any declaration of the *identifier* `var` is in scope, a *var_pattern* cannot be used. The verbatim identifier `@var` and Unicode-escaped equivalents are distinct identifiers from the contextual keyword `var` and are bound and used in patterns according to the entities they denote, like any other identifier. + +> *Note*: When a declaration of the *identifier* `var` is in scope, the consequences of the rule above are: +> +> - If `var` denotes a type, then `var` may be the *type* of a *declaration_pattern* ([§11.2.2](patterns.md#1122-declaration-pattern)). +> - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). +> - Otherwise (for example, when `var` denotes a field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid in pattern position. +> +> *end note* + +### 11.2.3 Constant pattern + +A *constant_pattern* is used to test the value of a pattern input value ([§11.1](patterns.md#111-general)) against the given constant value. + +```ANTLR +constant_pattern + : constant_expression + ; +``` + +When a declaration of the *identifier* `var` in scope resolves to a constant, `var` may appear as the *constant_expression* of a *constant_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). + +A constant pattern `P` is *applicable to* a type `T` if there is an implicit conversion from the constant expression of `P` to the type `T`. + +For a constant pattern `P`, its *converted value* is + +- if the pattern input value’s type is an integral type or an enum type, the pattern’s constant value converted to that type; otherwise +- if the pattern input value’s type is the nullable version of an integral type or an enum type, the pattern’s constant value converted to its underlying type; otherwise +- the value of the pattern’s constant value. + +Given a pattern input value *e* and a constant pattern `P` with converted value *v*, + +- if *e* has integral type or enum type, or a nullable form of one of those, and *v* has integral type, the pattern `P` *matches* the value *e* if result of the expression `e == v` is `true`; otherwise +- the pattern `P` *matches* the value *e* if `object.Equals(e, v)` returns `true`. + +> *Example*: The `switch` statement in the following method uses five constant patterns in its case labels. +> +> +> ```csharp +> static decimal GetGroupTicketPrice(int visitorCount) +> { +> switch (visitorCount) +> { +> case 1: return 12.0m; +> case 2: return 20.0m; +> case 3: return 27.0m; +> case 4: return 32.0m; +> case 0: return 0.0m; +> default: throw new ArgumentException(...); +> } +> } +> ``` +> +> *end example* + ### 11.2.2 Declaration pattern A *declaration_pattern* is used to test that a value has a given type and, if the test succeeds, to optionally provide the value in a variable of that type. @@ -94,9 +149,9 @@ When recognising a *simple_designation* if both the *discard_designation* and *s > *Note*: ANTLR makes the specified choice automatically due to the ordering of the alternatives of *simple_designation*. *end note* -The *type* of a *declaration_pattern* cannot be the *identifier* `var`. +When a declaration of the *identifier* `var` in scope resolves to a type, `var` may appear as the *type* of a *declaration_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). -> *Note*: In such a case the input may be recognisable as a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)). The exclusion applies only to the *identifier* `var`; the verbatim identifier `@var` and Unicode-escaped equivalents are distinct identifiers and remain valid as the *type* of a *declaration_pattern* when they name a type in scope. *end note* +The *type* of a *declaration_pattern* cannot be `dynamic`, because the runtime type test is defined in terms of the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)), which does not permit `dynamic`. It is a compile-time error if the *type* is a nullable value type ([§8.3.12](types.md#8312-nullable-value-types)) or a nullable reference type ([§8.9.3](types.md#893-nullable-reference-types)). @@ -141,56 +196,13 @@ A type `E` is said to be ***pattern compatible*** with the type `T` if there exi > > The condition of the `if` statement is `true` at runtime and the variable `v` holds the value `3` of type `int` inside the block. After the block the variable `v` is in scope, but not definitely assigned. *end example* -### 11.2.3 Constant pattern - -A *constant_pattern* is used to test the value of a pattern input value ([§11.1](patterns.md#111-general)) against the given constant value. - -```ANTLR -constant_pattern - : constant_expression - ; -``` - -A constant pattern `P` is *applicable to* a type `T` if there is an implicit conversion from the constant expression of `P` to the type `T`. - -For a constant pattern `P`, its *converted value* is - -- if the pattern input value’s type is an integral type or an enum type, the pattern’s constant value converted to that type; otherwise -- if the pattern input value’s type is the nullable version of an integral type or an enum type, the pattern’s constant value converted to its underlying type; otherwise -- the value of the pattern’s constant value. - -Given a pattern input value *e* and a constant pattern `P` with converted value *v*, - -- if *e* has integral type or enum type, or a nullable form of one of those, and *v* has integral type, the pattern `P` *matches* the value *e* if result of the expression `e == v` is `true`; otherwise -- the pattern `P` *matches* the value *e* if `object.Equals(e, v)` returns `true`. - -> *Example*: The `switch` statement in the following method uses five constant patterns in its case labels. -> -> -> ```csharp -> static decimal GetGroupTicketPrice(int visitorCount) -> { -> switch (visitorCount) -> { -> case 1: return 12.0m; -> case 2: return 20.0m; -> case 3: return 27.0m; -> case 4: return 32.0m; -> case 0: return 0.0m; -> default: throw new ArgumentException(...); -> } -> } -> ``` -> -> *end example* - ### 11.2.4 Var pattern A *var_pattern* *matches* every value. That is, a pattern-matching operation with a *var_pattern* always succeeds. A *var_pattern* is *applicable to* every type. -A *var_pattern* cannot be used when a type named `var` is in scope. +A *var_pattern* cannot be used when any declaration of the *identifier* `var` is in scope. See [§11.2.1](patterns.md#1121-general) for the interpretation of `var` in pattern position in that case. ```ANTLR var_pattern @@ -231,7 +243,19 @@ subpattern ; ``` -Let *n* be the number of *subpattern*s appearing between the parentheses. The matching strategy is selected at compile time by applying the following cases in order; the first case whose conditions are satisfied is used, and the remaining cases are not considered. Once a case is selected, that strategy is committed: any compile-time error stated within that case is reported, and matching does not fall through to a subsequent case. +Given a match of an input value to the pattern *type* `(` *subpatterns* `)`, a method is selected by searching in *type* for accessible declarations of `Deconstruct` and selecting one among them using the same rules as for the deconstruction declaration. +It is an error if a *positional_pattern* omits the type, has a single *subpattern* without an *identifier*, has no *property_subpattern* and has no *simple_designation*. This disambiguates between a *constant_pattern* that is parenthesized and a *positional_pattern*. + +> *Note*: A tuple literal can be matched by patterns of several different forms, which are not interchangeable: +> +> - `(int, int) x` is a *declaration_pattern* with type `(int, int)` and *simple_designation* `x`. +> - `var (x, y)` is a *var_pattern* with a *tuple_designation*. +> - `(int x, int y)` is a *positional_pattern*. +> - `(int, int) (x, y)` is not a valid pattern. +> +> *end note* + +In order to extract the values to match against the patterns in the list, 1. **Tuple form.** If *type* is omitted and the static type of the input value is a tuple type ([§8.3.11](types.md#8311-tuple-types)) or if the input value is a tuple literal ([§12.8.6](expressions.md#1286-tuple-literals)), then this case applies. It is a compile-time error if *n* is not equal to the arity of that tuple type. At runtime, each tuple element is matched against the corresponding *subpattern*; the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, that *identifier* shall name the tuple element at the corresponding position in the tuple type. 2. **Deconstruct form.** Otherwise, if either *type* is present, or *type* is omitted and the static type of the input value contains an accessible `Deconstruct` method ([§12.7](expressions.md#127-deconstruction)), then this case applies. Let *D* be *type* if *type* is present; otherwise let *D* be the static type of the input value. A `Deconstruct` method is selected from *D* using the same overload-resolution rules as for a deconstruction declaration, with the additional requirement that its number of `out` parameters is equal to *n*; it is a compile-time error if no such method exists. If *type* is present, it is a compile-time error if the static type of the input value is not pattern compatible ([§11.2.2](patterns.md#1122-declaration-pattern)) with *type*; at runtime the input value is tested against *type* and, if that test fails, the positional pattern match fails. Otherwise, the input value is converted to *D* and the selected `Deconstruct` method is invoked with fresh variables receiving its `out` parameters. Each received value is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, that *identifier* shall name the parameter at the corresponding position of `Deconstruct`. From e4efe2b61cda366233b5fe35bfd3178de30f7946 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 15 Jul 2026 10:47:58 -0400 Subject: [PATCH 3/3] =?UTF-8?q?Clarify=20handling=20of=20the=20identifier?= =?UTF-8?q?=20`var`=20in=20patterns=20(=C2=A711.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address reviewer feedback on the `var`/`@var` disambiguation in the pattern-form clauses: - Clarify that the verbatim identifier `@var` and Unicode-escaped spellings of `var` are never recognised as the contextual keyword of a `var_pattern`. Such a pattern is interpreted as a `declaration_pattern`, `constant_pattern`, or other form according to the entity the identifier denotes. - Promote the §11.2.1 note describing the consequences of an in-scope `var` declaration to normative body text, stating the three cases (type, constant, and otherwise) precisely. - State that when `var` denotes a type, the plain token `var` cannot be used as the *type* of a `declaration_pattern`; the type must be named another way, such as `@var` (§11.2.1 and §11.2.2). - Replace the vague phrase "pattern position" with "the first token of a *pattern*" for clarity and consistency (§11.2.1 and §11.2.4). - Narrow the `var_pattern` restriction: a `var_pattern` is unavailable only when the plain token `var` would refer to an in-scope type named `var`, not when `var` names a constant or namespace. The previous wording ("any declaration of `var`") was too broad; the refined rule matches observed compiler behaviour. - Record the C# 9 boundary change for a future draft-v9 update: a parenthesised `is (var)` / `is (@var)` binds as a type pattern in C# 9 while remaining unavailable in C# 8, and bare `is T` and pattern `is (T)` are distinct grammar and binding paths. --- standard/patterns.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index 34ac0be7d..f4e1ece9a 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -70,15 +70,15 @@ Each pattern form defines the set of values for which the pattern *matches* the The order of evaluation of operations and side effects during pattern-matching (calls to `Deconstruct`, property accesses, and invocations of members of `System.Runtime.CompilerServices.ITuple`) is not specified. -The *identifier* `var` appearing in any *pattern* is bound using the normal identifier-resolution rules. The *identifier* `var` may appear in patterns only where the entity to which it resolves is valid in that position. When no declaration of the *identifier* `var` is in scope, the form `var` *designation* is recognised as a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)); when any declaration of the *identifier* `var` is in scope, a *var_pattern* cannot be used. The verbatim identifier `@var` and Unicode-escaped equivalents are distinct identifiers from the contextual keyword `var` and are bound and used in patterns according to the entities they denote, like any other identifier. +The token `var` in a *pattern* is recognised as the contextual keyword of a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)) only in the form `var` *designation*. The verbatim identifier `@var` and Unicode-escaped spellings of `var` are never recognised as the contextual keyword of a *var_pattern*; they are ordinary identifiers denoting whatever entity is in scope. A pattern that uses such a spelling is therefore interpreted as a *declaration_pattern*, *constant_pattern*, or other pattern form according to the entity that the identifier denotes. -> *Note*: When a declaration of the *identifier* `var` is in scope, the consequences of the rule above are: -> -> - If `var` denotes a type, then `var` may be the *type* of a *declaration_pattern* ([§11.2.2](patterns.md#1122-declaration-pattern)). -> - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). -> - Otherwise (for example, when `var` denotes a field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid in pattern position. -> -> *end note* +When a declaration of the *identifier* `var` is in scope, the following rules apply: + +- If `var` denotes a type, then the plain token `var` cannot be used as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. +- If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). +- Otherwise (for example, when `var` denotes a namespace, field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid as the first token of a *pattern* unless it is used in the form `var` *designation*. + + ### 11.2.3 Constant pattern @@ -149,7 +149,7 @@ When recognising a *simple_designation* if both the *discard_designation* and *s > *Note*: ANTLR makes the specified choice automatically due to the ordering of the alternatives of *simple_designation*. *end note* -When a declaration of the *identifier* `var` in scope resolves to a type, `var` may appear as the *type* of a *declaration_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). +When a declaration of the *identifier* `var` in scope resolves to a type, the plain token `var` cannot appear as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. The general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). The *type* of a *declaration_pattern* cannot be `dynamic`, because the runtime type test is defined in terms of the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)), which does not permit `dynamic`. @@ -202,7 +202,7 @@ A *var_pattern* *matches* every value. That is, a pattern-matching operation wit A *var_pattern* is *applicable to* every type. -A *var_pattern* cannot be used when any declaration of the *identifier* `var` is in scope. See [§11.2.1](patterns.md#1121-general) for the interpretation of `var` in pattern position in that case. +A *var_pattern* cannot be used when the plain token `var` would refer to an in-scope type named `var`. See [§11.2.1](patterns.md#1121-general) for the interpretation of `var` when it is the first token of a *pattern* in that case. ```ANTLR var_pattern