-
Notifications
You must be signed in to change notification settings - Fork 86
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
C# 7.x: Patterns #61
Conversation
standard/expressions.md
Outdated
|
||
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
- 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. |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More work needed.
There was a problem hiding this comment.
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 constantvaluethatmatches acase
label in the switch section.- The switch expression
is a constantvaluethatdoesn't match anycase
label, and the switch section contains thedefault
label.- A switch label of the switch section is referenced by a reachable
goto case
orgoto 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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
standard/statements.md
Outdated
@@ -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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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;
}
standard/expressions.md
Outdated
|
||
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: |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe suceeds
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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;
}
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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...
standard/statements.md
Outdated
> ``` | ||
> *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*. |
There was a problem hiding this comment.
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.
b50399d
to
40d1623
Compare
; | ||
``` | ||
|
||
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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***. |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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. | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
standard/expressions.md
Outdated
|
||
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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* |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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? :-)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
- 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. |
There was a problem hiding this comment.
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.
|
||
#### §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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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. |
@@ -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 |
There was a problem hiding this comment.
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.
Add the scope descriptions for the 3 new places V7.3 allows pattern -generated local variables
Co-authored-by: Neal Gafter <neal@gafter.com>
8d40c49
to
e3bbb85
Compare
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. | ||
|
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
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*. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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, adefault:
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. |
There was a problem hiding this comment.
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*. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
Most languages call it after the keyword they use, using “switch expression” or “case expression”, and some just plain old “expression” or avoid naming it at all (e.g. Dart). C uses “controlling expression”, Pascal goes with “selector expression”, and Ruby picks “target expression”.
Of all those I’d follow Pascal as the value of the expression is used to select the branch.
… On 24/03/2023, at 9:40 am, Neal Gafter ***@***.***> wrote:
@gafter commented on this pull request.
In standard/statements.md <#61 (comment)>:
> ```
-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.
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.
—
Reply to this email directly, view it on GitHub <#61 (review)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABSYVW25LMQTDQDEL6QLXDTW5SYKTANCNFSM4USRCLXQ>.
You are receiving this because you commented.
|
Closed in favor of #757 |
No description provided.