From d908b4cbb762a448f8b01922211faa9bdbcaddbf Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 14 May 2026 12:07:08 -0400 Subject: [PATCH 1/3] Clarify rules for redundant arms - Make sure that redundancy is defined in terms of all preceding switch arms, not an arm. - Define "unguarded" in statements. - Add example to explain the rule that only unguarded switch arms apply to subsumption detection. --- standard/expressions.md | 2 +- standard/patterns.md | 23 +++++++++++++++++++++-- standard/statements.md | 4 +++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 72db7decd..4c62051b5 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3884,7 +3884,7 @@ If a switch expression is not subject to a *switch expression conversion*, then - The type of the *switch_expression* is the best common type [§12.6.3.16](expressions.md#126316-finding-the-best-common-type-of-a-set-of-expressions)) of the *switch_expression_arm_expression*s of the *switch_expression_arm*s, if such a type exists, and each *switch_expression_arm_expression* can be implicitly converted to that type. - It is an error if no such type exists. -It is an error if some *switch_expression_arm*’s pattern cannot affect the result because some previous pattern and guard will always match. +It is an error if the pattern of any *switch_expression_arm* is *subsumed* by ([§11.3](patterns.md#113-pattern-subsumption)) the set of patterns of earlier *unguarded* ([§13.8.3](statements.md#1383-the-switch-statement)) *switch_expression_arm*s of the switch expression. A switch expression is said to be *exhaustive* if every value of its input is handled by at least one arm of the switch expression. The compiler shall produce a warning if a switch expression is not exhaustive. At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*’s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.InvalidOperationException` (or a class derived from that). diff --git a/standard/patterns.md b/standard/patterns.md index 72d42d398..e8903ff76 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -409,8 +409,8 @@ It is a compile-time error to use a discard pattern in a *relational_expression* ## 11.3 Pattern subsumption -In a switch statement, it is an error if a case’s pattern is *subsumed* by the preceding set of unguarded cases ([§13.8.3](statements.md#1383-the-switch-statement)). -Informally, this means that any input value would have been matched by one of the previous cases. +In a switch statement ([§13.8.3](statements.md#1383-the-switch-statement)), it is an error if a case’s pattern is *subsumed* by the preceding set of *unguarded* ([§13.8.3](statements.md#1383-the-switch-statement)) cases. In a switch expression ([§12.11](expressions.md#1211-switch-expression)), it is an error if a *switch_expression_arm*’s pattern is *subsumed* by the preceding set of *unguarded* *switch_expression_arm*s’ patterns. +Informally, this means that any input value would have been matched by one of the previous cases or arms. The following rules define when a set of patterns subsumes a given pattern: A pattern `P` *would match* a constant `K` if the specification for that pattern’s runtime behavior is that `P` matches `K`. @@ -421,6 +421,25 @@ A set of patterns `Q` *subsumes* a pattern `P` if any of the following condition - `P` is a var pattern and the set of patterns `Q` is *exhaustive* ([§11.4](patterns.md#114-pattern-exhaustiveness)) for the type of the pattern input value ([§11.1](patterns.md#111-general)), and either the pattern input value is not of a nullable type or some pattern in `Q` would match `null`. - `P` is a declaration pattern with type `T` and the set of patterns `Q` is *exhaustive* for the type `T` ([§11.4](patterns.md#114-pattern-exhaustiveness)). +> *Note*: Only *unguarded* cases or arms ([§13.8.3](statements.md#1383-the-switch-statement)) contribute to subsumption; a *case_guard* whose expression is not a constant expression with the value `true` cannot in general be evaluated at compile time, so the corresponding case or arm is conservatively assumed not to match every input value to which its pattern is applicable. *end note* + +> *Example*: In the following switch expression, no arm is subsumed even though arms 1, 2, and 3 share the same pattern: +> +> +> ```csharp +> object x = 10; +> bool b = false; +> int y = x switch +> { +> int i when !b => 0, +> int i when b => 1, +> int i => 2, +> _ => 3 +> }; +> ``` +> +> Arms 1 and 2 have non-constant guards and so are not *unguarded*; only arm 3 is *unguarded* with pattern `int i`, which does not subsume the final `_` arm because it does not match a non-`int` value such as `null`. *end example* + ## 11.4 Pattern exhaustiveness Informally, a set of patterns is exhaustive for a type if, for every possible value of that type other than null, some pattern in the set is applicable. diff --git a/standard/statements.md b/standard/statements.md index 9b1f774eb..cd6d956f6 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -739,6 +739,8 @@ case_guard A *switch_statement* consists of the keyword `switch`, followed by a *tuple_literal* or parenthesized expression (each of which is called the *selector_expression*), followed by a *switch_block*. The *switch_block* consists of zero or more *switch_section*s, enclosed in braces. Each *switch_section* consists of one or more *switch_label*s followed by a *statement_list* ([§13.3.2](statements.md#1332-statement-lists)). Each *switch_label* containing `case` has an associated pattern ([§11](patterns.md#11-patterns-and-pattern-matching)) against which the value of the switch’s *selector_expression* is tested. If *case_guard* is present, its expression shall be implicitly convertible to the type `bool` and that expression is evaluated as an additional condition for the case to be considered satisfied. +A *switch_label* or *switch_expression_arm* ([§12.11](expressions.md#1211-switch-expression)) is said to be ***unguarded*** if it has no *case_guard*, or if its *case_guard*’s expression is a constant expression ([§12.25](expressions.md#1225-constant-expressions)) with the value `true`. + > *Note*: For convenience, the parentheses in *switch_statement* can be omitted when the *selector_expression* is a *tuple_literal*. For example, `switch ((a, b)) …` can be written as `switch (a, b) …`. *end note* The ***governing type*** of a `switch` statement is established by the switch’s *selector_expression*. @@ -751,7 +753,7 @@ There can be at most one `default` label in a `switch` statement. It is an error if the pattern of any switch label is not *applicable* ([§11.2.1](patterns.md#1121-general)) to the type of the input expression. -It is an error if the pattern of any switch label is *subsumed* by ([§11.3](patterns.md#113-pattern-subsumption)) the set of patterns of earlier switch labels of the switch statement that do not have a case guard or whose case guard is a constant expression with the value true. +It is an error if the pattern of any switch label is *subsumed* by ([§11.3](patterns.md#113-pattern-subsumption)) the set of patterns of earlier *unguarded* switch labels of the switch statement. > *Example*: > From b4b21cef010d7e8fa16d8ffde6d8dbb2dda109d0 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 14 May 2026 12:57:49 -0400 Subject: [PATCH 2/3] fix lint --- standard/patterns.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index e8903ff76..fe078e3d4 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -325,9 +325,9 @@ The *property_pattern* may be used to pattern-match with anonymous types. > ``` > > *end example* -> -> -> + + + > *Example*: A run-time type check and a variable declaration can be added to a property pattern, as follow: > > @@ -422,7 +422,9 @@ A set of patterns `Q` *subsumes* a pattern `P` if any of the following condition - `P` is a declaration pattern with type `T` and the set of patterns `Q` is *exhaustive* for the type `T` ([§11.4](patterns.md#114-pattern-exhaustiveness)). > *Note*: Only *unguarded* cases or arms ([§13.8.3](statements.md#1383-the-switch-statement)) contribute to subsumption; a *case_guard* whose expression is not a constant expression with the value `true` cannot in general be evaluated at compile time, so the corresponding case or arm is conservatively assumed not to match every input value to which its pattern is applicable. *end note* + + > *Example*: In the following switch expression, no arm is subsumed even though arms 1, 2, and 3 share the same pattern: > > From 8411f5d1a89cc01d3306185984c342bfedf915dc Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 10 Jun 2026 15:59:31 -0400 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com> Co-authored-by: Bill Wagner --- standard/patterns.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index fe078e3d4..378cf0b4a 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -410,7 +410,7 @@ It is a compile-time error to use a discard pattern in a *relational_expression* ## 11.3 Pattern subsumption In a switch statement ([§13.8.3](statements.md#1383-the-switch-statement)), it is an error if a case’s pattern is *subsumed* by the preceding set of *unguarded* ([§13.8.3](statements.md#1383-the-switch-statement)) cases. In a switch expression ([§12.11](expressions.md#1211-switch-expression)), it is an error if a *switch_expression_arm*’s pattern is *subsumed* by the preceding set of *unguarded* *switch_expression_arm*s’ patterns. -Informally, this means that any input value would have been matched by one of the previous cases or arms. +> *Note*: This means that any input value would have been matched by one of the previous cases or arms. *end note* The following rules define when a set of patterns subsumes a given pattern: A pattern `P` *would match* a constant `K` if the specification for that pattern’s runtime behavior is that `P` matches `K`. @@ -421,10 +421,6 @@ A set of patterns `Q` *subsumes* a pattern `P` if any of the following condition - `P` is a var pattern and the set of patterns `Q` is *exhaustive* ([§11.4](patterns.md#114-pattern-exhaustiveness)) for the type of the pattern input value ([§11.1](patterns.md#111-general)), and either the pattern input value is not of a nullable type or some pattern in `Q` would match `null`. - `P` is a declaration pattern with type `T` and the set of patterns `Q` is *exhaustive* for the type `T` ([§11.4](patterns.md#114-pattern-exhaustiveness)). -> *Note*: Only *unguarded* cases or arms ([§13.8.3](statements.md#1383-the-switch-statement)) contribute to subsumption; a *case_guard* whose expression is not a constant expression with the value `true` cannot in general be evaluated at compile time, so the corresponding case or arm is conservatively assumed not to match every input value to which its pattern is applicable. *end note* - - - > *Example*: In the following switch expression, no arm is subsumed even though arms 1, 2, and 3 share the same pattern: > >