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

avoid making substs of type aliases late bound when used as fn args #100508

Merged
merged 3 commits into from
Nov 9, 2022

Conversation

BoxyUwU
Copy link
Member

@BoxyUwU BoxyUwU commented Aug 13, 2022

fixes #47511
fixes #85533
(although I did not know theses issues existed when i was working on this 🙃)

currently Alias<...> is treated the same as Struct<...> when deciding if generics should be late bound or early bound but this is not correct as Alias might normalize to a projection which does not constrain the generics.

I think this needs more tests before merging
more explanation of PR here

Hackmd inline for future readers:

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.

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Aug 13, 2022
@rust-highfive
Copy link
Collaborator

r? @oli-obk

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 13, 2022
@oli-obk
Copy link
Contributor

oli-obk commented Aug 25, 2022

This probably needs lang team sign off as it adds potentially a lot more code that compiles.

@bors
Copy link
Contributor

bors commented Aug 29, 2022

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

@BoxyUwU
Copy link
Member Author

BoxyUwU commented Oct 2, 2022

I'm not sure that this is really "a lot more code that compiles" (the situations in which this matter are relatively uncommon since you need the lifetime to be used in the return type and only used in the arguments as a projection hidden behind a type alias). It's also imo a clear bug so it doesn't seem T-lang but more T-types, it would probably be worth getting t-types input anyway to make sure this is not going to mess up any future plans.

@oli-obk oli-obk added I-types-nominated Nominated for discussion during a types team meeting. T-types Relevant to the types team, which will review and decide on the PR/issue. labels Oct 2, 2022
@jackh726
Copy link
Member

Niko is going to review this r? @nikomatsakis

Going to unnominate

@jackh726 jackh726 removed the I-types-nominated Nominated for discussion during a types team meeting. label Oct 21, 2022
Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

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

r=me modulo the need for an explanatory comment and a cross-crate test

@nikomatsakis
Copy link
Contributor

Discussed with @BoxyUwU -- in the fullness of time, I hope this goes away by removing the early/late distinction (and moving everything into generics), but for the time being, I agree with the reasoning of the hackmd that this is the best path forward.

The only real alternative I can see is being very conservative and making everything early bound that appears in a type alias. It seems very likely this will break people and I would be hard pressed to explain to users why it does, so I'm not inclined to go that path unless we find a real complication with this approach beyond that it's a messy hack.

The major concerns I can see are

  • it might lead to cycles if it were possible for type_of on the type alias to invoke the function, but I don't see why that would happen, at least today
  • duplicate code paths that could get out of sync, but this code changes very infrequently

@BoxyUwU
Copy link
Member Author

BoxyUwU commented Nov 8, 2022

@bors r=nikomatsakis

@bors
Copy link
Contributor

bors commented Nov 8, 2022

📌 Commit 983a90d has been approved by nikomatsakis

It is now in the queue for this repository.

@bors bors 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-review Status: Awaiting review from the assignee but also interested parties. labels Nov 8, 2022
Manishearth added a commit to Manishearth/rust that referenced this pull request Nov 8, 2022
…d, r=nikomatsakis

avoid making substs of type aliases late bound when used as fn args

fixes rust-lang#47511
fixes rust-lang#85533
(although I did not know theses issues existed when i was working on this 🙃)

currently `Alias<...>` is treated the same as `Struct<...>` when deciding if generics should be late bound or early bound but this is not correct as `Alias` might normalize to a projection which does not constrain the generics.

I think this needs more tests before merging
more explanation of PR [here](https://hackmd.io/v44a-QVjTIqqhK9uretyQg?view)

Hackmd inline for future readers:
---

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:
```rust
fn foo<'a>() -> &'a ();
impl<'a> Fn<()> for FooFnDef {
    type Output = &'a (); // uh oh unconstrained lifetime
}
```
so we make make them early bound
```rust
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, [rust-lang#84385](rust-lang#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:
```rust
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):

```rust
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? [rust-lang#103448](rust-lang#103448) places all lifetimes in `Generics` regardless of late boundness so that may be a good step towards making this possible.
Manishearth added a commit to Manishearth/rust that referenced this pull request Nov 8, 2022
…d, r=nikomatsakis

avoid making substs of type aliases late bound when used as fn args

fixes rust-lang#47511
fixes rust-lang#85533
(although I did not know theses issues existed when i was working on this 🙃)

currently `Alias<...>` is treated the same as `Struct<...>` when deciding if generics should be late bound or early bound but this is not correct as `Alias` might normalize to a projection which does not constrain the generics.

I think this needs more tests before merging
more explanation of PR [here](https://hackmd.io/v44a-QVjTIqqhK9uretyQg?view)

Hackmd inline for future readers:
---

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:
```rust
fn foo<'a>() -> &'a ();
impl<'a> Fn<()> for FooFnDef {
    type Output = &'a (); // uh oh unconstrained lifetime
}
```
so we make make them early bound
```rust
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, [rust-lang#84385](rust-lang#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:
```rust
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):

```rust
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? [rust-lang#103448](rust-lang#103448) places all lifetimes in `Generics` regardless of late boundness so that may be a good step towards making this possible.
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 9, 2022
…earth

Rollup of 7 pull requests

Successful merges:

 - rust-lang#100508 (avoid making substs of type aliases late bound when used as fn args)
 - rust-lang#101381 (Test that target feature mix up with homogeneous floats is sound)
 - rust-lang#103353 (Fix Access Violation when using lld & ThinLTO on windows-msvc)
 - rust-lang#103521 (Avoid possible infinite  loop when next_point reaching the end of file)
 - rust-lang#103559 (first move on a nested span_label)
 - rust-lang#103778 (Update several crates for improved support of the new targets)
 - rust-lang#103827 (Properly remap and check for substs compatibility in `confirm_impl_trait_in_trait_candidate`)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
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. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
8 participants