Skip to content

Commit e8874ac

Browse files
committed
Apply delta from PR #1460 (Pattern matching) - 12 new review commits
1 parent 5155318 commit e8874ac

2 files changed

Lines changed: 125 additions & 24 deletions

File tree

standard/expressions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4008,10 +4008,10 @@ If a switch expression is not subject to a *switch expression conversion*, then
40084008
- 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.
40094009
- It is an error if no such type exists.
40104010

4011-
It is an error if some *switch_expression_arm*’s pattern cannot affect the result because some previous pattern and guard will always match.
4011+
It is an error if some *switch_expression_arm*’s pattern is *subsumed* by ([§11.3](patterns.md#113-pattern-subsumption)) the set of patterns of preceding *switch_expression_arm*s of the switch expression that do not have a *case_guard* or whose *case_guard* is a constant expression with the value `true`.
40124012

4013-
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.
4014-
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).
4013+
A switch expression is said to be *exhaustive* if the set of patterns of its *switch_expression_arm*s is *exhaustive* ([§11.4](patterns.md#114-pattern-exhaustiveness)) for the type of the switch expression's input. The compiler shall produce a warning if a switch expression is not exhaustive.
4014+
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.Runtime.CompilerServices.SwitchExpressionException`.
40154015

40164016
> *Example*: The following converts values of an enum representing visual directions on an online map to the corresponding cardinal directions:
40174017
>

standard/patterns.md

Lines changed: 122 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A pattern is tested against a value in a number of contexts:
1111
- In a switch expression, the *pattern* of a *switch_expression_arm* is tested against the expression on the switch-expression’s left-hand-side.
1212
- In nested contexts, the *sub-pattern* is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.
1313

14-
The value against which a pattern is tested is called the ***pattern input value***. Patterns may be combined using Boolean logic.
14+
The value against which a pattern is tested is called the ***pattern input value***.
1515

1616
## 11.2 Pattern forms
1717

@@ -21,7 +21,11 @@ A pattern may have one of the following forms:
2121

2222
```ANTLR
2323
pattern
24-
: '(' pattern ')'
24+
: logical_pattern
25+
;
26+
27+
primary_pattern
28+
: parenthesized_pattern
2529
| declaration_pattern
2630
| constant_pattern
2731
| var_pattern
@@ -30,10 +34,15 @@ pattern
3034
| discard_pattern
3135
| type_pattern
3236
| relational_pattern
33-
| logical_pattern
37+
;
38+
39+
parenthesized_pattern
40+
: '(' pattern ')'
3441
;
3542
```
3643

44+
The `'(' pattern ')'` production allows a pattern to be enclosed in parentheses to enforce the order of evaluation among patterns combined using one of the *logical_pattern*s.
45+
3746
Some *pattern*s can result in the declaration of a local variable.
3847

3948
Each pattern form defines the set of types for input values that the pattern may be applied to. A pattern `P` is *applicable to* a type `T` if `T` is among the types whose values the pattern may match. It is a compile-time error if a pattern `P` appears in a program to match a pattern input value ([§11.1](patterns.md#111-general)) of type `T` if `P` is not applicable to `T`.
@@ -421,6 +430,8 @@ type_pattern
421430
;
422431
```
423432
433+
A type pattern naming a type `T` is *applicable to* every type `E` for which `E` is *pattern compatible* with `T` (§11.2.2).
434+
424435
The runtime type of the value is tested against *type* using the same rules specified in the is-type operator ([§12.15.12.1](expressions.md#1215121-the-is-type-operator)). If the test succeeds, the pattern matches that value. It is a compile-time error if the *type* is a nullable type. This pattern form never matches a `null` value.
425436

426437
### 11.2.9 Relational pattern
@@ -429,18 +440,22 @@ A *relational_pattern* is used to relationally test the pattern input value ([§
429440

430441
```ANTLR
431442
relational_pattern
432-
: '<' constant_expression
433-
| '<=' constant_expression
434-
| '>' constant_expression
435-
| '>=' constant_expression
443+
: '<' relational_expression
444+
| '<=' relational_expression
445+
| '>' relational_expression
446+
| '>=' relational_expression
436447
;
437448
```
438449

450+
The *relational_expression* in a *relational_pattern* is required to evaluate to a constant value.
451+
439452
Relational patterns support the relational operators `<`, `<=`, `>`, and `>=` on all of the built-in types that support such binary relational operators with both operands having the same type: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `nint`, `nuint`, and enums.
440453

441-
It is a compile-time error if `constant_expression`is `double.NaN`, `float.NaN`, or `null_literal`.
454+
A *relational_pattern* is *applicable to* a type `T` if a suitable built-in binary relational operator is defined with both operands of type `T`, or if an explicit nullable or unboxing conversion exists from `T` to the type of the constant expression.
455+
456+
It is a compile-time error if the expression evaluates to `double.NaN`, `float.NaN`, or a null constant.
442457

443-
When the input value has a type for which a suitable built-in binary relational operator is defined, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise, the input value is converted to the type of `constant_expression` using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. The pattern is considered to not match if the conversion fails. If the conversion succeeds, the result of the pattern-matching operation is the result of evaluating the expression `e «op» v` where `e` is the converted input, «op» is the relational operator, and `v` is the `constant_expression`.
458+
When the input value has a type for which a suitable built-in binary relational operator is defined, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise, the input value is converted to the type of the constant expression using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. The pattern is considered to not match if the conversion fails. If the conversion succeeds, the result of the pattern-matching operation is the result of evaluating the expression `e «op» v` where `e` is the converted input, «op» is the relational operator, and `v` is the constant expression.
444459

445460
> *Example*:
446461
>
@@ -471,7 +486,7 @@ When the input value has a type for which a suitable built-in binary relational
471486
472487
### 11.2.10 Logical pattern
473488
474-
A *logical_pattern* is used to negate a pattern input value ([§11.1](patterns.md#111-general)) or to combine that value with a pattern using a Boolean operator.
489+
A *logical_pattern* is used to negate the result of a pattern match, or to combine the results of multiple pattern matches using conjunction (`and`) or disjunction (`or`).
475490
476491
```ANTLR
477492
logical_pattern
@@ -490,17 +505,33 @@ conjunctive_pattern
490505
491506
negated_pattern
492507
: 'not' negated_pattern
493-
| pattern
508+
| primary_pattern
494509
;
495510
```
496511
497512
`not`, `and`, and `or` are collectively called ***pattern operators***.
498513

499514
A *negated_pattern* matches if the pattern being negated does not match, and vice versa. A *conjunctive_pattern* requires both patterns to match. A *disjunctive_pattern* requires either pattern to match. Unlike their language operator counterparts, `&&` and `||`, `and` and `or` are *not* short-circuiting operators.
500515

516+
It is a compile-time error for a pattern variable to be declared beneath a `not` or `or` pattern operator.
517+
518+
> *Note*: Because neither `not` nor `or` can produce a definite assignment for a pattern variable, it is an error to declare one in those positions. *end note*
519+
520+
In a *conjunctive_pattern*, the *input type* of the second pattern is narrowed by the *type narrowing* requirements of first pattern of the `and`. The *narrowed type* of a pattern `P` is defined as follows:
521+
522+
- If `P` is a type pattern, the *narrowed type* is the type of the type pattern's type.
523+
- Otherwise, if `P` is a declaration pattern, the *narrowed type* is the type of the declaration pattern's type.
524+
- Otherwise, if `P` is a recursive pattern that gives an explicit type, the *narrowed type* is that type.
525+
- Otherwise, if `P` is matched via the rules for `ITuple` in a *positional_pattern* (§11.2.5), the *narrowed type* is the type `System.ITuple`.
526+
- Otherwise, if `P` is a constant pattern where the constant is not the null constant and where the expression has no *constant expression conversion* to the *input type*, the *narrowed type* is the type of the constant.
527+
- Otherwise, if `P` is a relational pattern where the constant expression has no *constant expression conversion* to the *input type*, the *narrowed type* is the type of the constant.
528+
- Otherwise, if `P` is an `or` pattern, the *narrowed type* is the common type of the *narrowed type* of the subpatterns if such a common type exists. For this purpose, the common type algorithm considers only identity, boxing, and implicit reference conversions, and it considers all subpatterns of a sequence of `or` patterns (ignoring parenthesized patterns).
529+
- Otherwise, if `P` is an `and` pattern, the *narrowed type* is the *narrowed type* of the right pattern. Moreover, the *narrowed type* of the left pattern is the *input type* of the right pattern.
530+
- Otherwise the *narrowed type* of `P` is `P`'s input type.
531+
501532
> *Note*: As indicated by the grammar, `not` has precedence over `and`, which has precedence over `or`. This can be explicitly indicated or overridden by using parentheses. *end note*
502533
503-
When a *pattern* is used with `is`, any pattern operators in that *pattern* have higher precedence than their logical operator counterparts. Otherwise, those pattern operators have lower precedence.
534+
When a *pattern* appears on the right-hand-side of `is`, the extent of the pattern is determined by the grammar; as a result, the pattern operators `and`, `or`, and `not` within the pattern bind more tightly than the logical operators `&&`, `||`, and `!` outside the pattern.
504535

505536
> *Example*:
506537
>
@@ -561,31 +592,100 @@ When a *pattern* is used with `is`, any pattern operators in that *pattern* have
561592
> ```
562593
>
563594
> *end example*
595+
<!-- markdownlint-disable MD028 -->
596+
597+
<!-- markdownlint-enable MD028 -->
598+
> *Example*:
599+
>
600+
> <!-- Example: {template:"standalone-console", name:"LogicalPattern3", inferOutput:true} -->
601+
> ```csharp
602+
> object msg = "msg";
603+
> object obj = 5;
604+
> bool flag = true;
605+
>
606+
> // This is parsed as: (msg is (not int) or string)
607+
> result = msg is not int or string;
608+
> Console.WriteLine($"msg (\"msg\"): msg is not int or string: {result}");
609+
>
610+
> // This is parsed as: (obj is (int or string)) && flag
611+
> bool result = obj is int or string && flag;
612+
> Console.WriteLine($"obj (5), flag (true): obj is int or string && flag: {result}");
613+
>
614+
> // This is parsed as: (obj is int) || ((obj is string) && flag)
615+
> result = obj is int || obj is string && flag;
616+
> Console.WriteLine($"obj (5), flag (true): obj is int || obj is string && flag: {result}");
617+
>
618+
> flag = false;
619+
> // This is parsed as: (obj is (int or string)) && flag
620+
> result = obj is int or string && flag;
621+
> Console.WriteLine($"obj (5), flag (false): obj is int or string && flag: {result}");
622+
>
623+
> // This is parsed as: (obj is int) || ((obj is string) && flag)
624+
> result = obj is int || obj is string && flag;
625+
> Console.WriteLine($"obj (5), flag (false): obj is int || obj is string && flag: {result}");
626+
> ```
627+
>
628+
> The output produced is
629+
>
630+
> ```console
631+
> msg ("msg"): msg is not int or string: True
632+
> obj (5), flag (true): obj is int or string && flag: True
633+
> obj (5), flag (true): obj is int || obj is string && flag: True
634+
> obj (5), flag (false): obj is int or string && flag: False
635+
> obj (5), flag (false): obj is int || obj is string && flag: True
636+
> ```
637+
>
638+
>
639+
> *end example*
564640
565641
## 11.3 Pattern subsumption
566642
567-
In a switch statement, it is an error if a cases pattern is *subsumed* by the preceding set of unguarded cases ([§13.8.3](statements.md#1383-the-switch-statement)).
568-
Informally, this means that any input value would have been matched by one of the previous cases.
569-
The following rules define when a set of patterns subsumes a given pattern:
643+
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. The following rules define when a set of patterns subsumes a given pattern:
570644
571-
A pattern `P` *would match* a constant `K` if the specification for that pattern’s runtime behavior is that `P` matches `K`.
645+
A pattern `P` *would match* a constant `K` if any of the following conditions hold:
646+
647+
- the specification for that pattern's runtime behavior is that `P` matches `K`.
648+
- `P` is a *type_pattern* for type `T` and `K` is not `null` and the runtime type of `K` is `T` or a type derived from `T` or a type that implements `T`.
649+
- `P` is a *relational_pattern* with operator «op» and constant `v`, and the expression `K` «op» `v` would evaluate to `true`.
650+
- `P` is a *negated_pattern* `not P₁` and `P₁` would not match `K`.
651+
- `P` is a *conjunctive_pattern* `P₁ and P₂` and both `P₁` would match `K` and `P₂` would match `K`.
652+
- `P` is a *disjunctive_pattern* `P₁ or P₂` and either `P₁` would match `K` or `P₂` would match `K`.
653+
- `P` is a *discard_pattern*.
572654
573655
A set of patterns `Q` *subsumes* a pattern `P` if any of the following conditions hold:
574656
575-
- `P` is a constant pattern and any of the patterns in the set `Q` would match `P`s *converted value*
657+
- `P` is a constant pattern and any of the patterns in the set `Q` would match `P`'s *converted value*
576658
- `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`.
577659
- `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)).
660+
- `P` is a *type_pattern* for type `T` and the set of patterns `Q` is *exhaustive* for the type `T`.
661+
- `P` is a *relational_pattern* with operator «op» and constant value `v`, and for every value of the input type satisfying the relation «op» `v`, some pattern in the set `Q` would match that value.
662+
- `P` is a *disjunctive_pattern* `P₁ or P₂` and the set of patterns `Q` subsumes `P₁` and `Q` subsumes `P₂`.
663+
- `P` is a *conjunctive_pattern* `P₁ and P₂` and at least one of the following holds: `Q` subsumes `P₁`, or `Q` subsumes `P₂`.
664+
- `P` is a *negated_pattern* `not P₁` and `Q` is *exhaustive* for the input type considering only the values not matched by `P₁`.
665+
- `P` is a *discard_pattern* and the set of patterns `Q` is *exhaustive* for the type of the pattern input value, and either the pattern input value is not of a nullable type or some pattern in `Q` would match `null`.
666+
- Some pattern in `Q` is a *disjunctive_pattern* `Q₁ or Q₂` and replacing that pattern with `Q₁` in `Q` would yield a set that subsumes `P`, or replacing it with `Q₂` would yield a set that subsumes `P`.
667+
- Some pattern in `Q` is a *negated_pattern* `not Q₁` and `P` would not match any value that `Q₁` would match.
578668
579669
## 11.4 Pattern exhaustiveness
580670
581-
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.
582-
The following rules define when a set of patterns is *exhaustive* for a type:
671+
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. The following rules define when a set of patterns is *exhaustive* for a type:
583672
584673
A set of patterns `Q` is *exhaustive* for a type `T` if any of the following conditions hold:
585674
586675
1. `T` is an integral or enum type, or a nullable version of one of those, and for every possible value of `T`’s non-nullable underlying type, some pattern in `Q` would match that value; or
587-
2. Some pattern in `Q` is a *var pattern*; or
588-
3. Some pattern in `Q` is a *declaration pattern* for type `D`, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from `T` to `D`.
676+
1. Some pattern in `Q` is a *var pattern*; or
677+
1. Some pattern in `Q` is a *declaration pattern* for type `D`, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from `T` to `D`; or
678+
1. Some pattern in `Q` is a *type_pattern* for type `D`, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from `T` to `D`; or
679+
1. Some pattern in `Q` is a *discard_pattern*; or
680+
1. The patterns in `Q` include a combination of *relational_pattern*s and *constant_pattern*s whose ranges collectively cover every possible value of `T`'s non-nullable underlying type. For `float` and `double` types, this includes `System.Double.NaN` or `System.Single.NaN` respectively, since `NaN` is not matched by any relational pattern; or
681+
1. Some pattern in `Q` is a *disjunctive_pattern* `P₁ or P₂`, and replacing that pattern with both `P₁` and `P₂` in `Q` yields a set that is *exhaustive* for `T`; or
682+
1. Some pattern in `Q` is a *negated_pattern* `not P₁`, and the patterns in `Q` together with the values not matched by `P₁` cover every possible value of `T`. A *negated_pattern* `not P₁` is exhaustive by itself when `P₁` matches no possible value of `T`; or
683+
1. Some pattern in `Q` is a *conjunctive_pattern* `P₁ and P₂`, and the set containing only `P₁` is *exhaustive* for `T` and the set containing only `P₂` is *exhaustive* for `T`.
684+
685+
> *Note*: When a type pattern includes nullable types, the pattern may be exhaustive for the type but still generate a warning because the type pattern won't match a `null` value. *end note*
686+
<!-- markdownlint-disable MD028 -->
687+
688+
> *Note*: For floating-point types, the combination of patterns `< 0` and `>= 0` is *not* exhaustive because neither relational pattern matches `NaN`. A correct exhaustive set would be `< 0`, `>= 0`, and `double.NaN` (or `float.NaN`). *end note*
589689
590690
> *Example*:
591691
>
@@ -605,3 +705,4 @@ A set of patterns `Q` is *exhaustive* for a type `T` if any of the following con
605705
> ```
606706
>
607707
> *end example*
708+
<!-- markdownlint-enable MD028 -->

0 commit comments

Comments
 (0)