Skip to content

Add support for pattern additions #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: draft-v9
Choose a base branch
from
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
6 changes: 3 additions & 3 deletions standard/lexical-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ The productions for *simple_name* ([§12.8.4](expressions.md#1284-simple-names))

If a sequence of tokens can be parsed (in context) as a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)), *member_access* ([§12.8.7](expressions.md#1287-member-access)), or *pointer_member_access* ([§23.6.3](unsafe-code.md#2363-pointer-member-access)) ending with a *type_argument_list* ([§8.4.2](types.md#842-type-arguments)), the token immediately following the closing `>` token is examined, to see 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; or
- 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.
Expand Down Expand Up @@ -590,10 +590,10 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has

```ANTLR
contextual_keyword
: 'add' | 'alias' | 'ascending' | 'async' | 'await'
: 'add' | 'alias' | 'and' | 'ascending' | 'async' | 'await'
| 'by' | 'descending' | 'dynamic' | 'equals' | 'from'
| 'get' | 'global' | 'group' | 'into' | 'join'
| 'let' | 'nameof' | 'on' | 'orderby' | 'partial'
| 'let' | 'nameof' | 'not' | 'on' | 'or' | 'orderby' | 'partial'
| 'remove' | 'select' | 'set' | 'unmanaged' | 'value'
| 'var' | 'when' | 'where' | 'yield'
;
Expand Down
159 changes: 157 additions & 2 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.12.12](expressions.md#121212-the-is-operator)) and in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)) to express the shape of data against which incoming data is to be compared. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)) and in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)) to express the shape of data against which incoming data is to be compared. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***. Patterns may be combined using Boolean logic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what "Patterns may be combined using Boolean logic" means. Yes, there are pattern forms for disjunction, conjunction, and negation, but the "Boolean logic" sentence suggests something more general.


## 11.2 Pattern forms

Expand All @@ -12,9 +12,13 @@ A pattern may have one of the following forms:

```ANTLR
pattern
: declaration_pattern
: '(' pattern ')'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is worth a sentence to say what this means.

| declaration_pattern
| constant_pattern
| var_pattern
| type_pattern
| relational_pattern
| logical_pattern
;
```

Expand Down Expand Up @@ -140,6 +144,157 @@ Given a pattern input value ([§11.1](patterns.md#111-general)) *e*, if *designa

It is an error if the name `var` would bind to a type where a *var_pattern* is used.

§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.12.12.1](expressions.md#1212121-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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly. It is not a syntactic constraint. It is a compile-time error if it evaluates to one of those values.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't negate the input value. It inverts (in the boolean sense) the result of the pattern-match. Similarly, the combination doesn't happen with the value, but with the result of pattern-match tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If is an operation of many names: not, invert & negate, and in C# it is indeed called logical negation (§12.9.4).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not concerned with which of those names we use. I'm pointing out that the meaning is not "to negate a pattern input value", but rather to negate the result of determining whether or not the input value matches the pattern.


```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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this means. An example would be helpful.


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