From 80d5a87607a7b4e6c836e3b1a13c080c7af3b21d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 2 Oct 2017 13:00:20 -0700 Subject: [PATCH 01/10] Create tuple-equality.md --- proposals/tuple-equality.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 proposals/tuple-equality.md diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md new file mode 100644 index 0000000000..45495f6a63 --- /dev/null +++ b/proposals/tuple-equality.md @@ -0,0 +1,28 @@ +# Support for == and != on tuple types + +Allow expressions `t1 == t2` where `t1` and `t2` are tuple types of same cardinality, and evaluate them as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). + +Conversely it would allow `t1 != t2` and evaluate it as `temp1.Item1 != temp2.Item1 || temp1.Item2 != temp2.Item2`. + +As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`). + +## Details + +When binding the `==` (or `!=`) operator, if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. + +Both operands (and, in the case of tuple literals, their elements) are evaluated in order from left to right. Each pair of elements is then used as operands to bind the operator `==` (or `!=`), recursively. Any elements with compile-time type `dynamic` cause an error. The results of those element-wise comparisons are used as operands in a chain of conditional AND (or OR) operators. + +For instance, in the context of `(int, (int, int)) t1, t2;`, `t1 == (1, (2, 3))` would evaluate as `temp1.Item1 == temp2.Item1 && temp1.Item2.Item1 == temp2.Item2.Item1 && temp2.Item2.Item2 == temp2.Item2.Item2`. + +When a tuple literal is used as operand (on either side), it receives a converted tuple type formed by the element-wise conversions which are introduced when binding the operator `==` (or `!=`) element-wise. + +For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. + +## Compatibility + +If someone wrote their own `ValueTuple` types, including an implementation of the comparison operator, the proposed rule would cause their operator to be ignored. + +---- + +Relates to https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#relational-and-type-testing-operators +Relates to https://github.com/dotnet/csharplang/issues/190 From 46ea8b9b0d647a148728836072e2ba0221057f63 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 6 Oct 2017 12:50:10 -0700 Subject: [PATCH 02/10] Add section on evaluation order --- proposals/tuple-equality.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index 45495f6a63..22b554c2a5 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -18,6 +18,17 @@ When a tuple literal is used as operand (on either side), it receives a converte For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. +## Evaluation order +The left-hand-side value is evaluated first (including conversions), then the right-hand-side value (also including conversions), then the element-wise comparisons from left to right (with early exit based on existing rules for conditional AND/OR operators). + +For instance, if there is a conversion from type `A` to type `B` and `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: +- `new A(1)` and convert to `B` +- `new B(2)` +- `new B(3)` +- `new A(1)` and convert to `B` +- `GetTuple()` and convert to `(B, B)` +- then the element-wise comparisons and conditional logic is evaluated + ## Compatibility If someone wrote their own `ValueTuple` types, including an implementation of the comparison operator, the proposed rule would cause their operator to be ignored. From b1578b9f9a521a239623708a0a1b62a36ea86d39 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 6 Oct 2017 12:50:35 -0700 Subject: [PATCH 03/10] Update tuple-equality.md --- proposals/tuple-equality.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index 22b554c2a5..2c561dd4ef 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -21,7 +21,7 @@ For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for bot ## Evaluation order The left-hand-side value is evaluated first (including conversions), then the right-hand-side value (also including conversions), then the element-wise comparisons from left to right (with early exit based on existing rules for conditional AND/OR operators). -For instance, if there is a conversion from type `A` to type `B` and `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: +For instance, if there is a conversion from type `A` to type `B` and a method `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: - `new A(1)` and convert to `B` - `new B(2)` - `new B(3)` From 8df37af7afd6112d77a00d7ac6144e423a458405 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 28 Nov 2017 10:48:13 -0800 Subject: [PATCH 04/10] Update tuple-equality.md --- proposals/tuple-equality.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index 2c561dd4ef..01629183d3 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -1,14 +1,16 @@ # Support for == and != on tuple types -Allow expressions `t1 == t2` where `t1` and `t2` are tuple types of same cardinality, and evaluate them as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). +Allow expressions `t1 == t2` where `t1` and `t2` are tuple or nullable tuple types of same cardinality, and evaluate them as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). Conversely it would allow `t1 != t2` and evaluate it as `temp1.Item1 != temp2.Item1 || temp1.Item2 != temp2.Item2`. +In the nullable case, additional checks for `temp1.HasValue` and `temp2.HasValue` are used. For instance, `nullableT1 == nullableT2` evaluates as `temp1.HasValue == temp2.HasValue ? (temp1.HasValue ? ... : true) : false`. + As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`). ## Details -When binding the `==` (or `!=`) operator, if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. +When binding the `==` (or `!=`) operator and existing binding rules failed, then if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. If either or both sides are nullable, then whether the nullables have values is checked before comparing elements. Both operands (and, in the case of tuple literals, their elements) are evaluated in order from left to right. Each pair of elements is then used as operands to bind the operator `==` (or `!=`), recursively. Any elements with compile-time type `dynamic` cause an error. The results of those element-wise comparisons are used as operands in a chain of conditional AND (or OR) operators. @@ -19,15 +21,15 @@ When a tuple literal is used as operand (on either side), it receives a converte For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. ## Evaluation order -The left-hand-side value is evaluated first (including conversions), then the right-hand-side value (also including conversions), then the element-wise comparisons from left to right (with early exit based on existing rules for conditional AND/OR operators). +The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators). For instance, if there is a conversion from type `A` to type `B` and a method `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: -- `new A(1)` and convert to `B` +- `new A(1)` - `new B(2)` - `new B(3)` -- `new A(1)` and convert to `B` -- `GetTuple()` and convert to `(B, B)` -- then the element-wise comparisons and conditional logic is evaluated +- `new A(1)` +- `GetTuple()` +- then the element-wise conversions and comparisons and conditional logic is evaluated (convert `new A(1)` to type `B`, then compare it with `new B(4)`, and so on). ## Compatibility From 6749e14070eb9f1f5d519fdca3585523086bdd1c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 30 Jan 2018 12:39:31 -0800 Subject: [PATCH 05/10] Update tuple-equality.md --- proposals/tuple-equality.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index 01629183d3..8fdf6d4cba 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -33,7 +33,7 @@ For instance, if there is a conversion from type `A` to type `B` and a method `( ## Compatibility -If someone wrote their own `ValueTuple` types, including an implementation of the comparison operator, the proposed rule would cause their operator to be ignored. +Although the initial proposal had a compatiblity issue if someone wrote their own `ValueTuple` types with an implementation of the comparison operator, the current proposal maintains the existing behavior because the new rule comes into play last. ---- From eacff7068e5a63ee1759da747b1bfd3220482e13 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 31 Jan 2018 13:26:16 -0800 Subject: [PATCH 06/10] Fix B(4) --- proposals/tuple-equality.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index 8fdf6d4cba..cae6637cdb 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -27,7 +27,7 @@ For instance, if there is a conversion from type `A` to type `B` and a method `( - `new A(1)` - `new B(2)` - `new B(3)` -- `new A(1)` +- `new B(4)` - `GetTuple()` - then the element-wise conversions and comparisons and conditional logic is evaluated (convert `new A(1)` to type `B`, then compare it with `new B(4)`, and so on). From abf942ef76f30cdcf871d09c0705ba13b0bfb15d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 9 Feb 2018 23:43:15 -0800 Subject: [PATCH 07/10] Update tuple-equality.md --- proposals/tuple-equality.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index cae6637cdb..e8d879eddd 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -1,11 +1,13 @@ # Support for == and != on tuple types -Allow expressions `t1 == t2` where `t1` and `t2` are tuple or nullable tuple types of same cardinality, and evaluate them as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). +Allow expressions `t1 == t2` where `t1` and `t2` are tuple or nullable tuple types of same cardinality, and evaluate them roughly as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). Conversely it would allow `t1 != t2` and evaluate it as `temp1.Item1 != temp2.Item1 || temp1.Item2 != temp2.Item2`. In the nullable case, additional checks for `temp1.HasValue` and `temp2.HasValue` are used. For instance, `nullableT1 == nullableT2` evaluates as `temp1.HasValue == temp2.HasValue ? (temp1.HasValue ? ... : true) : false`. +When an element-wise comparison returns a non-bool result (for instance, when a non-bool user-defined `operator ==` or `operator !=` is used, or in a dynamic comparison), then that result will be either converted to `bool` or run through `operator true` or `operator false` to get a `bool`. The tuple comparison always ends up returning a `bool`. + As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`). ## Details @@ -20,6 +22,8 @@ When a tuple literal is used as operand (on either side), it receives a converte For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. +TODO add more details on conversion back to `bool` when a non-bool user-defined `operator ==` or a `dynamic` operand is involed in one of the element-wise comparisons. + ## Evaluation order The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators). From 2d52e6fac5f9ae1fdf44597d6929e3d35cbf3a30 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 13 Feb 2018 15:37:55 -0800 Subject: [PATCH 08/10] Update tuple-equality.md --- proposals/tuple-equality.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index e8d879eddd..b151b9d629 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -22,7 +22,12 @@ When a tuple literal is used as operand (on either side), it receives a converte For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. -TODO add more details on conversion back to `bool` when a non-bool user-defined `operator ==` or a `dynamic` operand is involed in one of the element-wise comparisons. +__TODO__ add more details on conversion back to `bool` when a non-bool user-defined `operator ==` or a `dynamic` operand is involed in one of the element-wise comparisons. + +### Deconstruction and conversions to tuple +In `(a, b) == x`, the fact that `x` can deconstruct into two elements does not play a role. That could conceivably be in a future proposal, although it would raise questions about `x == y` (is this a simple comparison or an element-wise comparison, and if so using what cardinality?). +Similarly, conversions to tuple play no role. + ## Evaluation order The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators). From 7ceeee2f465937231b9d2146a2c886daa9299e7b Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sun, 4 Mar 2018 12:43:13 -0800 Subject: [PATCH 09/10] Tuple case comes before overload resolution after all --- proposals/tuple-equality.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index b151b9d629..b8bd6f96a7 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -12,7 +12,8 @@ As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot b ## Details -When binding the `==` (or `!=`) operator and existing binding rules failed, then if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. If either or both sides are nullable, then whether the nullables have values is checked before comparing elements. +When binding the `==` (or `!=`) operator, the existing rules are: (1) dynamic case, (2) overload resolution, and (3) fail. +This proposal adds a tuple case between (1) and (2): if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. This tuple equality is also lifted onto nullable tuples. Both operands (and, in the case of tuple literals, their elements) are evaluated in order from left to right. Each pair of elements is then used as operands to bind the operator `==` (or `!=`), recursively. Any elements with compile-time type `dynamic` cause an error. The results of those element-wise comparisons are used as operands in a chain of conditional AND (or OR) operators. @@ -42,7 +43,7 @@ For instance, if there is a conversion from type `A` to type `B` and a method `( ## Compatibility -Although the initial proposal had a compatiblity issue if someone wrote their own `ValueTuple` types with an implementation of the comparison operator, the current proposal maintains the existing behavior because the new rule comes into play last. +If someone wrote their own `ValueTuple` types with an implementation of the comparison operator, it would have previously been picked up by overload resolution. But since the new tuple case comes before overload resolution, we would handle this case with tuple comparison instead of relying on the user-defined comparison. ---- From ce693ea4d360ba796688c654b8572434b84a5777 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 20 Mar 2018 14:07:13 -0700 Subject: [PATCH 10/10] Update tuple-equality.md --- proposals/tuple-equality.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/proposals/tuple-equality.md b/proposals/tuple-equality.md index b8bd6f96a7..dc2991a4e6 100644 --- a/proposals/tuple-equality.md +++ b/proposals/tuple-equality.md @@ -8,7 +8,7 @@ In the nullable case, additional checks for `temp1.HasValue` and `temp2.HasValue When an element-wise comparison returns a non-bool result (for instance, when a non-bool user-defined `operator ==` or `operator !=` is used, or in a dynamic comparison), then that result will be either converted to `bool` or run through `operator true` or `operator false` to get a `bool`. The tuple comparison always ends up returning a `bool`. -As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`). +As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`), unless there is a user-defined `operator==`. ## Details @@ -23,14 +23,29 @@ When a tuple literal is used as operand (on either side), it receives a converte For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. -__TODO__ add more details on conversion back to `bool` when a non-bool user-defined `operator ==` or a `dynamic` operand is involed in one of the element-wise comparisons. ### Deconstruction and conversions to tuple In `(a, b) == x`, the fact that `x` can deconstruct into two elements does not play a role. That could conceivably be in a future proposal, although it would raise questions about `x == y` (is this a simple comparison or an element-wise comparison, and if so using what cardinality?). Similarly, conversions to tuple play no role. +### Tuple element names -## Evaluation order +When converting a tuple literal, we warn when an explicit tuple element name was provided in the literal, but it doesn't match the target tuple element name. +We use the same rule in tuple comparison, so that assuming `(int a, int b) t` we warn on `d` in `t == (c, d: 0)`. + +### Non-bool element-wise comparison results + +If an element-wise comparison is dynamic in a tuple equality, we use a dynamic invocation of the operator `false` and negate that to get a `bool` and continue with further element-wise comparisons. + +If an element-wise comparison returns some other non-bool type in a tuple equality, there are two cases: +- if the non-bool type converts to `bool`, we apply that conversion, +- if there is no such conversion, but the type has an operator `false`, we'll use that and negate the result. + +In a tuple inequality, the same rules apply except that we'll use the operator `true` (without negation) instead of the operator `true`. + +Those rules are similar the rules involved for using a non-bool type in an `if` statement and some other existing contexts. + +## Evaluation order and special cases The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators). For instance, if there is a conversion from type `A` to type `B` and a method `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: @@ -41,6 +56,17 @@ For instance, if there is a conversion from type `A` to type `B` and a method `( - `GetTuple()` - then the element-wise conversions and comparisons and conditional logic is evaluated (convert `new A(1)` to type `B`, then compare it with `new B(4)`, and so on). +### Comparing `null` to `null` + +This is a special case from regular comparisons, that carries over to tuple comparisons. The `null == null` comparison is allowed, and the `null` literals do not get any type. +In tuple equality, this means, `(0, null) == (0, null)` is also allowed and the `null` and tuple literals don't get a type either. + +### Comparing a nullable struct to `null` without `operator==` + +This is another special case from regular comparisons, that carries over to tuple comparisons. +If you have a `struct S` without `operator==`, the `(S?)x == null` comparison is allowed, and it is interpreted as `((S?).x).HasValue`. +In tuple equality, the same rule is applied, so `(0, (S?)x) == (0, null)` is allowed. + ## Compatibility If someone wrote their own `ValueTuple` types with an implementation of the comparison operator, it would have previously been picked up by overload resolution. But since the new tuple case comes before overload resolution, we would handle this case with tuple comparison instead of relying on the user-defined comparison.