From 2da9bf1198e88bd512d935b3a7edffda9aacc768 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 14:00:41 -0500 Subject: [PATCH 1/7] support list and slice patterns --- standard/patterns.md | 161 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/standard/patterns.md b/standard/patterns.md index 6f3b9adad..b959652d4 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -34,6 +34,8 @@ primary_pattern | discard_pattern | type_pattern | relational_pattern + | list_pattern + | slice_pattern ; parenthesized_pattern @@ -688,6 +690,134 @@ When a *pattern* appears on the right-hand-side of `is`, the extent of the patte > > *end example* +### §list-pattern-new-clause List pattern + +A *list_pattern* matches a sequence of elements in a list or an array. + +```ANTLR +list_pattern + : list_pattern_clause simple_designation? + ; + +list_pattern_clause + : '[' (pattern (',' pattern)* ','?)? ']' + ; +``` + +A *list_pattern* is compatible with any type that is *countable* ([§14.7.1](classes.md#1471-general[rcj1.1])) as well as *indexable* ([§xxx](classes.md#xxx))[rcj2.1]—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See ([§xxx](classes.md#xxx-implicit-index-support))[rcj3.1] for details of implicit index support.) + +A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: + +```csharp +expr.Length is 3 +&& expr[new Index(0, fromEnd: false)] is 1 +&& expr[new Index(1, fromEnd: false)] is 2 +&& expr[new Index(2, fromEnd: false)] is 3 +``` + +> *Example*: +> +> +> ```csharp +> int[] numbers = { 1, 2, 3 }; +> +> Console.WriteLine(numbers is [1, 2, 3]); // True +> Console.WriteLine(numbers is [1, 2, 4]); // False +> Console.WriteLine(numbers is [1, 2, 3, 4]); // False +> Console.WriteLine(numbers is [0 or 1, <= 2, >= 3 and not 7]); // True +> ``` +> +> *end example* + +The discard pattern ([§11.2.7](patterns.md#1127-discard-pattern)) matches any single element. + +> *Example*: +> +> +> ```csharp +> List numbers = new() { 1, 2, 3 }; +> +> if (numbers is [_, var second, _]) +> { +> Console.WriteLine($"The second element is {second}."); +> } +> ``` +> +> *end example* + +### §slice-pattern-new-clause Slice pattern + +A *slice_pattern* discards zero or more elements. It shall only be used directly in a *list_pattern_clause*, and then only once at most in that clause. + +```ANTLR +slice_pattern + : '..' pattern? + ; +``` + +A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. A *slice_pattern* with a subpattern is compatible with any type that is *countable* ([§14.7.1](classes.md#1471-general[rcj4.1])) as well as *sliceable* ([§xxx](classes.md#xxx))[rcj5.1]—it has an accessible indexer that takes a `Range` as an argument, or an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. (See ([§xxx](classes.md#xxx-implicit-index-support))[rcj6.1] for details of implicit index support.) + +A *slice_pattern* acts like a proper discard; that is, no tests shall be made for such pattern. Rather, it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): + +```csharp +expr.Length is >= 2 +&& expr[new Index(0, fromEnd: false)] is 1 +&& expr[new Range(new Index(1, fromEnd: false), new Index(1, fromEnd: true))] is var s +&& expr[new Index(1, fromEnd: true)] is 3 +``` + +The input type for a *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string`s and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray`, respectively, shall be used. + +> *Example*: A slice pattern can be used to match elements only at the start or/and the end of an input sequence. +> +> +> ```csharp +> Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True +> Console.WriteLine(new[] { 1, 1 } is [_, _, ..]); // True +> Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // False +> Console.WriteLine(new[] { 1 } is [1, 2, ..]); // False +> +> Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True +> Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // False +> Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // True +> +> Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True +> Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // True +> Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False +> ``` +> +> *end example* + + + +> *Example*: A subpattern can be nested within a slice pattern: +> +> +> ```csharp +> MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB. +> MatchMessage("apron"); // output: Message apron doesn't match. +> +> void MatchMessage(string message) +> { +> var result = message is ['a' or 'A', .. var s, 'a' or 'A'] +> ? $"Message {message} matches; inner part is {s}." +> : $"Message {message} doesn't match."; +> Console.WriteLine(result); +> } +> +> Validate(new[] { -1, 0, 1 }); // output: not valid +> Validate(new[] { -1, 0, 0, 1 }); // output: valid +> +> void Validate(int[] numbers) +> { +> var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] +> ? "valid" : "not valid"; +> Console.WriteLine(result); +> } +> ``` +> +> *end example* + ## 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.12](expressions.md#1212-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. @@ -735,6 +865,37 @@ A set of patterns `Q` *subsumes* a pattern `P` if any of the following condition > > 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* +Subsumption checking of *list_pattern*s and *slice_pattern*s works just like positional patterns with `ITuple` ([xxx](patterns.md#xxx-positional-pattern))—[rcj7.1]corresponding subpatterns are matched by position plus an additional node for testing length. +For example, the following code produces an error because both patterns yield the same DAG: + +```csharp +case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1 +case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1 +``` + +Unlike: + +```csharp +case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1 +case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1 +``` + +Given a specific length, it's possible that two subpatterns refer to the same element, in which case, a test for this value is inserted into the decision DAG. + +- For instance, `[_, >0, ..] or [.., <=0, _]` becomes `length >= 2 && ([1] > 0 || length == 3 || [^2] <= 0)` where the length value of 3 implies the other test. +- Conversely, `[_, >0, ..] and [.., <=0, _]` becomes `length >= 2 && [1] > 0 && length != 3 && [^2] <= 0` where the length value of 3 disallows the other test. + +As a result, an error shall result for something like `case [.., p]: case [p]:` because at runtime, the same element is being matched in the second case. + +If a slice subpattern matches a list or a length value, subpatterns shall be treated as if they were a direct subpattern of the containing list. For instance, `[..[1, 2, 3]]` subsumes a pattern of the form `[1, 2, 3]`. + +The following assumptions are made on the members being used: + +- The property that makes the type *countable* is assumed to always return a non-negative value, if and only if the type is *indexable*. For instance, the pattern `{ Length: -1 }` can never match an array. +- The member that makes the type *sliceable* is assumed to be well-behaved; that is, the return value is never `null` and that it is a proper subslice of the containing list. + +The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold.[ + ## 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. The following rules define when a set of patterns is *exhaustive* for a type: From c22c6d737dd482d4523244dda9e316d1a3ad2fb1 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 16:51:33 -0500 Subject: [PATCH 2/7] support list and slice patterns --- standard/portability-issues.md | 1 + 1 file changed, 1 insertion(+) diff --git a/standard/portability-issues.md b/standard/portability-issues.md index c893f0cdf..cade802e7 100644 --- a/standard/portability-issues.md +++ b/standard/portability-issues.md @@ -10,6 +10,7 @@ This annex collects some information about portability that appears in this spec The behavior is undefined in the following circumstances: +1. If certain assumptions re pattern subsumption do not hold ([§11.3](patterns.md#113-pattern-assumptions)). 1. The behavior of the enclosing async function when an awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` does not cause the resumption delegate to be invoked at most once ([§12.9.9.4](expressions.md#12994-run-time-evaluation-of-await-expressions)). 1. Passing pointers as `ref` or `out` parameters ([§24.3.2](unsafe-code.md#2432-data-pointers)). 1. When dereferencing the result of converting one pointer type to another and the resulting pointer is not correctly aligned for the pointed-to type. ([§24.5.1](unsafe-code.md#2451-general)). From 8cfb5188d7940cfce0e4d7bee608c086f5b9b5ed Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 17:18:25 -0500 Subject: [PATCH 3/7] support list and slice patterns --- standard/patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index b959652d4..b58108b1c 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -704,7 +704,7 @@ list_pattern_clause ; ``` -A *list_pattern* is compatible with any type that is *countable* ([§14.7.1](classes.md#1471-general[rcj1.1])) as well as *indexable* ([§xxx](classes.md#xxx))[rcj2.1]—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See ([§xxx](classes.md#xxx-implicit-index-support))[rcj3.1] for details of implicit index support.) +A *list_pattern* is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *indexable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.) A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: @@ -755,7 +755,7 @@ slice_pattern ; ``` -A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. A *slice_pattern* with a subpattern is compatible with any type that is *countable* ([§14.7.1](classes.md#1471-general[rcj4.1])) as well as *sliceable* ([§xxx](classes.md#xxx))[rcj5.1]—it has an accessible indexer that takes a `Range` as an argument, or an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. (See ([§xxx](classes.md#xxx-implicit-index-support))[rcj6.1] for details of implicit index support.) +A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. A *slice_pattern* with a subpattern is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *sliceable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes a `Range` as an argument, or an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.) A *slice_pattern* acts like a proper discard; that is, no tests shall be made for such pattern. Rather, it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): From 5a754bdc077ae781a65eba3ea650e063ec520dc2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 17:21:06 -0500 Subject: [PATCH 4/7] support list and slice patterns --- standard/patterns.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/standard/patterns.md b/standard/patterns.md index b58108b1c..e1633804e 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -865,7 +865,8 @@ A set of patterns `Q` *subsumes* a pattern `P` if any of the following condition > > 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* -Subsumption checking of *list_pattern*s and *slice_pattern*s works just like positional patterns with `ITuple` ([xxx](patterns.md#xxx-positional-pattern))—[rcj7.1]corresponding subpatterns are matched by position plus an additional node for testing length. +Subsumption checking of *list_pattern*s and *slice_pattern*s works just like positional patterns with `ITuple` ([§11.2.5](patterns.md#1125-positional-pattern)) corresponding subpatterns are matched by position plus an additional node for testing length. + For example, the following code produces an error because both patterns yield the same DAG: ```csharp From 3d0cb43c58f6b98eafbc24a2ff59b5ed38799947 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 17:33:26 -0500 Subject: [PATCH 5/7] fix md --- standard/patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/patterns.md b/standard/patterns.md index e1633804e..9c18c3648 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -893,7 +893,7 @@ If a slice subpattern matches a list or a length value, subpatterns shall be tre The following assumptions are made on the members being used: - The property that makes the type *countable* is assumed to always return a non-negative value, if and only if the type is *indexable*. For instance, the pattern `{ Length: -1 }` can never match an array. -- The member that makes the type *sliceable* is assumed to be well-behaved; that is, the return value is never `null` and that it is a proper subslice of the containing list. +- The member that makes the type *sliceable* is assumed to be well-behaved; that is, the return value is never `null` and that it is a proper subslice of the containing list. The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold.[ From 86009a6dc3280cee6b539610dbd22094d466df14 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 2 Mar 2026 17:34:06 -0500 Subject: [PATCH 6/7] fix link --- standard/portability-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/portability-issues.md b/standard/portability-issues.md index cade802e7..f3df1e8ce 100644 --- a/standard/portability-issues.md +++ b/standard/portability-issues.md @@ -10,7 +10,7 @@ This annex collects some information about portability that appears in this spec The behavior is undefined in the following circumstances: -1. If certain assumptions re pattern subsumption do not hold ([§11.3](patterns.md#113-pattern-assumptions)). +1. If certain assumptions re pattern subsumption do not hold ([§11.3](patterns.md#113-pattern-subsumption)). 1. The behavior of the enclosing async function when an awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` does not cause the resumption delegate to be invoked at most once ([§12.9.9.4](expressions.md#12994-run-time-evaluation-of-await-expressions)). 1. Passing pointers as `ref` or `out` parameters ([§24.3.2](unsafe-code.md#2432-data-pointers)). 1. When dereferencing the result of converting one pointer type to another and the resulting pointer is not correctly aligned for the pointed-to type. ([§24.5.1](unsafe-code.md#2451-general)). From 00c500b4b0bb54e32e7260620d529b105bd7a7ec Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 4 May 2026 11:18:07 -0400 Subject: [PATCH 7/7] review and minor fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix four issues in patterns.md: Remove the stray [ at end of the subsumption "assumptions" block (line ~748: …doesn't hold.[). Remove the leading space in the SlicePattern2 example annotation: name:" SlicePattern2" → name:"SlicePattern2" (would otherwise break ExampleExtractor lookups). Collapse three double-spaces in the new sections: …1842-implicit-index-support) for details, A slice pattern can be used, expr is [1, .. var s, 3] is equivalent. Fix one issue in portability-issues.md: Reword "re pattern subsumption" → "regarding pattern subsumption" to match the rest of that file's prose. --- standard/patterns.md | 10 +++++----- standard/portability-issues.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/standard/patterns.md b/standard/patterns.md index 9c18c3648..625d16560 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -704,7 +704,7 @@ list_pattern_clause ; ``` -A *list_pattern* is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *indexable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.) +A *list_pattern* is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *indexable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes an `Index` as an argument, or an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.) A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: @@ -757,7 +757,7 @@ slice_pattern A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. A *slice_pattern* with a subpattern is compatible with any type that is *countable* ([§18.1](ranges.md#181-general)) as well as *sliceable* ([§18.1](ranges.md#181-general))—it has an accessible indexer that takes a `Range` as an argument, or an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. (See [§18.4.2](ranges.md#1842-implicit-index-support) for details of implicit index support.) -A *slice_pattern* acts like a proper discard; that is, no tests shall be made for such pattern. Rather, it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): +A *slice_pattern* acts like a proper discard; that is, no tests shall be made for such pattern. Rather, it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): ```csharp expr.Length is >= 2 @@ -768,7 +768,7 @@ expr.Length is >= 2 The input type for a *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string`s and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray`, respectively, shall be used. -> *Example*: A slice pattern can be used to match elements only at the start or/and the end of an input sequence. +> *Example*: A slice pattern can be used to match elements only at the start or/and the end of an input sequence. > > > ```csharp @@ -792,7 +792,7 @@ The input type for a *slice_pattern* is the return type of the underlying `this[ > *Example*: A subpattern can be nested within a slice pattern: > -> +> > ```csharp > MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB. > MatchMessage("apron"); // output: Message apron doesn't match. @@ -895,7 +895,7 @@ The following assumptions are made on the members being used: - The property that makes the type *countable* is assumed to always return a non-negative value, if and only if the type is *indexable*. For instance, the pattern `{ Length: -1 }` can never match an array. - The member that makes the type *sliceable* is assumed to be well-behaved; that is, the return value is never `null` and that it is a proper subslice of the containing list. -The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold.[ +The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold. ## 11.4 Pattern exhaustiveness diff --git a/standard/portability-issues.md b/standard/portability-issues.md index f3df1e8ce..9d2b8b31e 100644 --- a/standard/portability-issues.md +++ b/standard/portability-issues.md @@ -10,7 +10,7 @@ This annex collects some information about portability that appears in this spec The behavior is undefined in the following circumstances: -1. If certain assumptions re pattern subsumption do not hold ([§11.3](patterns.md#113-pattern-subsumption)). +1. If certain assumptions regarding pattern subsumption do not hold ([§11.3](patterns.md#113-pattern-subsumption)). 1. The behavior of the enclosing async function when an awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` does not cause the resumption delegate to be invoked at most once ([§12.9.9.4](expressions.md#12994-run-time-evaluation-of-await-expressions)). 1. Passing pointers as `ref` or `out` parameters ([§24.3.2](unsafe-code.md#2432-data-pointers)). 1. When dereferencing the result of converting one pointer type to another and the resulting pointer is not correctly aligned for the pointed-to type. ([§24.5.1](unsafe-code.md#2451-general)).