Skip to content
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

C# 7.x: Patterns #61

Closed
wants to merge 13 commits into from
Closed

C# 7.x: Patterns #61

wants to merge 13 commits into from

Conversation

RexJaeschke
Copy link
Contributor

No description provided.

@RexJaeschke RexJaeschke added this to the C# 7.x milestone Dec 8, 2020
@RexJaeschke RexJaeschke mentioned this pull request Dec 8, 2020
@RexJaeschke RexJaeschke marked this pull request as ready for review April 2, 2021 14:42
@gafter gafter self-requested a review June 30, 2021 20:55
@gafter gafter self-assigned this Jun 30, 2021
standard/expressions.md Outdated Show resolved Hide resolved
standard/expressions.md Outdated Show resolved Hide resolved
standard/expressions.md Outdated Show resolved Hide resolved

The runtime type of the pattern context expression is tested against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is` operator is `true`. A pattern context expression with value `null` never tests true for this pattern.

Given a pattern context expression (§patterns-new-clause) *e*, if the *simple_designation* is the identifier `_` (see §discards-new-clause) the value of *e* is not bound to anything. (Although a declared variable with the name `_` may be in scope at that point, that named variable is not seen in this context.) If *designation* is any other identifier, a local variable ([§10.2.8](variables.md#1028-local-variables)) of the given type named by the given identifier is introduced, and that local variable is definitely assigned ([§10.4](variables.md#104-definite-assignment)) with the value of the pattern context expression when the result of the pattern-matching operation is true.
Copy link
Member

Choose a reason for hiding this comment

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

This form of description will not generalize to nested patterns gracefully.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take another look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter I'm actually writing up the v8 pattern extensions diffs spec this week, and looking at them, I don't see that they complicate things in this regard. In my v7 text above, I have

simple_designation
    : single_variable_designation
    ;

which in v8 will become

simple_designation
    : single_variable_designation
    | discard_designation
    ;

with the rule single_variable_designation remaining unchanged as identifier. I don't see that nested patterns impact this part of the grammar.

standard/expressions.md Outdated Show resolved Hide resolved
standard/statements.md Outdated Show resolved Hide resolved
standard/statements.md Outdated Show resolved Hide resolved
standard/statements.md Outdated Show resolved Hide resolved
- The switch expression is a non-constant value.
- The switch expression is a constant value that matches a `case` label in the switch section.
- The switch expression is a constant value that doesn't match any `case` label, and the switch section contains the `default` label.
- The switch expression value matches a `case` label in the switch section.
Copy link
Member

Choose a reason for hiding this comment

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

This is wrong... no, it isn't even wrong. It is trying to define a compile-time property (statement list of a given switch section is reachable) based on a runtime property (the switch expression value matches a case label in the switch section).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

More work needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter I think I see my mistake. Here's the whole bullet list with the bits I deleted struck-through:

The statement list of a given switch section is reachable if the switch statement is reachable and at least one of the following is true:

  • The switch expression is a non-constant value.
  • The switch expression is a constant value that matches a case label in the switch section.
  • The switch expression is a constant value that doesn't match any case label, and the switch section contains the default label.
  • A switch label of the switch section is referenced by a reachable goto case or goto default statement.

If I restore the deletions of the 2nd and 3rd bullet items, we're only talking about a constant value matching a constant_pattern, and that can be done at compile-time, right?

I'm thinking I should also restore the 1st bullet item, no?

Copy link
Member

@gafter gafter Apr 6, 2022

Choose a reason for hiding this comment

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

I don't think that's right. I think you've defined "matches" as something that is determined at run-time, not at compile-time. But reachability is a compile-time concept.

Copy link
Member

Choose a reason for hiding this comment

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

But yes, for the purpose of the semantics of a switch statement, we do compute statically some things based on the value of the case constant patterns, and the constant value of the switch expression if it happens to be a constant. For example, if the switch expression is a constant 3, we know it doesn't match case 4 and that switch block's statements are considered unreachable.

There are two concepts of reachability in play here. One is about the reachability of the statements in a switch block. The other is about the reachability of a switch case; we use the term "subsumed" to say that a pattern cannot be matched because of previous patterns. And we can have any combination of the values of these two kinds of reachabilities. The value of a switch expression, if it happens to be a constant, is used to affect the meaning of statement reachability but not subsumption. For example, if you switch on the constant value 3, you are allowed to have case 4, but it doesn't lead to the reachability of the statements in that switch block.

A switch case that is subsumed by the totality of the previous switch cases (that do not have a where clause) results in an error. See here.
A statement (in a switch block or elsewhere) that is not reachable results in a warning.

The statements in a switch block that contains a default label may not be reachable if all of the values of the switch expression are handled in previous switch blocks. See this example.

A switch case is unreachable if its pattern is subsumed by the totality of switch labels (that do not have when clauses) appearing before it in the switch. For example, if you switch on an expression of type byte, and have a switch block to handle each possible value of a byte, then a following case var _: would be subsumed (all of the values of byte are already handles) and would result in an error. However, a default label is OK (but would not lead to statements in the switch block being reachable).

The concept of subsumption is completely missing from this revision. The draft for this feature says:

It is an error if a switch_label can have no effect at runtime because its pattern is subsumed by previous cases. [TODO: We should be more precise about the techniques the compiler is required to use to reach this judgment.]

I will require some work to introduce and describe this concept fully. Breaking patterns into their own chapter may make it a tiny bit easier.

@@ -1008,7 +1043,7 @@ The target of a `goto` *identifier* statement is the labeled statement with the
> ```
> a `goto` statement is used to transfer control out of a nested scope. *end note*

The target of a `goto case` statement is the statement list in the immediately enclosing `switch` statement ([§13.8.3](statements.md#1383-the-switch-statement)) which contains a`case` label with the given constant value. If the `goto case` statement is not enclosed by a `switch` statement, if the *constant_expression* is not implicitly convertible ([§11.2](conversions.md#112-implicit-conversions)) to the governing type of the nearest enclosing `switch` statement, or if the nearest enclosing `switch` statement does not contain a `case` label with the given constant value, a compile-time error occurs.
The target of a `goto case` statement is the statement list in the immediately enclosing `switch` statement ([§13.8.3](statements.md#1383-the-switch-statement)) which contains a `case` label with the given *constant_pattern*. If the `goto case` statement is not enclosed by a `switch` statement or if the nearest enclosing `switch` statement does not contain a `case` label with the given *constant_pattern*, a compile-time error occurs.
Copy link
Member

Choose a reason for hiding this comment

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

With the given constant_pattern? Do we have a concept of when two constant patterns are the same?

Copy link
Member

Choose a reason for hiding this comment

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

There is also a requirement that the target case label must not have a when clause.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter Re your Q, "Do we have a concept of when two constant patterns are the same?" In the new section "Constant patterns," we have the following: "Given a pattern input value e and a constant_expression c, if e and c have integral types, the pattern is considered matched if the result of the expression e == c is true. Otherwise, the pattern is considered matched if object.Equals(e, c) returns true. In this case it is a compile-time error if the static type of e is not pattern compatible (§declaration-pattern-new-clause) with the type of the constant." Does that address your concern?

In the context of a switch, goto case 0;, goto case 10 - 10;, and goto case 5 + 2 - 7; all match case 0:.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter Re your comment, "There is also a requirement that the target case label must not have a when clause.", I have confirmed that. Given the case label case 10 when flag == true:, goto case 10; results in CS0159 "No such label 'case 10' within the scope of the goto statement."

I propose adding the following:

It is a compile-time error for constant_pattern to target a switch_label containing a case_guard.

Copy link
Member

Choose a reason for hiding this comment

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

No, that doesn't quite do it. The text "The target of a goto case statement is the statement list in the immediately enclosing switch statement (§13.8.3) which contains a case label with the given constant_pattern." needs to be modified, because there may be more than one such target.

int i = 12;
switch (i)
{
    case 1 when "".Length == 0:
        break;
    case 1:
        break;
    case 2:
        goto case 1;
}

@RexJaeschke RexJaeschke added the Review: complete at least one person has reviewed this label Sep 16, 2021
@RexJaeschke RexJaeschke marked this pull request as draft November 29, 2021 11:08

A *declaration_pattern* and a *var_pattern* can result in the declaration of a local variable. The scope of such a variable is as follows:
- If the pattern is a case label ([§13.8.3](statements.md#1383-the-switch-statement)), then the scope of the variable is the associated *case block*.
- Otherwise, the variable is part of the pattern that is the right-hand operand of the `is` operator ([§12.11.11](expressions.md#121111-the-is-operator)), and the variable’s scope is based on the construct immediately enclosing the `is` expression containing the pattern, as follows:
Copy link
Member

Choose a reason for hiding this comment

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

This prose should be coordinated and possibly merged with similar prose for out var declarations.

;
```

The runtime type of the value is tested against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is` operator is `true`. A pattern input value with value `null` never tests true for this pattern.
Copy link
Member

Choose a reason for hiding this comment

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

Rather than saying "the result of the is operator is true" ... because the pattern might not be part of an is operator ... we should say that the pattern matches. And for the is-pattern operator, we should say that it returns true if and only if the pattern matches on the input.

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe suceeds

Copy link
Contributor

Choose a reason for hiding this comment

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

@gafter – Go with your first thought. I think “pattern matches” is the more widely used phrasing and we should stick with it. Though you might also see “if the pattern matching succeeds/is successful”… ;-)


The constant expression of each `case` label shall denote a value of a type that is implicitly convertible ([§11.2](conversions.md#112-implicit-conversions)) to the governing type of the `switch` statement. A compile-time error occurs if two or more case labels in the same switch statement specify the same constant value.
A compile-time error occurs if two or more case labels in the same switch statement specify the same pattern and case guard.
Copy link
Member

Choose a reason for hiding this comment

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

This is not correct. The constraint only applies to case labels that are unguarded.

int i = 12;
switch (i)
{
    case 1 when "".Length == 0:
        break;
    case 1 when "".Length == 0: // OK
        break;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@gafter Ouch. What is the reason for this being OK? (I can think of some, not sure any are “good” ATM)

Copy link
Member

@gafter gafter Dec 16, 2021

Choose a reason for hiding this comment

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

We don't have a concept of case guards being "the same". For that matter, we don't have a concept of patterns being "the same".

We don't evaluate what the case guard does or assume it is idempotent. For all we know it rolls dice.

What we do have is an error if a case is unreachable because it is subsumed by previous (unguarded) cases. That needs to be described.

Related to that, we require that a switch expression is exhaustive. But switch expressions come later.

- If one of the constants specified in a `case` label in the same `switch` statement is equal to the value of the switch expression, control is transferred to the statement list following the matched `case` label.
- If none of the constants specified in `case` labels in the same `switch` statement is equal to the value of the switch expression, and if a `default` label is present, control is transferred to the statement list following the `default` label.
- If none of the constants specified in `case` labels in the same `switch` statement is equal to the value of the switch expression, and if no `default` label is present, control is transferred to the end point of the `switch` statement.
- The switch expression is evaluated.
Copy link
Member

Choose a reason for hiding this comment

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

After the switch expression is evaluated, we determine if there was a governing type according to the previous language rules, and if so convert to that type.

- If none of the constants specified in `case` labels in the same `switch` statement is equal to the value of the switch expression, and if a `default` label is present, control is transferred to the statement list following the `default` label.
- If none of the constants specified in `case` labels in the same `switch` statement is equal to the value of the switch expression, and if no `default` label is present, control is transferred to the end point of the `switch` statement.
- The switch expression is evaluated.
- The lexically first pattern in the set of `case` labels in the same `switch` statement that matches the value of the switch expression, causes control to be transferred to the statement list following the matched `case` label.
Copy link
Member

Choose a reason for hiding this comment

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

...for which the guard expression, if one is present, evaluates to true...

> ```
> *end example*

A pattern variable declared in a *switch_label* is definitely assigned ([§10.4](variables.md#104-definite-assignment)) in its case block if and only if that case block contains precisely one *switch_label*.
Copy link
Member

Choose a reason for hiding this comment

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

Probably something needs to be added to the section on definite assignment.

@BillWagner BillWagner force-pushed the Rex-v7-patterns branch 2 times, most recently from b50399d to 40d1623 Compare April 2, 2022 16:14
@BillWagner BillWagner mentioned this pull request Apr 18, 2022
;
```

A *declaration_pattern* and a *var_pattern* can result in the declaration of a local variable. The scope of such a variable is as follows:
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be lifted out to where scopes are generally described, and shared with the out var and deconstruction specs.

Copy link
Member

Choose a reason for hiding this comment

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

I believe we've agreed that such a thing should happen. Therefore I'm not reviewing this part very deeply.

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

Lots more comments. This covers all the issues and subtleties I am aware of.


#### §patterns-new-clause-general General

A ***pattern*** is a syntactic form that can be used with the `is` operator ([§11.11.11](expressions.md#111111-the-is-operator)) and in a *switch_statement* ([§12.8.3](statements.md#1283-the-switch-statement)) to express the shape of data against which incoming data is to be compared. A pattern is tested in the context of a switch expression or a *relational_expression* that is on the left-hand side of an `is` operator. Let us call this a ***pattern input value***.
Copy link
Member

@gafter gafter May 11, 2022

Choose a reason for hiding this comment

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

Regarding "A pattern is tested in the context of a switch expression"... this is correct for this version of the specification, but as soon as we start specifying nested patterns it will have to be revised because the value being tested doesn't appear as an expression in the program.

;
```

A *declaration_pattern* and a *var_pattern* can result in the declaration of a local variable. The scope of such a variable is as follows:
Copy link
Member

Choose a reason for hiding this comment

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

I believe we've agreed that such a thing should happen. Therefore I'm not reviewing this part very deeply.

- Otherwise if the expression is in some other statement form, the variable's scope is the scope containing the statement.

For the purpose of determining the scope, an *embedded_statement* is considered to be in its own scope.

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 see the following reflected in the specification:

In C# 7.3 we added the following contexts in which a pattern variable may be declared:

  • If the expression is in a constructor initializer, its scope is the constructor initializer and the constructor's body.
  • If the expression is in a field initializer, its scope is the equals_value_clause in which it appears.
  • If the expression is in a query clause that is specified to be translated into the body of a lambda, its scope is just that expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gafter Lines 64-66 cover those 7.3 additions.

Copy link
Member

Choose a reason for hiding this comment

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

All three are incorrect. I believe what I wrote in the comment above is correct. But there is another PR for that stuff.

Neither this (nor the other PR) handles variables declared in a case_guard.


The runtime type of the value is tested against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is` operator is `true`. A pattern input value with value `null` never tests true for this pattern.

Given a pattern context expression (§patterns-new-clause) *e*, if the *simple_designation* is the identifier `_` (see §discards-new-clause) the value of *e* is not bound to anything. (Although a declared variable with the name `_` may be in scope at that point, that named variable is not seen in this context.) If *simple_designation* is any other identifier, a local variable ([§9.2.8](variables.md#928-local-variables)) of the given type named by the given identifier is introduced, and that local variable is definitely assigned ([§9.4](variables.md#94-definite-assignment)) with the value of the pattern context expression when the result of the pattern-matching operation is true.
Copy link
Member

Choose a reason for hiding this comment

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

This definite assignment rule "a local variable (§9.2.8) of the given type named by the given identifier is introduced, and that local variable is definitely assigned (§9.4) with the value of the pattern context expression when the result of the pattern-matching operation is true" only makes sense for the is-pattern operator, as that is the only pattern-matching operation that can possibly be "true". I suggest changing "when the result ... is true" to "where the pattern matches". The precise definite assignment rules must be specified in section 9.4 (Definite Assignment).


```ANTLR
constant_pattern
: constant_expression
Copy link
Member

Choose a reason for hiding this comment

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

This is not quite right. In an is-pattern-expression, the constant pattern is restricted to being a relational expression. So you can write e is a && b and the pattern is a, not the expression a && b. This needs to be spelled out, but I'm not quite sure how or where.

Copy link
Member

Choose a reason for hiding this comment

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

An example is a is b & c. The pattern is b.

@@ -4328,6 +4478,8 @@ where `x` is an expression of a nullable value type, if operator overload resolu

### 11.11.11 The is operator

When the form `is` *pattern* is used, it is a compile-time error if the corresponding *relational_expression* does not designate a value or does not have a type.
Copy link
Member

Choose a reason for hiding this comment

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

We need to say that the result is true iff the value matches the pattern.

```csharp
( ) ] : ; , . ? == !=
```
If a sequence of tokens can be parsed (in context) as a *simple_name* ([§11.7.4](expressions.md#1174-simple-names)), *member_access* ([§11.7.6](expressions.md#1176-member-access)), or *pointer_member_access* ([§22.6.3](unsafe-code.md#2263-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
Copy link
Member

Choose a reason for hiding this comment

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

This is correct and the edit to this text in #44 is not correct.

Copy link
Member

@BillWagner BillWagner Oct 2, 2022

Choose a reason for hiding this comment

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

From @RexJaeschke notes:

Note to TG2 members: for rationale on the following replacements, see "Changes to syntactic disambiguation" at https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/pattern-matching.md#proposed-change-to-the-disambiguation-rule.

@@ -101,10 +104,12 @@ then the *type_argument_list* is retained as part of the *simple_name*, *member_
> x = y is C<T> && z;
> ```
>
> the tokens `C<T>` are interpreted as a *namespace_or_type_name* with a *type_argument_list* due to being on the right-hand side of the `is` operator ([§11.11.1](expressions.md#11111-general)). Because `C<T>` parses as a *namespace_or_type_name*, not a *simple_name*, *member_access*, or *pointer_member_access*, the above rule does not apply, and it is considered to have a *type_argument_list* regardless of the token that follows.
> the tokens `C<T>` are interpreted as a *namespace_or_type_name* with a *type_argument_list* due to being on the right-hand side of the `is` operator ([§11.11.11](expressions.md#111111-the-is-operator)). Because `C<T>` parses as a *namespace_or_type_name*, not a *simple_name*, *member_access*, or *pointer_member_access*, the above rule does not apply, and it is considered to have a *type_argument_list* regardless of the token that follows.
Copy link
Member

Choose a reason for hiding this comment

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

This is no longer correct. The right-hand-side of an is operator can now be either an expression (constant pattern) or a type, so it is indeed ambiguous. See HERE for a demonstration of this.

@@ -101,10 +104,12 @@ then the *type_argument_list* is retained as part of the *simple_name*, *member_
> x = y is C<T> && z;
> ```
>
> the tokens `C<T>` are interpreted as a *namespace_or_type_name* with a *type_argument_list* due to being on the right-hand side of the `is` operator ([§11.11.1](expressions.md#11111-general)). Because `C<T>` parses as a *namespace_or_type_name*, not a *simple_name*, *member_access*, or *pointer_member_access*, the above rule does not apply, and it is considered to have a *type_argument_list* regardless of the token that follows.
> the tokens `C<T>` are interpreted as a *namespace_or_type_name* with a *type_argument_list* due to being on the right-hand side of the `is` operator ([§11.11.11](expressions.md#111111-the-is-operator)). Because `C<T>` parses as a *namespace_or_type_name*, not a *simple_name*, *member_access*, or *pointer_member_access*, the above rule does not apply, and it is considered to have a *type_argument_list* regardless of the token that follows.
>
> *end example*
Copy link
Member

@gafter gafter May 11, 2022

Choose a reason for hiding this comment

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

The following examples are worth adding, I think:

The expression (A < B, C > D) is a tuple with two elements, each a comparison.

The expression (A<B,C> D, E) is a tuple with two elements, the first of which is a declaration expression.

The invocation M(A < B, C > D, E) has three arguments.

The invocation M(out A<B,C> D, E) has two arguments, the first of which is an out declaration.

The expression e is A<B> C uses a declaration pattern.

The case label case A<B> C: uses a declaration pattern.

Copy link
Member

Choose a reason for hiding this comment

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

Spaces have no effect.

- The `switch` statement is reachable, the switch expression is a non-constant value, and no `default` label is present.
- The `switch` statement is reachable, the switch expression is a constant value that doesn’t match any `case` label, and no `default` label is present.

If a `case` label is unreachable, a compile-time occurs. (However, it is *not* a compile-time error for a `default` label to be unreachable).
Copy link
Member

Choose a reason for hiding this comment

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

We need rules to be given for a case label to be reachable or unreachable. We will need something saying that it is an error if a case pattern is subsumed by previous patterns. See HERE. The behavior of this must be specified. I'm afraid the margin here is too small to contain the specification.

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 also worth saying "The order in which patterns are matched is not defined. A compiler is permitted to match patterns out of order, and to reuse the results of already matched patterns to compute the result of matching of other patterns."

Although this fact may not be observable in C# 7 except by inspecting the generated code or measuring performance, it will have real semantic consequences in future versions. This is worth adding today so it doesn't get lost.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, matching in any order with non-idempotent patterns... here be dragons! Can we change Rosyln so the order of evaluation is randomised on compile? :-)

Copy link
Member

Choose a reason for hiding this comment

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

When we get to nested patterns, it will be recommended that properties tested in patterns be idempotent.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, there is a policy in the compiler team that we will avoid changing the order in which subpatterns are matched from one revision to another absent very good reasons.

@gafter gafter removed their assignment May 11, 2022
- One of `( ) ] } : ; , . ? == != | ^ && || & [`; or
- One of the relational operators `< > <= >= is as`; or
- A contextual query keyword appearing inside a query expression; or
- In certain contexts, we treat *identifier* 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

“we” don’t treat identifier as anything, the Standard defines. I expect there will be more of this kind of thing due to the likely origin of the material for these PRs.

@gafter gafter self-assigned this May 11, 2022

#### §declaration-pattern-new-clause Declaration pattern

A *declaration_pattern* is used to test that a value has a given type and if the test succeeds, to cast that value to that type.
Copy link
Member

Choose a reason for hiding this comment

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

We should introduce the terminology of a pattern possibly matching a value, and use it consistently.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe matching is trying, and it succeeds or fails?

@dotnet dotnet deleted a comment from Nigel-Ecma May 11, 2022
@jskeet
Copy link
Contributor

jskeet commented Jun 15, 2022

Not sure whether this is actually ready for broader review - I suspect there's more work required as part of Mads' work on variables before we look at this in detail.

@RexJaeschke RexJaeschke mentioned this pull request Jun 19, 2022
@jskeet jskeet added Review: work pending and removed Review: complete at least one person has reviewed this labels Jul 13, 2022
@@ -37,6 +37,155 @@ Most of the constructs that involve an expression ultimately require the express
- The value of a property access expression is obtained by invoking the *get_accessor* of the property. If the property has no *get_accessor*, a compile-time error occurs. Otherwise, a function member invocation ([§11.6.6](expressions.md#1166-function-member-invocation)) is performed, and the result of the invocation becomes the value of the property access expression.
- The value of an indexer access expression is obtained by invoking the *get_accessor* of the indexer. If the indexer has no *get_accessor*, a compile-time error occurs. Otherwise, a function member invocation ([§11.6.6](expressions.md#1166-function-member-invocation)) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression.

### §patterns-new-clause Patterns and pattern matching
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, this subclause (and its subordinate subclauses) is under §11.2 Expression classifications. Perhaps it belongs elsewhere, maybe as a new 11.x topic, somewhere before §11.7 Primary expressions.

We had left a note on an edit in the text. Move it to a comment on this PR

The runtime type of the value is tested against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is` operator is `true`. A pattern input value with value `null` never tests true for this pattern.

Given a pattern context expression (§patterns-new-clause) *e*, if the *simple_designation* is the *identifier* `_`, it denotes a discard (§discards-new-clause) the value of *e* is not bound to anything. (Although a declared variable with the name `_` may be in scope at that point, that named variable is not seen in this context.) If *simple_designation* is any other identifier, a local variable ([§9.2.8](variables.md#928-local-variables)) of the given type named by the given identifier is introduced, and that local variable is definitely assigned ([§9.4](variables.md#94-definite-assignment)) with the value of the pattern context expression when the result of the pattern-matching operation is true.
Copy link
Member

Choose a reason for hiding this comment

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

The reference to discards-new-clause here is causing one of the two warnings.

;
```

Given a pattern input value *e*, if *designation* is the *identifier* `_`, it denotes a discard (§discards-new-clause), and the value of *e* is not bound to anything. (Although a declared variable with that name may be in scope at that point, that named variable is not seen in this context.) If *designation* is any other identifier, at runtime the value of *e* is bound to a newly introduced local variable ([§9.2.8](variables.md#928-local-variables)) of that name whose type is the static type of *e*, and that local variable is definitely assigned ([§9.4](variables.md#94-definite-assignment)) with the value of the pattern input value.
Copy link
Member

Choose a reason for hiding this comment

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

The link to discards-new-clause is causing the second warning.

@BillWagner BillWagner marked this pull request as ready for review October 5, 2022 22:18
Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

I think we should move most of the discussion of patterns to a new chapter.


The runtime type of the value is tested against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is` operator is `true`. A pattern input value with value `null` never tests true for this pattern.

Given a pattern context expression (§patterns-new-clause) *e*, if the *simple_designation* is the *identifier* `_`, it denotes a discard (§discards-new-clause) the value of *e* is not bound to anything. (Although a declared variable with the name `_` may be in scope at that point, that named variable is not seen in this context.) If *simple_designation* is any other identifier, a local variable ([§9.2.8](variables.md#928-local-variables)) of the given type named by the given identifier is introduced, and that local variable is definitely assigned ([§9.4](variables.md#94-definite-assignment)) with the value of the pattern context expression when the result of the pattern-matching operation is true.
Copy link
Member

Choose a reason for hiding this comment

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

The phrase "pattern context expression" is no longer defined.

The problem is that there isn't a pattern context "expression" when there are nested patterns. There is a value, but not an expression. That was why we introduced the term "pattern input value". It isn't an expression. So I don't know what expression "e" corresponds to in this paragraph. It needs to be reworded to address that.

@@ -37,6 +37,155 @@ Most of the constructs that involve an expression ultimately require the express
- The value of a property access expression is obtained by invoking the *get_accessor* of the property. If the property has no *get_accessor*, a compile-time error occurs. Otherwise, a function member invocation ([§11.6.6](expressions.md#1166-function-member-invocation)) is performed, and the result of the invocation becomes the value of the property access expression.
- The value of an indexer access expression is obtained by invoking the *get_accessor* of the indexer. If the indexer has no *get_accessor*, a compile-time error occurs. Otherwise, a function member invocation ([§11.6.6](expressions.md#1166-function-member-invocation)) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression.

### §patterns-new-clause Patterns and pattern matching
Copy link
Member

Choose a reason for hiding this comment

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

This section doesn't belong under 11. Expressions at all. It should be a new heading at the same level as expressions. It should probably be moved to a new chapter after expressions.

- Otherwise if the expression is in some other statement form, the variable's scope is the scope containing the statement.

For the purpose of determining the scope, an *embedded_statement* is considered to be in its own scope.

Copy link
Member

Choose a reason for hiding this comment

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

All three are incorrect. I believe what I wrote in the comment above is correct. But there is another PR for that stuff.

Neither this (nor the other PR) handles variables declared in a case_guard.


#### §declaration-pattern-new-clause Declaration pattern

A *declaration_pattern* is used to test that a value has a given type and if the test succeeds, to cast that value to that type.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
A *declaration_pattern* is used to test that a value has a given type and if the test succeeds, to cast that value to that type.
A *declaration_pattern* is used to test that a *pattern input value* ([§??.??.??](#patterns-new-clause-general)) has a given type. The match succeeds if the test succeeds, in which case at runtime the value is cast to that type and assigned to the declared variable.

var_pattern
: 'var' designation
;
designation
Copy link
Member

Choose a reason for hiding this comment

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

designation should be introduced when needed for later (C# 8) pattern changes, rather than now, and should be used both in var_pattern and declaration_pattern in that future diff.


<!-- markdownlint-enable MD028 -->
> *Note*: The statement list of a switch section typically ends in a `break`, `goto case`, or `goto default` statement, but any construct that renders the end point of the statement list unreachable is permitted. For example, a `while` statement controlled by the Boolean expression `true` is known to never reach its end point. Likewise, a `throw` or `return` statement always transfers control elsewhere and never reaches its end point. Thus, the following example is valid:
A pattern variable declared in a *switch_label* is definitely assigned ([§9.4](variables.md#94-definite-assignment)) in its case block if and only if that case block contains precisely one *switch_label*.
Copy link
Member

Choose a reason for hiding this comment

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

I know I said that but... it isn't correct. Example 1. Example 2.

A correct formulation requires we define both reachability and subsumption for switch labels. Then, the rule is that the block must contain only one reachable switch label.

A default label is reachable if the set of switch cases are exhaustive. The set of switch cases are exhaustive if a hypothetical switch case case var _: added at the end would not be subsumed. Intuitively, that means that the set of switch cases might not handle all values.

The end point of a `switch` statement is reachable if at least one of the following is true:

- The `switch` statement contains a reachable `break` statement that exits the `switch` statement.
- The `switch` statement is reachable, the switch expression is a non-constant value, and no `default` label is present.
Copy link
Member

Choose a reason for hiding this comment

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

This list isn't correct for several reasons:

  • It only describes the behavior of constant labels. What about case int x:? case var _:?
  • It isn't even correct for constant values. If you switch on a value of type byte, and have cases to handle all values, a default: label does not lead to the reachability of its switch section.

@@ -1177,10 +1171,12 @@ The target of a `goto` *identifier* statement is the labeled statement with the
>
> *end note*

The target of a `goto case` statement is the statement list in the immediately enclosing `switch` statement ([§12.8.3](statements.md#1283-the-switch-statement)) which contains a`case` label with the given constant value. If the `goto case` statement is not enclosed by a `switch` statement, if the *constant_expression* is not implicitly convertible ([§10.2](conversions.md#102-implicit-conversions)) to the governing type of the nearest enclosing `switch` statement, or if the nearest enclosing `switch` statement does not contain a `case` label with the given constant value, a compile-time error occurs.
The target of a `goto case` statement is the statement list in the immediately enclosing `switch` statement ([§12.8.3](statements.md#1283-the-switch-statement)) which contains a `case` label with the given *constant_pattern*. If the `goto case` statement is not enclosed by a `switch` statement or if the nearest enclosing `switch` statement does not contain a `case` label with the given *constant_pattern*, a compile-time error occurs.
Copy link
Member

Choose a reason for hiding this comment

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

Need to add without a when clause twice to this paragraph.


The target of a `goto default` statement is the statement list in the immediately enclosing `switch` statement ([§12.8.3](statements.md#1283-the-switch-statement)), which contains a `default` label. If the `goto default` statement is not enclosed by a `switch` statement, or if the nearest enclosing `switch` statement does not contain a `default` label, a compile-time error occurs.

It is a compile-time error for *constant_pattern* to target a *switch_label* containing a *case_guard*.
Copy link
Member

Choose a reason for hiding this comment

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

This isn't right. We need to define the target of the goto case so that it never matches one with a case guard. See this example.

@@ -112,7 +112,7 @@ Within an instance constructor of a struct type, the `this` keyword behaves exac

### 9.2.8 Local variables

A ***local variable*** is declared by a *local_variable_declaration*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned.
Copy link
Member

Choose a reason for hiding this comment

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

We need to say (in this chapter where definite assignment is handled) that any pattern variables declared in the pattern part of an is-pattern expression are definitely assigned "when true" after the is-pattern expression.

A *switch_statement* consists of the keyword `switch`, followed by a parenthesized expression (called the ***switch expression***), followed by a *switch_block*. The *switch_block* consists of zero or more *switch_section*s, enclosed in braces. Each *switch_section* consists of one or more *switch_label*s followed by a *statement_list* ([§12.3.2](statements.md#1232-statement-lists)).

The ***governing type*** of a `switch` statement is established by the switch expression.
A *switch_statement* consists of the keyword `switch`, followed by a parenthesized expression (called the ***switch expression***), followed by a *switch_block*. The *switch_block* consists of zero or more *switch_section*s, enclosed in braces. Each *switch_section* consists of one or more *switch_label*s followed by a *statement_list* ([§12.3.2](statements.md#1232-statement-lists)). Each *switch_label* containing `case` has an associated pattern (§patterns-new-clause) against which the value of the switch expression is tested. If *case-guard* is present, its expression shall be of type `bool` and that expression is evaluated as an additional condition to be satisfied for the case to be considered satisfied.
Copy link
Member

Choose a reason for hiding this comment

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

We will have to rename switch expression to something else, because we will have a language construct by that name upcoming. Or we can do it then.

@Nigel-Ecma
Copy link
Contributor

Nigel-Ecma commented Mar 23, 2023 via email

@BillWagner
Copy link
Member

Closed in favor of #757

@BillWagner BillWagner closed this Apr 20, 2023
@RexJaeschke RexJaeschke deleted the Rex-v7-patterns branch June 9, 2023 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants