diff --git a/standard/patterns.md b/standard/patterns.md index 301adb22c..59fa639a7 100644 --- a/standard/patterns.md +++ b/standard/patterns.md @@ -31,6 +31,8 @@ pattern | type_pattern | relational_pattern | logical_pattern + | list_pattern + | slice_pattern ; ``` @@ -571,6 +573,134 @@ When a *pattern* is used with `is`, any pattern operators in that *pattern* have > > *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* ([§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: + +```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* ([§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): + +```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, 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)). @@ -585,6 +715,38 @@ 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)). +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 +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. diff --git a/standard/portability-issues.md b/standard/portability-issues.md index 4b44e9cd4..5d493b8ea 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-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)).