Skip to content

Commit 254942c

Browse files
authored
Rollup merge of rust-lang#100508 - BoxyUwU:make_less_things_late_bound, 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.
2 parents 85f4f41 + 983a90d commit 254942c

9 files changed

+184
-29
lines changed

compiler/rustc_hir_analysis/src/collect/lifetimes.rs

+99-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_middle::bug;
1818
use rustc_middle::hir::map::Map;
1919
use rustc_middle::hir::nested_filter;
2020
use rustc_middle::middle::resolve_lifetime::*;
21-
use rustc_middle::ty::{self, DefIdTree, TyCtxt};
21+
use rustc_middle::ty::{self, DefIdTree, TyCtxt, TypeSuperVisitable, TypeVisitor};
2222
use rustc_span::def_id::DefId;
2323
use rustc_span::symbol::{sym, Ident};
2424
use rustc_span::Span;
@@ -1781,7 +1781,7 @@ fn is_late_bound_map(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<&FxIndexSet<
17811781

17821782
let mut late_bound = FxIndexSet::default();
17831783

1784-
let mut constrained_by_input = ConstrainedCollector::default();
1784+
let mut constrained_by_input = ConstrainedCollector { regions: Default::default(), tcx };
17851785
for arg_ty in decl.inputs {
17861786
constrained_by_input.visit_ty(arg_ty);
17871787
}
@@ -1834,12 +1834,65 @@ fn is_late_bound_map(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<&FxIndexSet<
18341834
debug!(?late_bound);
18351835
return Some(tcx.arena.alloc(late_bound));
18361836

1837-
#[derive(Default)]
1838-
struct ConstrainedCollector {
1837+
/// Visits a `ty::Ty` collecting information about what generic parameters are constrained.
1838+
///
1839+
/// The visitor does not operate on `hir::Ty` so that it can be called on the rhs of a `type Alias<...> = ...;`
1840+
/// which may live in a separate crate so there would not be any hir available. Instead we use the `type_of`
1841+
/// query to obtain a `ty::Ty` which will be present even in cross crate scenarios. It also naturally
1842+
/// handles cycle detection as we go through the query system.
1843+
///
1844+
/// This is necessary in the first place for the following case:
1845+
/// ```
1846+
/// type Alias<'a, T> = <T as Trait<'a>>::Assoc;
1847+
/// fn foo<'a>(_: Alias<'a, ()>) -> Alias<'a, ()> { ... }
1848+
/// ```
1849+
///
1850+
/// If we conservatively considered `'a` unconstrained then we could break users who had written code before
1851+
/// we started correctly handling aliases. If we considered `'a` constrained then it would become late bound
1852+
/// causing an error during astconv as the `'a` is not constrained by the input type `<() as Trait<'a>>::Assoc`
1853+
/// but appears in the output type `<() as Trait<'a>>::Assoc`.
1854+
///
1855+
/// We must therefore "look into" the `Alias` to see whether we should consider `'a` constrained or not.
1856+
///
1857+
/// See #100508 #85533 #47511 for additional context
1858+
struct ConstrainedCollectorPostAstConv {
1859+
arg_is_constrained: Box<[bool]>,
1860+
}
1861+
1862+
use std::ops::ControlFlow;
1863+
use ty::Ty;
1864+
impl<'tcx> TypeVisitor<'tcx> for ConstrainedCollectorPostAstConv {
1865+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<!> {
1866+
match t.kind() {
1867+
ty::Param(param_ty) => {
1868+
self.arg_is_constrained[param_ty.index as usize] = true;
1869+
}
1870+
ty::Projection(_) => return ControlFlow::Continue(()),
1871+
_ => (),
1872+
}
1873+
t.super_visit_with(self)
1874+
}
1875+
1876+
fn visit_const(&mut self, _: ty::Const<'tcx>) -> ControlFlow<!> {
1877+
ControlFlow::Continue(())
1878+
}
1879+
1880+
fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<!> {
1881+
debug!("r={:?}", r.kind());
1882+
if let ty::RegionKind::ReEarlyBound(region) = r.kind() {
1883+
self.arg_is_constrained[region.index as usize] = true;
1884+
}
1885+
1886+
ControlFlow::Continue(())
1887+
}
1888+
}
1889+
1890+
struct ConstrainedCollector<'tcx> {
1891+
tcx: TyCtxt<'tcx>,
18391892
regions: FxHashSet<LocalDefId>,
18401893
}
18411894

1842-
impl<'v> Visitor<'v> for ConstrainedCollector {
1895+
impl<'v> Visitor<'v> for ConstrainedCollector<'_> {
18431896
fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
18441897
match ty.kind {
18451898
hir::TyKind::Path(
@@ -1850,6 +1903,47 @@ fn is_late_bound_map(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<&FxIndexSet<
18501903
// (defined above)
18511904
}
18521905

1906+
hir::TyKind::Path(hir::QPath::Resolved(
1907+
None,
1908+
hir::Path { res: Res::Def(DefKind::TyAlias, alias_def), segments, span },
1909+
)) => {
1910+
// See comments on `ConstrainedCollectorPostAstConv` for why this arm does not just consider
1911+
// substs to be unconstrained.
1912+
let generics = self.tcx.generics_of(alias_def);
1913+
let mut walker = ConstrainedCollectorPostAstConv {
1914+
arg_is_constrained: vec![false; generics.params.len()].into_boxed_slice(),
1915+
};
1916+
walker.visit_ty(self.tcx.type_of(alias_def));
1917+
1918+
match segments.last() {
1919+
Some(hir::PathSegment { args: Some(args), .. }) => {
1920+
let tcx = self.tcx;
1921+
for constrained_arg in
1922+
args.args.iter().enumerate().flat_map(|(n, arg)| {
1923+
match walker.arg_is_constrained.get(n) {
1924+
Some(true) => Some(arg),
1925+
Some(false) => None,
1926+
None => {
1927+
tcx.sess.delay_span_bug(
1928+
*span,
1929+
format!(
1930+
"Incorrect generic arg count for alias {:?}",
1931+
alias_def
1932+
),
1933+
);
1934+
None
1935+
}
1936+
}
1937+
})
1938+
{
1939+
self.visit_generic_arg(constrained_arg);
1940+
}
1941+
}
1942+
Some(_) => (),
1943+
None => bug!("Path with no segments or self type"),
1944+
}
1945+
}
1946+
18531947
hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => {
18541948
// consider only the lifetimes on the final
18551949
// segment; I am not sure it's even currently

src/test/ui/issues/issue-47511.stderr

-18
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub trait Trait<'a> {
2+
type Assoc;
3+
}
4+
5+
pub type Alias<'a, T> = <T as Trait<'a>>::Assoc;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// aux-build:upstream_alias.rs
2+
// check-pass
3+
4+
extern crate upstream_alias;
5+
6+
fn foo<'a, T: for<'b> upstream_alias::Trait<'b>>(_: upstream_alias::Alias<'a, T>) -> &'a () {
7+
todo!()
8+
}
9+
10+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// check-pass
2+
3+
trait Gats<'a> {
4+
type Assoc;
5+
type Assoc2;
6+
}
7+
8+
trait Trait: for<'a> Gats<'a> {
9+
fn foo<'a>(_: &mut <Self as Gats<'a>>::Assoc) -> <Self as Gats<'a>>::Assoc2;
10+
}
11+
12+
impl<'a> Gats<'a> for () {
13+
type Assoc = &'a u32;
14+
type Assoc2 = ();
15+
}
16+
17+
type GatsAssoc<'a, T> = <T as Gats<'a>>::Assoc;
18+
type GatsAssoc2<'a, T> = <T as Gats<'a>>::Assoc2;
19+
20+
impl Trait for () {
21+
fn foo<'a>(_: &mut GatsAssoc<'a, Self>) -> GatsAssoc2<'a, Self> {}
22+
}
23+
24+
fn main() {}

src/test/ui/issues/issue-47511.rs src/test/ui/late-bound-lifetimes/issue-47511.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
// check-fail
2-
// known-bug: #47511
3-
4-
// Regression test for #47511: anonymous lifetimes can appear
5-
// unconstrained in a return type, but only if they appear just once
6-
// in the input, as the input to a projection.
1+
// check-pass
72

83
fn f(_: X) -> X {
94
unimplemented!()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// check-pass
2+
3+
fn f(_: X) -> X {
4+
unimplemented!()
5+
}
6+
7+
fn g<'a>(_: X<'a>) -> X<'a> {
8+
unimplemented!()
9+
}
10+
11+
type X<'a> = &'a ();
12+
13+
fn main() {
14+
let _: for<'a> fn(X<'a>) -> X<'a> = g;
15+
let _: for<'a> fn(X<'a>) -> X<'a> = f;
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// ensures that we don't ICE when there are too many args supplied to the alias.
2+
3+
trait Trait<'a> {
4+
type Assoc;
5+
}
6+
7+
type Alias<'a, T> = <T as Trait<'a>>::Assoc;
8+
9+
fn bar<'a, T: Trait<'a>>(_: Alias<'a, 'a, T>) {}
10+
//~^ error: this type alias takes 1 lifetime argument but 2 lifetime arguments were supplied
11+
12+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0107]: this type alias takes 1 lifetime argument but 2 lifetime arguments were supplied
2+
--> $DIR/mismatched_arg_count.rs:9:29
3+
|
4+
LL | fn bar<'a, T: Trait<'a>>(_: Alias<'a, 'a, T>) {}
5+
| ^^^^^ -- help: remove this lifetime argument
6+
| |
7+
| expected 1 lifetime argument
8+
|
9+
note: type alias defined here, with 1 lifetime parameter: `'a`
10+
--> $DIR/mismatched_arg_count.rs:7:6
11+
|
12+
LL | type Alias<'a, T> = <T as Trait<'a>>::Assoc;
13+
| ^^^^^ --
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0107`.

0 commit comments

Comments
 (0)