Skip to content

Commit 0f49616

Browse files
committed
Auto merge of #39305 - eddyb:synelide, r=nikomatsakis
Perform lifetime elision (more) syntactically, before type-checking. The *initial* goal of this patch was to remove the (contextual) `&RegionScope` argument passed around `rustc_typeck::astconv` and allow converting arbitrary (syntactic) `hir::Ty` to (semantic) `Ty`. I've tried to closely match the existing behavior while moving the logic to the earlier `resolve_lifetime` pass, and [the crater report](https://gist.github.com/eddyb/4ac5b8516f87c1bfa2de528ed2b7779a) suggests none of the changes broke real code, but I will try to list everything: There are few cases in lifetime elision that could trip users up due to "hidden knowledge": ```rust type StaticStr = &'static str; // hides 'static trait WithLifetime<'a> { type Output; // can hide 'a } // This worked because the type of the first argument contains // 'static, although StaticStr doesn't even have parameters. fn foo(x: StaticStr) -> &str { x } // This worked because the compiler resolved the argument type // to <T as WithLifetime<'a>>::Output which has the hidden 'a. fn bar<'a, T: WithLifetime<'a>>(_: T::Output) -> &str { "baz" } ``` In the two examples above, elision wasn't using lifetimes that were in the source, not even *needed* by paths in the source, but rather *happened* to be part of the semantic representation of the types. To me, this suggests they should have never worked through elision (and they don't with this PR). Next we have an actual rule with a strange result, that is, the return type here elides to `&'x str`: ```rust impl<'a, 'b> Trait for Foo<'a, 'b> { fn method<'x, 'y>(self: &'x Foo<'a, 'b>, _: Bar<'y>) -> &str { &self.name } } ``` All 3 of `'a`, `'b` and `'y` are being ignored, because the `&self` elision rule only cares that the first argument is "`self` by reference". Due implementation considerations (elision running before typeck), I've limited it in this PR to a reference to a primitive/`struct`/`enum`/`union`, but not other types, but I am doing another crater run to assess the impact of limiting it to literally `&self` and `self: &Self` (they're identical in HIR). It's probably ideal to keep an "implicit `Self` for `self`" type around and *only* apply the rule to `&self` itself, but that would result in more bikeshed, and #21400 suggests some people expect otherwise. Another decent option is treating `self: X, ... -> Y` like `X -> Y` (one unique lifetime in `X` used for `Y`). The remaining changes have to do with "object lifetime defaults" (see RFCs [599](https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md) and [1156](https://github.com/rust-lang/rfcs/blob/master/text/1156-adjust-default-object-bounds.md)): ```rust trait Trait {} struct Ref2<'a, 'b, T: 'a+'b>(&'a T, &'b T); // These apply specifically within a (fn) body, // which allows type and lifetime inference: fn main() { // Used to be &'a mut (Trait+'a) - where 'a is one // inference variable - &'a mut (Trait+'b) in this PR. let _: &mut Trait; // Used to be an ambiguity error, but in this PR it's // Ref2<'a, 'b, Trait+'c> (3 inference variables). let _: Ref2<Trait>; } ``` What's happening here is that inference variables are created on the fly by typeck whenever a lifetime has no resolution attached to it - while it would be possible to alter the implementation to reuse inference variables based on decisions made early by `resolve_lifetime`, not doing that is more flexible and works better - it can compile all testcases from #38624 by not ending up with `&'static mut (Trait+'static)`. The ambiguity specifically cannot be an early error, because this is only the "default" (typeck can still pick something better based on the definition of `Trait` and whether it has any lifetime bounds), and having an error at all doesn't help anyone, as we can perfectly infer an appropriate lifetime inside the `fn` body. **TODO**: write tests for the user-visible changes. cc @nikomatsakis @arielb1
2 parents 0f8a296 + c0e474d commit 0f49616

39 files changed

+1799
-1856
lines changed

src/librustc/diagnostics.rs

+63
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,69 @@ struct ListNode {
327327
This works because `Box` is a pointer, so its size is well-known.
328328
"##,
329329

330+
E0106: r##"
331+
This error indicates that a lifetime is missing from a type. If it is an error
332+
inside a function signature, the problem may be with failing to adhere to the
333+
lifetime elision rules (see below).
334+
335+
Here are some simple examples of where you'll run into this error:
336+
337+
```compile_fail,E0106
338+
struct Foo { x: &bool } // error
339+
struct Foo<'a> { x: &'a bool } // correct
340+
341+
enum Bar { A(u8), B(&bool), } // error
342+
enum Bar<'a> { A(u8), B(&'a bool), } // correct
343+
344+
type MyStr = &str; // error
345+
type MyStr<'a> = &'a str; // correct
346+
```
347+
348+
Lifetime elision is a special, limited kind of inference for lifetimes in
349+
function signatures which allows you to leave out lifetimes in certain cases.
350+
For more background on lifetime elision see [the book][book-le].
351+
352+
The lifetime elision rules require that any function signature with an elided
353+
output lifetime must either have
354+
355+
- exactly one input lifetime
356+
- or, multiple input lifetimes, but the function must also be a method with a
357+
`&self` or `&mut self` receiver
358+
359+
In the first case, the output lifetime is inferred to be the same as the unique
360+
input lifetime. In the second case, the lifetime is instead inferred to be the
361+
same as the lifetime on `&self` or `&mut self`.
362+
363+
Here are some examples of elision errors:
364+
365+
```compile_fail,E0106
366+
// error, no input lifetimes
367+
fn foo() -> &str { }
368+
369+
// error, `x` and `y` have distinct lifetimes inferred
370+
fn bar(x: &str, y: &str) -> &str { }
371+
372+
// error, `y`'s lifetime is inferred to be distinct from `x`'s
373+
fn baz<'a>(x: &'a str, y: &str) -> &str { }
374+
```
375+
376+
Here's an example that is currently an error, but may work in a future version
377+
of Rust:
378+
379+
```compile_fail,E0106
380+
struct Foo<'a>(&'a str);
381+
382+
trait Quux { }
383+
impl Quux for Foo { }
384+
```
385+
386+
Lifetime elision in implementation headers was part of the lifetime elision
387+
RFC. It is, however, [currently unimplemented][iss15872].
388+
389+
[book-le]: https://doc.rust-lang.org/nightly/book/lifetimes.html#lifetime-elision
390+
[iss15872]: https://github.com/rust-lang/rust/issues/15872
391+
"##,
392+
330393
E0109: r##"
331394
You tried to give a type parameter to a type which doesn't need it. Erroneous
332395
code example:

src/librustc/hir/intravisit.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ pub trait Visitor<'v> : Sized {
301301
fn visit_ty_param_bound(&mut self, bounds: &'v TyParamBound) {
302302
walk_ty_param_bound(self, bounds)
303303
}
304-
fn visit_poly_trait_ref(&mut self, t: &'v PolyTraitRef, m: &'v TraitBoundModifier) {
304+
fn visit_poly_trait_ref(&mut self, t: &'v PolyTraitRef, m: TraitBoundModifier) {
305305
walk_poly_trait_ref(self, t, m)
306306
}
307307
fn visit_variant_data(&mut self,
@@ -421,7 +421,7 @@ pub fn walk_lifetime_def<'v, V: Visitor<'v>>(visitor: &mut V, lifetime_def: &'v
421421

422422
pub fn walk_poly_trait_ref<'v, V>(visitor: &mut V,
423423
trait_ref: &'v PolyTraitRef,
424-
_modifier: &'v TraitBoundModifier)
424+
_modifier: TraitBoundModifier)
425425
where V: Visitor<'v>
426426
{
427427
walk_list!(visitor, visit_lifetime_def, &trait_ref.bound_lifetimes);
@@ -547,8 +547,8 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty) {
547547
TyPtr(ref mutable_type) => {
548548
visitor.visit_ty(&mutable_type.ty)
549549
}
550-
TyRptr(ref opt_lifetime, ref mutable_type) => {
551-
walk_list!(visitor, visit_lifetime, opt_lifetime);
550+
TyRptr(ref lifetime, ref mutable_type) => {
551+
visitor.visit_lifetime(lifetime);
552552
visitor.visit_ty(&mutable_type.ty)
553553
}
554554
TyNever => {},
@@ -566,8 +566,11 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty) {
566566
visitor.visit_ty(ty);
567567
visitor.visit_nested_body(length)
568568
}
569-
TyTraitObject(ref bounds) => {
570-
walk_list!(visitor, visit_ty_param_bound, bounds);
569+
TyTraitObject(ref bounds, ref lifetime) => {
570+
for bound in bounds {
571+
visitor.visit_poly_trait_ref(bound, TraitBoundModifier::None);
572+
}
573+
visitor.visit_lifetime(lifetime);
571574
}
572575
TyImplTrait(ref bounds) => {
573576
walk_list!(visitor, visit_ty_param_bound, bounds);
@@ -695,7 +698,7 @@ pub fn walk_foreign_item<'v, V: Visitor<'v>>(visitor: &mut V, foreign_item: &'v
695698

696699
pub fn walk_ty_param_bound<'v, V: Visitor<'v>>(visitor: &mut V, bound: &'v TyParamBound) {
697700
match *bound {
698-
TraitTyParamBound(ref typ, ref modifier) => {
701+
TraitTyParamBound(ref typ, modifier) => {
699702
visitor.visit_poly_trait_ref(typ, modifier);
700703
}
701704
RegionTyParamBound(ref lifetime) => {

0 commit comments

Comments
 (0)