-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
(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)); | ||
} |
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.
Feel free to bikeshed this error message (it shows up in code that tries to explicitly bind '_
in the generics of an item declaration).
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.
An equivalent error is already issued for 'static
, so the wording can be reused:
invalid lifetime parameter name: `'static`
r? @eddyb |
src/libsyntax/feature_gate.rs
Outdated
@@ -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)), |
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.
Should be 1.22.0? (BTW match_beginning_vert
should also be 1.22.0?)
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 never know what to actually put here.
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 { |
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.
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
.
@cramertj |
0c3b9ef
to
942a85c
Compare
src/librustc/hir/mod.rs
Outdated
@@ -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 == "'_" |
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.
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
}
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.
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 == "'_" { |
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.
...if we were using an enum, this code would be rather cleaner
|
||
struct Foo<'a>(&'a u8); | ||
|
||
fn foo(x: &u8) -> Foo<'_> { |
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 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
@cramertj thanks for jumping on this btw -- so excited to see it land =) |
ac16bad
to
c91a1a7
Compare
src/librustc/hir/mod.rs
Outdated
Elided => keywords::Invalid.name(), | ||
Underscore => Symbol::intern("'_"), | ||
Static => Symbol::intern("'static"), | ||
Name(name) => name, |
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.
Nooooo.
This LifetimeName
over Name
is pure over-engineering, N extra lines of boilerplate just to avoid one string comparison.
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.
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.
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'd better turn '_
into a weak keyword.
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 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.
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.
@nikomatsakis
Ok, not a big deal.
(I'll revert this when you are on vacation, ha-ha.)
Gah! I didn't try building rustdoc and the others... |
cc @Mark-Simulacrum this is why I have |
c91a1a7
to
051b5cf
Compare
src/librustc/hir/mod.rs
Outdated
match *self { | ||
Elided => keywords::Invalid.name(), | ||
Underscore => Symbol::intern("'_"), | ||
Static => Symbol::intern("'static"), |
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.
Symbol::intern("'static")
=> keywords::StaticLifetime.name()
051b5cf
to
71c8ad4
Compare
@cramertj |
☔ The latest upstream changes (presumably #44551) made this pull request unmergeable. Please resolve the merge conflicts. |
8f5f35c
to
31fd099
Compare
src/librustc/hir/mod.rs
Outdated
Underscore, | ||
Static, | ||
Name(Name), | ||
} |
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.
@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
}
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.
(The variants are going to change anyway when lifetimes are first resolved, then lowered into HIR.)
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 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)
.
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.
@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.
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.
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.
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.
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.
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.
@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.
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.
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. =)
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.
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.
31fd099
to
f5505d1
Compare
@bors r+ |
📌 Commit f5505d1 has been approved by |
Implement underscore lifetimes Part of #44524
☀️ Test successful - status-appveyor, status-travis |
Part of #44524