diff --git a/.github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.1.nupkg b/.github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.2.nupkg similarity index 89% rename from .github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.1.nupkg rename to .github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.2.nupkg index ba1461fe5..13deaf400 100644 Binary files a/.github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.1.nupkg and b/.github/workflows/dependencies/EcmaTC49.BuildGrammar.1.0.0-alpha.2.nupkg differ diff --git a/.github/workflows/grammar-validator.yaml b/.github/workflows/grammar-validator.yaml index e1223bf52..243677862 100644 --- a/.github/workflows/grammar-validator.yaml +++ b/.github/workflows/grammar-validator.yaml @@ -34,7 +34,7 @@ jobs: # Install build grammar global tool - name: Install BuildGrammar tool run: | - dotnet tool install --version 1.0.0-alpha.1 --global --add-source ./.github/workflows/dependencies/ EcmaTC49.BuildGrammar + dotnet tool install --version 1.0.0-alpha.2 --global --add-source ./.github/workflows/dependencies/ EcmaTC49.BuildGrammar - name: run validate diff --git a/standard/clauses.json b/standard/clauses.json index 77b3aede2..da2b93ba6 100644 --- a/standard/clauses.json +++ b/standard/clauses.json @@ -14,6 +14,7 @@ "types.md", "variables.md", "conversions.md", + "patterns.md", "expressions.md", "statements.md", "namespaces.md", diff --git a/standard/expressions.md b/standard/expressions.md index bc4b92b50..ef75f1c6d 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3783,6 +3783,7 @@ relational_expression | relational_expression '<=' shift_expression | relational_expression '>=' shift_expression | relational_expression 'is' type + | relational_expression 'is' pattern | relational_expression 'as' type ; @@ -4152,7 +4153,11 @@ The tuple equality operator `x != y` is evaluated as follows: ### 11.12.12 The is operator -The `is` operator is used to check if the run-time type of an object is compatible with a given type. The check is performed at runtime. The result of the operation `E is T`, where `E` is an expression and `T` is a type other than `dynamic`, is a Boolean value indicating whether `E` is non-null and can successfully be converted to type `T` by a reference conversion, a boxing conversion, an unboxing conversion, a wrapping conversion, or an unwrapping conversion. +There are two forms of the `is` operator. One is the *is-type operator*, which has a type on the right-hand-side. The other is the *is-pattern operator*, which has a pattern on the right-hand-side. + +#### The is-type operator + +The *is-type operator* is used to check if the run-time type of an object is compatible with a given type. The check is performed at runtime. The result of the operation `E is T`, where `E` is an expression and `T` is a type other than `dynamic`, is a Boolean value indicating whether `E` is non-null and can successfully be converted to type `T` by a reference conversion, a boxing conversion, an unboxing conversion, a wrapping conversion, or an unwrapping conversion. The operation is evaluated as follows: @@ -4190,6 +4195,15 @@ User defined conversions are not considered by the `is` operator. > > *end note* +#### The is-pattern operator + +The *is-pattern operator* is used to check if the value computed by an expression *matches* a given pattern (XREF TO DEF OF "PATTERN MATCHES"). The check is performed at runtime. The result of the is-pattern operator is true if the value matches the pattern; otherwise it is false. + +For an expression of the form `E is P`, where `E` is a relational expression of type `T` and `P` is a pattern, it is a compile-time error if any of the following hold: + +- `E` does not designate a value or does not have a type. +- The pattern `P` is not applicable (XREF NEEDED) to the type `T`. + ### 11.12.13 The as operator The `as` operator is used to explicitly convert a value to a given reference type or nullable value type. Unlike a cast expression ([§11.9.7](expressions.md#1197-cast-expressions)), the `as` operator never throws an exception. Instead, if the indicated conversion is not possible, the resulting value is `null`. @@ -6322,6 +6336,7 @@ Constant expressions are required in the contexts listed below and this is indic - `goto case` statements ([§12.10.4](statements.md#12104-the-goto-statement)) - Dimension lengths in an array creation expression ([§11.8.16.5](expressions.md#118165-array-creation-expressions)) that includes an initializer. - Attributes ([§21](attributes.md#21-attributes)) +- In a *constant_pattern* (§constant-pattern-new-clause) An implicit constant expression conversion ([§10.2.11](conversions.md#10211-implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the constant expression is within the range of the destination type. diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 7a49ff17e..52b0194b0 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -65,13 +65,14 @@ The productions for *simple_name* ([§11.8.4](expressions.md#1184-simple-names)) > > *end example* -If a sequence of tokens can be parsed (in context) as a *simple_name* ([§11.8.4](expressions.md#1184-simple-names)), *member_access* ([§11.8.7](expressions.md#1187-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. If it is one of +If a sequence of tokens can be parsed (in context) as a *simple_name* ([§11.8.4](expressions.md#1184-simple-names)), *member_access* ([§11.8.7](expressions.md#1187-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 -```csharp -( ) ] : ; , . ? == != -``` +- 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. -then the *type_argument_list* is retained as part of the *simple_name*, *member_access*, or *pointer_member_access* and any other possible parse of the sequence of tokens is discarded. Otherwise, the *type_argument_list* is not considered part of the *simple_name*, *member_access*, or *pointer_member_access*, even if there is no other possible parse of the sequence of tokens. +If the following token is among this list, or an identifier in such a context, then the *type_argument_list* is retained as part of the *simple_name*, *member_access* or *pointer_member-access* and any other possible parse of the sequence of tokens is discarded. Otherwise, the *type_argument_list* is not considered to be part of the *simple_name*, *member_access* or *pointer_member_access*, even if there is no other possible parse of the sequence of tokens. (These rules are not applied when parsing a *type_argument_list* in a *namespace_or_type_name* [§7.8](basic-concepts.md#78-namespace-and-type-names).) > *Note*: These rules are not applied when parsing a *type_argument_list* in a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* @@ -106,10 +107,25 @@ then the *type_argument_list* is retained as part of the *simple_name*, *member_ > x = y is C && z; > ``` > -> the tokens `C` 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.12.1](expressions.md#11121-general)). Because `C` 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` are interpreted as a *namespace_or_type_name* with a *type_argument_list* due to the presence of +> the disambiguating token `&&` after the *type_argument_list*. +> +> The expression `(A < B, C > D)` is a tuple with two elements, each a comparison. +> +> The expression `(A 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 D, E)` has two arguments, the first of which is an `out` declaration. +> +> The expression `e is A C` uses a declaration pattern. +> +> The case label `case A C:` uses a declaration pattern. > > *end example* +A *relational_expression* ([§11.12.1](expressions.md#11121-general)) can have the form "*relational_expression* `is` *type*" or "*relational_expression* `is` *constant_pattern*," either of which might be a valid parse of a qualified identifier. In this case, an attempt is made to bind it as a type (XREF TO 7.8.1 NAMESPACES AND TYPES); however, if that fails, it is bound as an expression, and the result must be a constant. + ## 6.3 Lexical analysis ### 6.3.1 General diff --git a/standard/patterns.md b/standard/patterns.md new file mode 100644 index 000000000..055326ca7 --- /dev/null +++ b/standard/patterns.md @@ -0,0 +1,175 @@ +# §patterns-new-clause Patterns and pattern matching + +## §patterns-new-clause-general General + +A ***pattern*** is a syntactic form that can be used with the `is` operator ([§11.12.12](expressions.md#111212-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 against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator. We call this a ***pattern input value***. + +## §patterns-new-clause-forms Pattern Forms + +A pattern may have one of the following forms: + +```ANTLR +pattern + : declaration_pattern + | constant_pattern + | var_pattern + ; +``` + +A *declaration_pattern* and a *var_pattern* can result in the declaration of a local variable. + +Each pattern form defines the set of types for input values that the pattern may be applied to. We say a pattern `P` is *applicable to* a type `T` if `T` is among the types whose values the pattern may match. It is an error if a pattern `P` appears in a program to match a *pattern input value* of type `T` if `P` is not applicable to `T`. + +Each pattern form defines the set of values for which the pattern *matches* the value. + +### §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, provide the value in a variable of that type. + +```ANTLR +declaration_pattern + : type simple_designation + ; +simple_designation + : single_variable_designation + ; +single_variable_designation + : identifier + ; +``` + +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 pattern *matches* that value. This pattern form never matches a `null` value. + +Given a *pattern input value* *e*, if the *simple_designation* is the *identifier* `_`, it denotes a discard (§9.2.8.1) 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. That local variable is assigned the value of the *pattern input value* when the pattern *matches* the value. + +Certain combinations of static type of the pattern input value and the given type are considered incompatible and result in a compile-time error. A value of static type `E` is said to be ***pattern compatible*** with the type `T` if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from `E` to `T`, or if either `E` or `T` is an open type ([§8.4.3](types.md#843-open-and-closed-types)). A declaration pattern naming a type `T` is *applicable to* every type `E` for which `E` is *pattern compatible* with `T`. + +> *Note*: The support for open types can be most useful when checking types that may be either struct or class types, and boxing is to be avoided. *end note* + + +> *Example*: The declaration pattern is useful for performing run-time type tests of reference types, and replaces the idiom +> +> ```csharp +> var v = expr as Type; +> if (v != null) { /* code using v */ } +> ``` +> +> with the slightly more concise +> +> ```csharp +> if (expr is Type v) { /* code using v */ } +> ``` +> +> *end example* + +It is an error if *type* is a nullable value type. + +> *Example*: The declaration pattern can be used to test values of nullable types: a value of type `Nullable` (or a boxed `T`) matches a type pattern `T2 id` if the value is non-null and `T2` is `T`, or some base type or interface of `T`. For example, in the code fragment +> +> ```csharp +> int? x = 3; +> if (x is int v) { /* code using v */ } +> ``` +> +> The condition of the `if` statement is `true` at runtime and the variable `v` holds the value `3` of type `int` inside the block. *end example* + +### §constant-pattern-new-clause Constant pattern + +A *constant_pattern* is used to test the value of a pattern input value (§patterns-new-clause) against the given constant value. + +```ANTLR +constant_pattern + : constant_expression + ; +``` + +A constant pattern `P` is *applicable to* a type `T` if there is an implicit conversion from the constant expression of `P` to the type `T`. + +For a constant pattern `P`, we say its *converted value* is + +- if the input expression's type is an integral type or an enum type, the pattern's constant value converted to that type; otherwise +- if the input expression's type is the nullable version of an integral type or an enum type, the pattern's constant value converted to its underlying type; otherwise +- the value of the pattern's constant value. + +Given a *pattern input value* *e* and a constant pattern `P` with converted value *v*, + +- if *e* has integral type or enum type, or a nullable form of one of those, and *v* has integral type, the pattern `P` *matches* the value *e* if result of the expression `e == v` is `true`; otherwise +- the pattern `P` *matches* the value *e* if `object.Equals(e, v)` returns `true`. + +> *Example*: +> +> ```csharp +> public static decimal GetGroupTicketPrice(int visitorCount) +> { +> switch (visitorCount) { +> case 1: return 12.0m; +> case 2: return 20.0m; +> case 3: return 27.0m; +> case 4: return 32.0m; +> case 0: return 0.0m; +> default: throw new ArgumentException(…); +> } +> } +> ``` +> +> *end example* + +### §var-pattern-new-clause Var pattern + +A *var_pattern* matches every value. That is, a pattern-matching operation with a *var_pattern* always succeeds. + +A *var_pattern* is *applicable to* every type. + +```ANTLR +var_pattern + : 'var' designation + ; +designation + : simple_designation + ; +``` + +Given a *pattern input value* *e*, if *designation* is the *identifier* `_`, it denotes a discard (§9.2.8.1), 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 the pattern input value is assigned to that local variable. + +It is an error if the name `var` would bind to a type where a *var_pattern* is used. + +## Pattern Subsumption + +In a switch statement, it is an error if a case's pattern is *subsumed* by the preceding set of unguarded cases (XREF). +Informally, this means that any input value would have been matched by one of the previous cases. +Here we define when a set of patterns *subsumes* a given pattern. + +We say a pattern `P` *would match* a constant `K` if the specification for that pattern's runtime behavior is that `P` matches `K`. + +A set of patterns `Q` *subsumes* a pattern `P` if any of the following conditions hold: + +- `P` is a constant pattern and any of the patterns in the set `Q` would match `P`'s *converted value* +- `P` is a var pattern and the set of patterns `Q` is *exhaustive* for the type of the pattern input value, and either the pattern input value is not of a nullable type or some pattern in `Q` would match `null`. +- `P` is a declaration pattern with type `T` and the set of patterns `Q` is *exhaustive* for the type `T` (XREF). + +## Pattern Exhaustiveness + +Informally, we say that a set of patterns is exhaustive for a type if some pattern in the set is applicable to every possible value of that type other than null. +Here we define when a set of patterns is *exhaustive* for a type. + +A set of patterns `Q` is *exhaustive* for a type `T` if any of the following conditions hold: + +1. `T` is an integral or enum type, or a nullable version of one of those, and for every possible value of `T`'s underlying type, some pattern in `Q` would match that value; or +2. Some pattern in `Q` is a *var pattern*; or +3. Some pattern in `Q` is a *declaration pattern* for type `D`, and there is an identity conversion, an implicit reference conversion, or a boxing conversion from `T` to `D`. + +> *Example*: +> +> ```csharp +> static void M(byte b) +> { +> switch (b) { +> case 0: case 1: case 2: case 3: ... // handle every specific value of byte +> break; +> case byte other: // error: the pattern 'byte other' is subsumed by previous cases because the previous cases are exhaustive for byte +> break; +> } +> } +> ``` +> +> *end example* diff --git a/standard/statements.md b/standard/statements.md index 409647c26..81cff78f3 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -649,29 +649,54 @@ switch_section ; switch_label - : 'case' constant_expression ':' + : 'case' pattern case_guard? ':' | 'default' ':' ; + +case_guard + : 'when' 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)). +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 (XREF NEEDED). If *case-guard* is present, its expression shall be implicitly convertible to the type `bool` and that expression is evaluated as an additional condition for the case to be considered satisfied. The ***governing type*** of a `switch` statement is established by the switch expression. - If the type of the switch expression is `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `bool`, `string`, or an *enum_type*, or if it is the nullable value type corresponding to one of these types, then that is the governing type of the `switch` statement. -- Otherwise, exactly one user-defined implicit conversion shall exist from the type of the switch expression to one of the following possible governing types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `string`, or, a nullable value type corresponding to one of those types. -- Otherwise, a compile-time error occurs. - -The constant expression of each `case` label shall denote a value of a type that is implicitly convertible ([§10.2](conversions.md#102-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. +- Otherwise, if exactly one user-defined implicit conversion exists from the type of the switch expression to one of the following possible governing types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `string`, or, a nullable value type corresponding to one of those types, then the converted type is the governing type of the `switch` statement. +- Otherwise, the governing type of the `switch` statement is the type of the switch expression. It is an error if no such type exists. There can be at most one `default` label in a `switch` statement. +It is an error if the pattern of any switch label is not *applicable* (NEED AN XREF) to the type of the input expression. + +It is an error if the pattern of any switch label is *subsumed* by (NEED AN XREF) the set of patterns of earlier switch labels of the switch statement that do not have a case guard or whose case guard is a constant expression with the value true. + +> *Example*: +> +> ```csharp +> switch (shape) +> { +> case var x: +> break; +> case var _: // error: pattern subsumed, as previous case always matches +> break; +> default: +> break; // warning: unreachable, +> } +> ``` +> +> *end example* + A `switch` statement is executed as follows: - The switch expression is evaluated and converted to the governing type. -- 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. +- Control is transferred according to the value of the converted switch expression: + - The lexically first pattern in the set of `case` labels in the same `switch` statement that matches the value of the switch expression, and for which the guard expression is either absent or evaluates to true, causes control to be transferred to the statement list following the matched `case` label. + - Otherwise, if a `default` label is present, control is transferred to the statement list following the `default` label. + - Otherwise, control is transferred to the end point of the `switch` statement. + +> *Note*: The order in which patterns are matched at runtime is not defined. A compiler is permitted (but not required) to match patterns out of order, and to reuse the results of already matched patterns to compute the result of matching of other patterns. Nevertheless, the compiler is required to determine the lexically first pattern that matches the expression and for which the guard clause is either absent or evaluates to `true`. *end note* If the end point of the statement list of a switch section is reachable, a compile-time error occurs. This is known as the “no fall through” rule. @@ -839,18 +864,47 @@ When the governing type of a `switch` statement is `string` or a nullable value The *statement_list*s of a *switch_block* may contain declaration statements ([§12.6](statements.md#126-declaration-statements)). The scope of a local variable or constant declared in a switch block is the switch block. -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: +A switch label is reachable if at least one of the following is true: + +- The switch expression is a constant value and either + - the label is a `case` whose pattern *would match* (XREF to "would match" in patterns.md) that value, and label's guard is either absent or not a constant expression with the value false; or + - it is a `default` label, and no switch section contains a case label whose pattern would match that value, and whose guard is either absent or a constant expression with the value true. +- The switch expression is not a constant value and either + - the label is a `case` without a guard or with a guard whose value is not the constant false; or + - it is a `default` label and + - the set of patterns appearing among the cases of the switch statement that do not have guards or have guards whose value is the constant true, is not *exhaustive* (NEED XREF) for the switch controlling type; or + - the switch controlling type is a nullable type and the set of patterns appearing among the cases of the switch statement that do not have guards or have guards whose value is the constant true does not contain a pattern that would match the value `null`. +- The switch label is referenced by a reachable `goto case` or `goto default` statement. -- 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. +The statement list of a given switch section is reachable if the `switch` statement is reachable and the switch section contains a reachable switch label. -The end point of a `switch` statement is reachable if at least one of the following is true: +The end point of a `switch` statement is reachable if the switch statement is reachable and 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. -- 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. +- No `default` label is present and either + - The switch expression is a non-constant value, and the set of patterns appearing among the cases of the switch statement that do not have guards or have guards whose value is the constant true, is not *exhaustive* (NEED XREF) for the switch governing type. + - The switch expression is a non-constant value of a nullable type, and no pattern appearing among the cases of the switch statement that do not have guards or have guards whose value is the constant true would match the value `null` (XREF to "would match" in patterns). + - The switch expression is a constant value and no `case` label without a guard or whose guard is the constant true would match that value. + +> *Example*: The following code shows a succinct use of the when clause: +> +> ```csharp +> static object CreateShape(string shapeDescription) +> { +> switch (shapeDescription) +> { +> case "circle": +> return new Circle(2); +> … +> case var o when (o?.Trim().Length ?? 0) == 0: +> return null; +> default: +> return "invalid shape description"; +> } +> } +> ``` +> +> The var case matches `null`, the empty string, or any string that contains only white space. *end example* ## 12.9 Iteration statements @@ -1317,7 +1371,7 @@ 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 a constant pattern of the given constant value and no guard. If the `goto case` statement is not enclosed by a `switch` statement, if the nearest enclosing `switch` statement does not contain such a `case`, or 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, a compile-time error occurs. 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. diff --git a/standard/variables.md b/standard/variables.md index ccd37a67c..0ac89b9ff 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -113,7 +113,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*, *declaration_expression*, *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. +A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. A local variable can also be declared by certain kinds of *pattern*s (§patterns-new-clause). 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. A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and as a *tuple_element* that is the target of a deconstructing assignment ([§11.21.2](expressions.md#11212-simple-assignment)). @@ -314,8 +314,51 @@ if ( «expr» ) «then_stmt» else «else_stmt» For a `switch` statement *stmt* with a controlling expression *expr*: -- The definite-assignment state of *v* at the beginning of *expr* is the same as the state of *v* at the beginning of *stmt*. -- The definite-assignment state of *v* on the control flow transfer to a reachable switch block statement list is the same as the definite-assignment state of *v* at the end of *expr*. +The definite-assignment state of *v* at the beginning of *expr* is the same as the state of *v* at the beginning of *stmt*. + +The definite-assignment state of *v* at the beginning of a case's guard clause is + +- If *v* is a pattern variable declared in the *switch_label*: "definitely assigned". +- If the switch label containing that guard clause is not reachable (XREF): "definitely assigned". +- Otherwise, the state of *v* is the same as the state of *v* after *expr*. + +The definite-assignment state of *v* on the control flow transfer to a reachable switch block statement list is + +- If the control transfer was due to a 'goto case' or 'goto default' statement, then the state of *v* is the same as the state at the beginning of that 'goto' statement. +- If the control transfer was due to the `default` label of the switch, then the state of *v* is the same as the state of *v* after *expr*. +- If the control transfer was due to an unreachable switch label, then the state of *v* is "definitely assigned". +- If the control transfer was due to a reachable switch label with a guard clause, then the state of *v* is the same as the state of *v* after the guard clause. +- If the control transfer was due to a reachable switch label without a guard clause, then the state of *v* is + - If *v* is a pattern variable declared in the *switch_label*: "definitely assigned". + - Otherwise, the state of *v* is the same as the stat of *v* after *expr*. + +A consequence of these rules is that a pattern variable declared in a *switch_label* will be "not definitely assigned" in the statements of its switch section if it is not the only reachable switch label in its section. + +> *Example*: +> +> ```csharp +> public static double ComputeArea(object shape) +> { +> switch (shape) +> { +> case Square s when s.Side == 0: +> case Circle c when c.Radius == 0: +> case Triangle t when t.Base == 0 || t.Height == 0: +> case Rectangle r when r.Length == 0 || r.Height == 0: +> // none of s, c, t, or r is definitely assigned +> return 0; +> case Square s: +> // s is definitely assigned +> return s.Side * s.Side; +> case Circle c: +> // c is definitely assigned +> return c.Radius * c.Radius * Math.PI; +> … +> } +> } +> ``` +> +> *end example* #### 9.4.4.8 While statements @@ -919,6 +962,16 @@ Delegate conversions have a control flow path to the local function body. Captur > > *end example* +#### 9.4.4.34 is-pattern expressions + +For an expression *expr* of the form: + +*expr_operand* is *pattern* + +- The definite-assignment state of *v* before *expr_operand* is the same as the definite-assignment state of *v* before *expr*. +- If the variable 'v' is declared in *pattern*, then the definite-assignment state of 'v' after *expr* is "definitely assigned when true". +- Otherwise the definite assignment state of 'v' after *expr* is the same as the definite assignment state of 'v' after *expr_operand*. + ## 9.5 Variable references A *variable_reference* is an *expression* that is classified as a variable. A *variable_reference* denotes a storage location that can be accessed both to fetch the current value and to store a new value. diff --git a/tools/update-grammar-annex.sh b/tools/update-grammar-annex.sh index 3e883ff56..17ddd41dd 100755 --- a/tools/update-grammar-annex.sh +++ b/tools/update-grammar-annex.sh @@ -12,6 +12,7 @@ declare -a SPEC_FILES=( "types.md" "variables.md" "conversions.md" + "patterns.md" "expressions.md" "statements.md" "namespaces.md" diff --git a/tools/validate-grammar.sh b/tools/validate-grammar.sh index 267cbc222..b5bc17f29 100755 --- a/tools/validate-grammar.sh +++ b/tools/validate-grammar.sh @@ -9,6 +9,7 @@ declare -a SPEC_FILES=( "types.md" "variables.md" "conversions.md" + "patterns.md" "expressions.md" "statements.md" "namespaces.md"