Skip to content
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

Closed
wants to merge 6 commits into from
Closed

Conversation

sgrif
Copy link
Contributor

@sgrif sgrif commented Feb 25, 2017

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

@rust-highfive
Copy link
Collaborator

r? @nikomatsakis

(rust_highfive has picked a reviewer for you, use r? to override)

@sgrif sgrif force-pushed the sg-implement-rfc-1268 branch 2 times, most recently from 8dbb775 to eb6af2d Compare February 25, 2017 23:09
sgrif added a commit to diesel-rs/diesel that referenced this pull request Feb 26, 2017
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 added a commit to diesel-rs/diesel that referenced this pull request Feb 26, 2017
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.
@@ -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
Copy link
Contributor

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));
Copy link
Contributor

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?

Copy link
Contributor

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)

@petrochenkov
Copy link
Contributor

This needs a feature gate at least, since this may be rolled back (rust-lang/rfcs#1268 (comment)):

Also, the implementation concerns are still very real -- it seems however that we expect to address many of the same questions as part of the specialization work, so hopefully we will find a nice solution. (If not, we'll rollback the RFC.)

@sgrif
Copy link
Contributor Author

sgrif commented Mar 3, 2017

@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?

@petrochenkov
Copy link
Contributor

@sgrif
Here's an example of a feature gate reported lately.
If two impls overlap then an error is reported, right? The feature error can be added somewhere in the same place and suppress the normal error - "error: conflicting implementation" => "conflicting implementation of empty traits are unstable".

sgrif added a commit to diesel-rs/diesel that referenced this pull request Mar 3, 2017
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.
@nikomatsakis
Copy link
Contributor

@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).

@sgrif
Copy link
Contributor Author

sgrif commented Mar 3, 2017

@nikomatsakis No worries, take your time. 😄

sgrif added a commit to diesel-rs/diesel that referenced this pull request Mar 4, 2017
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.
sgrif added a commit to diesel-rs/diesel that referenced this pull request Mar 4, 2017
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.
@japaric
Copy link
Member

japaric commented Mar 8, 2017

(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. 😄

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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;
}
Copy link
Contributor

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.

@@ -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 {
Copy link
Contributor

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));
Copy link
Contributor

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.

@sgrif
Copy link
Contributor Author

sgrif commented Mar 16, 2017

Excellent. I'll add a feature gate and implement the suggestions tomorrow

@nikomatsakis
Copy link
Contributor

@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., impl<T> Marker for T and impl<T> Marker for Vec<T>, after enabling both feature gates), just to make sure that the two systems for permitting overlap don't fight with one another.

@sgrif
Copy link
Contributor Author

sgrif commented Mar 17, 2017

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.

@bors
Copy link
Contributor

bors commented Mar 19, 2017

☔ The latest upstream changes (presumably #40651) made this pull request unmergeable. Please resolve the merge conflicts.

// option. This file may not be copied, modified, or distributed
// except according to those terms.

trait MyMarker {}
Copy link
Contributor

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.

Copy link
Contributor

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;
Copy link
Contributor

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

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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

@nikomatsakis
Copy link
Contributor

ping @sgrif -- you still have time to make those last few changes? If not, I could take a stab at it, pretty minor stuff. :)

@sgrif
Copy link
Contributor Author

sgrif commented Mar 29, 2017

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.

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Apr 3, 2017

📌 Commit 665f852 has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented Apr 3, 2017

⌛ Testing commit 665f852 with merge e6fc718...

@bors
Copy link
Contributor

bors commented Apr 3, 2017

💔 Test failed - status-travis

@frewsxcv
Copy link
Member

frewsxcv commented Apr 3, 2017

@bors retry

#40474

@bors
Copy link
Contributor

bors commented Apr 4, 2017

⌛ Testing commit 665f852 with merge 7d09c65...

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Apr 4, 2017
…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
@frewsxcv
Copy link
Member

frewsxcv commented Apr 4, 2017

@bors retry

@frewsxcv
Copy link
Member

frewsxcv commented Apr 4, 2017

This fails the tidy checker:

[00:02:39] Unstable feature 'overlapping_marker_traits' needs to have a section in The Unstable Book

You'll need to add a section for the unstable feature in src/doc/unstable-book/. If you need help with that let me know

@bors r-

@bors
Copy link
Contributor

bors commented Apr 7, 2017

☔ The latest upstream changes (presumably #39987) made this pull request unmergeable. Please resolve the merge conflicts.

@TimNN
Copy link
Contributor

TimNN commented Apr 9, 2017

For some reason, bors think's it's testing this...

@bors r- retry

@Mark-Simulacrum Mark-Simulacrum added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Apr 14, 2017
@frewsxcv frewsxcv closed this Apr 14, 2017
@frewsxcv
Copy link
Member

reopening to try to fix bors which is stuck on this PR.

@frewsxcv frewsxcv reopened this Apr 14, 2017
@aturon
Copy link
Member

aturon commented Apr 14, 2017

I'm going to close this again...

@aturon aturon closed this Apr 14, 2017
@aturon aturon reopened this Apr 14, 2017
@aturon
Copy link
Member

aturon commented Apr 14, 2017

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?

@aturon aturon closed this Apr 14, 2017
@frewsxcv
Copy link
Member

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.

@aturon
Copy link
Member

aturon commented Apr 14, 2017

@frewsxcv sounds great, thanks!

@frewsxcv
Copy link
Member

Rebased in #41309.

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Apr 15, 2017
…ikomatsakis

Implement RFC 1268.

Rebased version of rust-lang#40097.

Tracking issue: rust-lang#29864.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.