-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open LDM Issues in Pattern-Matching (v2) #2095
Comments
Reiterating my comment on that, I think the disambiguation is fine as long as we don't have parenthesized patterns. But then, removing the discard would completely change the pattern semantics. Consider another example: if (o is (var x) _) // deconstruct and bind anything from deconstruction
if (o is (var x)) // matches everything; equivalent to `o is var x` I expect this'll be confusing because the designation is essentially optional in recursive patterns. |
Without parenthesized patterns, |
@alrz Another reason to disallow that is we can't distinguish between |
Right, I think the cast ambiguity has been resolved by LDM before (#1054). The proposed workaround was to use an empty property pattern clause: |
The good side is that extension methods can be opt-in by using namespaces, so we can still choose which one to use. |
Yes, but that resolution no longer "works" with relaxed rules for when a single-element positional pattern is permitted. |
Sure but |
Added Rename deconstruct_pattern to positional_pattern/cc @jcouv |
Added Disambiguate declaration patterns with (non-nullable) arrays of nullable typeDue to the strange order of nullable annotations on arrays, we will need some disambiguation rule to resolve the parsing of declaration expressions vs the if (o is A[] ? b
: c) // OK, this means `(o is A[]) ? b : c` because nullable types are not permitted for a pattern's type
if (o is A[] ? b
&& c) // error: 'cannot use nullable reference type for a pattern' or 'expected :'
if (o is A[][] ? b
: c) // OK, this means `(o is A[][]) ? b : c`
if (o is A[][] ? b
&& c) // OK, this means `(o is A[][]? b) && c` because `A[][]?` is not a nullable type See also dotnet/roslyn#31991 and dotnet/roslyn#32025 |
Changed the last item to Disambiguate declaration patterns with (non-nullable) arrays of nullable typeThe introduction of nullable reference types introduced a breaking change in the parsing of array types. It is recommended that we reconsider the order in which nullable annotations are considered in multidimensional arrays to resolve the ambiguity and permit a compatible implementation. |
Added Should SwitchExpressionException capture an unreified tuple input?See #31482 |
Added a resolution and a new issue: Disambiguate declaration patterns with (non-nullable) arrays of nullable typeThe introduction of nullable reference types introduced a breaking change in the parsing of array types. It is recommended that we reconsider the order in which nullable annotations are considered in multidimensional arrays to resolve the ambiguity and permit a compatible implementation. Partial Resolution per LDM 2019-01-07: Keep the order of array designators. But a new syntax resolution rule: following an No way to match a multidimensional array with an inner nullableAnother small issue with our resolution to See dotnet/roslyn#32141. How do I write a pattern-matching operation where the type is “an array of a nullable array”? That would (un)naturally be written if (o is string[][]? s) but we decided that, since there is an identifier after the |
Changed |
Say that ten times fast... |
Once we have parenthesized patterns, it could be written as |
@alrz Placing parens like |
There is a ternary-like syntax in patterns, because an expression is a pattern (to support constant patterns). |
I've marked a number of issues resolved based on today's LDM. Search for Resolution 2019-01-09. In particular we've changed the interaction of array dimension specifiers and the nullable type syntax. The new resolution is that the |
This is one ambiguity: if (e is int[][]? b // ternary or array of nullable array?
: c)
if (e is int[][]? b // ternary or array of nullable array?
&& c) it gets worse with recursive patterns if (e is int[][]? (3, 4) // ternary or array of nullable array in a recursive pattern?
: c)
if (e is int[][]? (3, 4) // ternary or array of nullable array in a recursive pattern?
&& c) Note that this could be semantically legal if you write an extension With the resolution from today's LDM, the nullable annotation for the inner array goes between the if (e is int[][]? b // obviously ternary
: c)
if (e is int[]?[] b // obviously declaration pattern
&& c)
if (e is int[][]? (3, 4) // obviously ternary
: c)
if (e is int[]?[] (3, 4) // obviously recursive pattern
&& c) |
I was saying that in a hypothetical parenthesized pattern, you can't have a ternary after if (e is (int[][]? x)) The possible token after |
Can using System;
struct X
{
// Deconstruct methods with side effects
public void Deconstruct() => Console.WriteLine("side effect in ()");
public void Deconstruct(out int x)
{
Console.WriteLine("side effect in (int)");
x = 0;
}
public void Deconstruct(out int x, out int y)
{
Console.WriteLine("side effect in (int, int)");
(x, y) = (0, 1);
}
}
class Program
{
static int Zero() => new X() switch { () => 0 };
static int One() => new X() switch { var (_) => 0 };
static int Two() => new X() switch { (_, _) => 0 };
static int Zero(object x) => x switch { X() => 0, _ => 1 };
static int One(object x) => x switch { X(_) => 0, _ => 1 };
static int Two(object x) => x switch { X(_, _) => 0, _ => 1 };
static void Main()
{
// There is no Deconstruct called, no side effect in VS 2019 Preview 2
Console.WriteLine(Zero());
Console.WriteLine(One());
Console.WriteLine(Two());
Console.WriteLine(Zero(new X()));
Console.WriteLine(One(new X()));
Console.WriteLine(Two(new X()));
}
} |
According to the specification, yes. See https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md#order-of-evaluation-in-pattern-matching which says in part
Note "should" is not "must". The compiler is permitted but discouraged from invoking |
Added Reserve
|
I posted this in #2268 static string Quadrant(Point p) => p switch
{
(0, 0) => "origin",
(var x, var y) when x > 0 && y > 0 => "Quadrant 1",
(var x, var y) when x < 0 && y > 0 => "Quadrant 2",
(var x, var y) when x < 0 && y < 0 => "Quadrant 3",
(var x, var y) when x > 0 && y < 0 => "Quadrant 4",
(var x, var y) => "on a border",
_ => "unknown"
}; I wondered if it can be shorter and more readable to achive the purpose of pattern matching, so I suggest this: static string Quadrant(Point p) => p switch
{
(0, 0) => "origin",
(> 0, > 0) => "Quadrant 1",
(< 0, > 0) => "Quadrant 2",
(< 0, < 0) => "Quadrant 3",
(> 0, < 0) => "Quadrant 4",
(_, _) => "on a border",
_ => "unknown"
}; |
@MohammadHamdyGhanem That idea is already championed by me at #2115 . |
class Point
{
public void Deconstruct(double Angle, double Length) => ...;
public void Deconstruct(int X, int Y) => ...;
}
Point p = ...;
if (p is (Y: 3, X: 4)) ...; // What will happen?
if (p is (X: 3.0, Y: 4.0)) ...; // What will happen?
if (p is (Angle: 3, Length: 4)) ...; // What will happen? |
@Thaina We don't disambiguate or reorder based on parameter names. All three are ambiguous. As a practical matter, you should have only one |
@gafter That what I ask |
All three are ambiguity errors at compile-time. |
@gafter Also this if (p is (Angle: 3, Length: 4)) ...; is ambiguous but should it did implicit conversion? Or is it not allow implicit conversion? What about this case? if (p is (Angle: 3f, Length: 4f)) ...; Also if we encourage that we would have many |
This is exactly the same behavior as deconstruction. If you have two deconstruct methods of the needed arity, it is just an error. |
AddedPropose to change precedence of switch expression to primary (open)The switch expression is currently at relational precedence. I propose to change it to primary precedence. See https://github.com/dotnet/csharplang/issues/2331 for details. |
All of these open issues are addressed. Closing. |
Open LDM Issues in Pattern-Matching
This is a summary of the remaining open issues for pattern-matching in C# 8.
Reserve
and
andor
in patterns (open)In anticipation of possibly permitting
and
andor
as pattern combinators in the future, we should forbid (or at least warn) when these identifiers are used as the designator in a declaration or recursive pattern.See also dotnet/roslyn#33425
Resolution: We did not do this in C# 8.0.
Propose to change precedence of switch expression to primary (open)
The switch expression is currently at relational precedence. I propose to change it to primary precedence. See https://github.com/dotnet/csharplang/issues/2331 for details.
Resolution: It gets a new precedence just looser than primary.
Single-element positional deconstruction (open)
The current LDM decision on single-element positional deconstruction pattern is that the type is required.
This is particularly inconvenient for tuple types and other generic types, and perhaps impossible for values deconstructed by a dynamic check via
ITuple
.We should consider if other disambiguation should be permitted (e.g. the presence of a designation).
Resolution 2018-10-10: Discussed but not resolved.
Disambiguating deconstruction using parameter names (open)
We could permit more than one
Deconstruct
method if we permit disambiguating by parameter names.Should we do this?
Resolution: No, not at this time.
var pattern for 0 and 1 elements (open)
The var pattern currently requires 2 or more elements because it inherits the grammar for variable_designation. However, both 0-element and 1-element var patterns would make sense and are syntactically unambiguous.
I propose we relax the grammar to permit 0-element and 1-element var patterns.
We could do the same for deconstruction.
Resolution: Approved for patterns. Left to future consideration for deconstruction.
Kind of member in a property pattern (open)
A property pattern
{ PropName : Pattern }
is used to check if the value of a property or field matches the given pattern.Besides readable properties and fields, what other kinds of members should be permitted here?
Resolution: None of these at this time.
Restricted types (open)
A recent bug (dotnet/roslyn#27803) revealed how pattern matching interacts with restricted types in interesting ways (when doing type checks).
We should discuss with LDM on whether this should be banned (like pointer types), and if there are actual use cases that need it.
Resolution: Pointers actually work, e.g.
p is null
orp is var x
. Restricted types work too. No action.Matching using ITuple (open)
I assume that we only intend to permit matching using
ITuple
when the type is omitted?Resolution: Confirmed.
Matching using
ITuple
in the presence of an extensionDeconstruct
(needs proposal)When an extension
Deconstruct
method is present and a type also implements theITuple
interface, it is not clear which should take priority. I believe the current LDM position (probably not intentional) is that the extension method is used for the deconstruction declaration or deconstruction assignment, andITuple
is used for pattern-matching. We probably want to reconcile these to be the same.Discussion 2018-10-24: No conclusion. We need a proposal to consider.
Resolution: The current LDM position is confirmed.
Switch expression as a statement expression (closed)
It has been requested that we permit a switch expression to be used as a statement expression. In order to make this work, we'd
void
.void
is more specific than any other type, so it can be inferred as the result type.Resolution 2018-10-10: Yes! This requires a more precise spec, and it needs to handle
a?.M()
whereM()
returns an uncontrainedT
. Excluded from C# 8.0.ITuple vs unconstrained type parameter (closed)
Our current rules require an error in the following, and it will not attempt to match using
ITuple
. Is that what we want?Resolution 2018-10-24: Lets keep it an error for now but be open to relaxing it in the future.
Permit a trailing comma in a switch expression (closed)
See #2098
Resolution 2019-01-09: Yes.
Rename deconstruct_pattern to positional_pattern (closed)
Resolution 2019-01-09: Yes.
Disambiguate declaration patterns with (non-nullable) arrays of nullable type (closed)
The introduction of nullable reference types introduced a breaking change in the parsing of array types. It is recommended that we reconsider the order in which nullable annotations are considered in multidimensional arrays to resolve the ambiguity and permit a compatible implementation.
See dotnet/roslyn#32141
Partial Resolution per LDM 2019-01-07: Keep the order of array designators. But a new syntax resolution rule: following an
is
,as
, ornew
in an expression, an array type may not end with a?
unless the?
is followed by a token that cannot start an expression. The?
token (if not considered to be part of the array specifier) will then naturally be considered to be the conditional operator, which restores compatibility with existing code.Resolution 2019-01-09: Reverted. We are going with second option in dotnet/roslyn#32141 (comment). See also the "No way to match a multidimensional array with an inner nullable" issue below.
Should SwitchExpressionException capture an unreified tuple input? (closed)
See dotnet/roslyn#31482
Resolution 2019-01-09: Yes.
No way to match a multidimensional array with an inner nullable (closed)
Another small issue with our resolution to See dotnet/roslyn#32141. How do I write a pattern-matching operation where the type is “an array of a nullable array”? That would (un)naturally be written
but we decided that, since there is an identifier after the
?
, the?
designates the start of a ternary operator and this code is a syntax error. This is something I think people are likely to want to do (since an array typically may contain nulls), so it would be a shame to disallow it.Resolution 2019-01-09: This (and its extension to a problem in recursive patterns) was the straw that broke the camel's back, and caused us to revisit the interaction of the nullable annotation and array dimension specifiers. The new resolution is that the
?
annotation is a type constructor that can be applied to array types, and an array type does not have any?
among its array specifiers. This is the second option in dotnet/roslyn#32141 (comment).The text was updated successfully, but these errors were encountered: