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
16 changes: 9 additions & 7 deletions standard/lexical-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ These productions occur in contexts where a value can occur in an expression, an

If a sequence of tokens can be parsed, in context, as one of the disambiguated productions including an optional *type_argument_list* ([§8.4.2](types.md#842-type-arguments)), then the token immediately following the closing `>` token shall be examined and if it is:

- one of `( ) ] } : ; , . ? == != | ^ && || & [`; or
- one of `( ) ] } : ; , . ? == != | ^ && || & [ =>`; or
- one of the relational operators `< <= >= is as`; or
- a contextual query keyword appearing inside a query expression.
- In certain contexts, *identifier* is treated as a disambiguating token. Those contexts are where the sequence of tokens being disambiguated is immediately preceded by one of the keywords `is`, `case` or `out`, or arises while parsing the first element of a tuple literal (in which case the tokens are preceded by `(` or `:` and the identifier is followed by a `,`) or a subsequent element of a tuple literal.

then the *type_argument_list* shall be retained as part of the disambiguated production and any other possible parse of the sequence of tokens discarded. Otherwise, the tokens parsed as a *type_argument_list* shall not be considered to be part of the disambiguated production, even if there is no other possible parse of those tokens.

Expand Down Expand Up @@ -606,12 +607,13 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has

```ANTLR
contextual_keyword
: 'add' | 'alias' | 'ascending' | 'async' | 'await'
| 'by' | 'descending' | 'dynamic' | 'equals' | 'from'
| 'get' | 'global' | 'group' | 'into' | 'join'
| 'let' | 'nameof' | 'notnull' | 'on' | 'orderby'
| 'partial' | 'remove' | 'select' | 'set' | 'unmanaged'
| 'value' | 'var' | 'when' | 'where' | 'yield'
: 'add' | 'alias' | 'and' | 'ascending' | 'async'
| 'await' | 'by' | 'descending' | 'dynamic' | 'equals'
| 'from' | 'get' | 'global' | 'group' | 'into'
| 'join' | 'let' | 'nameof' | 'not' | 'notnull'
| 'on' | 'or' | 'orderby' | 'partial' | 'remove'
| 'select' | 'set' | 'unmanaged' | 'value' | 'var'
| 'when' | 'where' | 'yield'
;
```

Expand Down
161 changes: 158 additions & 3 deletions standard/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 11.1 General

A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.14.12](expressions.md#121412-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* ([§12.11](expressions.md#1211-switch-expression)) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***.
A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.14.12](expressions.md#121412-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* ([§12.11](expressions.md#1211-switch-expression)) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***.

A pattern is tested against a value in a number of contexts:

Expand All @@ -11,7 +11,7 @@
- In a switch expression, the *pattern* of a *switch_expression_arm* is tested against the expression on the switch-expression’s left-hand-side.
- 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.

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

## 11.2 Pattern forms

Expand All @@ -21,12 +21,16 @@

```ANTLR
pattern
: declaration_pattern
: '(' pattern ')'
| declaration_pattern
| constant_pattern
| var_pattern
| positional_pattern
| property_pattern
| discard_pattern
| type_pattern
| relational_pattern
| logical_pattern
;
```

Expand Down Expand Up @@ -332,14 +336,14 @@
> <!-- Example: {template:"standalone-console", name:"PropertyPattern3", inferOutput:true} -->
> ```csharp
> Console.WriteLine(TakeFive("Hello, world!")); // output: Hello
> Console.WriteLine(TakeFive("Hi!")); // output: Hi!

Check warning on line 339 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L339

MDC032::Line length 91 > maximum 81
> Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345
> Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abc
>
> static string TakeFive(object input) => input switch
> {
> string { Length: >= 5 } s => s.Substring(0, 5),
> string s => s,

Check warning on line 346 in standard/patterns.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/patterns.md#L346

MDC032::Line length 87 > maximum 81
> ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
> ICollection<char> symbols => new string(symbols.ToArray()),
> null => throw new ArgumentNullException(nameof(input)),
Expand Down Expand Up @@ -406,6 +410,157 @@
> Here, a discard pattern is used to handle `null` and any integer value that doesn’t have the corresponding member of the `DayOfWeek` enumeration. That guarantees that the `switch` expression handles all possible input values.
> *end example*

### §type-pattern-new-clause Type pattern

A *type_pattern* is used to test that the pattern input value ([§11.1](patterns.md#111-general)) has a given type.

```ANTLR
type_pattern
: type
;
```

The runtime type of the value is tested against *type* using the same rules specified in the is-type operator ([§12.14.12.1](expressions.md#1214121-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.

### §relational-pattern-new-clause Relational pattern

A *relational_pattern* is used to relationally test the pattern input value ([§11.1](patterns.md#111-general)) against a constant value.

```ANTLR
relational_pattern
: '<' constant_expression
| '<=' constant_expression
| '>' constant_expression
| '>=' constant_expression
;
```

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.

It is a compile-time error if `constant_expression`is `double.NaN`, `float.NaN`, or `null_literal`.

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`.

> *Example*:
>
> <!-- Example: {template:"standalone-console", name:"RelationalPattern1", inferOutput:true} -->
> ```csharp
> Console.WriteLine(Classify(13));
> Console.WriteLine(Classify(double.NaN));
> Console.WriteLine(Classify(2.4));
>
> static string Classify(double measurement) => measurement switch
> {
> < -4.0 => "Too low",
> > 10.0 => "Too high",
> double.NaN => "Unknown",
> _ => "Acceptable",
> };
> ```
>
> The output produced is
>
> ```console
> Too high
> Unknown
> Acceptable
> ```
>
> *end example*

### §logical-pattern-new-clause Logical pattern

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.

```ANTLR
logical_pattern
: disjunctive_pattern
;

disjunctive_pattern
: disjunctive_pattern 'or' conjunctive_pattern
| conjunctive_pattern
;

conjunctive_pattern
: conjunctive_pattern 'and' negated_pattern
| negated_pattern
;

negated_pattern
: 'not' negated_pattern
| pattern
;
```

`not`, `and`, and `or` are collectively called ***pattern operators***.

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.

> *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*

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.

> *Example*:
>
> <!-- Example: {template:"standalone-console", name:"LogicalPattern1", inferOutput:true} -->
> ```csharp
> Console.WriteLine(Classify(13));
> Console.WriteLine(Classify(-100));
> Console.WriteLine(Classify(5.7));
>
> static string Classify(double measurement) => measurement switch
> {
> < -40.0 => "Too low",
> >= -40.0 and < 0 => "Low",
> >= 0 and < 10.0 => "Acceptable",
> >= 10.0 and < 20.0 => "High",
> >= 20.0 => "Too high",
> double.NaN => "Unknown",
> };
> ```
>
> The output produced is
>
> ```console
> High
> Too low
> Acceptable
> ```
>
> *end example*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Example*:
>
> <!-- Example: {template:"standalone-console", name:"LogicalPattern2", inferOutput:true} -->
> ```csharp
> Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));
> Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));
> Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));
>
> static string GetCalendarSeason(DateTime date) => date.Month switch
> {
> 3 or 4 or 5 => "spring",
> 6 or 7 or 8 => "summer",
> 9 or 10 or 11 => "autumn",
> 12 or 1 or 2 => "winter",
> _ => throw new ArgumentOutOfRangeException(nameof(date),
> $"Date with unexpected month: {date.Month}."),
> };
> ```
>
> The output produced is
>
> ```console
> winter
> autumn
> spring
> ```
>
> *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 Down
Loading