Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upIn-band lifetimes: Lint against single-use lifetime names #44752
Comments
nikomatsakis
added
E-mentor
T-compiler
WG-compiler-front
labels
Sep 21, 2017
nikomatsakis
referenced this issue
Sep 21, 2017
Open
Tracking issue for RFC 2115: In-band lifetime bindings #44524
aidanhs
added
the
C-enhancement
label
Sep 21, 2017
This comment has been minimized.
This comment has been minimized.
Also, I believe, only for lifetime names that appear in argument position. Currently we require explicitly binding output-only lifetimes with a name: fn bar<'a>() -> &'a u8 { &5 } // OK
fn bar() -> &'_ u8 { &5 } // ERROR: missing lifetime specifier |
This comment has been minimized.
This comment has been minimized.
|
@cramertj Hmm. I would consider the proper way to declare |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis Yes, |
nikomatsakis
changed the title
Lint against single-use lifetime names
In-band lifetimes: Lint against single-use lifetime names
Sep 25, 2017
carols10cents
added
the
hacktoberfest
label
Sep 29, 2017
This comment has been minimized.
This comment has been minimized.
|
Is this up for grabs? |
This comment has been minimized.
This comment has been minimized.
|
@gaurikholkar Go for it! |
This comment has been minimized.
This comment has been minimized.
|
Will start working on it |
This comment has been minimized.
This comment has been minimized.
|
@gaurikholkar hey, just checking in! How's it going? Any blockers? |
This comment has been minimized.
This comment has been minimized.
|
Some examples: fn foo<'x>(_: &'x u32) { }This should lint against struct Foo<'a> { x: &'a u32 }
fn foo<'x>(_: Foo<'x>) { }This should lint against struct Foo<'a, 'b> { f: &'a &'b u32 }
fn foo<'x, 'y>(foo: Foo<'x, 'y>) -> &'x u32 { foo.f }This should lint against struct Foo<'a, 'b> { f: &'a &'b u32 }
fn foo<'x, 'y>(foo: Foo<'x, 'y>) -> &'y u32 { foo.f }This should lint against fn foo<'x>() -> &'x u32 { &22 }This should not lint, because trait Trait<'a> { }
impl<'a, T> Trait<'a> for T { }
fn foo<'x, T>(t: T)
where T: Trait<'x>
{
}
fn main() {
foo(22);
}This should not lint, because at present |
This comment has been minimized.
This comment has been minimized.
|
OK, let's start with this specific test: fn deref<'x>(v: &'x u32) -> u32 {
*v
}
fn main() { }When we are done, we want to issue a warning, probably like this: fn deref<'x>(v: &'x u32) -> u32 {
// ^^ lifetime name `'x` only used once
*v
}
fn main() { }In this case, the one use of The first thing we want to do then is to figure out how many times each name is used and where. Now, accounting around lifetimes (e.g., early-bound, late-bound, etc) can get kind of complicated, but luckily we can ignore most of that crap for our purposes. I imagine we would want to add to the lifetime_uses: DefIdMap<LifetimeUseSet<'tcx>where a enum LifetimeUseSet<'tcx> {
One(&'tcx hir::Lifetime),
Many,
}We don't really need to track more detail than that -- once there are many uses of a lifetime, we don't want to warn about it anymore, so we don't need to track them. Now, when we are resolving a lifetime in rust/src/librustc/middle/resolve_lifetime.rs Lines 1516 to 1518 in 3cf28f3 Here, we want to match on the match def {
LateBoundAnon(..) | Static => {
// These are anonymous lifetimes or lifetimes that are not declared.
}
Free(_, def_id) | LateBound(_, def_id) | EarlyBound(_, def_id) => {
// A lifetime declared by the user.
if !self.lifetime_uses.contains_key(&def_id) {
self.lifetime_uses.insert(def_id, LifetimeUseSet::One(lifetime_ref));
} else {
self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
}
}
}Now we have a record of where each lifetime def was used. The next step will be going over the set of lifetime names defined and warning if they are |
This comment has been minimized.
This comment has been minimized.
|
I've been in touch with @nikomatsakis over this. The current status is: I've followed the instructions he has given above and written code for the same locally. |
This comment has been minimized.
This comment has been minimized.
|
So, if we follow the steps above, we wind up with a map from the def-id of each This is the main function for https://github.com/rust-lang/rust/blob/master/src/librustc/middle/resolve_lifetime.rs#L259-L285 I am imagining that we can basically write a second visitor, https://github.com/rust-lang/rust/blob/master/src/librustc/middle/resolve_lifetime.rs#L287-L290 It would only implement one method, though,
In there, it will check if the map contains OK, this is a not quite right -- we're going to eventually want to be a bit more selective, since it will depend whether that use is one that could be elided or replaced with |
This comment has been minimized.
This comment has been minimized.
|
Actually I take that back. Don't use a visitor. Just iterate over the map we built and look things up in the HIR map. For example, given the
|
This comment has been minimized.
This comment has been minimized.
|
Well, that won't allow you to catch lifetimes used zero times. To do that, we either need a separate visitor, or visit the map during |
This comment has been minimized.
This comment has been minimized.
|
Track the progress here |
This comment has been minimized.
This comment has been minimized.
Is what gets generated for now |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis it's compile-fail tests that are failing now |
bors
added a commit
that referenced
this issue
Dec 20, 2017
This comment has been minimized.
This comment has been minimized.
|
I started trying to record all places where this lint might be needed and document their expected behavior. This is not done, but I have to go, so I'm saving my status here for now. My goal is to make a checklist and try to mentor the remaining improvements. |
This comment has been minimized.
This comment has been minimized.
|
Here is an updated check-list of the overall goals: moved to issue header |
This comment has been minimized.
This comment has been minimized.
This isn't implemented either |
This comment has been minimized.
This comment has been minimized.
|
Ah, good point. I was wondering if we might want to actually handle the "multiple edits" this way -- that is, we could first suggest removing the |
This comment has been minimized.
This comment has been minimized.
|
Kind of annoying to have to run |
This comment has been minimized.
This comment has been minimized.
|
@gaurikholkar I updated the list of warnings |
This comment has been minimized.
This comment has been minimized.
The suggestions machinery is capable of having multiple suggestions at one time, and each suggestions being different EDIT The support to suggest multiple substitutions at once was added in #50943, and a slight tweak to the presentation will be added in #50987. |
This comment has been minimized.
This comment has been minimized.
Issuing warnings at the right timesThis is the first step for the mentoring instructions. The lint as is is partially implemented, but it often fires at the wrong times. Step one: Add the tests. I've created a fairly comprehensive set of test cases in this gist. The first step then would be to add these tests into your branch, by doing something like the following. This will put all of the tests into > cd src/test/ui/in-band-lifetimes
> mkdir single-use-lint
> git clone git@gist.github.com:f13da812c97b8094a9566ca9b3d9677d.git tmp
> mv tmp/*rs single-use-lint
> rm -rf tmp(Unfortunately, I forgot when making those tests that we already had a fair number of tests with names like We can then run all the tests by running this command from the main directory: > ./x.py test --stage 1 -i src/test/ui --test-args single-use-lintThat should run just the new tests and nothing else. You should see various failures. (You can learn more about rustc's test suite here, in the rustc-guide; these test are ui tests.) Step two: Examine existing code. The way that the current lint works is in the rust/src/librustc/middle/resolve_lifetime.rs Lines 61 to 66 in c19264f These are kept in a map, indexed by the rust/src/librustc/middle/resolve_lifetime.rs Line 258 in c19264f This map is populated in the rust/src/librustc/middle/resolve_lifetime.rs Line 2201 in c19264f Towards the bottom you will see this code, which upgrades the number of times that the reference is used: rust/src/librustc/middle/resolve_lifetime.rs Lines 2228 to 2233 in c19264f Examine the test failures. Looking at the test failures, we can group them into two categories:
Making lifetimes used in particular places issue a warning. I think what we want to do here is to extend the rust/src/librustc/middle/resolve_lifetime.rs Line 228 in c19264f Let's call the flag if self.track_individual_lifetime_uses && !self.lifetime_uses.contains_key(&def_id) {
self.lifetime_uses.insert(def_id, LifetimeUseSet::One(lifetime_ref));
} else {
self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
}The basic idea is that this field should be rust/src/librustc/middle/resolve_lifetime.rs Lines 1247 to 1249 in c19264f
Usually, we will want to pass rust/src/librustc/middle/resolve_lifetime.rs Lines 1627 to 1637 in c19264f and function bodies: rust/src/librustc/middle/resolve_lifetime.rs Lines 451 to 466 in c19264f Anyway, that should be the rough idea. We'll have to see if it starts to issue warnings at strange times! Making lifetimes that are never used issue a warning. Warnings are actually issued in this loop: rust/src/librustc/middle/resolve_lifetime.rs Lines 1273 to 1299 in c19264f The problem here is that, if a lifetime is never used, it will never be added to the map, so we never see it to issue a warning! To fix that, we have to keep a separate set of all lifetimes and iterate over that set. Otherwise, we could walk the IR one more time with a separate visitor to find each lifetime definition. |
nikomatsakis
removed
the
hacktoberfest
label
Mar 21, 2018
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis thanks for the detailed instructions! The approach you outline doesn't totally work out, I think. Consider two-uses-in-fn-argument-and-return - we want to permit the lifetime here because it has two uses, but we want to pass I think we need to always populate Other than that, to do the last part (finding lifetimes with zero uses): where would you populate the set of lifetimes? |
This comment has been minimized.
This comment has been minimized.
|
Also, a few of the tests are incoherent:
|
This comment has been minimized.
This comment has been minimized.
|
Sorry I missed those comments! Regarding the incoherent tests:
|
This comment has been minimized.
This comment has been minimized.
Regarding this... I think actually that the two-uses-in-fn-argument-and-return test: fn c<'a>(x: &'a u32) -> &'a u32 { // OK: used twice
&22
}is ok for two independent reasons: first, because the lifetime is used twice, but also because it is used in the return type. So it would be ok to invoke Put another way, I do not yet see the problem. =)
Hmm. I was thinking about this later. Keeping a set of "all lifetimes" doesn't feel right to me. In fact, the point where we iterate over the lifetimes in order to issue warnings is precisely the point where a set of declared lifetimes are going out of scope: rust/src/librustc/middle/resolve_lifetime.rs Lines 1273 to 1299 in c19264f That is, the purpose of the rust/src/librustc/middle/resolve_lifetime.rs Lines 261 to 315 in c19264f we could iterate over all the lifetimes that were declared in rust/src/librustc/middle/resolve_lifetime.rs Line 268 in c19264f For each rust/src/librustc/middle/resolve_lifetime.rs Lines 68 to 83 in c19264f then we can lookup that |
This comment has been minimized.
This comment has been minimized.
|
@tamird -- do you think you are going to try and tackle this issue? That would be great! (If not, though, let me know so I can find someone else; I'd like to see this get done.) |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis yep, I'm looking at this. Is there any reason to keep |
This comment has been minimized.
This comment has been minimized.
If you think you see a cleaner way, go for it! |
This comment has been minimized.
This comment has been minimized.
|
@tamird btw, just checking -- how goes? I was away last week for PTO |
This comment has been minimized.
This comment has been minimized.
|
ping @tamird -- not trying to bug ya', just want to know if you're still working on this. I'm doing my weekly sweep and trying to figure out what I should do to ensure that this moves forward. It's .. mildly high priority, mostly because we'd like to encourage people to try out the in-band lifetimes feature, and for them to do that effectively, this lint ought to be a big help... |
nikomatsakis
referenced this issue
May 4, 2018
Merged
Improve single-use and zero-use lifetime lints #50440
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this issue
May 5, 2018
bors
added a commit
that referenced
this issue
May 11, 2018
This comment has been minimized.
This comment has been minimized.
|
Given that the 2018 edition preview included in-band lifetimes, it seems like we need to have this lint available for either the next preview or the 2018 edition stable release. |
scottmcm
added this to the Rust 2018 RC milestone
Aug 15, 2018
This comment has been minimized.
This comment has been minimized.
|
Even without in-band lifetimes, I think this is an important idiom lint, so I've added it to an edition milestone to get eyes on it. Edition folks: please move or remove if you think otherwise. |
nikomatsakis commentedSep 21, 2017
•
edited
Once support for
'_lands in #44691, the next step for #44524 is to implement a lint that warns against "single use" lifetime names.Current status
The lint is partially implemented but needs to be completed. Here is a checklist:
'aappearing in&'a T, suggest&T'aappearing in any other place, suggest'_Older Background
The idea is that an explicit name like
'ashould only be used (at least in a function or impl) to link together two things. Otherwise, you should just use'_to indicate that the lifetime is not linked to anything.Until #15872 is closed, we should only lint for single-use lifetime names that are bound in functions. Once #15872 is closed, we can also lint against those found in impl headers.
We can detect cases where a lint is valid by modifying the
resolve_lifetimescode:with()is used to push new scopes on the stack. It is also given a closure which will execute with the new name bindings in scope.with()gets called for impls and other kinds of items from here; for methods and functions in particular it is called fromvisit_early_late).resolve_lifetime_ref()is called to resolve an actual reference to a named lifetime.Scope, e.g. counting how many times a particular lifetime was referenced.with()returns, we could scan the lifetimes and check for those that were only referenced 1 time (or 0 times...) and issue a lint warning.(There are some directions for how to add a lint under the header "Issuing future compatibility warnings" in the rustc-bug-fix-procedure page on forge -- we can skip the "future compatibility" parts here.)