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

Suggestion for 'static impl Trait return #51444

Merged
merged 4 commits into from
Jun 28, 2018
Merged

Conversation

estebank
Copy link
Contributor

@estebank estebank commented Jun 8, 2018

When encountering a named or anonymous sup requirement (for example,
&'a self) and a 'static impl Trait return type, suggest adding the
'_ lifetime constraing to the return type.

Fix #43719, #51282.

error: cannot infer an appropriate lifetime
  --> $DIR/static-return-lifetime-infered.rs:17:16
   |
LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
   |                                   ----------------------- this return type evaluates to the `'static` lifetime...
LL |         self.x.iter().map(|a| a.0)
   |         ------ ^^^^
   |         |
   |         ...but this borrow...
   |
note: ...can't outlive the anonymous lifetime #1 defined on the method body at 16:5
  --> $DIR/static-return-lifetime-infered.rs:16:5
   |
LL | /     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
LL | |         self.x.iter().map(|a| a.0)
LL | |     }
   | |_____^
help: you can add a constraint to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the method body at 16:5
   |
LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> + '_ {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When encountering a named or anonymous sup requirement (for example,
`&'a self`) and a `'static` impl Trait return type, suggest adding the
`'_` lifetime constraing to the return type.
@rust-highfive
Copy link
Collaborator

r? @cramertj

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

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jun 8, 2018
LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
| ----------------------- this return type evaluates to the `'static` lifetime...
LL | self.x.iter().map(|a| a.0)
| ------ ^^^^ can't infer an appropriate lifetime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this cant infer an appropriate lifetime hint seems out-of-place to me. it interjects in the middle of the sentence "this return type evaluates to the 'static lifetime but this borrow...". Perhaps move it or combine it somehow with the "can't outlive the anonymous lifetime" error?

Copy link
Contributor Author

@estebank estebank Jun 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I tried a couple of different outputs, including:

error: can't infer an appropriate lifetime
  --> file.rs:11:16
   |
11 |         self.x.iter().map(|a| a.0)
   |                ^^^^ can't infer an appropriate lifetime for this
   |
note: this return type evaluates to the `'static` lifetime...
  --> file.rs:10:35
   |
10 |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^
note: ...but this borrow...
  --> file.rs:11:9
   |
11 |         self.x.iter().map(|a| a.0)
   |         ^^^^^^
note: ...can't outlive the anonymous lifetime #1 defined on the method body at 10:5
  --> file.rs:10:5
   |
10 | /     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
11 | |         self.x.iter().map(|a| a.0)
12 | |     }
   | |_____^
help: you can add a constraint to make the `impl Trait` non`'static` and match the anonymous lifetime #1 defined on the method body at 10:5
   |
10 |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> + '_ {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I feel that the current output without any primary label would look reasonably good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually like what you have above, but I think you could remove the "can't infer an appropriate lifetime for this" at the top, since that's covered in the "but this borrow... can't outlive the anonymous lifetime".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you take a look at the current output? I removed the primary label and it does seem slightly easier to read.

less than `'static` and match {}",
lifetime,
),
format!("{} + '_", snippet),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case where the lifetime is named, think it would be more useful to recommend adding a named lifetime here (rather than '_).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree somewhat, the only reason I didn't implement it that way is that from my cursory tests '_ always works, but I could contrive a case where 'a wouldn't. That being said, I can change it if you feel strongly enough (or we could also leave that as a follow up task).

Copy link
Member

@cramertj cramertj Jun 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Thanks for the investigation!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to suggest 'a.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@estebank

This comment has been minimized.

@estebank
Copy link
Contributor Author

cc @nikomatsakis you might be interested in providing feedback here too.

@cramertj
Copy link
Member

r=me once @nikomatsakis has had a chance to look, too. I still feel strange about recommending + '_ in cases like fn foo(x: &'a u8, y: &'a u8) -> impl Trait. I considered suggesting that we check the lifetime of the return type against the input lifetimes to the function, but that gets complicated once you have multiple lifetimes with no outlives relations, where the result could be any of the input lifetimes or an entirely separate lifetime that two or three of them outlive.

@estebank
Copy link
Contributor Author

@cramertj I can add the code to do that in the trivial case (self has only one lifetime), and give up with the current diagnostic otherwise. That would make it easy to implement with a fallback not worse than what we already have.

@cramertj
Copy link
Member

@estebank That sounds worth it to me! By "self has only one lifetime", you mean the case where all lifetimes in input types are named and identical?

@estebank
Copy link
Contributor Author

@cramertj right

@pietroalbini
Copy link
Member

r? @nikomatsakis

@estebank
Copy link
Contributor Author

Ping.

) => {
let anon_reg_sup = self.is_suitable_region(sup_r)?;
if sub_r == &RegionKind::ReStatic &&
self._is_return_type_impl_trait(anon_reg_sup.def_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We don't normally use method names with a leading _.

sup_r,
) => {
let anon_reg_sup = self.is_suitable_region(sup_r)?;
if sub_r == &RegionKind::ReStatic &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we just concluding that any time that we have a bound of 'static and a fn with an impl Trait return, the two are connected?

e.g., what happens with this example?

use std::fmt::Debug;
fn foo(x: &u32) -> impl Debug {
  let _: &'static u32 = x;
  ()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we just concluding that any time that we have a bound of 'static and a fn with an impl Trait return, the two are connected?

Close, unless I misunderstand, the code checks that the return type is impl Trait and that it is bound to 'static.

what happens with

The two provided examples keep the current output:

error[E0621]: explicit lifetime required in the type of `x`
 --> file.rs:3:25
  |
2 | fn foo(x: &u32) -> impl Debug {
  |        - consider changing the type of `x` to `&'static u32`
3 |   let _: &'static u32 = x;
  |                         ^ lifetime `'static` required

error[E0621]: explicit lifetime required in the type of `x`
 --> file.rs:7:29
  |
7 | fn bar(x: &u32, y: &u32) -> impl Debug {
  |        -                    ^^^^^^^^^^ lifetime `'static` required
  |        |
  |        consider changing the type of `x` to `&'static u32`

For the second case, we could expand this code handle it and suggest the following, but it'll be very involved:

fn bar<'a>(x: &'a u32, y: &u32) -> impl Debug + 'a {
  x
}

@nikomatsakis
Copy link
Contributor

I agree with @cramertj that always suggesting '_ feels wrong. I believe that in we can check out sub to see whether it is (e.g.) a named region or not -- '_ is basically only right in the case where sub is either the only region in the arguments or appears in self.

Another related example:

use std::fmt::Debug;
fn foo(x: &u32, y: &u32) -> impl Debug {
  x
}

if we suggest '_ here, that will result in an error, which seems suboptimal.

@estebank
Copy link
Contributor Author

Output for the two samples doesn't change:

error[E0621]: explicit lifetime required in the type of `x`
 --> file.rs:3:25
  |
2 | fn foo(x: &u32) -> impl Debug {
  |        - consider changing the type of `x` to `&'static u32`
3 |   let _: &'static u32 = x;
  |                         ^ lifetime `'static` required

error[E0621]: explicit lifetime required in the type of `x`
 --> file.rs:7:29
  |
7 | fn bar(x: &u32, y: &u32) -> impl Debug {
  |        -                    ^^^^^^^^^^ lifetime `'static` required
  |        |
  |        consider changing the type of `x` to `&'static u32`

Now using the lifetime name if available, as reflected in the test:

error: cannot infer an appropriate lifetime
  --> $DIR/static-return-lifetime-infered.rs:21:16
   |
LL |     fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> {
   |                                     ----------------------- this return type evaluates to the `'static` lifetime...
LL |         self.x.iter().map(|a| a.0)
   |         ------ ^^^^
   |         |
   |         ...but this borrow...
   |
note: ...can't outlive the lifetime 'a as defined on the method body at 20:5
  --> $DIR/static-return-lifetime-infered.rs:20:5
   |
LL |     fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: you can add a constraint to the return type to make it last less than `'static` and match the lifetime 'a as defined on the method body at 20:5
   |
LL |     fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> + 'a {
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

@pietroalbini
Copy link
Member

Ping from triage @nikomatsakis! This PR needs your review.

@nikomatsakis
Copy link
Contributor

Ok, try this example @estebank

use std::fmt::Debug;

struct Foo { }

impl Foo {
    fn foo(&self) -> impl Debug {
        let _: &'static Foo = self;
        ()
    }
}

fn main() { }

I think the reason that this code didn't fire is because a "better" pretty-printed region error was triggering.

) => {
let anon_reg_sup = self.is_suitable_region(sup_r)?;
if sub_r == &RegionKind::ReStatic &&
self.is_return_type_impl_trait(anon_reg_sup.def_id)
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 that it would be better if we could somehow detect that the 'static was actually coming from the impl trait (as opposed to some other source). One way to do that might be to make use of the sub_origin variable -- but we'd have to update the origin that appears here:

let bound_region = |r| self.sub_regions(infer::CallReturn(span), least_region, r);

and maybe here too, though that could come later and is a bit more annoying (since that is an obligation cause). The current values are totally bogus anyway: they claim that impl Trait requirements arise "because returns types must be sized", which is just..not even kind of true.

Anyway, if we added a new SubregionOrigin for "impl trait obligation" and then checked here for that case, I think this would continue to work in the cases that we want, but rule out the cases we don't.

That said, I am not that keen on adding more uses of SubregionOrigin, which is "deprecated" (essentially) in the NLL checker. Still, I'm not sure what better thing to use for now.

@estebank
Copy link
Contributor Author

The output for

use std::fmt::Debug;

struct Foo { }

impl Foo {
    fn foo(&self) -> impl Debug {
        let _: &'static Foo = self;
        ()
    }
}

fn main() { }

is

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
 --> file.rs:7:31
  |
7 |         let _: &'static Foo = self;
  |                               ^^^^
  |
  = note: ...the reference is valid for the static lifetime...
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 6:5
 --> file.rs:6:5
  |
6 | /     fn foo(&self) -> impl Debug {
7 | |         let _: &'static Foo = self;
8 | |         ()
9 | |     }
  | |_____^

@nikomatsakis
Copy link
Contributor

@estebank huh, ok. Why doesn't your new error message trigger in that case?

@estebank
Copy link
Contributor Author

estebank commented Jun 26, 2018

Why doesn't your new error message trigger in that case?

Still checking, but it seems that case is not a SubSupConflict.


It doesn't trigger because it only triggers on SubSupConflict, but the last sample is a ConcreteFailure(Reborrow(file.rs:7:31: 7:35), ReStatic, ReFree(DefId(0/0:6 ~ file[317d]::{{impl}}[0]::foo[0]), BrAnon(0))). The other samples don't even call try_report_static_impl_trait.


I feel pretty comfortable merging this because it is purposely restricted to a specific case which is also a common pitfall for people trying impl Trait.

@nikomatsakis
Copy link
Contributor

OK, I still feel like the test is too broad, but I guess we can wait for someone to report problems to us.

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Jun 27, 2018

📌 Commit 612657d has been approved by nikomatsakis

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 27, 2018
bors added a commit that referenced this pull request Jun 28, 2018
Suggestion for 'static impl Trait return

When encountering a named or anonymous sup requirement (for example,
`&'a self`) and a `'static` impl Trait return type, suggest adding the
`'_` lifetime constraing to the return type.

Fix #43719, #51282.

```
error: cannot infer an appropriate lifetime
  --> $DIR/static-return-lifetime-infered.rs:17:16
   |
LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
   |                                   ----------------------- this return type evaluates to the `'static` lifetime...
LL |         self.x.iter().map(|a| a.0)
   |         ------ ^^^^
   |         |
   |         ...but this borrow...
   |
note: ...can't outlive the anonymous lifetime #1 defined on the method body at 16:5
  --> $DIR/static-return-lifetime-infered.rs:16:5
   |
LL | /     fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
LL | |         self.x.iter().map(|a| a.0)
LL | |     }
   | |_____^
help: you can add a constraint to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the method body at 16:5
   |
LL |     fn iter_values_anon(&self) -> impl Iterator<Item=u32> + '_ {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
@bors
Copy link
Contributor

bors commented Jun 28, 2018

⌛ Testing commit 612657d with merge ba196bd...

@bors
Copy link
Contributor

bors commented Jun 28, 2018

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing ba196bd to master...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants