changed 2 years ago
Linked with GitHub

what the hell


This assumes reader is familiar with the concept of early/late bound lifetimes. There's a section on rustc-dev-guide if not (although i think some details are a bit out of date)

problem & background

Not all lifetimes on a fn can be late bound:

fn foo<'a>() -> &'a ();
impl<'a> Fn<()> for FooFnDef {
    type Output = &'a (); // uh oh unconstrained lifetime
}

so we make make them early bound

fn foo<'a>() -> &'a ();
impl<'a> Fn<()> for FooFnDef<'a> {// wow look at all that lifetimey
     type Output = &'a ();   
}   

(Closures have the same constraint however it is not enforced leading to soundness bugs, #84385 implements this "downgrading late bound to early bound" for closures)

lifetimes on fn items are only late bound when they are "constrained" by the fn args:

fn foo<'a>(_: &'a ()) -> &'a ();
//               late bound, not present on `FooFnItem`
//               vv
impl<'a> Trait<(&'a (),)> for FooFnItem {
    type Output = &'a ();    
}

// projections do not constrain inputs
fn bar<'a, T: Trait>(_: <T as Trait<'a>>::Assoc) -> &'a (); //  early bound
                                                            //  vv
impl<'a, T: Trait> Fn<(<T as Trait<'a>>::Assoc,)> for BarFnItem<'a, T> {
    type Output = &'a ();
}

current logic for determining if inputs "constrain" a lifetime works off of HIR so does not normalize aliases. It also assumes that any path with no self type constrains all its substs (i.e. Foo<'a, u32> has no self type but T::Assoc does). This falls apart for top level type aliases (see linked issues):

type Alias<'a, T> = <T as Trait<'a>>::Assoc;
//                      wow look its a path with no self type uwu
//                      i bet that constrains `'a` so it should be latebound
//                      vvvvvvvvvvv
fn foo<'a, T: Trait>(_: Alias<'a, T>) -> &'a ();
//                     `Alias` normalized to make things clearer
//                     vvvvvvvvvvvvvvvvvvvvvvv
impl<'a, T: Trait> Fn<(<T as Trait<'a>>::Assoc,)> for FooFnDef<T> {
    type Output = &'a (); 
    // oh no `'a` isnt constrained wah wah waaaah *trumbone noises* 
    // i think, idk what musical instrument that is
}

solution

The PR solves this by having the hir visitor that checks for lifetimes in constraining uses check if the path is a DefKind::Alias. If it is we ""normalize"" it by calling type_of and walking the returned type. This is a bit hacky as it requires a mapping between the substs on the path in hir, and the generics of the type Alias<...> which is on the ty layer.

Alternative solutions may involve calculating the "late boundness" of lifetimes after/during astconv rather than relying on hir at all. We already have code to determine whether a lifetime SHOULD be late bound or not as this is currently how the error for fn foo<'a, T: Trait>(_: Alias<'a, T>) -> &'a (); gets emitted.

It is probably not possible to do this right now, late boundness is used by generics_of and gather_explicit_predicates_of as we currently do not put late bound lifetimes in Generics. Although this seems sus to me as the long term goal is to make all generics late bound which would result in generics_of(function) being empty? #103448 places all lifetimes in Generics regardless of late boundness so that may be a good step towards making this possible.

Select a repo