From 5268b51794041c31eab5173a66fa04fa2b91325b Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Sat, 10 Oct 2020 22:04:11 +0330 Subject: [PATCH 01/16] Add list-patterns.md --- proposals/list-patterns.md | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 proposals/list-patterns.md diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md new file mode 100644 index 0000000000..71cca3dfbb --- /dev/null +++ b/proposals/list-patterns.md @@ -0,0 +1,170 @@ + +# List patterns + +## Summary + +Lets you to match an array or a list with a sequence of patterns e.g. `array is [1, 2, 3]` will match an integer array of the length three with 1, 2, 3 as its elements, respectively. + +## Detailed Design + +### Fixed-length list patterns + +The syntax will be modified to include a *list_pattern* defined as below: + +```antlr +primary_pattern + : list_pattern +list_pattern + : '[' (pattern (',' pattern)* ','?)? ']' +``` + +**Pattern compatibility:** A *list_pattern* is compatible with any type that conforms to the ***range indexer pattern***: + +1. Has an accessible property getter that returns an `int` and has the name `Length` or `Count` +2. Has an accessible indexer with a single `int` parameter +3. Has an accessible `Slice` method that takes two `int` parameters (for slice subpatterns) + +This rule includes `T[]`, `string`, `Span`, `ImmutableArray` and more. + +**Subsumption checking:** This construct will be built entirely on top of the existing DAG nodes. The subsumption is checked just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. + +**Lowering:** A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: +```cs +expr.Length is 3 +&& expr[0] is 1 +&& expr[1] is 2 +&& expr[2] is 3 +``` + +### Variable-length list patterns + +The syntax will be modified to include a *slice_pattern* defined as below: +```antlr +primary_pattern + : slice_pattern +slice_pattern + : '..' +``` + +A *slice_pattern* is only permitted once and only directly in a *list_pattern* and discards ***zero or more*** elements. Note that it's possible to use a *slice_pattern* in a nested *list_pattern* e.g. `[.., [.., 1]]` will match `new int[][]{new[]{1}}`. + +A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .., 3]` is equivalent to the following code: +```cs +expr.Length is >= 2 +&& expr[0] is 1 +&& expr[^1] is 3 +``` +Note: the lowering is presented in the pattern form here to show how subsumption checking works, for example, the following code produces an error because both patterns yield the same DAG: + +```cs +case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1 +case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1 +``` +Unlike: +```cs +case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1 +case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1 +``` + +Note: the pattern `[..]` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. + +### Slice subpatterns + +We can further extend the *slice_pattern* to be able to capture the skipped sequence: + + +```antlr +slice_pattern + : '..' unary_pattern? +``` + +A pattern of the form `expr is [1, ..var s, 3]` would be equivalent to the following code: + +```cs +expr.Length is >= 2 +&& expr[0] is 1 +&& expr[1..^1] is var s +&& expr[^1] is 3 +``` + +### Additional types + +Beyond the pattern-based mechanism outlined above, there are an additional two set of types we can cover as a special case. + +#### Multi-dimensional arrays + + +```cs +array is [[1]] + +array.GetLength(0) == 1 && +array.GetLength(1) == 1 && +array[0, 0] is 1 +``` +All multi-dimensional arrays can be non-zero-based. We can either cut this support or either: + +1. Add a runtime helper to check if the array is zero-based across all dimensions. +2. Call `GetLowerBound` and add it to each indexer access to pass the *correct* index. +3. Assume all arrays are zero-based since that's the default for arrays created by `new` expressions. + + +The following rules determine if a pattern is valid for a multi-dimensional array: +- For an array of rank N, only N-1 level of nested list-patterns are accepted. +- Except for that last level, all subpatterns must be either a slice pattern without a subpattern (`..`) or a list-pattern. +- For slice patterns, the usual rules apply - only permitted once and only directly inside the pattern. +- All nested list-patterns must be of an exact or minimum size. Given X = the minimum or exact required length so far and Y = the new length from current nested list pattern, the expected size is calculated as follow: + ``` + AtLeast(X) + AtLeast(Y) = AtLeast(Max(X, Y)) + Exactly(X) + Exactly(Y) = Exactly(X) only if X==Y + Exactly(X) + AtLeast(Y) = Exactly(X) only if X>=Y + ``` + Note: The presence of a slice pattern implies a minimum required length. +#### Foreach-able types +We can reuse `foreach` rules to determine if a type is viable for the match, so this includes pattern-based and extension `GetEnumerator`. + +```cs +switch (expr) +{ + case [0]: // e.MoveNext() && e.Current is 0 && !e.MoveNext() + break; + case [0, 1]: // e.MoveNext() && e.Current is 0 && e.MoveNext() && e.Current is 1 && !e.MoveNext() + break; +} + +using (var e = expr.GetEnumerator()) +{ + if (e.MoveNext()) + { + var t0 = e.Current; + if (t0 is 0) + { + if (!e.MoveNext()) + { + goto case0; + } + else + { + var t1 = e.Current; + if (t1 is 1) + { + if (!e.MoveNext()) + { + goto case1; + } + } + } + } + } + + goto @default; +} +``` +Like multi-dimensional arrays, we cannot support slice subpatterns, but we do permit `..` only as the last element in which case we simply omit the last call to `MoveNext`. + +Note: Unlike other types, `[..]` is actually considered as a catch-all here, since no tests will be emitted for such pattern. + +## Questions + +- Should we support a trailing designator to capture the input? e.g. `[] v` +- Should we support `this[System.Index]` and `this[System.Range]` indexers? +- Should we support matching an `object` with a type check for `IEnumerable`? From 93ff432382503a288e85d0c721f584cd09be432b Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 2 Mar 2021 06:36:38 +0330 Subject: [PATCH 02/16] Update list-patterns.md --- proposals/list-patterns.md | 198 ++++++++++++++----------------------- 1 file changed, 75 insertions(+), 123 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 71cca3dfbb..b7635fe81a 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -1,170 +1,122 @@ - # List patterns ## Summary -Lets you to match an array or a list with a sequence of patterns e.g. `array is [1, 2, 3]` will match an integer array of the length three with 1, 2, 3 as its elements, respectively. +Lets you to match an array or a list with a sequence of patterns e.g. `array is {1, 2, 3}` will match an integer array of the length three with 1, 2, 3 as its elements, respectively. ## Detailed Design -### Fixed-length list patterns - -The syntax will be modified to include a *list_pattern* defined as below: +The pattern syntax is modified as follow: ```antlr +recursive_pattern + : type? positional_pattern_clause? length_pattern_clause? property_or_list_pattern_clause? simple_designation? + +length_pattern_clause + : '[' pattern ']' + +property_or_list_pattern_clause + : list_pattern_clause + | property_pattern_clause + +property_pattern_clause + : '{' (subpattern (',' subpattern)* ','?)? '}' + +list_pattern_clause + : '{' pattern (',' pattern)* ','? '}' + +slice_pattern + : '..' negated_pattern? + primary_pattern - : list_pattern -list_pattern - : '[' (pattern (',' pattern)* ','?)? ']' + : recursive_pattern + | slice_pattern + ``` +There are two new additions to the *recursive pattern* syntax as well as a *slice pattern*: -**Pattern compatibility:** A *list_pattern* is compatible with any type that conforms to the ***range indexer pattern***: +- The *list pattern clause* is used to match elements and the *length pattern clause* is used to match the length. +- A _slice pattern_ is only permitted once and only directly in a *list pattern* and discards _**zero or more**_ elements. -1. Has an accessible property getter that returns an `int` and has the name `Length` or `Count` -2. Has an accessible indexer with a single `int` parameter -3. Has an accessible `Slice` method that takes two `int` parameters (for slice subpatterns) -This rule includes `T[]`, `string`, `Span`, `ImmutableArray` and more. +Due to the ambiguity with *property pattern clause*, the *list pattern clause* cannot be empty and a *length pattern* should be used. -**Subsumption checking:** This construct will be built entirely on top of the existing DAG nodes. The subsumption is checked just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. -**Lowering:** A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: -```cs -expr.Length is 3 -&& expr[0] is 1 -&& expr[1] is 2 -&& expr[2] is 3 -``` -### Variable-length list patterns -The syntax will be modified to include a *slice_pattern* defined as below: -```antlr -primary_pattern - : slice_pattern -slice_pattern - : '..' -``` -A *slice_pattern* is only permitted once and only directly in a *list_pattern* and discards ***zero or more*** elements. Note that it's possible to use a *slice_pattern* in a nested *list_pattern* e.g. `[.., [.., 1]]` will match `new int[][]{new[]{1}}`. -A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .., 3]` is equivalent to the following code: -```cs -expr.Length is >= 2 -&& expr[0] is 1 -&& expr[^1] is 3 -``` -Note: the lowering is presented in the pattern form here to show how subsumption checking works, for example, the following code produces an error because both patterns yield the same DAG: +#### Pattern compatibility -```cs -case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1 -case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1 -``` -Unlike: -```cs -case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1 -case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1 -``` +A *length pattern* is compatible with any type that conforms to the following rules: + +1. Has an accessible property getter that returns an `int` and has the name `Length` or `Count` -Note: the pattern `[..]` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. +A *list pattern* is compatible with any type that conforms to the following rules: -### Slice subpatterns +1. Is compatible with the length pattern +2. Has an accessible indexer with a single `int` parameter -We can further extend the *slice_pattern* to be able to capture the skipped sequence: +A *slice pattern* is compatible with any type that conforms to the following rules: +1. Is compatible with the list pattern +2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) -```antlr -slice_pattern - : '..' unary_pattern? -``` +This set of rules is already specified as the ***range indexer pattern*** and required for range indexers. + +#### Subsumption checking -A pattern of the form `expr is [1, ..var s, 3]` would be equivalent to the following code: + Subsumption checking works just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. +#### Lowering + + A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code: +```cs +expr.Length is 3 +&& expr[0] is 1 +&& expr[1] is 2 +&& expr[2] is 3 +``` +A *slice pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code: ```cs expr.Length is >= 2 && expr[0] is 1 && expr[1..^1] is var s && expr[^1] is 3 ``` +Note: the lowering is presented in the pattern form here to show how subsumption checking works, for example, the following code produces an error because both patterns yield the same DAG: + +```cs +case {_, .., 1}: // expr.Length is >= 2 && expr[^1] is 1 +case {.., _, 1}: // expr.Length is >= 2 && expr[^1] is 1 +``` +Unlike: +```cs +case {_, 1, ..}: // expr.Length is >= 2 && expr[1] is 1 +case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1 +``` + +Note: The pattern `{..}` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. ### Additional types -Beyond the pattern-based mechanism outlined above, there are an additional two set of types we can cover as a special case. +Beyond the pattern-based mechanism outlined above, there are an additional two set of types that can be covered as a special case. -#### Multi-dimensional arrays +- **Multi-dimensional arrays**: All nested list patterns must agree to a length range. +- **Foreach-able types**: This includes pattern-based and extension `GetEnumerator`. +A slice subpattern is disallowed for either of the above. -```cs -array is [[1]] +## Unresolved questions -array.GetLength(0) == 1 && -array.GetLength(1) == 1 && -array[0, 0] is 1 -``` -All multi-dimensional arrays can be non-zero-based. We can either cut this support or either: +All multi-dimensional arrays can be non-zero-based. We can either: 1. Add a runtime helper to check if the array is zero-based across all dimensions. 2. Call `GetLowerBound` and add it to each indexer access to pass the *correct* index. 3. Assume all arrays are zero-based since that's the default for arrays created by `new` expressions. +## Design meetings -The following rules determine if a pattern is valid for a multi-dimensional array: -- For an array of rank N, only N-1 level of nested list-patterns are accepted. -- Except for that last level, all subpatterns must be either a slice pattern without a subpattern (`..`) or a list-pattern. -- For slice patterns, the usual rules apply - only permitted once and only directly inside the pattern. -- All nested list-patterns must be of an exact or minimum size. Given X = the minimum or exact required length so far and Y = the new length from current nested list pattern, the expected size is calculated as follow: - ``` - AtLeast(X) + AtLeast(Y) = AtLeast(Max(X, Y)) - Exactly(X) + Exactly(Y) = Exactly(X) only if X==Y - Exactly(X) + AtLeast(Y) = Exactly(X) only if X>=Y - ``` - Note: The presence of a slice pattern implies a minimum required length. -#### Foreach-able types -We can reuse `foreach` rules to determine if a type is viable for the match, so this includes pattern-based and extension `GetEnumerator`. - -```cs -switch (expr) -{ - case [0]: // e.MoveNext() && e.Current is 0 && !e.MoveNext() - break; - case [0, 1]: // e.MoveNext() && e.Current is 0 && e.MoveNext() && e.Current is 1 && !e.MoveNext() - break; -} - -using (var e = expr.GetEnumerator()) -{ - if (e.MoveNext()) - { - var t0 = e.Current; - if (t0 is 0) - { - if (!e.MoveNext()) - { - goto case0; - } - else - { - var t1 = e.Current; - if (t1 is 1) - { - if (!e.MoveNext()) - { - goto case1; - } - } - } - } - } - - goto @default; -} -``` -Like multi-dimensional arrays, we cannot support slice subpatterns, but we do permit `..` only as the last element in which case we simply omit the last call to `MoveNext`. - -Note: Unlike other types, `[..]` is actually considered as a catch-all here, since no tests will be emitted for such pattern. - -## Questions - -- Should we support a trailing designator to capture the input? e.g. `[] v` -- Should we support `this[System.Index]` and `this[System.Range]` indexers? -- Should we support matching an `object` with a type check for `IEnumerable`? +https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-14.md +https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-16.md +https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-03.md From 87bcacf4ad476d9309a22db3a3a18646f352f5a3 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 09:21:43 +0330 Subject: [PATCH 03/16] Update list-patterns.md --- proposals/list-patterns.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index b7635fe81a..c8dd540045 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -11,53 +11,53 @@ The pattern syntax is modified as follow: ```antlr recursive_pattern : type? positional_pattern_clause? length_pattern_clause? property_or_list_pattern_clause? simple_designation? + ; length_pattern_clause : '[' pattern ']' + ; property_or_list_pattern_clause : list_pattern_clause | property_pattern_clause + ; property_pattern_clause : '{' (subpattern (',' subpattern)* ','?)? '}' - + ; + list_pattern_clause : '{' pattern (',' pattern)* ','? '}' + ; slice_pattern : '..' negated_pattern? - + ; + primary_pattern : recursive_pattern | slice_pattern - + ; ``` -There are two new additions to the *recursive pattern* syntax as well as a *slice pattern*: - -- The *list pattern clause* is used to match elements and the *length pattern clause* is used to match the length. -- A _slice pattern_ is only permitted once and only directly in a *list pattern* and discards _**zero or more**_ elements. - - -Due to the ambiguity with *property pattern clause*, the *list pattern clause* cannot be empty and a *length pattern* should be used. - - - +There are two new additions to the *recursive_pattern* syntax as well as a *slice pattern*: +- The *list_pattern_clause* is used to match elements and the *length_pattern_clause* is used to match the length. +- A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. +Due to the ambiguity with *property_pattern_clause*, the *list_pattern_clause* cannot be empty and a *length_pattern_clause* should be used instead. #### Pattern compatibility -A *length pattern* is compatible with any type that conforms to the following rules: +A *length_pattern_clause* is compatible with any type that conforms to the following rules: 1. Has an accessible property getter that returns an `int` and has the name `Length` or `Count` -A *list pattern* is compatible with any type that conforms to the following rules: +A *list_pattern_clause* is compatible with any type that conforms to the following rules: 1. Is compatible with the length pattern 2. Has an accessible indexer with a single `int` parameter -A *slice pattern* is compatible with any type that conforms to the following rules: +A *slice_pattern* is compatible with any type that conforms to the following rules: 1. Is compatible with the list pattern 2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) @@ -77,7 +77,7 @@ expr.Length is 3 && expr[1] is 2 && expr[2] is 3 ``` -A *slice pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code: +A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code: ```cs expr.Length is >= 2 && expr[0] is 1 @@ -102,10 +102,10 @@ Note: The pattern `{..}` lowers to `expr.Length >= 0` so it would not be conside Beyond the pattern-based mechanism outlined above, there are an additional two set of types that can be covered as a special case. -- **Multi-dimensional arrays**: All nested list patterns must agree to a length range. +- **Multi-dimensional arrays**: All nested list patterns must agree to a length range. - **Foreach-able types**: This includes pattern-based and extension `GetEnumerator`. -A slice subpattern is disallowed for either of the above. +A slice subpattern (i.e. the pattern followed by `..` in a *slice_pattern*) is disallowed for either of the above. ## Unresolved questions From 44660f5bdd4349c9408494edbd00db9786dacc7f Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 09:22:49 +0330 Subject: [PATCH 04/16] Update list-patterns.md --- proposals/list-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index c8dd540045..09f55b56b5 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -39,7 +39,7 @@ primary_pattern | slice_pattern ; ``` -There are two new additions to the *recursive_pattern* syntax as well as a *slice pattern*: +There are two new additions to the *recursive_pattern* syntax as well as a *slice_pattern*: - The *list_pattern_clause* is used to match elements and the *length_pattern_clause* is used to match the length. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. From c546f664cbb32a7439685b142e38ad0865183421 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 09:24:50 +0330 Subject: [PATCH 05/16] Update list-patterns.md --- proposals/list-patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 09f55b56b5..b3928b7a76 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -66,11 +66,11 @@ This set of rules is already specified as the ***range indexer pattern*** and re #### Subsumption checking - Subsumption checking works just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. +Subsumption checking works just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. #### Lowering - A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code: +A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code: ```cs expr.Length is 3 && expr[0] is 1 From 2a046a16aa656460d076f67772a9752f411214ab Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 20:12:47 +0330 Subject: [PATCH 06/16] Update list-patterns.md --- proposals/list-patterns.md | 40 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index b3928b7a76..a0f5b16ce9 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -1,3 +1,4 @@ + # List patterns ## Summary @@ -44,25 +45,37 @@ There are two new additions to the *recursive_pattern* syntax as well as a *slic - The *list_pattern_clause* is used to match elements and the *length_pattern_clause* is used to match the length. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. -Due to the ambiguity with *property_pattern_clause*, the *list_pattern_clause* cannot be empty and a *length_pattern_clause* should be used instead. +Notes: -#### Pattern compatibility +- Due to the ambiguity with *property_pattern_clause*, the *list_pattern_clause* cannot be empty and a *length_pattern_clause* should be used instead to match a list with the length of zero, e.g. `[0]`. +- The *length_pattern_clause* must be in agreement with the inferred length from the pattern (if any), e.g. `[0] {1}` is an error. + - However, `[1] {}` is **not** an error due to the length mismatch, rather, `{}` would be always parsed as an empty *property_pattern_clause*. We may want to add a warning for it so it would not be confused that way. +- If the *type* is an *array_type*, the *length_pattern_clause* is disambiguated so that `int[] [0]` would match an empty integer array. +- All other combinations are valid, for instance `T (p0, p1) [p2] { name: p3 } v` or `T (p0, p1) [p2] { p3 } v` where each clause can be omitted. + +> **Open question**: Should we support all these combinations? -A *length_pattern_clause* is compatible with any type that conforms to the following rules: -1. Has an accessible property getter that returns an `int` and has the name `Length` or `Count` + +#### Pattern compatibility + +A *length_pattern_clause* is compatible with any type that is *countable*, i.e. has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. A *list_pattern_clause* is compatible with any type that conforms to the following rules: -1. Is compatible with the length pattern +1. Is compatible with the *length_pattern_clause* 2. Has an accessible indexer with a single `int` parameter + > **Open question**: Should we support `this[Index]` indexers? If so, which one is preferred if `this[int]` is also present? + A *slice_pattern* is compatible with any type that conforms to the following rules: -1. Is compatible with the list pattern +1. Is compatible with the *length_pattern_clause* 2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) -This set of rules is already specified as the ***range indexer pattern*** and required for range indexers. + > **Open question**: Should we support `this[Range]` indexers? If so, which one is preferred if `this[int]` is also present? + +This set of rules is already specified as the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) and required for range indexers. #### Subsumption checking @@ -84,6 +97,8 @@ expr.Length is >= 2 && expr[1..^1] is var s && expr[^1] is 3 ``` +The *input type* for the *slice_pattern* is the return type of the underlying `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively. + Note: the lowering is presented in the pattern form here to show how subsumption checking works, for example, the following code produces an error because both patterns yield the same DAG: ```cs @@ -96,7 +111,7 @@ case {_, 1, ..}: // expr.Length is >= 2 && expr[1] is 1 case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1 ``` -Note: The pattern `{..}` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. +> **Open question**: The pattern `{..}` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. Should we omit such test (assuming `Length` is always non-negative)? ### Additional types @@ -105,7 +120,8 @@ Beyond the pattern-based mechanism outlined above, there are an additional two s - **Multi-dimensional arrays**: All nested list patterns must agree to a length range. - **Foreach-able types**: This includes pattern-based and extension `GetEnumerator`. -A slice subpattern (i.e. the pattern followed by `..` in a *slice_pattern*) is disallowed for either of the above. +A slice subpattern (i.e. the pattern following `..` in a *slice_pattern*) is disallowed for either of the above. + ## Unresolved questions @@ -114,9 +130,3 @@ All multi-dimensional arrays can be non-zero-based. We can either: 1. Add a runtime helper to check if the array is zero-based across all dimensions. 2. Call `GetLowerBound` and add it to each indexer access to pass the *correct* index. 3. Assume all arrays are zero-based since that's the default for arrays created by `new` expressions. - -## Design meetings - -https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-14.md -https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-16.md -https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-03.md From febbdddb464c84ef037d62d814fd799b714c72f4 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 20:21:34 +0330 Subject: [PATCH 07/16] Update list-patterns.md --- proposals/list-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index a0f5b16ce9..b8f1f346ab 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -73,7 +73,7 @@ A *slice_pattern* is compatible with any type that conforms to the following rul 1. Is compatible with the *length_pattern_clause* 2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) - > **Open question**: Should we support `this[Range]` indexers? If so, which one is preferred if `this[int]` is also present? + > **Open question**: Should we support `this[Range]` indexers? If so, which one is preferred if `Slice(int, int)` is also present? This set of rules is already specified as the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) and required for range indexers. From 8f300a585629bd43d6ef76cacd22a810cb431889 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 21:03:29 +0330 Subject: [PATCH 08/16] Update list-patterns.md --- proposals/list-patterns.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index b8f1f346ab..8944f21d07 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -1,4 +1,3 @@ - # List patterns ## Summary @@ -55,8 +54,6 @@ Notes: > **Open question**: Should we support all these combinations? - - #### Pattern compatibility A *length_pattern_clause* is compatible with any type that is *countable*, i.e. has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. @@ -122,7 +119,6 @@ Beyond the pattern-based mechanism outlined above, there are an additional two s A slice subpattern (i.e. the pattern following `..` in a *slice_pattern*) is disallowed for either of the above. - ## Unresolved questions All multi-dimensional arrays can be non-zero-based. We can either: From b92f67720826ae6e0f42c998e5f0ec2c9948044f Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 9 Mar 2021 21:21:02 +0330 Subject: [PATCH 09/16] Update list-patterns.md --- proposals/list-patterns.md | 1 + 1 file changed, 1 insertion(+) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 8944f21d07..fd0ffdf0eb 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -37,6 +37,7 @@ slice_pattern primary_pattern : recursive_pattern | slice_pattern + | // all of the pattern forms previously defined ; ``` There are two new additions to the *recursive_pattern* syntax as well as a *slice_pattern*: From 57468d053d6a6cebf5a40b1cb8f3d0b2e25cafcc Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 10 Mar 2021 00:25:18 +0330 Subject: [PATCH 10/16] Update list-patterns.md --- proposals/list-patterns.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index fd0ffdf0eb..5f7b149376 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -1,3 +1,4 @@ + # List patterns ## Summary @@ -9,8 +10,20 @@ Lets you to match an array or a list with a sequence of patterns e.g. `array is The pattern syntax is modified as follow: ```antlr -recursive_pattern - : type? positional_pattern_clause? length_pattern_clause? property_or_list_pattern_clause? simple_designation? +positional_pattern + : type? positional_pattern_clause length_pattern_clause? property_or_list_pattern_clause? simple_designation? + ; + +length_pattern + : type? length_pattern_clause property_or_list_pattern_clause? simple_designation? + ; + +list_pattern + : type? list_pattern_clause simple_designation? + ; + +property_pattern + : type? property_pattern_clause simple_designation? ; length_pattern_clause @@ -35,20 +48,21 @@ slice_pattern ; primary_pattern - : recursive_pattern + : list_pattern + | length_pattern | slice_pattern | // all of the pattern forms previously defined ; ``` -There are two new additions to the *recursive_pattern* syntax as well as a *slice_pattern*: +There are three new patterns: -- The *list_pattern_clause* is used to match elements and the *length_pattern_clause* is used to match the length. +- The *list_pattern* is used to match elements and the *length_pattern* is used to match the length. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. Notes: -- Due to the ambiguity with *property_pattern_clause*, the *list_pattern_clause* cannot be empty and a *length_pattern_clause* should be used instead to match a list with the length of zero, e.g. `[0]`. -- The *length_pattern_clause* must be in agreement with the inferred length from the pattern (if any), e.g. `[0] {1}` is an error. +- Due to the ambiguity with *property_pattern*, a *list_pattern* cannot be empty and a *length_pattern* should be used instead to match a list with the length of zero, e.g. `[0]`. +- The *length_pattern_clause* must be in agreement with the inferred length from the *list_pattern_clause* (if any), e.g. `[0] {1}` is an error. - However, `[1] {}` is **not** an error due to the length mismatch, rather, `{}` would be always parsed as an empty *property_pattern_clause*. We may want to add a warning for it so it would not be confused that way. - If the *type* is an *array_type*, the *length_pattern_clause* is disambiguated so that `int[] [0]` would match an empty integer array. - All other combinations are valid, for instance `T (p0, p1) [p2] { name: p3 } v` or `T (p0, p1) [p2] { p3 } v` where each clause can be omitted. @@ -57,18 +71,18 @@ Notes: #### Pattern compatibility -A *length_pattern_clause* is compatible with any type that is *countable*, i.e. has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. +A *length_pattern* is compatible with any type that is *countable*, i.e. has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. -A *list_pattern_clause* is compatible with any type that conforms to the following rules: +A *list_pattern* is compatible with any type that conforms to the following rules: -1. Is compatible with the *length_pattern_clause* +1. Is compatible with the *length_pattern* 2. Has an accessible indexer with a single `int` parameter > **Open question**: Should we support `this[Index]` indexers? If so, which one is preferred if `this[int]` is also present? A *slice_pattern* is compatible with any type that conforms to the following rules: -1. Is compatible with the *length_pattern_clause* +1. Is compatible with the *length_pattern* 2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) > **Open question**: Should we support `this[Range]` indexers? If so, which one is preferred if `Slice(int, int)` is also present? @@ -77,7 +91,7 @@ This set of rules is already specified as the [***range indexer pattern***](http #### Subsumption checking -Subsumption checking works just like recursive patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. +Subsumption checking works just like positional patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. #### Lowering From d21b55c8f7b75e2002b861d470030685f0bb7499 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 10 Mar 2021 00:27:47 +0330 Subject: [PATCH 11/16] Update list-patterns.md --- proposals/list-patterns.md | 1 - 1 file changed, 1 deletion(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 5f7b149376..d2d2ce5688 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -1,4 +1,3 @@ - # List patterns ## Summary From 9dc65a76639195a2049f3db7f06ea868a2731d5a Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 17 Mar 2021 08:12:20 +0330 Subject: [PATCH 12/16] Update list-patterns.md --- proposals/list-patterns.md | 50 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index d2d2ce5688..5f138ecded 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -58,6 +58,8 @@ There are three new patterns: - The *list_pattern* is used to match elements and the *length_pattern* is used to match the length. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. +> **Open question**: Should we accept a general *pattern* following `..` in a *slice_pattern*? + Notes: - Due to the ambiguity with *property_pattern*, a *list_pattern* cannot be empty and a *length_pattern* should be used instead to match a list with the length of zero, e.g. `[0]`. @@ -70,28 +72,34 @@ Notes: #### Pattern compatibility -A *length_pattern* is compatible with any type that is *countable*, i.e. has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. - -A *list_pattern* is compatible with any type that conforms to the following rules: +A *length_pattern* is compatible with any type that is *countable* - it has an accessible property getter that returns an `int` and has the name `Length` or `Count`. If both properties are present, the former is preferred. -1. Is compatible with the *length_pattern* -2. Has an accessible indexer with a single `int` parameter +A *list_pattern* is compatible with any type that is *countable* as well as *indexable* - it has an accessible indexer that takes an `Index` or `int` argument. If both indexers are present, the former is preferred. - > **Open question**: Should we support `this[Index]` indexers? If so, which one is preferred if `this[int]` is also present? +A *slice_pattern* is compatible with any type that is *countable* as well as *sliceable* - it has an accessible indexer that takes a `Range` argument or otherwise an accessible `Slice` method that takes two `int` arguments. If both are present, the former is preferred. -A *slice_pattern* is compatible with any type that conforms to the following rules: +This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) but relaxed to ignore optional or `params` parameters, if any. -1. Is compatible with the *length_pattern* -2. Has an accessible `Slice` method that takes two `int` parameters (required only if a subpattern is specified) +#### Subsumption checking - > **Open question**: Should we support `this[Range]` indexers? If so, which one is preferred if `Slice(int, int)` is also present? +Subsumption checking works just like [positional patterns with `ITuple`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md#positional-pattern) - corresponding subpatterns are matched by position plus an additional node for testing length. -This set of rules is already specified as the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) and required for range indexers. +For example, the following code produces an error because both patterns yield the same DAG: -#### Subsumption checking +```cs +case {_, .., 1}: // expr.Length is >= 2 && expr[^1] is 1 +case {.., _, 1}: // expr.Length is >= 2 && expr[^1] is 1 +``` +Unlike: +```cs +case {_, 1, ..}: // expr.Length is >= 2 && expr[1] is 1 +case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1 +``` -Subsumption checking works just like positional patterns with `ITuple` - corresponding subpatterns are matched by position plus an additional node for testing length. +The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns. +> **Open question**: The pattern `{..}` tests for `expr.Length >= 0`. Should we omit such test (assuming `Length` is always non-negative)? +> #### Lowering A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code: @@ -108,21 +116,7 @@ expr.Length is >= 2 && expr[1..^1] is var s && expr[^1] is 3 ``` -The *input type* for the *slice_pattern* is the return type of the underlying `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively. - -Note: the lowering is presented in the pattern form here to show how subsumption checking works, for example, the following code produces an error because both patterns yield the same DAG: - -```cs -case {_, .., 1}: // expr.Length is >= 2 && expr[^1] is 1 -case {.., _, 1}: // expr.Length is >= 2 && expr[^1] is 1 -``` -Unlike: -```cs -case {_, 1, ..}: // expr.Length is >= 2 && expr[1] is 1 -case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1 -``` - -> **Open question**: The pattern `{..}` lowers to `expr.Length >= 0` so it would not be considered as a catch-all. Should we omit such test (assuming `Length` is always non-negative)? +The *input type* for the *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively. ### Additional types From 4f0ce7a16f87ceb09af3233294db66e1e396abaf Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 17 Mar 2021 21:33:50 +0330 Subject: [PATCH 13/16] Update list-patterns.md --- proposals/list-patterns.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 5f138ecded..f24e809713 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -80,6 +80,8 @@ A *slice_pattern* is compatible with any type that is *countable* as well as *sl This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) but relaxed to ignore optional or `params` parameters, if any. +> *Open question*: We should define the exact binding rules for any of these members and decide how much we're willing to diverge from the range spec. + #### Subsumption checking Subsumption checking works just like [positional patterns with `ITuple`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md#positional-pattern) - corresponding subpatterns are matched by position plus an additional node for testing length. From 5d338451ef005baaf4e09f093ae1b44d52a72a67 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 17 Mar 2021 21:35:13 +0330 Subject: [PATCH 14/16] Update list-patterns.md --- proposals/list-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index f24e809713..4321995ffc 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -80,7 +80,7 @@ A *slice_pattern* is compatible with any type that is *countable* as well as *sl This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) but relaxed to ignore optional or `params` parameters, if any. -> *Open question*: We should define the exact binding rules for any of these members and decide how much we're willing to diverge from the range spec. +> **Open question**: We should define the exact binding rules for any of these members and decide if we can diverge from the range spec. #### Subsumption checking From 644187ab36f51cabb1cf5e8e18a3af11622cca12 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 17 Mar 2021 22:06:09 +0330 Subject: [PATCH 15/16] Update list-patterns.md --- proposals/list-patterns.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index 4321995ffc..de739bffd8 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -4,7 +4,7 @@ Lets you to match an array or a list with a sequence of patterns e.g. `array is {1, 2, 3}` will match an integer array of the length three with 1, 2, 3 as its elements, respectively. -## Detailed Design +## Detailed design The pattern syntax is modified as follow: @@ -80,7 +80,7 @@ A *slice_pattern* is compatible with any type that is *countable* as well as *sl This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support) but relaxed to ignore optional or `params` parameters, if any. -> **Open question**: We should define the exact binding rules for any of these members and decide if we can diverge from the range spec. +> **Open question**: We should define the exact binding rules for any of these members and decide if we want to diverge from the range spec. #### Subsumption checking @@ -104,19 +104,19 @@ The order in which subpatterns are matched at runtime is unspecified, and a fail > #### Lowering -A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code: +A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code (if compatible via implicit `Index` support): ```cs expr.Length is 3 && expr[0] is 1 && expr[1] is 2 && expr[2] is 3 ``` -A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code: +A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is {1, .. var s, 3}` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): ```cs -expr.Length is >= 2 -&& expr[0] is 1 -&& expr[1..^1] is var s -&& expr[^1] is 3 +expr.Length is >= 2 +&& expr[new Index(0)] is 1 +&& expr[new Range(1, new Index(1, true))] is var s +&& expr[new Index(1, true)] is 3 ``` The *input type* for the *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively. From 9f5eaf9a8a8bd6487a9900f3b03b80406439d1b6 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Fri, 19 Mar 2021 13:33:43 +0330 Subject: [PATCH 16/16] Update list-patterns.md --- proposals/list-patterns.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/proposals/list-patterns.md b/proposals/list-patterns.md index de739bffd8..c86c784829 100644 --- a/proposals/list-patterns.md +++ b/proposals/list-patterns.md @@ -13,33 +13,33 @@ positional_pattern : type? positional_pattern_clause length_pattern_clause? property_or_list_pattern_clause? simple_designation? ; -length_pattern - : type? length_pattern_clause property_or_list_pattern_clause? simple_designation? +property_or_list_pattern_clause + : list_pattern_clause + | property_pattern_clause ; -list_pattern - : type? list_pattern_clause simple_designation? +property_pattern_clause + : '{' (subpattern (',' subpattern)* ','?)? '}' ; -property_pattern - : type? property_pattern_clause simple_designation? +list_pattern_clause + : '{' pattern (',' pattern)* ','? '}' ; length_pattern_clause : '[' pattern ']' ; -property_or_list_pattern_clause - : list_pattern_clause - | property_pattern_clause +length_pattern + : type? length_pattern_clause property_or_list_pattern_clause? simple_designation? ; -property_pattern_clause - : '{' (subpattern (',' subpattern)* ','?)? '}' +list_pattern + : type? list_pattern_clause simple_designation? ; -list_pattern_clause - : '{' pattern (',' pattern)* ','? '}' +property_pattern + : type? property_pattern_clause simple_designation? ; slice_pattern @@ -55,7 +55,8 @@ primary_pattern ``` There are three new patterns: -- The *list_pattern* is used to match elements and the *length_pattern* is used to match the length. +- The *list_pattern* is used to match elements. +- The *length_pattern* is used to match the length. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. > **Open question**: Should we accept a general *pattern* following `..` in a *slice_pattern*? @@ -101,7 +102,7 @@ case {.., 1, _}: // expr.Length is >= 2 && expr[^2] is 1 The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns. > **Open question**: The pattern `{..}` tests for `expr.Length >= 0`. Should we omit such test (assuming `Length` is always non-negative)? -> + #### Lowering A pattern of the form `expr is {1, 2, 3}` is equivalent to the following code (if compatible via implicit `Index` support):