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

Anonymous bound region BrAnon(0) in return but not args #47511

Closed
dtolnay opened this issue Jan 17, 2018 · 23 comments · Fixed by #100508
Closed

Anonymous bound region BrAnon(0) in return but not args #47511

dtolnay opened this issue Jan 17, 2018 · 23 comments · Fixed by #100508
Assignees
Labels
C-bug Category: This is a bug. P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@dtolnay
Copy link
Member

dtolnay commented Jan 17, 2018

Looks just like #43567. Mentioning @petrochenkov and @eddyb who were involved in that issue. The following code compiles with 1.21.0 but ICE as of 1.22.0.

fn f(_: X) -> X {
    unimplemented!()
}

type X<'a> = <&'a () as Trait>::Value;

trait Trait {
    type Value;
}

impl<'a> Trait for &'a () {
    type Value = ();
}

fn main() {}
error: internal compiler error: librustc_typeck/astconv.rs:1212: anonymous bound region BrAnon(0) in return but not args
 --> src/main.rs:1:15
  |
1 | fn f(_: X) -> X {
  |               ^

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.25.0-nightly (da569fa9d 2018-01-16) running on x86_64-unknown-linux-gnu
@dtolnay dtolnay added I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. regression-from-stable-to-stable Performance or correctness regression from one stable version to another. C-bug Category: This is a bug. labels Jan 17, 2018
@eddyb
Copy link
Member

eddyb commented Jan 17, 2018

cc @nikomatsakis Looks like astconv is eagerly normalizing, but not when checking the return.
fn f<'a>(x: X<'a>) -> X<'a> { x } produces a non-ICE error which is also wrong (IMO).

@nikomatsakis
Copy link
Contributor

triage: P-high

@topecongiro
Copy link
Contributor

let late_bound_in_ret = tcx.collect_referenced_late_bound_regions(&output);

Is changing collect_referenced_late_bound_regions to collect_constrained_late_bound_regions ok?

My understanding is that fn f<'a>(x: X<'a>) -> X<'a> is ok, whereas fn g<'a>(x: X<'a>) -> &'a X<'a> is not and rustc should produce an error.

@nikomatsakis
Copy link
Contributor

OK, looked a bit closer. I think the code is correct, but the span_bug was just there to catch an edge-case in the error message formation that I didn't feel like handling (and thought was impossible) -- in particular, the case where a region is anonymous and yet somehow appears only in the return type.

I thought this was impossible because the elision code (which kicks in in such a case) ought to have detected that and repored an error. For example, fn foo() -> &u32 will error out in a different way.

But I overlooked the fact that there could be a lifetime which appeared in the parameters but was not constrained by the parameters. In particular, type and lifetime inputs to an associated type are not constrained (which is of course what we have here, though the use of a type-alias kind of hides it).

So I think we basically just want to tweak the error message code in this case.

@nikomatsakis
Copy link
Contributor

Hmm, actually, while we can simply fix the error message, I'm now second guessing if this is the right place to detect an error, or if this should be an error at all. We could certainly handle it (alternatively) by treating the anonymous lifetime as an early-bound region.

@nikomatsakis
Copy link
Contributor

Well, I opened a PR to fix the ICE, anyway.

@nikomatsakis
Copy link
Contributor

Regarding the regression, it was caused by this bug fix #33685, for which we already underwent the warning cycle etc. I'm not sure why however things were working until 1.21. Seems like a bug.

@nikomatsakis
Copy link
Contributor

The bug was fixed by @petrochenkov in 80cf3f9, if I'm not mistaken. This moved a check that previously appears to only have been in place for fn() types to be applied more generally. I'm not inclined to do much more on this issue besides fix the ICE -- @dtolnay is this causing a problem for anyone, or just something you noticed?

@dtolnay
Copy link
Member Author

dtolnay commented Jan 25, 2018

@dtolnay is this causing a problem for anyone, or just something you noticed?

This bug prevented me from implementing SCARY assignments for Punctuated iterators in Syn.

Punctuated<T, P> is a punctuated sequence with elements of type T separated by punctuation of type P. I have an IntoIterator implementation for Punctuated, &Punctuated, and &mut Punctuated which iterate over just the sequence elements of type T, &T, &mut T (there are other ways to iterate over both elements and punctuation). In Syn 0.12 the iterator types are parameterized over both T and P so you have IntoIter<T, P>, Iter<T, P>, IterMut<T, P>. This is inconvenient because the second type parameter is useless, there is no way to access a value of type P from these iterators, and is a candidate for SCARY assignments.

In the next breaking release of Syn I plan to expose simply IntoIter<T>, Iter<T>, IterMut<T> with a single type parameter. However in the near term I wanted to do this cleanup without a breaking change. Idea: provide a type parameter default for P and use type system tricks to make *Iter<T, P1> an identical type to *Iter<T, P2>. This is technically a breaking change if someone had implemented a trait for iterators with different P type parameters that used to be different types but would now be the same type, but I felt comfortable making this change in a minor version because of the way people use these iterators in practice.

A proof of concept works great for IntoIter and allows SCARY assignments and referring to iterators with a single type parameter.

use std::marker::PhantomData;

pub struct Punctuated<T, P> {
    t: PhantomData<T>,
    p: PhantomData<P>,
}

impl<T, P> IntoIterator for Punctuated<T, P> {
    type Item = T;
    type IntoIter = IntoIter<T, P>;

    fn into_iter(self) -> Self::IntoIter {
        unimplemented!()
    }
}

pub type IntoIter<T, P = ()> = <Punctuated<T, P> as private::IntoIterator>::IntoIter;

mod private {
    use super::Punctuated;
    use std::marker::PhantomData;

    pub trait IntoIterator {
        type IntoIter;
    }

    impl<T, P> IntoIterator for Punctuated<T, P> {
        type IntoIter = IntoIter<T>;
    }

    pub struct IntoIter<T> {
        t: PhantomData<T>,
    }

    impl<T> Iterator for IntoIter<T> {
        type Item = T;

        fn next(&mut self) -> Option<Self::Item> {
            unimplemented!()
        }
    }
}

struct A;
struct B;
struct C;
fn f(arg: IntoIter<A, B>) -> IntoIter<A, C> {
    // SCARY assignment
    arg
}

But it falls apart if we attempt exactly the same thing for Iter and IterMut. I believe the following is valid Rust code and should compile successfully.

use std::marker::PhantomData;

pub struct Punctuated<T, P> {
    t: PhantomData<T>,
    p: PhantomData<P>,
}

impl<'a, T, P> IntoIterator for &'a Punctuated<T, P> {
    type Item = T;
    type IntoIter = Iter<'a, T, P>;

    fn into_iter(self) -> Self::IntoIter {
        unimplemented!()
    }
}

pub type Iter<'a, T, P = ()> = <&'a Punctuated<T, P> as private::IntoIterator>::IntoIter;

mod private {
    use super::Punctuated;
    use std::marker::PhantomData;

    pub trait IntoIterator {
        type IntoIter;
    }

    impl<'a, T, P> IntoIterator for &'a Punctuated<T, P> {
        type IntoIter = Iter<'a, T>;
    }

    pub struct Iter<'a, T: 'a> {
        t: PhantomData<&'a T>,
    }

    impl<'a, T> Iterator for Iter<'a, T> {
        type Item = &'a T;

        fn next(&mut self) -> Option<Self::Item> {
            unimplemented!()
        }
    }
}

struct A;
struct B;
struct C;
fn f(arg: Iter<A, B>) -> Iter<A, C> {
    // SCARY assignment
    arg
}
error: internal compiler error: librustc_typeck/astconv.rs:1212: anonymous bound region BrAnon(0) in return but not args
  --> src/main.rs:47:26
   |
47 | fn f(arg: Iter<A, B>) -> Iter<A, C> {
   |                          ^^^^^^^^^^

note: the compiler unexpectedly panicked. this is a bug.

@nikomatsakis
Copy link
Contributor

This bug prevented me from implementing SCARY assignments for Punctuated iterators in Syn.

Hmm, so, I had in mind issuing a future compatibility warning, which means this code would break eventually.

However, an alternative, with perhaps a bit more effort, might be to promote 'a here to be early-bound, in which case the function could be compiled.

@dtolnay
Copy link
Member Author

dtolnay commented Jan 25, 2018

I guess I am unclear where this is going wrong. The second code sample does not immediately look like #33685 to me because every lifetime in the code also appears in the trait input type.

Note that it is perfectly fine for a higher-ranked lifetime to appear in an associated type or function return type if it also appears in the trait inputs or function arguments.

Is there an easy way to sum up the rules for whether a type alias with generic parameters is valid and behaves in a sensible way? Does every lifetime parameter and type parameter need to be mentioned in the Associated type?

type Iter<'a, T, P> = <&'a Punctuated<T, P> as Trait>::Associated;

If it is possible to relax the restrictions by promoting 'a to early-bound, that seems like the way to go.

@nikomatsakis
Copy link
Contributor

@dtolnay

because every lifetime in the code also appears in the trait input type.

The point is that the type of the argument is not the trait inputs, it's the projection. i.e., the type is <&'a Punctuated<T, P> as private::IntoIterator>::IntoIter -- and that might very well be some type like u32 once normalizes, so in that case 'a would not be constrained in any way.

Still, as I said, the ordinary thing for us to do is to make lifetimes that meet this criteria early-bound. For example, if you do fn foo<'a>(x: u32) -> u32 (which, as I said, could very well be the signature of f post normalization), that is legal, and it's because we make 'a early-bound. What's going wrong here is that 'a appears in the input types, just in an uncontrained position, and so our early-bound checks get confused.

@nikomatsakis
Copy link
Contributor

I'll try to prep a full fix, I think, @dtolnay's example convinces me that this is a bug.

@bors bors closed this as completed in 0ee698e Jan 26, 2018
@dtolnay dtolnay reopened this Jan 26, 2018
@nikomatsakis
Copy link
Contributor

Huh. It appears that the code is trying to do the right thing here, actually. The problem is that the resolve_lifetime code runs before type-aliases are expanded, and so it (incorrectly) considers the 'a in X<'a> to be constrained by X<'a>. Pain in the neck. I suppose we could expand type aliases in the resolve_lifetime code as well.

@nikomatsakis
Copy link
Contributor

(Or, better, make some kind of query that we can use for a def-id to tell which of its substs are constrained; for a type definition, this would be all of them, but for a type alias it would check the definition.)

@eddyb
Copy link
Member

eddyb commented Jan 30, 2018

Wait, why would resolve_lifetime care about type aliases? Since we want to eventually use projections for them anyway, riiight? Is this about early vs late? Can't we somehow defer that?

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jan 30, 2018

Ugh. Solving this properly is a horrible pain in the neck.

Is this about early vs late?

Yes it is.

Can't we somehow defer that?

Maybe, I'm going to investigate that tomorrow I suppose, because unfolding the type aliases basically amounts to a specialized re-implementation of lifetime resolution (perhaps factoring out into the "map lifetime name to def-id" part that we've talked about would help here, not sure, but that's its own job).

@nikomatsakis
Copy link
Contributor

However, deferring that would be also quite annoying. It's very nice the way it is now =)

@nikomatsakis
Copy link
Contributor

Specifically, the problem is this code:

// consider only the lifetimes on the final
// segment; I am not sure it's even currently
// valid to have them elsewhere, but even if it
// is, those would be potentially inputs to
// projections
if let Some(last_segment) = path.segments.last() {
self.visit_path_segment(path.span, last_segment);
}

which treats all type parameters to something like X<'a> as constrained.

@nikomatsakis
Copy link
Contributor

triage: P-medium

This is not a burning problem right now, but it's a definite bug. I'm thinking about trying to mentor out (or do) some refactorings that would make a proper fix more feasible.

@rust-highfive rust-highfive added P-medium Medium priority and removed P-high High priority labels Feb 15, 2018
@estebank
Copy link
Contributor

Current output no longer ICEs but is still rejected:

error[E0581]: return type references an anonymous lifetime which is not constrained by the fn input types
 --> src/main.rs:1:15
  |
1 | fn f(_: X) -> X {
  |               ^
  |
  = note: lifetimes appearing in an associated type are not considered constrained

@estebank estebank removed the I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ label Dec 14, 2019
@Xiphoseer
Copy link

Is this issue the reason why in this playground, line 45 compiles but line 52 produces an error?

@marmeladema
Copy link
Contributor

Hi!
In a discussion around lifetime-only GAT not really requiring GAT, @steffahn ended up sharing this code: https://gist.github.com/rust-play/a050229041bd8924c1891ab4756827d7#file-playground-rs-L72

impl ValueOperationWithGat for Box<dyn for<'e> Fn(&'e ExecutionContext<'e>) -> Value<'e>> {
    #[inline]
    // the 'e: 'e works around a compiler bug
    fn execute<'e: 'e>(&self, ctx: ExecutionContextOf<'e, Self>) -> Value<'e> {
        (self)(ctx)
    }
}

We believe it's another instance of this bug! If you just introduce the <'e> lifetime, you get this error:

error[[E0581]](https://doc.rust-lang.org/stable/error-index.html#E0581): return type references lifetime `'e`, which is not constrained by the fn input types
  [--> src/main.rs:73:65
](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a050229041bd8924c1891ab4756827d7#)   |
73 |     fn execute<'e>(&self, ctx: ExecutionContextOf<'e, Self>) -> Value<'e> {
   |                                                                 ^^^^^^^^^

For more information about this error, try `rustc --explain E0581`.

Is there a path forward to fix it?

Dylan-DPC pushed a commit to Dylan-DPC/rust that referenced this issue Mar 5, 2022
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Mar 5, 2022
…r=jackh726

Add known-bug directive to issue rust-lang#47511 test case
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Mar 5, 2022
…r=jackh726

Add known-bug directive to issue rust-lang#47511 test case
bors added a commit to rust-lang-ci/rust that referenced this issue Mar 5, 2022
Rollup of 6 pull requests

Successful merges:

 - rust-lang#94446 (UNIX `remove_dir_all()`: Try recursing first on the slow path)
 - rust-lang#94460 (Reenable generator drop tracking tests and fix mutation handling)
 - rust-lang#94620 (Edit docs on consistency of `PartialOrd` and `PartialEq`)
 - rust-lang#94624 (Downgrade `#[test]` on macro call to warning)
 - rust-lang#94626 (Add known-bug directive to issue rust-lang#47511 test case)
 - rust-lang#94631 (Fix typo in c-variadic)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
@jackh726 jackh726 added the S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. label Mar 12, 2022
Manishearth added a commit to Manishearth/rust that referenced this issue 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 issue 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 bors closed this as completed in f162e3a Nov 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. P-medium Medium priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants