From 80d4ce931598ace3c4acf282346cc783d968aae1 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Mon, 11 Sep 2017 23:29:55 +0300 Subject: [PATCH 01/12] Eye of sauron --- text/0000-eye-of-sauron.md | 201 +++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 text/0000-eye-of-sauron.md diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md new file mode 100644 index 00000000000..9581234f7e0 --- /dev/null +++ b/text/0000-eye-of-sauron.md @@ -0,0 +1,201 @@ +- Feature Name: eye_of_sauron +- Start Date: 2017-09-11 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow for method-dispatch-style trait-driven autoderef and autoref in operators. + +# Motivation +[motivation]: #motivation + +In today's Rust, working with operators is annoying, because they are supposed to be a "lightweight" syntax like methods and field accesses, but unlike them, they do not have coercions on their LHS and therefore require explicit autoderefs and autorefs. + +One common example where this is a nuisance is when using iterator adapters, working which can easily create multiple-reference types such as `&&u32`. For example: + +```Rust + let v : Vec = vec![0, 1, 2, 3]; + // Why can't I just write `x > 1`? Why do I have to ** for the compiler??? + v.iter().filter(|x| **x > 1).count(); +``` + +There are several cases where these operators are used. The most popular case is indexing associative maps: + +```Rust + use std::collections::HashMap; + let x: HashMap<_, _> = + vec![(format!("hello"), format!("world"))].into_iter().collect(); + let s = format!("hello"); + + // I would like to write... + println!("{}", x[s]); + // But instead, I have to write... + println!("{}", x[&s]); +``` + +In this case, these are merely annoying papercuts. In some other cases, they can be a much worse problem. + +One of thim is Isis Lovecruft's "Eye of Sauron" case, which is a problem when using non-`Copy` bignums: + +```Rust,ignore +struct Bignum(..); +// fair enough +impl<'a> Add for &'a Bignum { + type Output = Bignum; + // ... +} +// ... + +// I can't see what my code is doing! It looks like one big piece of &. +let a = &(-(&A)) * &(&one + &nrr).invert(); +let z = &u * &(&(&u.square() + &(&A * &u)) + &one); + +// Would be better: +let a = (-A) * (one + nrr).invert(); +let z = u * (u.square() + A * u + one); +``` + +Allowing method-style autoref could make operator uses as clean as methods and fields, making code that uses them intensively far more ergonomic. It's also a fairly non-invasive extension. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Like methods, operators and indexing support automatic referencing and dereferencing. When you use an operator, the compiler will automatically add `&`, `&mut` and `*` operators to match the signature of the operator. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +"Operators" here refers to several typeck operators: +- The "standard" eager binary operators: `+`, `-`, `*`, `/`, `%`, `^`, `&`, `|`, `<<`, `>>`, `==`, `<`, `<=`, `!=`, `>=`, `>`. +- The "standard" unary operators: `!`, `-`. This does *NOT* include the dereference operator `*`. +- The indexing operator `...[...]` + +Operator type-checking behaves similarly to method type-checking. It works as follows: + +## S1. Subexpression checking + +Both the LHS and the RHS (if binary) of the operator are first type-checked with no expected type. + +This differs from rustc 1.20, in which an expected type was sometimes propagated from the LHS into the RHS, potentially triggering coercions within the RHS. I should probably come up with an example in which this matters. + +## S2. Adjustment selection + +Afterwards, an adjustment list is selected for both operands as follows: + +Adjustment lists for the LHS of an indexing operation are selected from these matching the following regular expression: +``` +"Deref"* "Autoref(Immutable)" "ConvertArrayToSlice"? +``` + +Adjustment lists for all other operands (including the RHS of indexing operations) are selected from these matching the following regular expression +``` +"Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? +``` + +The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that +A1. Both adjustment lists match the relevant regular expressions +A2. Both adjustment lists must be valid to apply to their operand types. +A3. After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). + A3.1. NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. + +If the smallest adjustment can't be determined because of the presence of inference variables (because it is not obvious whether an adjustment list would be valid to apply), this is a compilation error. + +## S3. Fixups + +After adjustments are selected, the following fixups are made. They do not affect adjustment selection. + +### Mutability Fixup + +If overloaded indexing is used in a mutable context, the `Autoref(Immutable)` adjustment of the LHS is replaced with an `Autoref(Mutable)` adjustment, and the entire chain is required to be consistent with the new mutability (using the `DerefMut` and `IndexMut` traits when needed). If they can't be, this is a compilation error. + +### Arithmetic Fixup + +If an arithmetic operator was used, and both types are integer or float inference variables, their types are unified as if there existed an impl generic over integer inference variables, e.g. + ```Rust + impl Add for I { // or modify for other operators + type Output = I; + // .. + } + ``` + +This is required in order to make `1 + 2` (both parameters are integer inference variables) be known to be an integer before integer defaulting. + +## Operator Adjustments + +These are basically the same as method adjustments, but because these are underdocumented: for the purpose of overloaded operators, an adjustment is defined as follows: + +```Rust +type Adjustments = Vec; +#[derive(PartialOrd, Ord, PartialEq, Eq)] +enum Mutability { + Immutable, + Mutable +} + +#[derive(PartialOrd, Ord, PartialEq, Eq)] +enum Adjustment { + Autoref(Mutability), + ConvertArrayToSlice, + // this must be last, and means that k+1 derefs is always > k derefs + Deref, +} +``` + +Adjustments have the following effect on types +``` +adjust(Deref, ty) = /* do immutable autoderef */ +adjust(Autoref(Immutable), ty) = Some(`&$ty`) +adjust(ConvertArrayToSlice, &[ty; N]) = Some(`&[$ty]`) +adjust(ConvertArrayToSlice, &mut [ty; N]) = Some(`&mut [$ty]`) +adjust(ConvertArrayToSlice, _) = None + +adjust_list(adjustments, ty) = + let mut ty = ty; + for adjustment in adjustments { + ty = if let Some(ty) = adjust(adjustment, ty) { + ty + } else { + return None; + } + } + Some(ty) +``` + +And have the obvious effect on values. Adjustments are ordered using the standard lexicographical order. + +# Drawbacks +[drawbacks]: #drawbacks + +The 2-argument inference adds more complexity to type-checking. It probably has some "interesting" and unexpected uses in some situations, and we need to gain some experience with it before stabilization. + +The automatic references can make it less obvious when values are passed by reference or by value. Because operator traits are very general, the created autorefs can easily escape outside of the operator. + +# Rationale and Alternatives +[alternatives]: #alternatives + +The "Eye of Sauron" case is very annoying for mathematical code, and therefore is something we want to fix. This fix feels rather elegant and is contained to binary operators. + +We need to use special coercion logic rather than the "standard" expected type propagation because we are coercing on the basis of trait impls. Therefore, options such as [RFC 2111] will not help in all cases. We could instead try to add trait-impl-based propagation, but that would not solve the "binary" nature of operator traits. + +[RFC 2111]: https://github.com/rust-lang/rfcs/pull/2111 + +Indexing is included in this feature basically because it behaves like the other operators. It's possible that this should not be done. + +Because the arithmetic fixup does not apply with references, there could be inference issues with integer variables: +``` +let x = &1 + &2; // this uses the <&i32 as Add<&i32>> impl, + // so `x` has type `_` before integer fallback +x.foo(); //~ ERROR cannot infer the type +``` +therefore, We might want to extend the arithmetic fixup to references to integer variables in some way. + +We might want to allow mutable autorefs in more cases. That would be more consistent with method lookup, but is probablly a bad idea. + +# Unresolved questions +[unresolved]: #unresolved-questions + +- Should we change the set of operators? +- What priority order should operators use? I think lexicographic is best, but there are other options. +- Should we allow for more general coercions than autoref and autoderef? e.g. function item to function pointer coercions? Is there any use for that? Does it bring disadvantages? From a605a22be0ab34de3785268c45bc05a31a5d6265 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 00:09:52 +0300 Subject: [PATCH 02/12] V2 --- text/0000-eye-of-sauron.md | 69 ++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index 9581234f7e0..9e2c103da84 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -168,21 +168,71 @@ And have the obvious effect on values. Adjustments are ordered using the standar # Drawbacks [drawbacks]: #drawbacks +### Inference Complexity + The 2-argument inference adds more complexity to type-checking. It probably has some "interesting" and unexpected uses in some situations, and we need to gain some experience with it before stabilization. +### Unexpected References + The automatic references can make it less obvious when values are passed by reference or by value. Because operator traits are very general, the created autorefs can easily escape outside of the operator. +First, I don't feel this is worse than the situation with method calls - they can also create unexpected references under pretty much the same situations. + +Second, and similarly to method calls, this is somewhat alleviated by the compiler picking the by-value implementation if the type matches, possibly causing move errors even if autoref would have worked: + +```Rust,ignore +struct Bignum(..); +// fair enough +impl<'a> Add for &'a Bignum { + type Output = Bignum; + // ... impl, allocating +} +impl<'a> Add<&'a Bignum> for Bignum { + type Output = Bignum; + // ... impl, not allocating +} + +let a = bignum1 + bignum2; // moves bignum1 +let b = bignum1 + 1; //~ ERROR use of moved value + +let c = &bignum3 + bignum4; // allocates, does not use bignum3. +let d = bignum3 + 1; // works +``` + +However, this actually might tempt people to write only by-ref allocating impls for their types to avoid "unexpected" moves. We might want to gather more experience here. + # Rationale and Alternatives [alternatives]: #alternatives The "Eye of Sauron" case is very annoying for mathematical code, and therefore is something we want to fix. This fix feels rather elegant and is contained to binary operators. +## Alternative Solutions + We need to use special coercion logic rather than the "standard" expected type propagation because we are coercing on the basis of trait impls. Therefore, options such as [RFC 2111] will not help in all cases. We could instead try to add trait-impl-based propagation, but that would not solve the "binary" nature of operator traits. -[RFC 2111]: https://github.com/rust-lang/rfcs/pull/2111 +The essential reason we need to consider the "binary" nature is because of the `v.iter().filter(|x| x > 1)` case. If we did method-style inference, we would pick 0 dereferences for `x` - using the `PartialOrd<&&u32> for &&u32` impl combination - and will then fail when trying to coerce the `1`. + +If we already want a "binary" solution, this "double method lookup" is the simplest solution I know of. + +# Unresolved questions +[unresolved]: #unresolved-questions + +## Flexibility Points + +These are small places where the RFC can be changed. I wrote down the version I liked the most, and this RFC was getting too long and pedantic even without me trying to include alternatives. + +Possible alternatives are: + +### Different set of operators Indexing is included in this feature basically because it behaves like the other operators. It's possible that this should not be done. +### Different way to pick the adjustment list + +Lexicographic ordering feels like the right way to pick the adjustment list, but it might have unexpected edge cases which might justify a more complicated ordering. + +### Extended arithmetic fiixup + Because the arithmetic fixup does not apply with references, there could be inference issues with integer variables: ``` let x = &1 + &2; // this uses the <&i32 as Add<&i32>> impl, @@ -191,11 +241,16 @@ x.foo(); //~ ERROR cannot infer the type ``` therefore, We might want to extend the arithmetic fixup to references to integer variables in some way. -We might want to allow mutable autorefs in more cases. That would be more consistent with method lookup, but is probablly a bad idea. +### Mutable autorefs -# Unresolved questions -[unresolved]: #unresolved-questions +We might want to allow mutable autorefs in more cases. That would be more consistent with method lookup, but is probably a bad idea because it allows for too easy implicit mutability. + +### Lifetime limits -- Should we change the set of operators? -- What priority order should operators use? I think lexicographic is best, but there are other options. -- Should we allow for more general coercions than autoref and autoderef? e.g. function item to function pointer coercions? Is there any use for that? Does it bring disadvantages? +[RFC 2111] limits the lifetime of implicit autorefs to the containing expression. We might also want to do that here to avoid confusing escaping autorefs. + +### General Coercions + +We might want to allow for more general coercions than autoref and autoderef. For example, function item to function pointer coercions. Is there any use for that? Does it bring disadvantages? + +[RFC 2111]: https://github.com/rust-lang/rfcs/pull/2111 From 910b8765fdcaba1b041a1322a1d093bcf3d4b71a Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 01:01:24 +0300 Subject: [PATCH 03/12] fiixed --- text/0000-eye-of-sauron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index 9e2c103da84..641b6648f3d 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -231,7 +231,7 @@ Indexing is included in this feature basically because it behaves like the other Lexicographic ordering feels like the right way to pick the adjustment list, but it might have unexpected edge cases which might justify a more complicated ordering. -### Extended arithmetic fiixup +### Extended arithmetic fixup Because the arithmetic fixup does not apply with references, there could be inference issues with integer variables: ``` From 5f5d2145e681bbb26944b92608149304ab5a6f66 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 14:58:52 +0300 Subject: [PATCH 04/12] improvements --- text/0000-eye-of-sauron.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index 641b6648f3d..a76640516f7 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -62,7 +62,7 @@ Allowing method-style autoref could make operator uses as clean as methods and f # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Like methods, operators and indexing support automatic referencing and dereferencing. When you use an operator, the compiler will automatically add `&`, `&mut` and `*` operators to match the signature of the operator. +Like methods, operators and indexing support automatic referencing and dereferencing. When you use an operator, the compiler will automatically add `&` and `*` operators to match the signature of the operator. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -249,6 +249,37 @@ We might want to allow mutable autorefs in more cases. That would be more consis [RFC 2111] limits the lifetime of implicit autorefs to the containing expression. We might also want to do that here to avoid confusing escaping autorefs. +### Improving impls + +Instead of adding autoderef for operators, we could try adding enough trait impls for operators. + +For example, with lattice specialization, we could have a trait with the following impls: +```Rust +trait MyAutoderef {} +impl MyAutoderef for T {} +impl<'a, U, V> MyAutoderef for V where + V: Deref, + V::Target: MyAutoderef {} +// this is the special "lattice join" impl that is needed +// to make the compiler shut up. +impl<'a, T> MyAutoderef for T + where T: Deref, + T::Target: MyAutoderef +{} +``` + +And then we could have impls +```Rust +impl, V: MyAutoderef> Add for V { + // ... +} +``` + +However: +A. Lattice specialization requires that specialization will work well, which might take quite a bit of time to figure out. +B. As written, the operator impls will all conflict with each-other and all other impls of operators and wildly break type inference. For every pair of types `U` and `V`, you'll have to convince the compiler that a type couldn't be both `MyAutoderef` and `MyAutoderef`, which would be non-trivial, or find some way to prioritize the impls, which will add more complexity and won't work cross-crate. +C. Without autoref, types that are not `Copy` will be moved. For example, with this RFC you can index a `HashMap` with a `String` *without moving the `String`*, which can't be done by an impl. + ### General Coercions We might want to allow for more general coercions than autoref and autoderef. For example, function item to function pointer coercions. Is there any use for that? Does it bring disadvantages? From eb55528de2fe8b897747ed2bee55347f5a333ec9 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 22:58:38 +0300 Subject: [PATCH 05/12] clarify --- text/0000-eye-of-sauron.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index a76640516f7..b691fcc897f 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -74,35 +74,33 @@ Like methods, operators and indexing support automatic referencing and dereferen Operator type-checking behaves similarly to method type-checking. It works as follows: -## S1. Subexpression checking +## Step 1 - Subexpression checking Both the LHS and the RHS (if binary) of the operator are first type-checked with no expected type. This differs from rustc 1.20, in which an expected type was sometimes propagated from the LHS into the RHS, potentially triggering coercions within the RHS. I should probably come up with an example in which this matters. -## S2. Adjustment selection +## Step 2 - Adjustment selection Afterwards, an adjustment list is selected for both operands as follows: -Adjustment lists for the LHS of an indexing operation are selected from these matching the following regular expression: +Adjustment lists for the LHS of an indexing operator (the `X` in `X[Y]`) are selected from these matching the following regular expression: ``` "Deref"* "Autoref(Immutable)" "ConvertArrayToSlice"? ``` -Adjustment lists for all other operands (including the RHS of indexing operations) are selected from these matching the following regular expression +Adjustment lists for all other operands (including the RHS of indexing operator, as well as all operands of all other operators) are selected from these matching the following regular expression ``` "Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? ``` The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that A1. Both adjustment lists match the relevant regular expressions -A2. Both adjustment lists must be valid to apply to their operand types. +A2. Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that would apply, that is a compilation error (this is the "can't autoderef because of inference variables" case). A3. After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). A3.1. NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. - -If the smallest adjustment can't be determined because of the presence of inference variables (because it is not obvious whether an adjustment list would be valid to apply), this is a compilation error. -## S3. Fixups +## Step 3 - Fixups After adjustments are selected, the following fixups are made. They do not affect adjustment selection. From 0fbad4bcf875281273905285befc9b5c5aac8363 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 22:59:35 +0300 Subject: [PATCH 06/12] clarify --- text/0000-eye-of-sauron.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index b691fcc897f..b0b7787a27f 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -96,7 +96,7 @@ Adjustment lists for all other operands (including the RHS of indexing operator, The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that A1. Both adjustment lists match the relevant regular expressions -A2. Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that would apply, that is a compilation error (this is the "can't autoderef because of inference variables" case). +A2. Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that applies and might match the operator trait, that is a compilation error (this is the "can't autoderef because of inference variables" case). A3. After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). A3.1. NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. From 9373938bf2bdcb8ea8598af673ed9f6268a8f49a Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Tue, 12 Sep 2017 23:07:37 +0300 Subject: [PATCH 07/12] Fix markdown --- text/0000-eye-of-sauron.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index b0b7787a27f..c508c69a28c 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -94,11 +94,12 @@ Adjustment lists for all other operands (including the RHS of indexing operator, "Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? ``` -The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that -A1. Both adjustment lists match the relevant regular expressions -A2. Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that applies and might match the operator trait, that is a compilation error (this is the "can't autoderef because of inference variables" case). -A3. After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). - A3.1. NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. +The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that: + + - **A1** – Both adjustment lists match the relevant regular expressions + - **A2** – Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that applies and might match the operator trait, that is a compilation error (this is the "can't autoderef because of inference variables" case). + - **A3** – After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). + - **A3.1** – NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. ## Step 3 - Fixups From e1a26242eb3dd4fe71a9e66a7497d69965d26fbf Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 13 Sep 2017 19:00:22 +0300 Subject: [PATCH 08/12] more method lookup details + examples --- text/0000-eye-of-sauron.md | 325 +++++++++++++++++++++++++++++++++---- 1 file changed, 291 insertions(+), 34 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index c508c69a28c..b070e875d9a 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -69,41 +69,257 @@ Like methods, operators and indexing support automatic referencing and dereferen "Operators" here refers to several typeck operators: - The "standard" eager binary operators: `+`, `-`, `*`, `/`, `%`, `^`, `&`, `|`, `<<`, `>>`, `==`, `<`, `<=`, `!=`, `>=`, `>`. +- The "standard" in-place binary operators: `+=`, `-=`, `*=`, `/=`, `%=`, `^=`, `&=`, `|=`, `<<=`, `>>=`. - The "standard" unary operators: `!`, `-`. This does *NOT* include the dereference operator `*`. - The indexing operator `...[...]` -Operator type-checking behaves similarly to method type-checking. It works as follows: - -## Step 1 - Subexpression checking +After this RFC, operator type-checking behaves as follows: Both the LHS and the RHS (if binary) of the operator are first type-checked with no expected type. This differs from rustc 1.20, in which an expected type was sometimes propagated from the LHS into the RHS, potentially triggering coercions within the RHS. I should probably come up with an example in which this matters. -## Step 2 - Adjustment selection +Then, it performs method-style lookup as this: + +1. The following dispatchable arguments: argument #0 has uncoerced type `lhs_ty`, and, if this is a binary operator, argument #1 has uncoerced type `rhs_ty`. +2. For just the LHS of an indexing operator (the `X` in `X[Y]`), and both operands of a comparison operator (i.e. `==`, `<`, `<=`, `!=`, `>=`, `>`), adjustment lists must match the following regular expression: + ``` + "Deref"* "Autoref(Immutable)" "ConvertArrayToSlice"? + ``` + For just the LHS of in-place binary operators, adjustment lists must match the following regular expression: + ``` + "Deref"* "Autoref(Mutable)" "ConvertArrayToSlice"? + ``` + For all other operators (including the RHS of an indexing operator), adjustment lists must match the following regular expression: + ``` + "Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? + ``` +3. One method candidate - this is the obvious operator method. For indexing, this is always `Index::index` - if needed, it will be "upgraded" to `IndexMut::index_mut` through a mutability fixup (this might matter for some edge cases in inference, but rustc 1.20 is inconsistent in that regard - sometimes it can combine the lookup and the mutability fixup). + +Then, if indexing was used in a mutable context, the [Mutablity Fixup] will be applied to it. + +## Method-Style Lookup + +Now this description depended on a few details of method type-checking, some of them slightly modified. + +Operator type-checking behaves similarly to method type-checking. + +Now, I normally would have documented the few needed *changes* to method lookup, but it is ill-documented today, so here's a description of it. + +Method lookup is parameterized on several things: +1. An ordered list of (argument #, type) list of unadjusted dispatchable arguments (before this RFC, there could only be 1 dispatchable argument - the method receiver - but the logic generalizes). +2. For each dispatchable argument, the set of usable adjustment lists for it. +3. The set of method candidates - this is a set of methods, one of them is to be selected. + +### Step 1 - Adjustment list set determination + +First, we determine the final set of `(adjustment list, adjusted argument type)` pairs for each dispatchable argument + +For each usable adjustment list for that argument: +- If it can be successfully applied to the (unadjusted) argument type, add the adjustment list along with the adjusted argument type to the final set. +- If it can be proven to fail when applied to the type, ignore it. +- Otherwise, this is an ambiguity and a compilation error. + +#### EXAMPLE 1. + +For example, this code yields a compilation error (in all versions of rustc): +```Rust + trait Id { fn id(self); } + impl Id for T { fn id(self) {} } + + let mut x = None; + + if let Some(ref x) = x { + // x: &_ here + // the adjustment list `Deref Deref` works only if `_: Deref`, and that + // can either fail or succeed, so we get an error. Note that the *empty* + // adjustment list would have worked just fine, but we determine the + // set of adjustment lists first + x.id(); //~ ERROR + } + + x = Some(()); +``` + +Similar examples could be created for operator dispatch (that would not fail in rustc 1.20), and I hope these will not be much of a problem in practice. + +### Step 2 - Adjustment list selection + +Then, we pick the assignment of adjustment lists from the cartesian product of the adjustment list sets - one for each dispatchable argument. + +We pick the first-in-lexicographic-order assignment where there is at least 1 candidate in the candidate set that might apply to that assignment. If no such assignment exists, it is a compilation error. + +A candidate might apply to an assignment unless subtyping the candidate's dispatchable argument types with the assignment's respective adjusted dispatchable argument types proves that one of the candidate's predicates can't hold (if the subtyping can't be done, that vacuously proves that the predicates can't hold). + +#### EXAMPLE 2. (Arithmetic) + +For example, with operators: +```Rust +trait Add { + type Output; + fn add(self, rhs: Self) -> Self::Output + // there's a `where Self: Add` implicit predicate + ; +} +/* A */ impl Add for u32 { type Output=u32; /* .. */ } +/* B */ impl<'a> Add<&'a u32> for u32 { type Output=u32; /* .. */ } +/* C */ impl<'b> Add for &'b u32 { type Output=u32; /* .. */ } +/* D */ impl<'a, 'b> Add<&'a u32> for &'b u32 { type Output=u32; /* .. */ } +/* E */ impl Add for i32 { type Output=i32; /* .. */ } +/* F */ impl<'a> Add<&'a i32> for i32 { type Output=i32; /* .. */ } +/* G */ impl<'b> Add for &'b i32 { type Output=i32; /* .. */ } +/* H */ impl<'a, 'b> Add<&'a i32> for &'b i32 { type Output=i32; /* .. */ } +// + +println!("{}", 1 + 1); +``` + +Our candidate method is `Add::add`, and both arguments have (different) inference variable types `$int0` and `$int1`. + +The usable adjustment lists are of the form `"Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )?`. Because `Deref` and `ConvertArrayToSlice` can't be used on integers, we are left with the following adjustment lists (they set is identical for both locals except the variable changes names): + +``` +([], $int0/$int1) +([Autoref(Immutable)], &$int0/&$int1) +``` + +The cartesian product, in order, is +``` +(arg0=([], ty=$int0), arg1=([], ty=$int1)) +(arg0=([Autoref(Immutable)], ty=&$int0), arg1=([], ty=$int1)) +(arg0=([], ty=$int0), arg1=([Autoref(Immutable)], ty=&$int1)) +(arg0=([Autoref(Immutable)], ty=&$int0), arg1=([Autoref(Immutable)], ty=&$int1)) +``` + +For the first assignment, we see that our candidate might apply: `$int0: Add<$int1>` can hold using both impls `A` and `E`, and there are no other interesting predicates, so we select the first adjustment list and candidate. + +Later on, arithmetic fixup gives us a return type, and at the end inference fallback picks `i32` for the variable. + +#### EXAMPLE 3. (Reference arithmetic) + +Suppose we are now checking the `>`-operator in following method: +```Rust +trait PartialOrd { + fn lt(&self, other: &Rhs) -> bool; + // (irrelevant code omitted) +} + +/* I */ impl PartialOrd for i32 { /* .. */ } +/* J */ impl<'a, 'b, A, B> PartialOrd<&'b B> for &'a A where A: PartialOrd + { /* .. */ } + +fn foo(v: Vec) { + v.iter().filter(|x: &&i32| x > 0); +} +``` + +In older versions of rustc, this would fail and require playing with inference to make it work. With the new operator semantics, let's see how it works. + +`>` is a by-ref operator, so our adjustment lists must include an autoref. For the LHS, we can have either 0, 1, or 2 derefs, and `ConvertArrayToSlice` is irrelevant, so we have the following CLS: +``` +([Autoref(Immutable)], &&&i32) +([Deref, Autoref(Immutable)], &&i32) +([Deref, Deref, Autoref(Immutable)], &i32) +``` + +For the RHS, we can't have any non-zero number of derefs, s +``` +([Autoref(Immutable)], &$int0) +``` + +We then go over the cartesian product: +``` +lhs=([Autoref(Immutable)], &&&i32), +rhs=([Autoref(Immutable)], &$int0) + - subtype `&Self <: &&&i32, &RHS <: &$int0` + - we have Self=&&i32, RHS=$int0 + - &&i32: PartialOrd<$int0> can't hold, ignoring +lhs=([Deref, Autoref(Immutable)], &&i32) +rhs=([Autoref(Immutable)], &$int0) + - subtype `&Self <: &&i32, &RHS <: &$int0` + - we have Self=&i32, RHS=$int0 + - &i32: PartialOrd<$int0> can't hold, ignoring +lhs=([Deref, Deref, Autoref(Immutable)], &i32) +rhs=([Autoref(Immutable)], &$int0) + - subtype `&Self <: &i32, &RHS <: &$int0` + - we have Self=i32, RHS=$int0 + - i32: PartialOrd<$int0> can hold (impl I), success! +``` + +So we perform 2 derefs of the LHS and 0 derefs of the RHS (plus 2 autorefs) and succeed. + +#### EXAMPLE 4. (Adding strings) + +One nice thing this RFC solves is adding strings. + +The current (and future) relevant impls are: + +```Rust +/* K */ impl Deref for String { type Target = str; /* .. */ } +/* L */ impl<'a, B: ?Sized> Deref for Cow<'a, B> { type Target = B; /* .. */ } +/* M */ impl<'a> Add<&'a str> for String { type Output = String; /* .. */ } +/* N */ impl<'a> Add> for Cow<'a, str> { type Output = Self; /* .. */ } +/* O */ impl<'a> Add<&'a str> for Cow<'a, str> { type Output = Self; /* .. */ } +``` -Afterwards, an adjustment list is selected for both operands as follows: +Now, `String + &str` and `Cow + &str` obviously work. I want to look at the `String + String` and `Cow + Cow` cases. -Adjustment lists for the LHS of an indexing operator (the `X` in `X[Y]`) are selected from these matching the following regular expression: +For `String + String`, we have the following CLS for both the LHS and RHS: ``` -"Deref"* "Autoref(Immutable)" "ConvertArrayToSlice"? +([], String) +([Autoref(Immutable)], &String) +([Deref], str) - yes this is unsized +([Deref, Autoref(Immutable)], &str) ``` -Adjustment lists for all other operands (including the RHS of indexing operator, as well as all operands of all other operators) are selected from these matching the following regular expression +We then go over the cartesian product: ``` -"Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? +lhs=String, rhs=String - no match +lhs=String, rhs=&String - no match +lhs=String, rhs=str - no match +lhs=String, rhs=&str - match! ``` -The adjustment lists selected are the lexicographically first pair of adjustment lists `(lhs_adjust, rhs_adjust)` (or with an unary op, just the `lhs_adjust`) such that: +So we do a deref + autoref of the RHS. This means that only the LHS will be moved out - the RHS will only be borrowed, so you can write: +```Rust +let x = "foo".to_string(); +let y = "bar".to_string(); +let z = x + y; +println!("{} {}", z, y); +``` + +This works just as well for `Cow + String`. When adding `Cow + Cow`, the situation is different: +``` +lhs=Cow<'a, str>, rhs=Cow<'a, str> - match! (using impl N) +``` + +This means that we are doing by-value addition, and will move out the RHS (same as today). Removing impl N would be a breaking change at this moment, but it would improve UX so it might we worth investigating. - - **A1** – Both adjustment lists match the relevant regular expressions - - **A2** – Both adjustment lists must be valid to apply to their operand types. If, due to the presence of inference variables, it can't be determined whether these adjustment lists would be valid to apply, and we didn't find a smaller adjustment list that applies and might match the operator trait, that is a compilation error (this is the "can't autoderef because of inference variables" case). - - **A3** – After applying both adjustment lists, the adjusted operand types are a potential match for the operator trait (if there is an ambiguity because of inference variables, it is counted as a match). - - **A3.1** – NOTE: the operator trait for overloaded indexing is `Index`, not `IndexMut`, even if indexing is done in a mutable context. rustc 1.20 is inconsistent in that regard. - -## Step 3 - Fixups -After adjustments are selected, the following fixups are made. They do not affect adjustment selection. +#### EXAMPLE 5. (Adding bignums, refs only) + +The relevant impls are just: + +```Rust +struct FieldElement; +impl<'a, 'b> Add<&'b FieldElement> for &'a FieldElement { + type Output = FieldElement; + // .. +} +``` + +And we are adding 2 bignums `a + b`. There are no derefs, so the CLS for both the LHS and RHS are: + +### Step 3 - Candidate selection + +After adjustments are selected, we select the candidate (for operators, this is trivial, because there is only ever 1 candidate). + +- If there is exactly 1 candidate, it is selected +- If there are multiple candidates, but exactly 1 high-priority candidate, it is selected. +- Otherwise, this is a compilation error. + + +Then following fixups are made. They do not affect adjustment or candidate selection. ### Mutability Fixup @@ -121,7 +337,7 @@ If an arithmetic operator was used, and both types are integer or float inferen This is required in order to make `1 + 2` (both parameters are integer inference variables) be known to be an integer before integer defaulting. -## Operator Adjustments +## Adjustments These are basically the same as method adjustments, but because these are underdocumented: for the purpose of overloaded operators, an adjustment is defined as follows: @@ -144,24 +360,58 @@ enum Adjustment { Adjustments have the following effect on types ``` -adjust(Deref, ty) = /* do immutable autoderef */ -adjust(Autoref(Immutable), ty) = Some(`&$ty`) -adjust(ConvertArrayToSlice, &[ty; N]) = Some(`&[$ty]`) -adjust(ConvertArrayToSlice, &mut [ty; N]) = Some(`&mut [$ty]`) -adjust(ConvertArrayToSlice, _) = None - -adjust_list(adjustments, ty) = - let mut ty = ty; - for adjustment in adjustments { - ty = if let Some(ty) = adjust(adjustment, ty) { - ty - } else { - return None; - } - } - Some(ty) +~ is eqty, !~ is "not eqty", "!:" is "no impl for any substitution of inference variables". + +Adjust rules: + +T : Deref +------------ +adjust(Deref, T) = Success(::Target) + +T !: Deref +------------ +adjust(Deref, T) = Failure + +T type +------------ +adjust(Autoref(Immutable), T) = Success(&T) +adjust(Autoref(Mutable), T) = Success(&mut T) + +T, E types +n constant usize +T ~ &[E; n] +------------ +adjust(ConvertArrayToSlice, T) = Success(&[E]) + +T, E types +n constant usize +T ~ &mut [E; n] +------------ +adjust(ConvertArrayToSlice, T) = Success(&mut [E]) + +T type +∀E type, n constant usize. T !~ &[E; n] +∀E type, n constant usize. T !~ &mut [E; n] +------------ +adjust(ConvertArrayToSlice, T) = Failure + +And `adjust_list` is just adjust mapped over lists: +------------ +adjust_list([], T) = Success(T) + +adjust(a, T) = Failure +------------ +adjust_list([a, as], T) = Failure + +RESULT result +adjust(a, T) = Success(U) +adjust_list([as], U) = RESULT +------------ +adjust_list([a, as]) T = RESULT ``` +The intent of the "included middle"-style rules is that if we can't determine whether we can apply an adjustment due to inference variables, we can't determine success or failure (and that should result in a compilation error). + And have the obvious effect on values. Adjustments are ordered using the standard lexicographical order. # Drawbacks @@ -284,3 +534,10 @@ C. Without autoref, types that are not `Copy` will be moved. For example, with t We might want to allow for more general coercions than autoref and autoderef. For example, function item to function pointer coercions. Is there any use for that? Does it bring disadvantages? [RFC 2111]: https://github.com/rust-lang/rfcs/pull/2111 + +### Appendix A. Method Dispatch + +This is supposed to describe method dispatch as it was before this RFC. + +TBD + From fd05a42f4c946243b28cb2d5af79cff3e782de3d Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 13 Sep 2017 19:05:31 +0300 Subject: [PATCH 09/12] finish eye of sauron example --- text/0000-eye-of-sauron.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index b070e875d9a..f80d5931ad0 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -295,20 +295,33 @@ lhs=Cow<'a, str>, rhs=Cow<'a, str> - match! (using impl N) This means that we are doing by-value addition, and will move out the RHS (same as today). Removing impl N would be a breaking change at this moment, but it would improve UX so it might we worth investigating. +#### EXAMPLE 5. (Adding field elements, refs only) -#### EXAMPLE 5. (Adding bignums, refs only) - -The relevant impls are just: +This is the case from Isis Lovecruft's "eye of sauron" example. The relevant impls are just: ```Rust struct FieldElement; -impl<'a, 'b> Add<&'b FieldElement> for &'a FieldElement { +/* P */ impl<'a, 'b> Add<&'b FieldElement> for &'a FieldElement { type Output = FieldElement; // .. } ``` -And we are adding 2 bignums `a + b`. There are no derefs, so the CLS for both the LHS and RHS are: +And we are adding 2 field elements `a + b`. There are no derefs, so the CLS for both the LHS and RHS are: +``` +([], FieldElement) +([Autoref(Immutable)], &FieldElement) +``` + +And we go over the cartesian product, and pick the impl with both autorefs: +``` +lhs=FieldElement, rhs=FieldElement - no match +lhs=FieldElement, rhs=&FieldElement - no match +lhs=&FieldElement, rhs=FieldElement - no match +lhs=&FieldElement, rhs=&FieldElement - match! +``` + +We aren't doing any moves, and everything works! ### Step 3 - Candidate selection From d666beddc00f63ab777921209ac8f0223f39eedd Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 13 Sep 2017 21:49:46 +0300 Subject: [PATCH 10/12] readability --- text/0000-eye-of-sauron.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index f80d5931ad0..fa6ebc15e3a 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -79,7 +79,7 @@ Both the LHS and the RHS (if binary) of the operator are first type-checked with This differs from rustc 1.20, in which an expected type was sometimes propagated from the LHS into the RHS, potentially triggering coercions within the RHS. I should probably come up with an example in which this matters. -Then, it performs method-style lookup as this: +Then, it performs method-style lookup with the following parameters: 1. The following dispatchable arguments: argument #0 has uncoerced type `lhs_ty`, and, if this is a binary operator, argument #1 has uncoerced type `rhs_ty`. 2. For just the LHS of an indexing operator (the `X` in `X[Y]`), and both operands of a comparison operator (i.e. `==`, `<`, `<=`, `!=`, `>=`, `>`), adjustment lists must match the following regular expression: @@ -96,24 +96,22 @@ Then, it performs method-style lookup as this: ``` 3. One method candidate - this is the obvious operator method. For indexing, this is always `Index::index` - if needed, it will be "upgraded" to `IndexMut::index_mut` through a mutability fixup (this might matter for some edge cases in inference, but rustc 1.20 is inconsistent in that regard - sometimes it can combine the lookup and the mutability fixup). -Then, if indexing was used in a mutable context, the [Mutablity Fixup] will be applied to it. +Then, if indexing was used in a mutable context, the [Mutablity Fixup](#mutability-fixup) will be applied to it. ## Method-Style Lookup -Now this description depended on a few details of method type-checking, some of them slightly modified. - -Operator type-checking behaves similarly to method type-checking. - -Now, I normally would have documented the few needed *changes* to method lookup, but it is ill-documented today, so here's a description of it. +This description depended on a few details of method type-checking, some of them slightly modified. I normally would have documented the few needed *changes* to method lookup, but it is ill-documented today, so here's a description of it: Method lookup is parameterized on several things: 1. An ordered list of (argument #, type) list of unadjusted dispatchable arguments (before this RFC, there could only be 1 dispatchable argument - the method receiver - but the logic generalizes). 2. For each dispatchable argument, the set of usable adjustment lists for it. 3. The set of method candidates - this is a set of methods, one of them is to be selected. +Method-style lookup proceeds as follows: + ### Step 1 - Adjustment list set determination -First, we determine the final set of `(adjustment list, adjusted argument type)` pairs for each dispatchable argument +First, final set of `(adjustment list, adjusted argument type)` pairs is determined for each dispatchable argument For each usable adjustment list for that argument: - If it can be successfully applied to the (unadjusted) argument type, add the adjustment list along with the adjusted argument type to the final set. @@ -145,9 +143,11 @@ Similar examples could be created for operator dispatch (that would not fail in ### Step 2 - Adjustment list selection -Then, we pick the assignment of adjustment lists from the cartesian product of the adjustment list sets - one for each dispatchable argument. +Then, the best assignment of adjustment lists is picked from the cartesian product of the adjustment list sets - one for each dispatchable argument. -We pick the first-in-lexicographic-order assignment where there is at least 1 candidate in the candidate set that might apply to that assignment. If no such assignment exists, it is a compilation error. +The picked assignment is the first assignment (in lexicographic order) from the cartesian product such that is at least 1 candidate in the candidate set that might apply to that assignment. + +If no such assignment exists, it is a compilation error. A candidate might apply to an assignment unless subtyping the candidate's dispatchable argument types with the assignment's respective adjusted dispatchable argument types proves that one of the candidate's predicates can't hold (if the subtyping can't be done, that vacuously proves that the predicates can't hold). @@ -325,20 +325,23 @@ We aren't doing any moves, and everything works! ### Step 3 - Candidate selection -After adjustments are selected, we select the candidate (for operators, this is trivial, because there is only ever 1 candidate). +After adjustments are selected, the candidate is selected (for operators, this is trivial, because there is only ever 1 candidate) according to the following rules: - If there is exactly 1 candidate, it is selected - If there are multiple candidates, but exactly 1 high-priority candidate, it is selected. - Otherwise, this is a compilation error. +### Step 4 - Fixups Then following fixups are made. They do not affect adjustment or candidate selection. -### Mutability Fixup +#### Mutability Fixup + +If we performed a mutable autoref, or are performing overloaded indexing in a mutable context, we need to apply a *mutability fixup* to the adjustments and to other lvalue components on the way. -If overloaded indexing is used in a mutable context, the `Autoref(Immutable)` adjustment of the LHS is replaced with an `Autoref(Mutable)` adjustment, and the entire chain is required to be consistent with the new mutability (using the `DerefMut` and `IndexMut` traits when needed). If they can't be, this is a compilation error. +This proceeds by replacing immutable borrows in the lvalue path with mutable borrows, and adding `DerefMut` and `IndexMut` trait bounds when appropriate. If the trait bounds fail, this is of course a compilation error. -### Arithmetic Fixup +#### Arithmetic Fixup If an arithmetic operator was used, and both types are integer or float inference variables, their types are unified as if there existed an impl generic over integer inference variables, e.g. ```Rust @@ -352,7 +355,7 @@ This is required in order to make `1 + 2` (both parameters are integer inference ## Adjustments -These are basically the same as method adjustments, but because these are underdocumented: for the purpose of overloaded operators, an adjustment is defined as follows: +For the purpose of method lookup, adjustments are as follows: for the purpose of overloaded operators, an adjustment is defined as follows: ```Rust type Adjustments = Vec; From 15de261a528d2837960d48e35ab66c1cdce98334 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 13 Sep 2017 22:05:00 +0300 Subject: [PATCH 11/12] adjustment list/adjustment -> adjustment/adjustment step The previous naming was ugly. --- text/0000-eye-of-sauron.md | 76 ++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index fa6ebc15e3a..3201cc1cd57 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -82,15 +82,15 @@ This differs from rustc 1.20, in which an expected type was sometimes propagated Then, it performs method-style lookup with the following parameters: 1. The following dispatchable arguments: argument #0 has uncoerced type `lhs_ty`, and, if this is a binary operator, argument #1 has uncoerced type `rhs_ty`. -2. For just the LHS of an indexing operator (the `X` in `X[Y]`), and both operands of a comparison operator (i.e. `==`, `<`, `<=`, `!=`, `>=`, `>`), adjustment lists must match the following regular expression: +2. For just the LHS of an indexing operator (the `X` in `X[Y]`), and both operands of a comparison operator (i.e. `==`, `<`, `<=`, `!=`, `>=`, `>`), adjustments must match the following regular expression: ``` "Deref"* "Autoref(Immutable)" "ConvertArrayToSlice"? ``` - For just the LHS of in-place binary operators, adjustment lists must match the following regular expression: + For just the LHS of in-place binary operators, adjustments must match the following regular expression: ``` "Deref"* "Autoref(Mutable)" "ConvertArrayToSlice"? ``` - For all other operators (including the RHS of an indexing operator), adjustment lists must match the following regular expression: + For all other operators (including the RHS of an indexing operator), adjustments must match the following regular expression: ``` "Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )? ``` @@ -104,17 +104,17 @@ This description depended on a few details of method type-checking, some of them Method lookup is parameterized on several things: 1. An ordered list of (argument #, type) list of unadjusted dispatchable arguments (before this RFC, there could only be 1 dispatchable argument - the method receiver - but the logic generalizes). -2. For each dispatchable argument, the set of usable adjustment lists for it. +2. For each dispatchable argument, the set of usable adjustments for it. 3. The set of method candidates - this is a set of methods, one of them is to be selected. Method-style lookup proceeds as follows: ### Step 1 - Adjustment list set determination -First, final set of `(adjustment list, adjusted argument type)` pairs is determined for each dispatchable argument +First, final set of `(adjustment, adjusted argument type)` pairs is determined for each dispatchable argument -For each usable adjustment list for that argument: -- If it can be successfully applied to the (unadjusted) argument type, add the adjustment list along with the adjusted argument type to the final set. +For each usable adjustment for that argument: +- If it can be successfully applied to the (unadjusted) argument type, add the adjustment along with the adjusted argument type to the final set. - If it can be proven to fail when applied to the type, ignore it. - Otherwise, this is an ambiguity and a compilation error. @@ -129,10 +129,10 @@ For example, this code yields a compilation error (in all versions of rustc): if let Some(ref x) = x { // x: &_ here - // the adjustment list `Deref Deref` works only if `_: Deref`, and that + // the adjustment `[Deref, Deref]` works only if `_: Deref`, and that // can either fail or succeed, so we get an error. Note that the *empty* - // adjustment list would have worked just fine, but we determine the - // set of adjustment lists first + // adjustment would have worked just fine, but we determine the + // set of adjustments first x.id(); //~ ERROR } @@ -143,7 +143,7 @@ Similar examples could be created for operator dispatch (that would not fail in ### Step 2 - Adjustment list selection -Then, the best assignment of adjustment lists is picked from the cartesian product of the adjustment list sets - one for each dispatchable argument. +Then, the best assignment of adjustments is picked from the cartesian product of the adjustment sets - one for each dispatchable argument. The picked assignment is the first assignment (in lexicographic order) from the cartesian product such that is at least 1 candidate in the candidate set that might apply to that assignment. @@ -176,7 +176,7 @@ println!("{}", 1 + 1); Our candidate method is `Add::add`, and both arguments have (different) inference variable types `$int0` and `$int1`. -The usable adjustment lists are of the form `"Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )?`. Because `Deref` and `ConvertArrayToSlice` can't be used on integers, we are left with the following adjustment lists (they set is identical for both locals except the variable changes names): +The usable adjustments are of the form `"Deref"* ( "Autoref(Immutable)" "ConvertArrayToSlice"? )?`. Because `Deref` and `ConvertArrayToSlice` can't be used on integers, we are left with the following adjustments (they set is identical for both locals except the variable changes names): ``` ([], $int0/$int1) @@ -191,7 +191,7 @@ The cartesian product, in order, is (arg0=([Autoref(Immutable)], ty=&$int0), arg1=([Autoref(Immutable)], ty=&$int1)) ``` -For the first assignment, we see that our candidate might apply: `$int0: Add<$int1>` can hold using both impls `A` and `E`, and there are no other interesting predicates, so we select the first adjustment list and candidate. +For the first assignment, we see that our candidate might apply: `$int0: Add<$int1>` can hold using both impls `A` and `E`, and there are no other interesting predicates, so we select the first adjustment and candidate. Later on, arithmetic fixup gives us a return type, and at the end inference fallback picks `i32` for the variable. @@ -215,7 +215,7 @@ fn foo(v: Vec) { In older versions of rustc, this would fail and require playing with inference to make it work. With the new operator semantics, let's see how it works. -`>` is a by-ref operator, so our adjustment lists must include an autoref. For the LHS, we can have either 0, 1, or 2 derefs, and `ConvertArrayToSlice` is irrelevant, so we have the following CLS: +`>` is a by-ref operator, so our adjustment must include an autoref. For the LHS, we can have either 0, 1, or 2 derefs, and `ConvertArrayToSlice` is irrelevant, so we have the following CLS: ``` ([Autoref(Immutable)], &&&i32) ([Deref, Autoref(Immutable)], &&i32) @@ -355,10 +355,13 @@ This is required in order to make `1 + 2` (both parameters are integer inference ## Adjustments -For the purpose of method lookup, adjustments are as follows: for the purpose of overloaded operators, an adjustment is defined as follows: +For the purpose of method lookup, adjustments are described as follows: + +An *adjustment* is a list of *adjustment-steps*, which work in-order when applied. Note that adjustment-steps are lexprs that take lexprs, so when a non-trivial adjustment is performed on a vexpr, a vexpr-to-lexpr conversion (a temporary) is created with the appropriate temporary lifetime before the first step, and a lexpr-to-vexpr conversion happens after the last step. ```Rust -type Adjustments = Vec; +struct Adjustment = Vec; + #[derive(PartialOrd, Ord, PartialEq, Eq)] enum Mutability { Immutable, @@ -366,7 +369,7 @@ enum Mutability { } #[derive(PartialOrd, Ord, PartialEq, Eq)] -enum Adjustment { +enum AdjustmentStep { Autoref(Mutability), ConvertArrayToSlice, // this must be last, and means that k+1 derefs is always > k derefs @@ -374,56 +377,59 @@ enum Adjustment { } ``` -Adjustments have the following effect on types +Adjustments are typed using the following rules: ``` ~ is eqty, !~ is "not eqty", "!:" is "no impl for any substitution of inference variables". -Adjust rules: +******** TYPING RULES FOR EACH STEP ******** T : Deref ------------ -adjust(Deref, T) = Success(::Target) +adjust-step(Deref, T) = Success(::Target) T !: Deref ------------ -adjust(Deref, T) = Failure +adjust-step(Deref, T) = Failure T type ------------ -adjust(Autoref(Immutable), T) = Success(&T) -adjust(Autoref(Mutable), T) = Success(&mut T) +adjust-step(Autoref(Immutable), T) = Success(&T) +adjust-step(Autoref(Mutable), T) = Success(&mut T) T, E types n constant usize T ~ &[E; n] ------------ -adjust(ConvertArrayToSlice, T) = Success(&[E]) +adjust-step(ConvertArrayToSlice, T) = Success(&[E]) T, E types n constant usize T ~ &mut [E; n] ------------ -adjust(ConvertArrayToSlice, T) = Success(&mut [E]) +adjust-step(ConvertArrayToSlice, T) = Success(&mut [E]) T type ∀E type, n constant usize. T !~ &[E; n] ∀E type, n constant usize. T !~ &mut [E; n] ------------ -adjust(ConvertArrayToSlice, T) = Failure +adjust-step(ConvertArrayToSlice, T) = Failure + +******** TYPING RULES FOR ADJUST ******** + +`adjust` is just `adjust-step` lifted over a list, but because typing rules are supposed to be formal: -And `adjust_list` is just adjust mapped over lists: ------------ -adjust_list([], T) = Success(T) +adjust([], T) = Success(T) -adjust(a, T) = Failure +adjust-step(a, T) = Failure ------------ -adjust_list([a, as], T) = Failure +adjust([a, as], T) = Failure RESULT result -adjust(a, T) = Success(U) -adjust_list([as], U) = RESULT +adjust-step(a, T) = Success(U) +adjust([as], U) = RESULT ------------ -adjust_list([a, as]) T = RESULT +adjust([a, as]) T = RESULT ``` The intent of the "included middle"-style rules is that if we can't determine whether we can apply an adjustment due to inference variables, we can't determine success or failure (and that should result in a compilation error). @@ -492,9 +498,9 @@ Possible alternatives are: Indexing is included in this feature basically because it behaves like the other operators. It's possible that this should not be done. -### Different way to pick the adjustment list +### Different way to pick the adjustment -Lexicographic ordering feels like the right way to pick the adjustment list, but it might have unexpected edge cases which might justify a more complicated ordering. +Lexicographic ordering feels like the right way to pick the adjustment, but it might have unexpected edge cases which might justify a more complicated ordering. ### Extended arithmetic fixup From df42b1df220d27876976b54dc93cdcb0b592cad3 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 13 Sep 2017 23:39:23 +0300 Subject: [PATCH 12/12] document method dispatch --- text/0000-eye-of-sauron.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/text/0000-eye-of-sauron.md b/text/0000-eye-of-sauron.md index 3201cc1cd57..d5b0d9df619 100644 --- a/text/0000-eye-of-sauron.md +++ b/text/0000-eye-of-sauron.md @@ -149,7 +149,7 @@ The picked assignment is the first assignment (in lexicographic order) from the If no such assignment exists, it is a compilation error. -A candidate might apply to an assignment unless subtyping the candidate's dispatchable argument types with the assignment's respective adjusted dispatchable argument types proves that one of the candidate's predicates can't hold (if the subtyping can't be done, that vacuously proves that the predicates can't hold). +A candidate might apply to an assignment unless subtyping the candidate's dispatchable argument types with the assignment's respective adjusted dispatchable argument types, proves that one of the candidate's predicates can't hold (if the subtyping can't be done, that vacuously proves that the predicates can't hold). #### EXAMPLE 2. (Arithmetic) @@ -559,7 +559,20 @@ We might want to allow for more general coercions than autoref and autoderef. Fo ### Appendix A. Method Dispatch -This is supposed to describe method dispatch as it was before this RFC. +This is supposed to describe method dispatch as it is before this RFC. -TBD +Method dispatch does method-style lookup, with the following parameters: +1. The only dispatchable argument is the receiver, which must be the first argument. +2. Adjustments match the following regular expressions: + ``` + "Deref"* ( ( "Autoref(Immutable)" | "Autoref(Mutable)" ) "ConvertArrayToSlice"? )? + ``` +3. The applicable candidates are described below. + 3.1. All (non-static) methods with the right name from traits in scope are normal-priority candidates. + 3.2. All inherent impl methods with the right name are high-priority candidates. + 3.3. Let the "autoderef list" be the types that can be received by repeatedly autoderef-ing the receiver (there can be no inference variiables causing ambiguity there, because these are a part of the adjustment regular expression). Then, the following methods are added as high-priority candidates: **NOTE: now that I've wrote these out loud, I see that they might warrant changing in a post-Chalk universe** + 3.3.1. For each type parameter `T` in the autoderef list, for each where-clause or supertrait of a where-clause in the parameter environment that is equivalent to one of the form `for<...> T: Trait<...>`, every method from that trait with the right name. + 3.3.2. For each trait object `Trait<..>` in the autoderef list, for each bound on that trait object and each supertrait of it, every method from that bound or supertrait of a bound with the right name. + +If one of the candidates added by rule 3.3 is selected, the respective where-clause or trait-object-bound is unified with the method's trait reference. If this fails, this is a compilation error.