Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions standard/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pattern
| type_pattern
| relational_pattern
| logical_pattern
| list_pattern
| slice_pattern
;
```

Expand Down Expand Up @@ -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*:
>
> <!-- Example: {template:"standalone-console", name:"ListPattern1", expectedOutput:["True", "False", "False", "True"]} -->
> ```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*:
>
> <!-- Example: {template:"standalone-console", name:"ListPattern2", expectedOutput:["The second element is 2."]} -->
> ```csharp
> List<int> 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.
>
> <!-- Example: {template:"standalone-console", name:"SlicePattern1", expectedOutput:[ "True", "True", "False", "False", "True", "False", "True", "True", "True", "False"]} -->
> ```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*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Example*: A subpattern can be nested within a slice pattern:
>
> <!-- Example: {template:"standalone-console", name:" SlicePattern2", expectedOutput:["Message aBBA matches; inner part is BB.", "Message apron doesn't match.", "not valid", "valid"]} -->
> ```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)).
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions standard/portability-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
Loading