Skip to content

Commit beb1b62

Browse files
committed
rustdoc: elide cross-crate default generic arguments
1 parent a8b905c commit beb1b62

File tree

8 files changed

+273
-30
lines changed

8 files changed

+273
-30
lines changed

src/librustdoc/clean/utils.rs

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ use rustc_ast::tokenstream::TokenTree;
1414
use rustc_hir as hir;
1515
use rustc_hir::def::{DefKind, Res};
1616
use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
17+
use rustc_infer::infer::at::ToTrace;
18+
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
1719
use rustc_middle::mir;
1820
use rustc_middle::mir::interpret::ConstValue;
21+
use rustc_middle::traits::ObligationCause;
1922
use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt};
23+
use rustc_middle::ty::{TypeVisitable, TypeVisitableExt};
2024
use rustc_span::symbol::{kw, sym, Symbol};
25+
use rustc_trait_selection::infer::TyCtxtInferExt;
26+
use rustc_trait_selection::traits::ObligationCtxt;
2127
use std::fmt::Write as _;
2228
use std::mem;
2329
use std::sync::LazyLock as Lazy;
@@ -76,38 +82,129 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
7682

7783
pub(crate) fn ty_args_to_args<'tcx>(
7884
cx: &mut DocContext<'tcx>,
79-
args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
80-
mut skip_first: bool,
85+
ty_args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
86+
skip_first: bool,
8187
container: Option<DefId>,
8288
) -> Vec<GenericArg> {
83-
let mut ret_val =
84-
Vec::with_capacity(args.skip_binder().len().saturating_sub(if skip_first { 1 } else { 0 }));
89+
let param_env = ty::ParamEnv::empty();
90+
let cause = ObligationCause::dummy();
91+
let params = container.map(|container| &cx.tcx.generics_of(container).params);
92+
let mut elision_has_failed_once_before = false;
8593

86-
ret_val.extend(args.iter().enumerate().filter_map(|(index, kind)| {
87-
match kind.skip_binder().unpack() {
88-
GenericArgKind::Lifetime(lt) => {
89-
Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
90-
}
91-
GenericArgKind::Type(_) if skip_first => {
92-
skip_first = false;
93-
None
94+
let offset = if skip_first { 1 } else { 0 };
95+
let mut args = Vec::with_capacity(ty_args.skip_binder().len().saturating_sub(offset));
96+
97+
let ty_arg_to_arg = |(index, arg): (usize, &ty::GenericArg<'tcx>)| match arg.unpack() {
98+
GenericArgKind::Lifetime(lt) => {
99+
Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
100+
}
101+
GenericArgKind::Type(_) if skip_first && index == 0 => None,
102+
GenericArgKind::Type(ty) => {
103+
if !elision_has_failed_once_before
104+
&& let Some(params) = params
105+
&& let Some(default) = params[index].default_value(cx.tcx)
106+
{
107+
let default =
108+
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_ty());
109+
110+
if can_elide_generic_arg(
111+
cx.tcx,
112+
&cause,
113+
param_env,
114+
ty_args.rebind(ty),
115+
default,
116+
params[index].def_id,
117+
) {
118+
return None;
119+
}
120+
121+
elision_has_failed_once_before = true;
94122
}
95-
GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty(
96-
kind.rebind(ty),
123+
124+
Some(GenericArg::Type(clean_middle_ty(
125+
ty_args.rebind(ty),
97126
cx,
98127
None,
99128
container.map(|container| crate::clean::ContainerTy::Regular {
100129
ty: container,
101-
args,
130+
args: ty_args,
102131
arg: index,
103132
}),
104-
))),
105-
GenericArgKind::Const(ct) => {
106-
Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
133+
)))
134+
}
135+
GenericArgKind::Const(ct) => {
136+
if !elision_has_failed_once_before
137+
&& let Some(params) = params
138+
&& let Some(default) = params[index].default_value(cx.tcx)
139+
{
140+
let default =
141+
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_const());
142+
143+
if can_elide_generic_arg(
144+
cx.tcx,
145+
&cause,
146+
param_env,
147+
ty_args.rebind(ct),
148+
default,
149+
params[index].def_id,
150+
) {
151+
return None;
152+
}
153+
154+
elision_has_failed_once_before = true;
107155
}
156+
157+
Some(GenericArg::Const(Box::new(clean_middle_const(ty_args.rebind(ct), cx))))
108158
}
109-
}));
110-
ret_val
159+
};
160+
161+
// FIXME(fmease): This doesn't look performant to me!
162+
args.extend(ty_args.skip_binder().iter().enumerate().rev().filter_map(ty_arg_to_arg));
163+
args.reverse();
164+
args
165+
}
166+
167+
fn can_elide_generic_arg<'tcx, T: ToTrace<'tcx> + TypeVisitable<TyCtxt<'tcx>>>(
168+
tcx: TyCtxt<'tcx>,
169+
cause: &ObligationCause<'tcx>,
170+
param_env: ty::ParamEnv<'tcx>,
171+
actual: ty::Binder<'tcx, T>,
172+
default: ty::Binder<'tcx, T>,
173+
did: DefId,
174+
) -> bool {
175+
assert!(!actual.has_infer());
176+
assert!(!default.has_infer());
177+
178+
// We intentionally look for *arbitrary* late-bound regions instead of only *escaping* ones as we
179+
// want to identify non-escaping alpha-equivalent late-bound regions later.
180+
if !actual.has_late_bound_regions()
181+
&& !actual.has_projections()
182+
&& !default.has_late_bound_regions()
183+
&& !default.has_projections()
184+
{
185+
// fast path
186+
return actual.skip_binder() == default.skip_binder();
187+
}
188+
189+
let infcx = tcx.infer_ctxt().build();
190+
let ocx = ObligationCtxt::new(&infcx);
191+
192+
// FIXME: This seems incorrect. Not sure what one is supposed to do in this situation though.
193+
let aggressively_liberate = |arg: ty::Binder<'tcx, T>| {
194+
tcx.fold_regions(arg.skip_binder(), |r, _| match *r {
195+
ty::ReLateBound(_, br) => ty::Region::new_free(tcx, did, br.kind),
196+
_ => r,
197+
})
198+
};
199+
let actual = aggressively_liberate(actual);
200+
let default = aggressively_liberate(default);
201+
202+
let actual = ocx.normalize(cause, param_env, actual);
203+
let default = ocx.normalize(cause, param_env, default);
204+
205+
ocx.eq(cause, param_env, actual, default).is_ok()
206+
&& ocx.select_all_or_error().is_empty()
207+
&& infcx.resolve_regions(&OutlivesEnvironment::new(param_env)).is_empty()
111208
}
112209

113210
fn external_generic_args<'tcx>(

tests/rustdoc/const-generics/add-impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub struct Simd<T, const WIDTH: usize> {
77
inner: T,
88
}
99

10-
// @has foo/struct.Simd.html '//div[@id="trait-implementations-list"]//h3[@class="code-header"]' 'impl Add<Simd<u8, 16>> for Simd<u8, 16>'
10+
// @has foo/struct.Simd.html '//div[@id="trait-implementations-list"]//h3[@class="code-header"]' 'impl Add for Simd<u8, 16>'
1111
impl Add for Simd<u8, 16> {
1212
type Output = Self;
1313

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
pub type BoxedStr = Box<str>;
2+
pub type IntMap = std::collections::HashMap<i64, u64>;
3+
4+
pub struct TyPair<T, U = T>(T, U);
5+
6+
pub type T0 = TyPair<i32>;
7+
pub type T1 = TyPair<i32, u32>;
8+
pub type T2<K> = TyPair<i32, K>;
9+
pub type T3<Q> = TyPair<Q, Q>;
10+
11+
pub struct CtPair<const C: u32, const D: u32 = C>;
12+
13+
pub type C0 = CtPair<43, 43>;
14+
pub type C1 = CtPair<0, 1>;
15+
pub type C2 = CtPair<{1 + 2}, 3>;
16+
17+
pub struct Re<'a, T, U = &'a T>(&'a T, U);
18+
19+
pub type R0<'q> = Re<'q, ()>;
20+
pub type R1<'q> = Re<'q, (), &'q ()>;
21+
pub type R2<'q> = Re<'q, (), &'static ()>;
22+
pub type H0 = fn(for<'a> fn(Re<'a, ()>));
23+
pub type H1 = for<'b> fn(for<'a> fn(Re<'a, (), &'b ()>));
24+
pub type H2 = for<'a> fn(for<'b> fn(Re<'a, (), &'b ()>));
25+
26+
// FIXME(fmease): Add a GAT test case, too!
27+
28+
pub struct Proj<T: Trait, U = <T as Trait>::Assoc>(T, U);
29+
pub trait Trait { type Assoc; }
30+
impl Trait for () { type Assoc = bool; }
31+
32+
pub type P0 = Proj<()>;
33+
pub type P1 = Proj<(), bool>;
34+
pub type P2 = Proj<(), ()>;
35+
36+
pub struct Alpha<T = for<'any> fn(&'any ())>(T);
37+
38+
pub type A0 = Alpha;
39+
pub type A1 = Alpha<for<'arbitrary> fn(&'arbitrary ())>;
40+
41+
pub struct Multi<A = u64, B = u64>(A, B);
42+
43+
pub type M0 = Multi<u64, ()>;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#![crate_name = "user"]
2+
// aux-crate:default_generic_args=default-generic-args.rs
3+
// edition:2021
4+
5+
// @has user/type.BoxedStr.html
6+
// @has - '//*[@class="rust item-decl"]//code' "Box<str>"
7+
pub use default_generic_args::BoxedStr;
8+
9+
// @has user/type.IntMap.html
10+
// @has - '//*[@class="rust item-decl"]//code' "HashMap<i64, u64>"
11+
pub use default_generic_args::IntMap;
12+
13+
// @has user/type.T0.html
14+
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32>"
15+
pub use default_generic_args::T0;
16+
17+
// @has user/type.T1.html
18+
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32, u32>"
19+
pub use default_generic_args::T1;
20+
21+
// @has user/type.T2.html
22+
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32, K>"
23+
pub use default_generic_args::T2;
24+
25+
// @has user/type.T3.html
26+
// @has - '//*[@class="rust item-decl"]//code' "TyPair<Q>"
27+
pub use default_generic_args::T3;
28+
29+
// @has user/type.C0.html
30+
// @has - '//*[@class="rust item-decl"]//code' "CtPair<43>"
31+
pub use default_generic_args::C0;
32+
33+
// @has user/type.C1.html
34+
// @has - '//*[@class="rust item-decl"]//code' "CtPair<0, 1>"
35+
pub use default_generic_args::C1;
36+
37+
// @has user/type.C2.html
38+
// FIXME: This isn't the fault of this PR but printing the def-path str instead of
39+
// the normalized constant (which would be `3`) isn't all that great :'(
40+
// Test that we normalize constants in this case:
41+
// @has - '//*[@class="rust item-decl"]//code' "CtPair<default_generic_args::::C2::{constant#0}>"
42+
pub use default_generic_args::C2;
43+
44+
// @has user/type.R0.html
45+
// @has - '//*[@class="rust item-decl"]//code' "Re<'q, ()>"
46+
pub use default_generic_args::R0;
47+
48+
// @has user/type.R1.html
49+
// @has - '//*[@class="rust item-decl"]//code' "Re<'q, ()>"
50+
pub use default_generic_args::R1;
51+
52+
// @has user/type.R2.html
53+
// Check that we consider regions:
54+
// @has - '//*[@class="rust item-decl"]//code' "Re<'q, (), &'static ()>"
55+
pub use default_generic_args::R2;
56+
57+
// @has user/type.H0.html
58+
// FIXME: This is not the fault of this PR but ideally we would also print the *binders* here.
59+
// Check that we handle higher-ranked regions correctly:
60+
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, ()>))"
61+
pub use default_generic_args::H0;
62+
63+
// @has user/type.H1.html
64+
// FIXME: This is not the fault of this PR but ideally we would also print the *binders* here.
65+
// Check that we don't conflate distinct universially quantified regions (#1):
66+
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, (), &'b ()>))"
67+
pub use default_generic_args::H1;
68+
69+
// @has user/type.H2.html
70+
// Check that we don't conflate distinct universially quantified regions (#2):
71+
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, (), &'b ()>))"
72+
pub use default_generic_args::H2;
73+
74+
// @has user/type.P0.html
75+
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
76+
pub use default_generic_args::P0;
77+
78+
// @has user/type.P1.html
79+
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
80+
pub use default_generic_args::P1;
81+
82+
// @has user/type.P2.html
83+
// @has - '//*[@class="rust item-decl"]//code' "Proj<(), ()>"
84+
pub use default_generic_args::P2;
85+
86+
// @has user/type.A0.html
87+
// Ensure that we elide generic arguments that are alpha-equivalent to their respective
88+
// generic parameter (modulo substs) (#1):
89+
// @has - '//*[@class="rust item-decl"]//code' "Alpha"
90+
pub use default_generic_args::A0;
91+
92+
// @has user/type.A1.html
93+
// Ensure that we elide generic arguments that are alpha-equivalent to their respective
94+
// generic parameter (modulo substs) (#1):
95+
// @has - '//*[@class="rust item-decl"]//code' "Alpha"
96+
pub use default_generic_args::A1;
97+
98+
// @has user/type.M0.html
99+
// Test that we don't elide `u64` even if it coincides with `A`'s default precisely because
100+
// `()` is not the default of `B`. Mindlessly eliding `u64` would lead to `M<()>` which is a
101+
// different type (`M<(), u64>` versus `M<u64, ()>`).
102+
// @has - '//*[@class="rust item-decl"]//code' "Multi<u64, ()>"
103+
pub use default_generic_args::M0;

tests/rustdoc/inline_cross/dyn_trait.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,16 @@ pub use dyn_trait::AmbiguousBoundWrappedEarly1;
7575
pub use dyn_trait::AmbiguousBoundWrappedStatic;
7676

7777
// @has user/type.NoBoundsWrappedDefaulted.html
78-
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait, Global>;"
78+
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait>;"
7979
pub use dyn_trait::NoBoundsWrappedDefaulted;
8080
// @has user/type.NoBoundsWrappedEarly.html
81-
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait + 'e, Global>;"
81+
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait + 'e>;"
8282
pub use dyn_trait::NoBoundsWrappedEarly;
8383
// @has user/fn.nbwl.html
84-
// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box<dyn Trait + 'l, Global>)"
84+
// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box<dyn Trait + 'l>)"
8585
pub use dyn_trait::no_bounds_wrapped_late as nbwl;
8686
// @has user/fn.nbwel.html
87-
// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box<dyn Trait + '_, Global>)"
87+
// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box<dyn Trait + '_>)"
8888
// NB: It might seem counterintuitive to display the explicitly elided lifetime `'_` here instead of
8989
// eliding it but this behavior is correct: The default is `'static` here which != `'_`.
9090
pub use dyn_trait::no_bounds_wrapped_elided as nbwel;

tests/rustdoc/inline_cross/impl_trait.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
extern crate impl_trait_aux;
55

66
// @has impl_trait/fn.func.html
7-
// @has - '//pre[@class="rust item-decl"]' "pub fn func<'a>(_x: impl Clone + Into<Vec<u8, Global>> + 'a)"
7+
// @has - '//pre[@class="rust item-decl"]' "pub fn func<'a>(_x: impl Clone + Into<Vec<u8>> + 'a)"
88
// @!has - '//pre[@class="rust item-decl"]' 'where'
99
pub use impl_trait_aux::func;
1010

@@ -38,7 +38,7 @@ pub use impl_trait_aux::func5;
3838
pub use impl_trait_aux::async_fn;
3939

4040
// @has impl_trait/struct.Foo.html
41-
// @has - '//*[@id="method.method"]//h4[@class="code-header"]' "pub fn method<'a>(_x: impl Clone + Into<Vec<u8, Global>> + 'a)"
41+
// @has - '//*[@id="method.method"]//h4[@class="code-header"]' "pub fn method<'a>(_x: impl Clone + Into<Vec<u8>> + 'a)"
4242
// @!has - '//*[@id="method.method"]//h4[@class="code-header"]' 'where'
4343
pub use impl_trait_aux::Foo;
4444

tests/rustdoc/normalize-assoc-item.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn f2() -> <isize as Trait>::X {
3030
}
3131

3232
pub struct S {
33-
// @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.box_me_up"]' 'box_me_up: Box<S, Global>'
33+
// @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.box_me_up"]' 'box_me_up: Box<S>'
3434
pub box_me_up: <S as Trait>::X,
3535
// @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.generic"]' 'generic: (usize, isize)'
3636
pub generic: <Generic<usize> as Trait>::X,
@@ -76,7 +76,7 @@ extern crate inner;
7676
// @has 'normalize_assoc_item/fn.foo.html' '//pre[@class="rust item-decl"]' "pub fn foo() -> i32"
7777
pub use inner::foo;
7878

79-
// @has 'normalize_assoc_item/fn.h.html' '//pre[@class="rust item-decl"]' "pub fn h<T>() -> IntoIter<T, Global>"
79+
// @has 'normalize_assoc_item/fn.h.html' '//pre[@class="rust item-decl"]' "pub fn h<T>() -> IntoIter<T>"
8080
pub fn h<T>() -> <Vec<T> as IntoIterator>::IntoIter {
8181
vec![].into_iter()
8282
}

tests/rustdoc/where-clause-order.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ where
77
}
88

99
// @has 'foo/trait.SomeTrait.html'
10-
// @has - "//*[@id='impl-SomeTrait%3C(A,+B,+C,+D,+E)%3E-for-(A,+B,+C,+D,+E)']/h3" "impl<A, B, C, D, E> SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E)where A: PartialOrd<A> + PartialEq<A>, B: PartialOrd<B> + PartialEq<B>, C: PartialOrd<C> + PartialEq<C>, D: PartialOrd<D> + PartialEq<D>, E: PartialOrd<E> + PartialEq<E> + ?Sized, "
10+
// @has - "//*[@id='impl-SomeTrait-for-(A,+B,+C,+D,+E)']/h3" "impl<A, B, C, D, E> SomeTrait for (A, B, C, D, E)where A: PartialOrd<A> + PartialEq<A>, B: PartialOrd<B> + PartialEq<B>, C: PartialOrd<C> + PartialEq<C>, D: PartialOrd<D> + PartialEq<D>, E: PartialOrd<E> + PartialEq<E> + ?Sized, "
1111
impl<A, B, C, D, E> SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E)
1212
where
1313
A: PartialOrd<A> + PartialEq<A>,

0 commit comments

Comments
 (0)