Skip to content

Commit 385f3ac

Browse files
Fix issues with Positional Patterns (#1685)
* Fix issues with Positional Patterns Fixes #1647 Rewrite §11.2.5 Positional pattern in patterns.md so the three matching strategies and their priority order are unambiguous, and add System.Runtime.CompilerServices.ITuple to the standard library annex. Roslyn-verified: named subpatterns are rejected when matching goes through ITuple, so that prohibition stays in the spec. I had to add the `System.Runtime.CompilerServices.ITuple` interface to Annex C.3. I followed the practice we agreed to in the last meeting to add `///` comments that describe the necessary semantics for the type. * temporary fix to enable tools * Remove link text * Address feedback 1. **§11.2.5 intro** — Remove the `[§12.7](expressions.md#127-deconstruction)` link from the opening sentence. §12.7 defines deconstruction as a source-level transformation producing a tuple-literal; that model doesn't match pattern-matching's direct use of `Deconstruct`. 2. **Move disambiguation rule out of §11.2.5 into §11.2.1.** Delete the paragraph ("It is an error if a *positional_pattern* omits the *type*..."). Add a new paragraph immediately after the `pattern` grammar block, modeled on the deconstructing_assignment disambiguation — e.g. *"If the input can be syntactically recognised as both a `constant_pattern` (whose constant expression is parenthesized) and a `positional_pattern` with a single subpattern lacking an identifier, no property_subpattern, and no simple_designation, then the `constant_pattern` shall be chosen."* 3. **§11.2.5 bullet 2 (Deconstruct form)** — Rewrite to state explicitly: if *type* is present, search *type*; otherwise search the static type of the input value. Drop the second `§12.7` link. Break the long sentence into smaller ones. 4. **Clarify per-bullet error semantics** — Add a sentence before the numbered list stating that once a bullet's selection conditions are satisfied, that strategy is committed; any "compile-time error" inside that bullet is reported (no fall-through). Addresses "compile-time error means move to next bullet?" question. Rewrite each bullet for clarity on "this case applies" * Apply suggestions from code review Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com> * respond to feedback from meeting This should now be ready to merge. --------- Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
1 parent 354abd3 commit 385f3ac

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

standard/patterns.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pattern
3030
;
3131
```
3232

33+
If the input can be syntactically recognised as both a *constant_pattern* and a *positional_pattern* then the *constant_pattern* shall be chosen.
34+
3335
Some *pattern*s can result in the declaration of a local variable.
3436

3537
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`.
@@ -60,7 +62,7 @@ Each pattern form defines the set of types for input values that the pattern may
6062
6163
Each pattern form defines the set of values for which the pattern *matches* the value at runtime.
6264
63-
The order of evaluation of operations and side effects during pattern-matching (calls to `Deconstruct`, property accesses, and invocations of methods in `System.ITuple`) is not specified.
65+
The order of evaluation of operations and side effects during pattern-matching (calls to `Deconstruct`, property accesses, and invocations of members of `System.Runtime.CompilerServices.ITuple`) is not specified.
6466
6567
### 11.2.2 Declaration pattern
6668
@@ -200,7 +202,9 @@ If *designation* is a *tuple_designation*, the pattern is equivalent to a *posit
200202

201203
### 11.2.5 Positional pattern
202204

203-
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`.
205+
A *positional_pattern* checks that the input value is not `null`, extracts a sequence of values from it, and matches each extracted value against a corresponding *subpattern*. The values are extracted in one of three ways: by treating the input as a tuple, by invoking a `Deconstruct` method, or by indexing the input through `System.Runtime.CompilerServices.ITuple`.
206+
207+
> *Note*: The use of `Deconstruct` here is distinct from the source-level deconstruction transformation defined in [§12.7](expressions.md#127-deconstruction). *end note*
204208
205209
```ANTLR
206210
positional_pattern
@@ -215,14 +219,12 @@ subpattern
215219
;
216220
```
217221

218-
Given a match of an input value to the pattern *type* `(` *subpatterns* `)`, a method is selected by searching in *type* for accessible declarations of `Deconstruct` and selecting one among them using the same rules as for the deconstruction declaration.
219-
It is an error if a *positional_pattern* omits the type, has a single *subpattern* without an *identifier*, has no *property_subpattern* and has no *simple_designation*. This disambiguates between a *constant_pattern* that is parenthesized and a *positional_pattern*.
220-
In order to extract the values to match against the patterns in the list,
222+
Let *n* be the number of *subpattern*s appearing between the parentheses. The matching strategy is selected at compile time by applying the following cases in order; the first case whose conditions are satisfied is used, and the remaining cases are not considered. Once a case is selected, that strategy is committed: any compile-time error stated within that case is reported, and matching does not fall through to a subsequent case.
221223

222-
- If *type* is omitted and the input expression’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.
223-
- 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 ([§11.2.2](patterns.md#1122-declaration-pattern)) 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, viewed as an expression of *type*, is deconstructed ([§12.7](expressions.md#127-deconstruction)) using that `Deconstruct`, and each element of the resulting tuple is matched against the corresponding *subpattern*. 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`.
224-
- 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`.
225-
- Otherwise, the pattern is invalid and a compile-time error shall be issued.
224+
1. **Tuple form.** If *type* is omitted and the static type of the input value is a tuple type ([§8.3.11](types.md#8311-tuple-types)) or if the input value is a tuple literal (§12.8.6), then this case applies. It is a compile-time error if *n* is not equal to the arity of that tuple type. At runtime, each tuple element is matched against the corresponding *subpattern*; the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, that *identifier* shall name the tuple element at the corresponding position in the tuple type.
225+
2. **Deconstruct form.** Otherwise, if either *type* is present, or *type* is omitted and the static type of the input value contains an accessible `Deconstruct` method (§12.7), then this case applies. Let *D* be *type* if *type* is present; otherwise let *D* be the static type of the input value. A `Deconstruct` method is selected from *D* using the same overload-resolution rules as for a deconstruction declaration, with the additional requirement that its number of `out` parameters is equal to *n*; it is a compile-time error if no such method exists. If *type* is present, it is a compile-time error if the static type of the input value is not pattern compatible (§11.2.2) with *type*; at runtime the input value is tested against *type* and, if that test fails, the positional pattern match fails. Otherwise, the input value is converted to *D* and the selected `Deconstruct` method is invoked with fresh variables receiving its `out` parameters. Each received value is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, that *identifier* shall name the parameter at the corresponding position of `Deconstruct`.
226+
3. **ITuple form.** Otherwise, if *type* is omitted, no *subpattern* has an *identifier*, and the static type of the input value is `object`, `System.Runtime.CompilerServices.ITuple`, or a type that has an implicit reference conversion to `System.Runtime.CompilerServices.ITuple`, then this case applies. At runtime, the input value is tested for being a non-`null` instance of `System.Runtime.CompilerServices.ITuple`; if that test fails, the positional pattern match fails. Otherwise, the value's `Length` property is read and, if it is not equal to *n*, the positional pattern match fails. Otherwise, for each *i* from 1 to *n*, the value obtained by indexing the input value with *i* − 1 is matched against the *i*-th *subpattern*, and the match succeeds if all of these succeed.
227+
4. Otherwise, no case applies and the *positional_pattern* is a compile-time error.
226228

227229
The order in which subpatterns are matched at runtime is unspecified, and a failed match might not attempt to match all subpatterns.
228230

@@ -314,7 +316,7 @@ It is a compile-time error if the *type* is a nullable value type ([§8.3.12](ty
314316
>
315317
> *end note*
316318
317-
Given a match of an expression *e* to the pattern *type* `{` *subpatterns* `}`, it is a compile-time error if the expression *e* is not pattern-compatible ([§11.2.2](patterns.md#1122-declaration-pattern)) with the type *T* designated by *type*. If the type is absent, the type is assumed to be the static type of *e*. Each of the identifiers appearing on the left-hand-side of its *subpatterns* shall designate an accessible readable property or field of *T*. If the *simple_designation* of the *property_pattern* is present, it declares a pattern variable of type *T*.
319+
Given a match of an expression *e* to the pattern *type* `{` *subpatterns* `}`, it is a compile-time error if the expression *e* is not pattern compatible ([§11.2.2](patterns.md#1122-declaration-pattern)) with the type *T* designated by *type*. If the type is absent, the type is assumed to be the static type of *e*. Each of the identifiers appearing on the left-hand-side of its *subpatterns* shall designate an accessible readable property or field of *T*. If the *simple_designation* of the *property_pattern* is present, it declares a pattern variable of type *T*.
318320
319321
At runtime, the expression is tested against *T*. If this fails then the property pattern match fails, and the result is `false`. If it succeeds, then each *property_subpattern* field or property is read, and its value matched against its corresponding pattern. The result of the whole match is `false` only if the result of any of these is `false`. The order in which subpatterns are matched is not specified, and a failed match may not test all subpatterns at runtime. If the match succeeds and the *simple_designation* of the *property_pattern* is a *single_variable_designation*, the declared variable is assigned the matched value.
320322

standard/standard-library.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,42 @@ namespace System.Runtime.CompilerServices
813813
void OnCompleted(Action continuation);
814814
}
815815

816+
/// <summary>
817+
/// Provides indexed access to the elements of a
818+
/// tuple-like value at runtime. This interface is
819+
/// used by the language to implement positional
820+
/// pattern matching (§11.2.5) when no static
821+
/// tuple type or <c>Deconstruct</c> method is
822+
/// available.
823+
/// </summary>
824+
public interface ITuple
825+
{
826+
/// <summary>
827+
/// The number of elements in the tuple.
828+
/// </summary>
829+
/// <remarks>
830+
/// The value returned shall be non-negative,
831+
/// and shall not change for the lifetime of
832+
/// the instance.
833+
/// </remarks>
834+
int Length { get; }
835+
836+
/// <summary>
837+
/// Returns the element at the specified
838+
/// zero-based position.
839+
/// </summary>
840+
/// <param name="index">
841+
/// The zero-based position of the element
842+
/// to return; shall be ≥ 0 and &lt; <see cref="Length"/>.
843+
/// </param>
844+
/// <exception cref="IndexOutOfRangeException">
845+
/// Thrown if <paramref name="index"/> is
846+
/// outside the range
847+
/// <c>[0, Length)</c>.
848+
/// </exception>
849+
object? this[int index] { get; }
850+
}
851+
816852
public readonly struct TaskAwaiter : ICriticalNotifyCompletion,
817853
INotifyCompletion
818854
{
@@ -1364,6 +1400,7 @@ The following library types are referenced in this specification. The full names
13641400
- `global::System.Runtime.CompilerServices.ICriticalNotifyCompletion`
13651401
- `global::System.Runtime.CompilerServices.IndexerNameAttribute`
13661402
- `global::System.Runtime.CompilerServices.INotifyCompletion`
1403+
- `global::System.Runtime.CompilerServices.ITuple`
13671404
- `global::System.Runtime.CompilerServices.TaskAwaiter`
13681405
- `global::System.Runtime.CompilerServices.TaskAwaiter<TResult>`
13691406
- `global::System.Runtime.CompilerServices.ValueTaskAwaiter`

0 commit comments

Comments
 (0)