-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Implement RFC 1268 #40097
Implement RFC 1268 #40097
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
8dbb775
to
eb6af2d
Compare
The change in #709 had the side effect of re-introducing #104. With the design that we have right now, nullability isn't propagating upwards. This puts the issue of "expressions aren't validating that the type of its arguments haven't become nullable, and thus nulls are slipping in where they shouldn't be" at odds with "we can't use complex expressions in filters for joins because the SQL type changed". This semi-resolves the issue by restricting when we care about nullability. Ultimately the only time it really matters is when we're selecting data, as we need to enforce that the result goes into an `Option`. For places where we don't see the bytes in Rust (filter, order, etc), `NULL` is effectively `false`. This change goes back to fully fixing #104, but brings back a small piece of #621. I've changed everything that is a composite expression to only be selectable if the SQL type hasn't changed. This means that you won't be able to do things like `users.left_outer_join(posts).select(posts::id + 1)`, but you will be able to use whatever you want in `filter`. This change is also to support what I think will fix the root of all these issues. The design of "Here's the SQL type on this query source" is just fundamentally not what we need. There is only one case where the type changes, and that is to become null when it is on the right side of a left join, the left side of a right join, or either side of a full join. One of the changes that #709 made was to require that you explicitly call `.nullable()` on a tuple if you wanted to get `Option<(i32, String)>` instead of `(Option<i32>, Option<String>)`. This has worked out fine, and isn't a major ergonomic pain. The common case is just to use the default select clause anyway. So I want to go further down this path. The longer term plan is to remove `SqlTypeForSelect` entirely, and *not* implement `SelectableExpression` for columns on the nullable side of a join. We will then provide these two blanket impls: ```rust impl<Left, Right, T> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<T> where T: SelectableExpression<Right>, {} impl<Left, Right, Head, Tail> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<Cons<Head, Tail>> where Nullable<Head>: SelectableExpression<LeftOuterJoin<Left, Right>>, Nullable<Tail>: SelectableExpression<LeftOuterJoin<Left, Right>>, {} ``` (Note: Those impls overlap. Providing them as blanket impls would require rust-lang/rust#40097. Providing them as non-blanket impls would require us to mark `Nullable` and possibly `Cons` as `#[fundamental]`) The end result will be that nullability naturally propagates as we want it to. Given `sql_function!(lower, lower_t, (x: Text) -> Text)`, doing `select(lower(posts::name).nullable())` will work. `lower(posts::name)` will fail because `posts::name` doesn't impl `SelectableExpression`. `lower(posts::name.nullable())` will fail because while `SelectableExpression` will be met, the SQL type of the argument isn't what's expected. Putting `.nullable` at the very top level naturally follows SQL's semantics here.
The change in #709 had the side effect of re-introducing #104. With the design that we have right now, nullability isn't propagating upwards. This puts the issue of "expressions aren't validating that the type of its arguments haven't become nullable, and thus nulls are slipping in where they shouldn't be" at odds with "we can't use complex expressions in filters for joins because the SQL type changed". This semi-resolves the issue by restricting when we care about nullability. Ultimately the only time it really matters is when we're selecting data, as we need to enforce that the result goes into an `Option`. For places where we don't see the bytes in Rust (filter, order, etc), `NULL` is effectively `false`. This change goes back to fully fixing #104, but brings back a small piece of #621. I've changed everything that is a composite expression to only be selectable if the SQL type hasn't changed. This means that you won't be able to do things like `users.left_outer_join(posts).select(posts::id + 1)`, but you will be able to use whatever you want in `filter`. This change is also to support what I think will fix the root of all these issues. The design of "Here's the SQL type on this query source" is just fundamentally not what we need. There is only one case where the type changes, and that is to become null when it is on the right side of a left join, the left side of a right join, or either side of a full join. One of the changes that #709 made was to require that you explicitly call `.nullable()` on a tuple if you wanted to get `Option<(i32, String)>` instead of `(Option<i32>, Option<String>)`. This has worked out fine, and isn't a major ergonomic pain. The common case is just to use the default select clause anyway. So I want to go further down this path. The longer term plan is to remove `SqlTypeForSelect` entirely, and *not* implement `SelectableExpression` for columns on the nullable side of a join. We will then provide these two blanket impls: ```rust impl<Left, Right, T> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<T> where T: SelectableExpression<Right>, {} impl<Left, Right, Head, Tail> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<Cons<Head, Tail>> where Nullable<Head>: SelectableExpression<LeftOuterJoin<Left, Right>>, Nullable<Tail>: SelectableExpression<LeftOuterJoin<Left, Right>>, {} ``` (Note: Those impls overlap. Providing them as blanket impls would require rust-lang/rust#40097. Providing them as non-blanket impls would require us to mark `Nullable` and possibly `Cons` as `#[fundamental]`) The end result will be that nullability naturally propagates as we want it to. Given `sql_function!(lower, lower_t, (x: Text) -> Text)`, doing `select(lower(posts::name).nullable())` will work. `lower(posts::name)` will fail because `posts::name` doesn't impl `SelectableExpression`. `lower(posts::name.nullable())` will fail because while `SelectableExpression` will be met, the SQL type of the argument isn't what's expected. Putting `.nullable` at the very top level naturally follows SQL's semantics here.
src/librustc/ty/mod.rs
Outdated
@@ -2222,6 +2222,16 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { | |||
|| self.sess.cstore.impl_trait_ref(self.global_tcx(), id)) | |||
} | |||
|
|||
/// Returns true if the impl is positive and is for a triat which contains |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: trait
} | ||
|
||
fn main() { | ||
assert_eq!(1, foo(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you also test a type which is Copy
but not Eq
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and yeah it seems like it'd be better to have Clone || Display
impls or something, since Vec<T>
doesn't impl Display
(but is Clone
)
This needs a feature gate at least, since this may be rolled back (rust-lang/rfcs#1268 (comment)):
|
@petrochenkov I'm not entirely sure how to add a feature gate for something that has no explicit syntax or direct usage. Could you give me an example of when that was done in the past so I can see how it was implemented? |
@sgrif |
The change in #709 had the side effect of re-introducing #104. With the design that we have right now, nullability isn't propagating upwards. This puts the issue of "expressions aren't validating that the type of its arguments haven't become nullable, and thus nulls are slipping in where they shouldn't be" at odds with "we can't use complex expressions in filters for joins because the SQL type changed". This semi-resolves the issue by restricting when we care about nullability. Ultimately the only time it really matters is when we're selecting data, as we need to enforce that the result goes into an `Option`. For places where we don't see the bytes in Rust (filter, order, etc), `NULL` is effectively `false`. This change goes back to fully fixing #104, but brings back a small piece of #621. I've changed everything that is a composite expression to only be selectable if the SQL type hasn't changed. This means that you won't be able to do things like `users.left_outer_join(posts).select(posts::id + 1)`, but you will be able to use whatever you want in `filter`. This change is also to support what I think will fix the root of all these issues. The design of "Here's the SQL type on this query source" is just fundamentally not what we need. There is only one case where the type changes, and that is to become null when it is on the right side of a left join, the left side of a right join, or either side of a full join. One of the changes that #709 made was to require that you explicitly call `.nullable()` on a tuple if you wanted to get `Option<(i32, String)>` instead of `(Option<i32>, Option<String>)`. This has worked out fine, and isn't a major ergonomic pain. The common case is just to use the default select clause anyway. So I want to go further down this path. The longer term plan is to remove `SqlTypeForSelect` entirely, and *not* implement `SelectableExpression` for columns on the nullable side of a join. We will then provide these two blanket impls: ```rust impl<Left, Right, T> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<T> where T: SelectableExpression<Right>, {} impl<Left, Right, Head, Tail> SelectableExpression<LeftOuterJoin<Left, Right>> for Nullable<Cons<Head, Tail>> where Nullable<Head>: SelectableExpression<LeftOuterJoin<Left, Right>>, Nullable<Tail>: SelectableExpression<LeftOuterJoin<Left, Right>>, {} ``` (Note: Those impls overlap. Providing them as blanket impls would require rust-lang/rust#40097. Providing them as non-blanket impls would require us to mark `Nullable` and possibly `Cons` as `#[fundamental]`) The end result will be that nullability naturally propagates as we want it to. Given `sql_function!(lower, lower_t, (x: Text) -> Text)`, doing `select(lower(posts::name).nullable())` will work. `lower(posts::name)` will fail because `posts::name` doesn't impl `SelectableExpression`. `lower(posts::name.nullable())` will fail because while `SelectableExpression` will be met, the SQL type of the argument isn't what's expected. Putting `.nullable` at the very top level naturally follows SQL's semantics here.
@sgrif sorry, not trying to ignore you -- just been slammed. Just for some context, I'm starting work on a big refactoring of the trait system, that should support RFC 1268 out of the box (chalk, the prototype of sorts, already does). I'm...pretty sure that it will take more work than this PR, though I could be wrong, so I've been holding off on reading this PR until I have a bit of time to dedicate to thinking about it (it's really easy to take unfortunate shortcuts in this code if you're not careful). |
@nikomatsakis No worries, take your time. 😄 |
This removes the flawed "SQL type varies based on the query source" design in favor of "things become nullable if they're on the wrong side of an outer join". This means that columns from the right side of a left join can no longer directly be selected. `.nullable` must be manually called. This sounds like a huge deal, but that's already the case today with tuples (which is a more common case), and it hasn't caused a huge ergonomics issue. Ultimately most queries just use the default select clause. This also causes the nullability to bubble up. If you're building a compound expression that involves a column from the right side of a left join, you don't have to worry about that in the compound expression. You just have to make the whole thing nullable at the top level. This mirrors SQL's semantics quite nicely. One downside of this is that `.nullable` can no longer be used in arbitrary select clauses. Doing so is questionably useful at best, but I'd still like to bring back that support in the future. Ultimately doing so more requires rust-lang/rust#40097 or similar.
This removes the flawed "SQL type varies based on the query source" design in favor of "things become nullable if they're on the wrong side of an outer join". This means that columns from the right side of a left join can no longer directly be selected. `.nullable` must be manually called. This sounds like a huge deal, but that's already the case today with tuples (which is a more common case), and it hasn't caused a huge ergonomics issue. Ultimately most queries just use the default select clause. This also causes the nullability to bubble up. If you're building a compound expression that involves a column from the right side of a left join, you don't have to worry about that in the compound expression. You just have to make the whole thing nullable at the top level. This mirrors SQL's semantics quite nicely. One downside of this is that `.nullable` can no longer be used in arbitrary select clauses. Doing so is questionably useful at best, but I'd still like to bring back that support in the future. Ultimately doing so more requires rust-lang/rust#40097 or similar.
(sorry for the noise) I just ran into the need for this today. Checked out this branch and worked flawlessly so I'm looking forward to this PR landing (it would let me write a single overloaded method instead of N differently named methods and N^2 very similar implementations). Thanks for working on this, @sgrif. 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm sorry for the big delay. I read into this code. This definitely works out simpler than I had thought, in large part because specialization paved the way, which is cool. Kudos @sgrif.
This is leveraging the specialization pathway, where we defer "confirming" a candidate unless we know for sure that it will successfully apply. I generally plan to refactor the trait code to work like this overall, so leaning more on this pathway seems fine to me.
That said, I have a few suggestions for how we could clean things up (see comments below).
Also, we do need a feature-gate.
if tcx.impl_always_allowed_to_overlap(impl1_def_id) | ||
&& tcx.impl_always_allowed_to_overlap(impl2_def_id) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer not to modify specializes
to return true. It makes the specialization graph "cyclic". I think we can make a more direct modification to select.rs
so that where it calls specialized, it would do this instead:
return impls_are_allowed_to_ovelap(other_def, victim_def) ||
traits::specializes(tcx, other_def, victim_def);
This seems to more directly express what is going on.
src/librustc/ty/mod.rs
Outdated
@@ -2222,6 +2222,16 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { | |||
|| self.sess.cstore.impl_trait_ref(self.global_tcx(), id)) | |||
} | |||
|
|||
/// Returns true if the impl is positive and is for a trait which contains | |||
/// no items | |||
pub fn impl_always_allowed_to_overlap(self, def_id: DefId) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should change this to take two impls, and call it impls_are_allowed_to_overlap(impl1, impl2)
. For one thing, we always call it in pairs, but for another we probably want to permit overlap so long as the "polarity" of both impls is the same (that is, I see no reason you can't have overlapping negative impls as well, but indeed you do not want overlapping positive and negative impls).
@@ -113,6 +113,11 @@ impl<'a, 'gcx, 'tcx> Children { | |||
possible_sibling, | |||
impl_def_id); | |||
if let Some(impl_header) = overlap { | |||
if tcx.impl_always_allowed_to_overlap(impl_def_id) | |||
&& tcx.impl_always_allowed_to_overlap(possible_sibling) { | |||
return Ok((true, true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it doesn't matter, but I think Ok((false, false))
would be a better return. It'll do the same thing either way though.
Excellent. I'll add a feature gate and implement the suggestions tomorrow |
@sgrif it'd also be good to have some more tests. One thing that came to mind is tests around the interaction with negative impls (i.e., that a pos + neg still cannot overlap). Another would be some tests that cover specialization cases (i.e., |
Updated and added a feature gate. I've left the error message unchanged when the feature is not enabled, as it was just easier and I recall that occurring for similar feature gates in the past without issue. |
88d53f0
to
ba7510b
Compare
☔ The latest upstream changes (presumably #40651) made this pull request unmergeable. Please resolve the merge conflicts. |
794d4d5
to
8d8f5c6
Compare
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
trait MyMarker {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a comment explaining that this test is targeting the absnce of a feature-gate?
Also, I think I'd prefer to have the T: Eq
vs T: Debug
example (or whatever) rather than this one, since this also works with specialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(well, I guess the comment isn't that imp't because of the filename...)
impl<T: Copy> MyMarker for T {} | ||
impl<T: Eq> MyMarker for T {} | ||
|
||
struct MyStruct; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the role of MyStruct
here? oh, to show that there can be two !Send
impls? can we break that into a separate test, and add some comments describing what it's trying to test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking good; I left a few further requests for modified tests
ping @sgrif -- you still have time to make those last few changes? If not, I could take a stab at it, pretty minor stuff. :) |
I should have time later this week. Been busy with the baby the past several days. If you don't mind making the changes, that'd be great. |
8b20c25
to
665f852
Compare
@bors r+ |
📌 Commit 665f852 has been approved by |
⌛ Testing commit 665f852 with merge e6fc718... |
💔 Test failed - status-travis |
⌛ Testing commit 665f852 with merge 7d09c65... |
…matsakis Implement RFC 1268 This patch allows overlap to occur between any two impls of a trait for traits which have no associated items. Several compile-fail tests around coherence had to be changed to add at least one item to the trait they test against. Ref rust-lang#29864
@bors retry |
This fails the tidy checker:
You'll need to add a section for the unstable feature in @bors r- |
☔ The latest upstream changes (presumably #39987) made this pull request unmergeable. Please resolve the merge conflicts. |
For some reason, bors think's it's testing this... @bors r- retry |
reopening to try to fix bors which is stuck on this PR. |
I'm going to close this again... |
OK, looks like every time this is re-opened, homu gets freshly confused. @sgrif, sorry for the annoyance here, but can you open a fresh version of this PR? |
The only thing that's missing for this PR is the unstable docs stuff, which I'm willing to take on. So unless I hear from @sgrif, I can open a new PR with their commits and a new commit for the Unstable Book changes. |
@frewsxcv sounds great, thanks! |
Rebased in #41309. |
…ikomatsakis Implement RFC 1268. Rebased version of rust-lang#40097. Tracking issue: rust-lang#29864.
This patch allows overlap to occur between any two impls of a trait for
traits which have no associated items.
Several compile-fail tests around coherence had to be changed to add at
least one item to the trait they test against.
Ref #29864