Skip to content

Commit 854627a

Browse files
authored
Clarify discard patterns (#1688)
* Clarify discard patterns pecify that when the lone token `_` could parse as a `discard_pattern` but `_` also resolves to an accessible declaration in scope (a constant or a type), `_` is interpreted as a reference to that declaration (i.e. a `constant_pattern` or a type reference for the `is` operator) rather than as a `discard_pattern`. This preserves backward compatibility with code that defined `_` (typically a type) prior to discard support. 1. **Edit `standard/patterns.md` §11.2.7 (Discard pattern)** — after the grammar block, before/around the existing compile-time error sentence. 2. **Add a `*Note*`** under §11.2.7 contrasting with §11.2.2's `discard_designation` rule: the `_` in `Type _` is *always* a `discard_designation` (the in-scope `_` is hidden), whereas a standalone `_` pattern *is* hidden by an in-scope `_` for compatibility. 3. **Add a small `*Example*`** under §11.2.7 showing a `switch_expression` with an in-scope type/constant named `_`, demonstrating that the arm `_ => …` is interpreted against that name (e.g. as a type or constant pattern), not as a discard. Use `// compile-time warning expected` comment if applicable; pick a template such as `standalone-console` consistent with the surrounding examples. Keep it minimal. 4. **Cross-reference touch-up in §11.2.2**: append a brief parenthetical or `*Note*` stating "Contrast with §11.2.7, where a standalone `_` pattern is interpreted as a reference to an in-scope declaration named `_` if one exists." * Respond to feedback. is-type for type binding: The demotion rule is now a bulleted list. The type bullet explicitly says that relational_expression is _ is interpreted as the is-type operator (§12.14.12.1) when _ resolves to a type, and notes that a bare _ resolving to a type is not a valid pattern in other pattern contexts (it can still appear as the type of a declaration_pattern, e.g., _ x). "demotes" wording removed: The *Note* now reads "… for _ an in-scope constant or type causes _ to be interpreted as a reference to that declaration rather than producing an error." Scope clarified: Added an explicit sentence — "If _ resolves to anything other than an accessible constant or type (for example, a local variable, parameter, field, or method), the rule does not apply and _ remains a discard_pattern." The error sentence now reads "If, after applying the preceding rule, the token _ is still a discard_pattern, it is a compile-time error…", making it unambiguous that the is/switch_label error always applies whenever _ remains a discard. Example refactored: Now uses two static methods WithoutUnderscore(int n) and WithUnderscore(int n), both returning string. No nested block, no s1/s2, consistent indentation. * Clarify discard rule The `_` can't be a top-level pattern, but can be a subpattern (in a positional pattern, or a parenthesized pattern).
1 parent e48adcc commit 854627a

1 file changed

Lines changed: 37 additions & 1 deletion

File tree

standard/patterns.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ The runtime type of the value is tested against the *type* in the pattern using
9292
9393
Given a pattern input value ([§11.1](patterns.md#111-general)) *e*, if the *simple_designation* is a *discard_designation*, denoting a discard ([§9.2.9.2](variables.md#9292-discards)), the value of *e* is not bound to anything. Otherwise, if the *simple_designation* is a *single_variable_designation*, a local variable ([§9.2.9](variables.md#929-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.
9494

95+
> *Note*: This treatment of `_` within a *declaration_pattern* differs from that of a standalone `_` written as a *pattern* ([§11.2.7](patterns.md#1127-discard-pattern)): in the latter case, an in-scope constant or type named `_`, if any, is *not* hidden. *end note*
96+
9597
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 or explicit reference conversion, a boxing conversion, an unboxing conversion, or an implicit or explicit nullable value type 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`.
9698

9799
> *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*
@@ -371,7 +373,16 @@ discard_pattern
371373
;
372374
```
373375
374-
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*.
376+
Where the syntactic context permits a *pattern*, if the token `_` would resolve as a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)) to an accessible constant or to a type, then `_` is *not* treated as a *discard_pattern*. Instead:
377+
378+
- If `_` resolves to an accessible constant, the `_` is interpreted as a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) whose constant expression is that constant.
379+
- If `_` resolves to a type, then in the right-hand side of an `is` operator the construct *relational_expression* `is _` is interpreted as the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)) testing against that type. In any other syntactic context that admits a *pattern*, a bare `_` resolving to a type is not by itself a valid *pattern*; however, `_` may appear as the *type* of a *declaration_pattern* (e.g., `_ x`) or in other pattern forms that explicitly name a type.
380+
381+
This rule preserves backward compatibility with code that defined `_` as a type or identifier prior to the introduction of the discard pattern. If `_` resolves to anything other than an accessible constant or type (for example, a local variable, parameter, field, or method), the rule does not apply and `_` remains a *discard_pattern*.
382+
383+
> *Note*: This is analogous to the rule for `var` in §11.2.4, except that for `_` an in-scope constant or type causes `_` to be interpreted as a reference to that declaration rather than producing an error. *end note*
384+
385+
If, after applying the preceding rule, the token `_` is still a *discard_pattern*, it is a compile-time error for that *discard_pattern* to appear as the entire *pattern* of a *relational_expression* of the form *relational_expression* `is` *pattern*, or as the entire *pattern* of a *switch_label*. A *discard_pattern* may, however, appear as a *subpattern* of an enclosing pattern (for example, as a *subpattern* of a *positional_pattern* or *property_pattern*).
375386

376387
> *Note*: In those cases, to match any expression, use a *var_pattern* with a discard `var _`. *end note*
377388
<!-- markdownlint-disable MD028 -->
@@ -408,6 +419,31 @@ It is a compile-time error to use a discard pattern in a *relational_expression*
408419
>
409420
> Here, a discard pattern is used to handle `null` and any integer value that does not have the corresponding member of the `DayOfWeek` enumeration. That guarantees that the `switch` expression handles all possible input values.
410421
> *end example*
422+
<!-- markdownlint-disable MD028 -->
423+
424+
<!-- markdownlint-enable MD028 -->
425+
> *Example*: The following illustrates how an in-scope constant named `_` changes the interpretation of an `_` arm in a `switch` expression. In `WithoutUnderscore`, the `_` arm is a *discard_pattern* and matches any value. In `WithUnderscore`, the in-scope constant `_` causes the `_` arm to be interpreted as a *constant_pattern* that matches only the value `0`.
426+
>
427+
> ```csharp
428+
> static string WithoutUnderscore(int n) => n switch
429+
> {
430+
> 1 => "one",
431+
> _ => "other",
432+
> };
433+
>
434+
> static string WithUnderscore(int n)
435+
> {
436+
> const int _ = 0;
437+
> return n switch
438+
> {
439+
> 1 => "one",
440+
> _ => "zero",
441+
> var x => "other: " + x,
442+
> };
443+
> }
444+
> ```
445+
>
446+
> *end example*
411447
412448
## 11.3 Pattern subsumption
413449

0 commit comments

Comments
 (0)