Skip to content

Conversation

RexJaeschke
Copy link
Contributor

@RexJaeschke RexJaeschke commented Aug 4, 2023

Prior to V8, we have a term “switch expression” defined as the expression inside parens in a switch statement.

Now, we’re introducing a switch as an expression, and we have a new grammar rule called switch_expression. The difference between “switch expression” (without a *…* fence or separating underscore) and *switch_expression* likely is too subtle. So, how to differentiate them?

Although C calls such a switch expression a “controlling expression” (and uses that term as well for if, while, for, and do statements), we already state, “The governing type of a switch statement is established by the switch expression.”

As such, I have replaced all occurrences of “switch expression” (which exist only in the statements chapter) with “switch’s governing expression.”

Fixes #576.
Fixes #300.

@RexJaeschke RexJaeschke added the type: feature This issue describes a new feature label Aug 4, 2023
@RexJaeschke RexJaeschke added this to the C# 8.0 milestone Aug 4, 2023
@RexJaeschke RexJaeschke self-assigned this Aug 4, 2023
@RexJaeschke RexJaeschke marked this pull request as draft August 4, 2023 17:54
@BillWagner BillWagner force-pushed the add-new-pattern-kinds branch from 0d6dda3 to 237570c Compare September 25, 2023 14:15
@BillWagner
Copy link
Member

Successfully rebased on the updated draft-v8 branch on 09/25/2023

@gafter gafter self-assigned this Oct 4, 2023
@RexJaeschke RexJaeschke added the Review: pending Proposal is available for review label Oct 13, 2023
Copy link
Contributor

@Nigel-Ecma Nigel-Ecma left a comment

Choose a reason for hiding this comment

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

I haven't managed to work my way through all of this yet but am submitting what I have so far. Overall this seems to have carried over too much non-Standardese from the original proposals and some issues repeating/rewording stuff already in the Standard.. There are also grammar issues that may break expression parsing…


```ANTLR
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
Copy link
Contributor

Choose a reason for hiding this comment

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

Something has gone wrong here:

  1. range_expression does not appear to be defined anywhere
  2. it appears the intention is to slot switch_expression between multiplicative_expression & unary_expression in the expression hierarchy, if this is the case then switch_expression needs to include unary_expression so as not to break the grammar layering

Copy link
Contributor Author

Choose a reason for hiding this comment

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

range_expression is defined by the V8 PR #605, "Add Support for Indexers and Ranges," which has not yet been merged.

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Oct 29, 2023

Choose a reason for hiding this comment

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

Thanks @RexJaeschke. From #605 we have:

range_expression
    : unary_expression
    | range_expression? '..' range_expression?
    ;

This does connect to unary_expression but the grammar is still broken. The switch_expression grammar allows for empty braces,switch { }, which doesn’t seem right, was the intended grammar:

switch_expression
    : range_expression ( 'switch' '{' switch_expression_arms ','? '}' )?
    ;

I haven't checked that all fits (see next para) but it makes a bit more logical sense and should at least fix the layering issue.

Also how is e switch { ... } switch { ... } to be handled, if at all? I.e. using one switch to provide the value to switch on for a second (third, …) one? Something like:

switch_expression
    : range_expression
    | switch_expression ( 'switch' '{' switch_expression_arms ','? '}' )?
    ;

might be wanted, but that is left-recursive… Requiring parentheses ((e switch { ... }) switch { ... } is another option.

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Jul 10, 2024

Choose a reason for hiding this comment

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

Fix to my own comment of Oct 30, 2023: It doesn’t matter if the rule is left recursive per se. While we avoid it when not required, left/right recursion is the way left/right associativity is represented.

So the question here is how should switch { … } switch { … } switch { … } associate, if it is supported?

Probably right associative surely?

Once supported/associtivity is decided the grammar will follow… (famous last words ;-))

(I haven’t checked what any implementation does)

Copy link
Member

Choose a reason for hiding this comment

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

Since the switch expression has only one value operand, there is no associativity issue. There is only one way it could possibly associate. According to the compiler, the following precedence levels are from looser to tighter:

            Multiplicative,
            Switch,
            Range,
            Unary,

A switch expression may indeed have a switch expression as its left operand.

Copy link
Member

Choose a reason for hiding this comment

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

Syntactically, a switch expression's block may be empty. Because of the target-typing rule, there are legal programs that contain no switch expression arms, such as this statement: int x = 1 switch {}; (which will always throw an exception and produces a warning that "The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.").

However, without a target type it is expected to be a semantic error, because there is no common type among the switch arms.

The (left) operand to a switch expression may be a switch expression:

switch_expression
    : range_expression
    | switch_expression 'switch' '{' ( switch_expression_arms ','? )? '}'
    ;

I suppose we ought to reorganize this to avoid left recursion:

switch_expression
    | range_expression ( 'switch' '{' ( switch_expression_arms ','? )? '}' )*
    ;

There is no issue of precedence of a switch expression because it has no right-hand expression operand. There is only one possible way to interpret a switch { b => c } switch { d => e }.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gafter – There is no need to remove self left-recursion in this sort of case, it occurs a lot in the grammar – stick with the first grammar which follows the pattern of many rules in the expression stack.

I must admit I’ve just jumped into this but “there are legal programs that contain no switch expression arms” followed an example that always throws an exception is, lets say, curious. Is there a no-arm example which doesn’t always throw? (Would not a better warning include that fact that it will throw?)

Whatever the answer the semantics need to be clearly defined in the Standard, and this comment suggests to me they are not.

Comment on lines 3622 to 3625
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
Copy link
Contributor

Choose a reason for hiding this comment

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

Something has gone wrong here, see comment on line 3565 above

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 be resolved if suggestions above are adopted.

<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` involved `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.
Copy link
Contributor

Choose a reason for hiding this comment

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

The preceding note on line 4085 needs work and is covered under issue #935. This note also needs work (style, “first thing found”, etc.) and is related so maybe should be covered under #935 as well; i.e. remove it here and add a comment to #935.

Copy link
Member

@gafter gafter Dec 15, 2024

Choose a reason for hiding this comment

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

Suggested change
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` involved `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` on the right-hand-side of `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Agree with involved => involving, but also that we need to tighten up the language.)

@@ -4438,6 +4498,8 @@ For an expression of the form `E is P`, where `E` is a relational expression of
- `E` does not designate a value or does not have a type.
- The pattern `P` is not applicable ([§11.2](patterns.md#112-pattern-forms)) to the type `T`.

Every *identifier* of the pattern introduces a new local variable that is definitely assigned once the corresponding *relational_expression* tests `true`.
Copy link
Contributor

Choose a reason for hiding this comment

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

“…assigned if the…” maybe? "Once" sounds like it is a temporal thing waiting on the relational expression to become 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 section on definite assignment is in variables.md and needs to be extended to cover the is-pattern operator.


### §positional-pattern-new-clause Positional pattern

A *positional_pattern* checks that the input value is not `null`, invokes an appropriate `Deconstruct` method ([§12.7](expressions.md#127-deconstruction)), and performs further pattern matching on the resulting values. It also supports a tuple-like pattern syntax (without the type being provided) when the type of the input value is the same as the type containing `Deconstruct`, or if the type of the input value is a tuple type, or if the type of the input value is `object` or `System.ITuple` and the runtime type of the expression implements `System.ITuple`.
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like it is repeating stuff which is (or should be, e.g. System.ITuple is not in §12.7 at present) in §12.7 Deconstruction and this here should be something like:

A positional_pattern checks that the input value is not null, performs a deconstruction (§12.7), and performs further pattern matching on the resulting values.

;
single_variable_designation
: identifier
;
```

> *Note*: The ordering of the grammar rules in *simple_designation* is important. By putting *discard_designation” first, the source token `_` is recognized as a discard rather than as a named identifier. *end note*
Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Oct 27, 2023

Choose a reason for hiding this comment

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

We have not previously specified that the alternatives in a C# grammar rule are ordered, even though we use ANTLR as a notation. Rather we have specified a disambiguation rule, and sometimes a note saying ANTLR semantics give an implementer a free implementation of the rule; e.g. in §12.19.1 we have:

When recognising an anonymous_function_body if both the null_conditional_invocation_expression and expression alternatives are applicable then the former shall be chosen.

Note: The overlapping of, and priority between, alternatives here is solely for descriptive convenience; the grammar rules could be elaborated to remove the overlap. ANTLR, and other grammar systems, adopt the same convenience and so anonymous_function_body has the specified semantics automatically. end note

This note here should either be re-written in our current style or we should re-consider how we describe overlapping/ambiguous alternatives and re-write them all to consistent to whatever we decide.

Copy link
Member

Choose a reason for hiding this comment

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

I think the note should say that a single_variable_designation cannot be the identifier _, which is taken as a discard_designation.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, my suggestion following the pattern of §12.19.1:

Suggested change
> *Note*: The ordering of the grammar rules in *simple_designation* is important. By putting *discard_designation” first, the source token `_` is recognized as a discard rather than as a named identifier. *end note*
When recognising an *simple_designation* if both the *discard_designation* and *single_variable_designation* alternatives are applicable then the former shall be chosen.
> *Note*: Using ANTLR *simple_designation* will have the required semantics automatically, as ANTLR adopts the convenience of selecting the first alternative when multiple alternatives are applicable. *end note*

Possible wording for @gafter’s follows, 👍 which you prefer…

Copy link
Contributor

Choose a reason for hiding this comment

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

And possible wording for @gafter’s suggestion:

Suggested change
> *Note*: The ordering of the grammar rules in *simple_designation* is important. By putting *discard_designation” first, the source token `_` is recognized as a discard rather than as a named identifier. *end note*
The *identifier* of a *single_variable_designation* may not be “_”.
> Note: This restriction exists to avoid an ambiguity in *simple_designation*.

(@gafter – feel free to reword!)

👍 your pick…


Each pattern form defines the set of types for input values that the pattern may be applied to. A pattern `P` is *applicable to* a type `T` if `T` is among the types whose values the pattern may match. It is a compile-time error if a pattern `P` appears in a program to match a pattern input value ([§11.1](patterns.md#111-general)) 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 at runtime.

With regard to the order of evaluation of operations and side effects during pattern-matching, an implementation is permitted to reorder calls to `Deconstruct`, property accesses, and invocations of methods in `System.ITuple`, and it may assume that returned values are the same from multiple calls. The implementation should not invoke functions that cannot affect the result.
Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Oct 27, 2023

Choose a reason for hiding this comment

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

Line 192 below says the order is not specified, so an implementation cannot “reorder” – it just picks an order. What the Standard needs to say is that the order of operations, and any resulting side effects, is implementation defined/specified or unspecified (whichever term from the collection is appropriate here).

The last sentence (“The implementation should not invoke functions that cannot affect the result”) is either redundant or should be a Note.

Copy link
Member

Choose a reason for hiding this comment

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

In code such as

var x = e switch {
    A(1, 2) => 1
    _       => 1
}

Is the compiler allowed to get the properties and test the values of A, or is that not allowed because it does not affect the 'result'?

How normative is a 'should' and what is a 'result'?

@RexJaeschke RexJaeschke marked this pull request as ready for review January 11, 2024 14:08
@gafter gafter self-requested a review January 16, 2024 18:41

Each pattern form defines the set of types for input values that the pattern may be applied to. A pattern `P` is *applicable to* a type `T` if `T` is among the types whose values the pattern may match. It is a compile-time error if a pattern `P` appears in a program to match a pattern input value ([§11.1](patterns.md#111-general)) 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 at runtime.

With regard to the order of evaluation of operations and side effects during pattern-matching, an implementation is permitted to reorder calls to `Deconstruct`, property accesses, and invocations of methods in `System.ITuple`, and it may assume that returned values are the same from multiple calls. The implementation should not invoke functions that cannot affect the result.
Copy link
Member

Choose a reason for hiding this comment

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

In code such as

var x = e switch {
    A(1, 2) => 1
    _       => 1
}

Is the compiler allowed to get the properties and test the values of A, or is that not allowed because it does not affect the 'result'?

How normative is a 'should' and what is a 'result'?

<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` involved `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.
Copy link
Member

@gafter gafter Dec 15, 2024

Choose a reason for hiding this comment

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

Suggested change
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` involved `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` on the right-hand-side of `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.

@@ -2,7 +2,7 @@

## 11.1 General

A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)) and in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)) to express the shape of data against which incoming data is to be compared. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* (§switch-expression-new-clause) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
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 ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* (§switch-expression-new-clause) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* (§switch-expression-new-clause) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***.
A pattern is tested against a value in a number of contexts:
- In a switch statement, the *pattern* of a case label is tested against the *expression* of the switch statement.
- In an *is-pattern* operator, the *pattern* on the right-hand-side is tested against the expression on the left.
- In a switch expression, the *pattern* of a *switch_expression_arm* is tested against the expression on the switch-expression's left-hand-side.
- In nested contexts, the *sub-pattern* is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.
The value against which a pattern is tested is called the ***pattern input value*** for the pattern.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍’ed the above but a minor grammar question: should the first comma of each bullet be a semi-colon?

Copy link
Contributor

Choose a reason for hiding this comment

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

In nested contexts, the sub-pattern is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.

When we get to C# 9 with parenthesized patterns and logical patterns, we might want to remember to come back if we don't generalize this statement now, since and takes two subpatterns that do not retrieve new values to test against.

;
single_variable_designation
: identifier
;
```

> *Note*: The ordering of the grammar rules in *simple_designation* is important. By putting *discard_designation” first, the source token `_` is recognized as a discard rather than as a named identifier. *end note*
Copy link
Member

Choose a reason for hiding this comment

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

I think the note should say that a single_variable_designation cannot be the identifier _, which is taken as a discard_designation.

;
single_variable_designation
: identifier
;
```

> *Note*: The ordering of the grammar rules in *simple_designation* is important. By putting *discard_designation” first, the source token `_` is recognized as a discard rather than as a named identifier. *end note*

The runtime type of the value is tested against the *type* in the pattern using the same rules specified in the is-type operator ([§12.12.12.1](expressions.md#1212121-the-is-type-operator)). If the test succeeds, the pattern *matches* that value. It is a compile-time error if the *type* is a nullable value type ([§8.3.12](types.md#8312-nullable-value-types)). This pattern form never matches a `null` 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 constraint

It is a compile-time error if the type is a nullable value type

Appears both here and a couple of paragraphs below.

Comment on lines +361 to +362
It is a compile-time error to use a discard pattern in a *relational_expression* of the form *relational_expression* `is` *pattern* or a *switch_statement*.

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
It is a compile-time error to use a discard pattern in a *relational_expression* of the form *relational_expression* `is` *pattern* or a *switch_statement*.
It is a compile-time error to use a discard pattern in a *relational_expression* of the form *relational_expression* `is` *pattern* or as the pattern of a *switch_label*.

Copy link
Contributor

Choose a reason for hiding this comment

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

At least one compiler disagrees:

class _ { ... }
...
_ p = new _();
if (p is _)

Produces a warning that _ refers to the type and not the discard pattern.

This is (unfortunately, blame history) probably the required semantics.

>
> *end example*

### §discard-pattern-new-clause Discard pattern
Copy link
Member

Choose a reason for hiding this comment

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

The subsumption and exhaustiveness subsections need to be revised to account for these new pattern forms.


```ANTLR
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
Copy link
Member

Choose a reason for hiding this comment

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

Since the switch expression has only one value operand, there is no associativity issue. There is only one way it could possibly associate. According to the compiler, the following precedence levels are from looser to tighter:

            Multiplicative,
            Switch,
            Range,
            Unary,

A switch expression may indeed have a switch expression as its left operand.

Comment on lines +3566 to +3568
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
;
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
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
;
switch_expression
: switch_expression 'switch' '{' (switch_expression_arms ','?)? '}'
| range_expression
;

Copy link
Contributor

Choose a reason for hiding this comment

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

@gafter above changed the order of the alternatives but also allowed there to be no arms as per the original, however that is invalid as the switch must cover all possible values – that gives us:

Suggested change
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
;
switch_expression
: switch_expression 'switch' '{' switch_expression_arms ','? '}'
| range_expression
;

Copy link
Member

Choose a reason for hiding this comment

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

No. When there is a target type it is possible for there to be no switch expression arms.
https://sharplab.io/#v2:CYLg1APgAgzABFATHAwnA3gWAFBzwxZLXfUgSwDsAXOADzgF44BGOAZwHcyqBjACwwBfANw5SgnIKA==

```

A *switch_expression* may not be used as an *expression_statement*.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) 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
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s. It is an error if no such type exists. The expression in every arm of the switch expression is implicitly converted (§switch-expression-conversion) to that type.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the “expressions appearing to the right of the => tokens of the switch_expression_arms” of the original might be a case for introducing an extra rule to the grammar so simply the language. First replace the switch_expression_arm rule (lines 3573-5) with:

switch_expression_arm
    : pattern case_guard? '=>' switch_expression_arm_expression
    ;
    
switch_expression_arm_expression
    : expression
    ;

And then modify @gafter’s suggestion to match:

Suggested change
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the *switch_expression_arm_expression*s. It is an error if no such type exists. The expression in every arm of the switch expression is implicitly converted (§switch-expression-conversion) to that type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually need it to be an error for there to be no best common type? I'd only expect that to be a problem if we need the switch expression to have a type, e.g. for assignment to var. If we're just trying to convert the switch expression to a known type, it should be fine.

Example:

var now = DateTime.UtcNow;
// Valid: every arm is implicitly convertible to string?
string? x = now.Month switch { _ => null };
// Invalid: no best type
var y = now.Month switch { _ => null };

Copy link
Member

Choose a reason for hiding this comment

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

This specification does not describe target-typed switch expressions. See
dotnet/csharplang#2389
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md

The type of the switch_expression is the best common type (§12.6.3.15) of the expressions appearing to the right of the => tokens of the switch_expression_arms if such a type exists and the expression in every arm of the switch expression can be implicitly converted to that type. In addition, we add a new switch expression conversion, which is a predefined implicit conversion from a switch expression to every type T for which there exists an implicit conversion from each arm's expression to T.

We have to be careful defining when the common type must exist (when there is no target type) and when it doesn't (the switch is target-typed). The spec must say that when there is no target type, it is an error if there is no common type.

@gafter gafter removed their assignment Dec 16, 2024
@gafter gafter added Review: complete at least one person has reviewed this and removed Review: pending Proposal is available for review labels Dec 16, 2024
@RexJaeschke RexJaeschke added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Feb 2, 2025
;
```

A *switch_expression* may not be used as an *expression_statement*.
Copy link
Contributor

Choose a reason for hiding this comment

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

As always, I find "may not" to be somewhat ambiguous. Could we use "shall not" here?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps better: "is not", since it is simply not in the yield of the grammar of expression_statement.

```

A *switch_expression* may not be used as an *expression_statement*.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually need it to be an error for there to be no best common type? I'd only expect that to be a problem if we need the switch expression to have a type, e.g. for assignment to var. If we're just trying to convert the switch expression to a known type, it should be fine.

Example:

var now = DateTime.UtcNow;
// Valid: every arm is implicitly convertible to string?
string? x = now.Month switch { _ => null };
// Invalid: no best type
var y = now.Month switch { _ => null };

A *switch_expression* may not be used as an *expression_statement*.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
A switch expression is said to be *exhaustive* if every value of its input is handled by at least one arm of the switch expression. The compiler shall produce a warning if a switch expression is not exhaustive.

Or something like that - currently I read the proposal as "if there's some arm X such that X handles every value".

Copy link
Member

Choose a reason for hiding this comment

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

I like Jon's rewording.

The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Possibly reinforce that this last part can only happen if the switch expression is not exhaustive? (Or if there's a compiler bug that makes it think it's exhaustive when it isn't really...)

Copy link
Member

Choose a reason for hiding this comment

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

I don't know if it's necessary, except as a Note. The compiler emits a warning for a non-exhaustive switch expression, so it's a valid program for a non-exhaustive switch expression.

Copy link
Member

Choose a reason for hiding this comment

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

<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note*: There is a grammar ambiguity between *type* and *constant_pattern* in a `relational_expression` involved `is`; either might be a valid parse of a qualified identifier. In such a case, only if it fails to bind as a type (for compatibility with previous versions of the language), is it resolved to be the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand side of such an expression.
Copy link
Contributor

Choose a reason for hiding this comment

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

(Agree with involved => involving, but also that we need to tighten up the language.)


- If *type* is omitted and the input value's type is a tuple type, then the number of subpatterns shall to be the same as the cardinality of the tuple. Each tuple element is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a tuple element at the corresponding position in the tuple type.
- Otherwise, if a suitable `Deconstruct` exists as a member of *type*, it is a compile-time error if the type of the input value is not pattern-compatible with *type*. At runtime the input value is tested against *type*. If this fails, then the positional pattern match fails. If it succeeds, the input value is converted to this type and `Deconstruct` is invoked with fresh compiler-generated variables to receive the output parameters. Each value that was received is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a parameter at the corresponding position of `Deconstruct`.
- Otherwise, if *type* is omitted, and the input value is of type `object`, `System.ITuple`, or some type that can be converted to `System.ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then the match uses `System.ITuple`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- Otherwise, if *type* is omitted, and the input value is of type `object`, `System.ITuple`, or some type that can be converted to `System.ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then the match uses `System.ITuple`.
- Otherwise, if *type* is omitted, and the input value is of type `object` or some type that can be converted to `System.ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then the match uses `System.ITuple`.

(Basically this relies on the identity conversion from ITuple to ITuple which is, I believe, an implicit reference conversion.)

- If *type* is omitted and the input value's type is a tuple type, then the number of subpatterns shall to be the same as the cardinality of the tuple. Each tuple element is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a tuple element at the corresponding position in the tuple type.
- Otherwise, if a suitable `Deconstruct` exists as a member of *type*, it is a compile-time error if the type of the input value is not pattern-compatible with *type*. At runtime the input value is tested against *type*. If this fails, then the positional pattern match fails. If it succeeds, the input value is converted to this type and `Deconstruct` is invoked with fresh compiler-generated variables to receive the output parameters. Each value that was received is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a parameter at the corresponding position of `Deconstruct`.
- Otherwise, if *type* is omitted, and the input value is of type `object`, `System.ITuple`, or some type that can be converted to `System.ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then the match uses `System.ITuple`.
- Otherwise, the pattern is a compile-time error.
Copy link
Contributor

Choose a reason for hiding this comment

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

"results in" or something similar, rather than saying the pattern is an error? (Or perhaps "the pattern is invalid and a compile-time error shall be issued" or similar?

> if (s is object o) ... // o is of type object
> if (s is string x1) ... // x1 is of type string
> if (s is {} x2) ... // x2 is of type string
> if (s is {}) ...
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be worth noting the difference between:

if (s is {} x2)

and

if (s is var x3)

... the latter only matches on non-null values, whereas var would match on null (and the type of x3 is string? instead of string in a nullable context). I only mention this as a reader might wonder why {} x2 is useful when var exists.


```ANTLR
property_pattern
: type? property_subpattern simple_designation?
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that type here can't be a nullable reference type or nullable value type. I suspect we need to state that.


### §discard-pattern-new-clause Discard pattern

Every expression matches the discard pattern, which results in the value of the expression being discarded.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we have this in italics or bold? We're defining a term, right?

@RexJaeschke RexJaeschke removed their assignment Jun 11, 2025
@RexJaeschke RexJaeschke reopened this Jun 23, 2025
@BillWagner
Copy link
Member

See #579 for useful references.

@jskeet
Copy link
Contributor

jskeet commented Jul 2, 2025

We should make sure we get the precedence right, as per #300.

@gafter
Copy link
Member

gafter commented Jul 2, 2025

@gafter will try to summarize where we are on this PR.

@Nigel-Ecma
Copy link
Contributor

Prior to V8, we have a term “switch expression” defined as the expression inside parens in a switch statement.

Now, we’re introducing a switch as an expression, and we have a new grammar rule called switch_expression. The difference between “switch expression” (without a fence or separating underscore) and switch_expression likely is too subtle. So, how to differentiate them?

Although C calls such a switch expression a “controlling expression” (and uses that term as well for if, while, for, and do statements), we already state, “The governing type of a switch statement is established by the switch expression.”

As such, I have replaced all occurrences of “switch expression” (which exist only in the statements chapter) with “switch’s governing expression.”

Fixes #576. Fixes #300.

Instead of “governing expression” I’ll offer “selector expression”, which is used by at least Java & Pascal.

@jskeet jskeet added the meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting label Jul 23, 2025
@jskeet
Copy link
Contributor

jskeet commented Jul 29, 2025

Instead of “governing expression” I’ll offer “selector expression”, which is used by at least Java & Pascal.

Selector expression works for me. Should we change to "selector type" as well, instead of "governing type"?


```ANTLR
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
Copy link
Member

Choose a reason for hiding this comment

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

Syntactically, a switch expression's block may be empty. Because of the target-typing rule, there are legal programs that contain no switch expression arms, such as this statement: int x = 1 switch {}; (which will always throw an exception and produces a warning that "The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered.").

However, without a target type it is expected to be a semantic error, because there is no common type among the switch arms.

The (left) operand to a switch expression may be a switch expression:

switch_expression
    : range_expression
    | switch_expression 'switch' '{' ( switch_expression_arms ','? )? '}'
    ;

I suppose we ought to reorganize this to avoid left recursion:

switch_expression
    | range_expression ( 'switch' '{' ( switch_expression_arms ','? )? '}' )*
    ;

There is no issue of precedence of a switch expression because it has no right-hand expression operand. There is only one possible way to interpret a switch { b => c } switch { d => e }.

Comment on lines +3566 to +3568
switch_expression
: range_expression 'switch' '{' (switch_expression_arms ','?)? '}'
;
Copy link
Member

Choose a reason for hiding this comment

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

No. When there is a target type it is possible for there to be no switch expression arms.
https://sharplab.io/#v2:CYLg1APgAgzABFATHAwnA3gWAFBzwxZLXfUgSwDsAXOADzgF44BGOAZwHcyqBjACwwBfANw5SgnIKA==

;
```

A *switch_expression* may not be used as an *expression_statement*.
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps better: "is not", since it is simply not in the yield of the grammar of expression_statement.

```

A *switch_expression* may not be used as an *expression_statement*.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) 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.

This specification does not describe target-typed switch expressions. See
dotnet/csharplang#2389
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md

The type of the switch_expression is the best common type (§12.6.3.15) of the expressions appearing to the right of the => tokens of the switch_expression_arms if such a type exists and the expression in every arm of the switch expression can be implicitly converted to that type. In addition, we add a new switch expression conversion, which is a predefined implicit conversion from a switch expression to every type T for which there exists an implicit conversion from each arm's expression to T.

We have to be careful defining when the common type must exist (when there is no target type) and when it doesn't (the switch is target-typed). The spec must say that when there is no target type, it is an error if there is no common type.

The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`.
Copy link
Member

Choose a reason for hiding this comment

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

@@ -4177,7 +4233,11 @@ equality_expression
;
```

> *Note*: Lookup for the right operand of the `is` operator must first test as a *type*, then as an *expression* which may span multiple tokens. In the case where the operand is an *expression*, the pattern expression must have precedence at least as high as *shift_expression*. *end note*
> *Note*: Lookup for the right operand of the `is` operator must first test as a *type*, then as an *expression* which may span multiple tokens. In the case where the operand is an *expreesion*, the pattern expression must have precedence at least as high as *shift_expression*. *end note*
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
> *Note*: Lookup for the right operand of the `is` operator must first test as a *type*, then as an *expression* which may span multiple tokens. In the case where the operand is an *expreesion*, the pattern expression must have precedence at least as high as *shift_expression*. *end note*
> *Note*: Lookup for the right operand of the `is` operator must first test as a *type*, then as an *expression* which may span multiple tokens. In the case where the operand is an *expression*, the pattern expression must have precedence at least as high as *shift_expression*. *end note*

@@ -4589,6 +4649,8 @@ For an expression of the form `E is P`, where `E` is a relational expression of
- `E` does not designate a value or does not have a type.
- The pattern `P` is not applicable ([§11.2](patterns.md#112-pattern-forms)) to the type `T`.

Every *identifier* of the pattern introduces a new local variable that is definitely assigned once the corresponding *relational_expression* tests `true`.
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
Every *identifier* of the pattern introduces a new local variable that is definitely assigned once the corresponding *relational_expression* tests `true`.
Every *identifier* of the pattern introduces a new local variable that is definitely assigned where the corresponding *relational_expression* tests `true`.

@Nigel-Ecma
Copy link
Contributor

Instead of “governing expression” I’ll offer “selector expression”, which is used by at least Java & Pascal.

Selector expression works for me. Should we change to "selector type" as well, instead of "governing type"?

I’m going to say no, the different names here makes sense to me, I think ;-)

Copy link
Contributor

@jnm2 jnm2 left a comment

Choose a reason for hiding this comment

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

(More later)

@@ -729,17 +730,19 @@ switch_label
;

case_guard
: 'when' expression
: 'when' null_coalescing_expression
Copy link
Contributor

@jnm2 jnm2 Jul 29, 2025

Choose a reason for hiding this comment

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

What is the reason to specify null coalescing expressions for when clauses? If it is to block invalid expressions, I noticed that null_coalescing_expression would directly allow throw_expression. (https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1215-the-null-coalescing-operator) Throw expressions are not permitted by the current Roslyn compiler with any language version. Is this later language on line 737 sufficient to disallow throw expressions?

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

And again so I can follow along, what is the reason to use null_coalescing_expression in particular?

Copy link
Contributor

Choose a reason for hiding this comment

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

Under expression there is a whole stack (with some branches) of rules/productions which controls how subexpressions are group, i.e. the precedence. Part way down is null_coalescing_expression and down at the bottom is parenthesized_expression.

What this change is doing is changing what can directly occur after a when without being in parenthesis. A common reason for doing this is to avoid parsing ambiguities that would occur is something is not wrapped in parenthesis and I guess that is why this change was made here – something between expression and null_coalescing_expression would cause a parsing ambiguity if it was not wrapped.

The important thing is this change is not changing what constructs are valid here, just which ones must be wrapped to be valid – which isn’t a semantic change per se. So throw is there either way, and if it is indeed invalid then there is, or should be, a semantic rule specifying that.

@@ -2,7 +2,7 @@

## 11.1 General

A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)) and in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)) to express the shape of data against which incoming data is to be compared. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
A ***pattern*** is a syntactic form that can be used with the `is` operator ([§12.12.12](expressions.md#121212-the-is-operator)), in a *switch_statement* ([§13.8.3](statements.md#1383-the-switch-statement)), and in a *switch_expression* (§switch-expression-new-clause) to express the shape of data against which incoming data is to be compared. Patterns may be recursive, so that parts of the data may be matched against ***sub-patterns***. A pattern is tested against the *expression* of a switch statement, or against a *relational_expression* that is on the left-hand side of an `is` operator, each of which is referred to as a ***pattern input value***.
Copy link
Contributor

Choose a reason for hiding this comment

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

In nested contexts, the sub-pattern is tested against values retrieved from properties, fields, or indexed from other input values, depending on the pattern form.

When we get to C# 9 with parenthesized patterns and logical patterns, we might want to remember to come back if we don't generalize this statement now, since and takes two subpatterns that do not retrieve new values to test against.

The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`.
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException` if this type is defined in the compilation or references at compile time, or an instance of `System.InvalidOperationException` otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

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

This change does not seem right for the Standard, is it not adding an exception which a particular compiler uses when using an older version of its runtime? As such it is down to the implementation to document how it behaves in such a situation, while C# v8 specifies SwitchExpressionException. Or am I missing something?

- If *type* is omitted and the input value's type is a tuple type, then the number of subpatterns shall to be the same as the cardinality of the tuple. Each tuple element is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a tuple element at the corresponding position in the tuple type.
- Otherwise, if a suitable `Deconstruct` exists as a member of *type*, it is a compile-time error if the type of the input value is not pattern-compatible with *type*. At runtime the input value is tested against *type*. If this fails, then the positional pattern match fails. If it succeeds, the input value is converted to this type and `Deconstruct` is invoked with fresh compiler-generated variables to receive the output parameters. Each value that was received is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that shall name a parameter at the corresponding position of `Deconstruct`.
- Otherwise, if *type* is omitted, and the input value is of type `object`, `System.ITuple`, or some type that can be converted to `System.ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then the match uses `System.ITuple`.
- Otherwise, the pattern is a compile-time error.
Copy link
Contributor

@jnm2 jnm2 Jul 30, 2025

Choose a reason for hiding this comment

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

Patterns (including property patterns and positional patterns) implicitly unwrap System.Nullable<T>, as part of their "checks that the input value is not null." Do we need to say something to this effect here?

Example:

class C
{
    void M(Point? p)
    {
        if (p is (var x, var y)) // True
        {
        }
    }
}

struct Point
{
    public int X, Y;
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Nothing needs to be said, that's how checking whether a Nullable<T> is null works, which is defined elsewhere in the Standard.

Comment on lines +197 to +203
subpatterns
: subpattern (',' subpatterns)?
;
subpattern
: pattern
| identifier ':' pattern
;
Copy link
Contributor

@jnm2 jnm2 Jul 30, 2025

Choose a reason for hiding this comment

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

(In future versions of C#, there will be other kinds of subpatterns besides property subpatterns and positional subpatterns, such as <subpattern> and <subpattern> in C# 9, or is [<subpattern, <subpattern>] later on. At that point, calling this definition subpattern will seem overly broad.)

@gafter gafter self-assigned this Jul 30, 2025
@jskeet
Copy link
Contributor

jskeet commented Jul 30, 2025

We'll go with selector expression, but keep governing type.

@gafter will attempt to get this ready for review for the September meeting.

A *switch_expression* may not be used as an *expression_statement*.
The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
Copy link
Member

Choose a reason for hiding this comment

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

I like Jon's rewording.

The type of the *switch_expression* is the best common type [§12.6.3.15](expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted (§switch-expression-conversion) to that type.
It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match.
A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not exhaustive.
At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`.
Copy link
Member

Choose a reason for hiding this comment

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

I don't know if it's necessary, except as a Note. The compiler emits a warning for a non-exhaustive switch expression, so it's a valid program for a non-exhaustive switch expression.

@@ -3884,6 +3940,7 @@ The `*`, `/`, `%`, `+`, and `-` operators are called the arithmetic operators.
```ANTLR
multiplicative_expression
: range_expression
| switch_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 needs more work (see other comments on grammar). I added this as a placeholder to remember it while addressing merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
meeting: discuss This issue should be discussed at the next TC49-TG2 meeting meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting Review: complete at least one person has reviewed this type: feature This issue describes a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8.0 Tuple parentheses optional in switch expression and statement Change precedence of switch expression from relational to unary
6 participants