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 underscore lifetimes #44691

Merged
merged 4 commits into from
Sep 22, 2017
Merged

Conversation

cramertj
Copy link
Member

Part of #44524

@rust-highfive
Copy link
Collaborator

r? @nikomatsakis

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

self.err_handler()
.span_err(lt_def.lifetime.span,
&format!("invalid lifetime name `{}`", lt_def.lifetime.ident));
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Feel free to bikeshed this error message (it shows up in code that tries to explicitly bind '_ in the generics of an item declaration).

Copy link
Contributor

Choose a reason for hiding this comment

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

An equivalent error is already issued for 'static, so the wording can be reused:

invalid lifetime parameter name: `'static`

@arielb1
Copy link
Contributor

arielb1 commented Sep 19, 2017

r? @eddyb

@@ -383,6 +383,9 @@ declare_features! (

// allow '|' at beginning of match arms (RFC 1925)
(active, match_beginning_vert, "1.21.0", Some(44101)),

// allow `'_` placeholder lifetimes
(active, underscore_lifetimes, "1.21.0", Some(44524)),
Copy link
Member

Choose a reason for hiding this comment

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

Should be 1.22.0? (BTW match_beginning_vert should also be 1.22.0?)

Copy link
Member

Choose a reason for hiding this comment

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

I never know what to actually put here.

@eddyb
Copy link
Member

eddyb commented Sep 19, 2017

r=me with build & nit fixed

@@ -318,6 +310,13 @@ impl<'a> Visitor<'a> for AstValidator<'a> {

fn visit_generics(&mut self, g: &'a Generics) {
let mut seen_default = None;
for lt_def in &g.lifetimes {
Copy link
Contributor

Choose a reason for hiding this comment

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

Lifetime definitions can happen in for<'a, 'b, 'c> in fn pointers, trait objects, etc.
It's better to place the error in fn visit_lifetime_def.

@petrochenkov
Copy link
Contributor

@cramertj
What happens when '_ is written in some place where elided lifetimes are not supported currently?
For example bounds 'a: '_ + 'b, impl headers, struct definitions.
Could you add tests for these cases?

@cramertj cramertj force-pushed the underscore-lifetimes branch 2 times, most recently from 0c3b9ef to 942a85c Compare September 19, 2017 18:11
@@ -159,7 +159,8 @@ impl fmt::Debug for Lifetime {

impl Lifetime {
pub fn is_elided(&self) -> bool {
self.name == keywords::Invalid.name()
self.name == keywords::Invalid.name() ||
self.name == "'_"
Copy link
Contributor

Choose a reason for hiding this comment

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

So I've never been crazy about using a "sentinel value" here rather than an enum -- now that we've got more than one sentinel, I feel even less good. Can't we change this to something like

enum LifetimeName {
    Elided,
    Underscore,
    Static,
    Name(Token) // or whatever this is
}

Copy link
Member

Choose a reason for hiding this comment

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

It's a Symbol / ast::Name I think.

@@ -1422,7 +1422,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
let lifetime_i = &lifetimes[i];

for lifetime in lifetimes {
if lifetime.lifetime.is_static() {
if lifetime.lifetime.is_static() || lifetime.lifetime.name == "'_" {
Copy link
Contributor

Choose a reason for hiding this comment

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

...if we were using an enum, this code would be rather cleaner


struct Foo<'a>(&'a u8);

fn foo(x: &u8) -> Foo<'_> {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we have some tests that use multiple lifetime parameters in one function? For example:

fn foo(x: &'_ u8, y: &'_ u8) -> &'_ u8 { } // ERROR from elision

fn foo(x: &mut Vec<&'_ u8>, y: &'_ u8) { x.push(y); // ERROR }

struct Foo<'a, 'b> { a: &'a u32, b: &'b u32 }
fn foo_a<'b>(foo: Foo<'_, 'b>) -> &'b u32 { &foo.a } // ERROR
fn foo_b<'b>(foo: Foo<'_, 'b>) -> &'b u32 { &foo.b } // OK

@nikomatsakis
Copy link
Contributor

@cramertj thanks for jumping on this btw -- so excited to see it land =)

@arielb1 arielb1 added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Sep 19, 2017
Elided => keywords::Invalid.name(),
Underscore => Symbol::intern("'_"),
Static => Symbol::intern("'static"),
Name(name) => name,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nooooo.
This LifetimeName over Name is pure over-engineering, N extra lines of boilerplate just to avoid one string comparison.

Copy link
Member Author

@cramertj cramertj Sep 19, 2017

Choose a reason for hiding this comment

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

Heh. I don't have any strong opinion here. Take it up with @nikomatsakis 😄
I left it as its own commit so it's easy to pull out if that's the decision.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd better turn '_ into a weak keyword.

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree. Sentinel values mean you wind up with if-else-if chains like if foo.is_elided() { ... } which are easily forgotten. Having an enum means we can use exhaustive matching, so that when we add variants in the future, the compiler helps us catch cases that are missing etc.

Plus, there isn't a lot of boilerplate here.

Copy link
Contributor

Choose a reason for hiding this comment

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

@nikomatsakis
Ok, not a big deal.
(I'll revert this when you are on vacation, ha-ha.)

@cramertj
Copy link
Member Author

Gah! I didn't try building rustdoc and the others...

@eddyb
Copy link
Member

eddyb commented Sep 20, 2017

cc @Mark-Simulacrum this is why I have rustbuild in my build setup.

match *self {
Elided => keywords::Invalid.name(),
Underscore => Symbol::intern("'_"),
Static => Symbol::intern("'static"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Symbol::intern("'static") => keywords::StaticLifetime.name()

@petrochenkov
Copy link
Contributor

@cramertj
Could you add tests for '_ in impl headers and struct definitions? (But not as bounds or lifetime definitions.)

@bors
Copy link
Contributor

bors commented Sep 21, 2017

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

Underscore,
Static,
Name(Name),
}
Copy link
Contributor

@petrochenkov petrochenkov Sep 21, 2017

Choose a reason for hiding this comment

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

@cramertj
Actually, can you tweak the naming slightly.

enum LifetimeKind { // This is not a name
    Implicit, // `Elided` is not correct, because implicit can be both elided and inferred, but we don't discern between them yet when lowering into HIR
    Underscore,
    Static,
    Named(Name), // Named lifetime
}

Copy link
Contributor

Choose a reason for hiding this comment

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

(The variants are going to change anyway when lifetimes are first resolved, then lowered into HIR.)

Copy link
Member

Choose a reason for hiding this comment

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

I think what we want is name resolution to give us a Def, and the existing resolve_lifetimes pass to do elision and compute the semantic representation from Def::Lifetime(DefId).

Copy link
Contributor

Choose a reason for hiding this comment

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

@petrochenkov I don't object to calling "Implicit" for sure, but I'm curious: what does "elided" mean to you? To me, it means "not written". I would then say that the behavior around elided lifetimes depends on context: in function signatures, we use defaulting rules (as specified by the lifetime elision RFCs plus the object-lifetime RFCs), and in function bodies, we use inference.

Copy link
Member

Choose a reason for hiding this comment

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

Using "elided" to mean literally "not written" may be correct wrt the English language but then "elision rules" doesn't mean much anymore and we have to say "default lifetime rules" I guess? And "lifetime defaulting" for the process of picking a lifetime.

Copy link
Contributor

Choose a reason for hiding this comment

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

So personally, when I explain to people, I say something like "sometimes you can elide lifetimes. The meaning of this depends on context. In a function signature, we use the following system of defaults. etc". In other words, I think I do use the terms the way you described. =) But also I rely on context. That is, I might say "elision rules" when it's clear we're talking about function signatures, in which case it just means "defaulting rules" (but including, naturally, the object defaults).

In any case, it seems clear that calling things elided is ripe for confusion. I prefer either "implicit", "not written", or "not present", all of which seem reasonably clear.

Copy link
Contributor

@petrochenkov petrochenkov Sep 21, 2017

Choose a reason for hiding this comment

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

@nikomatsakis
Well, I usually associate "elided" with "lifetime elision RFCs", i.e. "defaulting", not inference.
It's useful to have some separate words for "not-written-in-signature" and "not-written-in-body", and "elided" and "inferred" already kinda serve this purpose given that "lifetime elision" is commonly used to describe what happens in signatures.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes some sense. I guess I prefer to have words that match their English meanings to the extent possible, though, and to me "elided" just means "not written" (whereas "defaulted" seems more specific -- we assigned it a default value). I admit that the distinction between defaulted (based on some simple rules) and inferred (based on complex analysis) is a subtle one as well. =)

Copy link
Contributor

Choose a reason for hiding this comment

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

Regardless, the question at hand is whether to use elided here with the broader meaning, right? In which case, "Implicit" or "Not written" seems fine.

@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Sep 21, 2017

📌 Commit f5505d1 has been approved by nikomatsakis

@nikomatsakis nikomatsakis 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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 21, 2017
@bors
Copy link
Contributor

bors commented Sep 22, 2017

⌛ Testing commit f5505d1 with merge 3eb19bf...

bors added a commit that referenced this pull request Sep 22, 2017
@bors
Copy link
Contributor

bors commented Sep 22, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing 3eb19bf 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.

8 participants