Skip to content

Commit

Permalink
Auto merge of rust-lang#112463 - fmease:rustdoc-elide-x-crate-def-gen…
Browse files Browse the repository at this point in the history
…-args, r=<try>

rustdoc: elide cross-crate default generic arguments

Early draft. Requesting perf run. Thx :) CC `@GuillaumeGomez`

Elide cross-crate generic arguments if they coincide with their default.
TL;DR: Most notably, no more `Box<…, Global>` in `std`'s docs, just `Box<…>` from now on.
Fixes rust-lang#80379.

Also helps with rust-lang#44306. Follow-up to rust-lang#103885,rust-lang#107637.

`@rustbot` label T-rustdoc A-cross-crate-reexports S-experimental
r? `@ghost`
  • Loading branch information
bors committed Sep 22, 2023
2 parents e4133ba + ac18bb3 commit 6bc50e2
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 31 deletions.
141 changes: 120 additions & 21 deletions src/librustdoc/clean/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ use rustc_ast::tokenstream::TokenTree;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
use rustc_infer::infer::at::ToTrace;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_metadata::rendered_const;
use rustc_middle::mir;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt};
use rustc_middle::ty::{TypeVisitable, TypeVisitableExt};
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_trait_selection::infer::TyCtxtInferExt;
use rustc_trait_selection::traits::ObligationCtxt;
use std::fmt::Write as _;
use std::mem;
use std::sync::LazyLock as Lazy;
Expand Down Expand Up @@ -76,40 +82,133 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {

pub(crate) fn ty_args_to_args<'tcx>(
cx: &mut DocContext<'tcx>,
args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
ty_args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>,
has_self: bool,
container: Option<DefId>,
) -> Vec<GenericArg> {
let mut skip_first = has_self;
let mut ret_val =
Vec::with_capacity(args.skip_binder().len().saturating_sub(if skip_first { 1 } else { 0 }));

ret_val.extend(args.iter().enumerate().filter_map(|(index, kind)| {
match kind.skip_binder().unpack() {
GenericArgKind::Lifetime(lt) => {
Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
}
GenericArgKind::Type(_) if skip_first => {
skip_first = false;
None
let param_env = ty::ParamEnv::empty();
let cause = ObligationCause::dummy();
let params = container.map(|container| &cx.tcx.generics_of(container).params);
let mut elision_has_failed_once_before = false;

let offset = if has_self { 1 } else { 0 };
let mut args = Vec::with_capacity(ty_args.skip_binder().len().saturating_sub(offset));

let ty_arg_to_arg = |(index, arg): (usize, &ty::GenericArg<'tcx>)| match arg.unpack() {
GenericArgKind::Lifetime(lt) => {
Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
}
GenericArgKind::Type(_) if has_self && index == 0 => None,
GenericArgKind::Type(ty) => {
if !elision_has_failed_once_before
&& let Some(params) = params
&& let Some(default) = params[index].default_value(cx.tcx)
{
let default =
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_ty());

if can_elide_generic_arg(
cx.tcx,
&cause,
param_env,
ty_args.rebind(ty),
default,
params[index].def_id,
) {
return None;
}

elision_has_failed_once_before = true;
}
GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty(
kind.rebind(ty),

Some(GenericArg::Type(clean_middle_ty(
ty_args.rebind(ty),
cx,
None,
container.map(|container| crate::clean::ContainerTy::Regular {
ty: container,
args,
args: ty_args,
has_self,
arg: index,
}),
))),
GenericArgKind::Const(ct) => {
Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
)))
}
GenericArgKind::Const(ct) => {
if !elision_has_failed_once_before
&& let Some(params) = params
&& let Some(default) = params[index].default_value(cx.tcx)
{
let default =
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_const());

if can_elide_generic_arg(
cx.tcx,
&cause,
param_env,
ty_args.rebind(ct),
default,
params[index].def_id,
) {
return None;
}

elision_has_failed_once_before = true;
}

Some(GenericArg::Const(Box::new(clean_middle_const(ty_args.rebind(ct), cx))))
}
}));
ret_val
};

args.extend(ty_args.skip_binder().iter().enumerate().rev().filter_map(ty_arg_to_arg));
args.reverse();
args
}

/// Check if the generic argument `actual` coincides with the `default` and can therefore be elided.
fn can_elide_generic_arg<'tcx, T: ToTrace<'tcx> + TypeVisitable<TyCtxt<'tcx>>>(
tcx: TyCtxt<'tcx>,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
actual: ty::Binder<'tcx, T>,
default: ty::Binder<'tcx, T>,
did: DefId,
) -> bool {
// The operations below are only correct if we don't have any inference variables.
debug_assert!(!actual.has_infer());
debug_assert!(!default.has_infer());

// Since we don't properly keep track of bound variables, don't attempt to make
// any sense out of escaping bound variables (we just don't have enough context).
if actual.has_escaping_bound_vars() || default.has_escaping_bound_vars() {
return false;
}

// If the arguments contain projections or (non-escaping) late-bound regions, we have to examine
// them more closely and can't take the fast path.
// Having projections means that there's potential to be further normalized thereby revealing if
// they are equal after all. Regarding late-bound regions, they can be liberated allowing us to
// consider more types to be equal by ignoring the names of binders.
if !actual.has_late_bound_regions()
&& !actual.has_projections()
&& !default.has_late_bound_regions()
&& !default.has_projections()
{
// Check the memory addresses of the interned arguments for equality.
return actual.skip_binder() == default.skip_binder();
}

let actual = tcx.liberate_late_bound_regions(did, actual);
let default = tcx.liberate_late_bound_regions(did, default);

let infcx = tcx.infer_ctxt().build();
let ocx = ObligationCtxt::new(&infcx);

let actual = ocx.normalize(cause, param_env, actual);
let default = ocx.normalize(cause, param_env, default);

ocx.eq(cause, param_env, actual, default).is_ok()
&& ocx.select_all_or_error().is_empty()
&& infcx.resolve_regions(&OutlivesEnvironment::new(param_env)).is_empty()
}

fn external_generic_args<'tcx>(
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc/const-generics/add-impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct Simd<T, const WIDTH: usize> {
inner: T,
}

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

Expand Down
45 changes: 45 additions & 0 deletions tests/rustdoc/inline_cross/auxiliary/default-generic-args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
pub type BoxedStr = Box<str>;
pub type IntMap = std::collections::HashMap<i64, u64>;

pub struct TyPair<T, U = T>(T, U);

pub type T0 = TyPair<i32>;
pub type T1 = TyPair<i32, u32>;
pub type T2<K> = TyPair<i32, K>;
pub type T3<Q> = TyPair<Q, Q>;

pub struct CtPair<const C: u32, const D: u32 = C>;

pub type C0 = CtPair<43, 43>;
pub type C1 = CtPair<0, 1>;
pub type C2 = CtPair<{1 + 2}, 3>;

pub struct Re<'a, U = &'a ()>(&'a (), U);

pub type R0<'q> = Re<'q>;
pub type R1<'q> = Re<'q, &'q ()>;
pub type R2<'q> = Re<'q, &'static ()>;
pub type H0 = fn(for<'a> fn(Re<'a>));
pub type H1 = for<'b> fn(for<'a> fn(Re<'a, &'b ()>));
pub type H2 = for<'a> fn(for<'b> fn(Re<'a, &'b ()>));

pub struct Proj<T: Basis, U = <T as Basis>::Assoc>(T, U);
pub trait Basis { type Assoc; }
impl Basis for () { type Assoc = bool; }

pub type P0 = Proj<()>;
pub type P1 = Proj<(), bool>;
pub type P2 = Proj<(), ()>;

pub struct Alpha<T = for<'any> fn(&'any ())>(T);

pub type A0 = Alpha;
pub type A1 = Alpha<for<'arbitrary> fn(&'arbitrary ())>;

pub struct Multi<A = u64, B = u64>(A, B);

pub type M0 = Multi<u64, ()>;

pub trait Trait<'a, T = &'a ()> {}

pub type F = dyn for<'a> Trait<'a>;
108 changes: 108 additions & 0 deletions tests/rustdoc/inline_cross/default-generic-args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#![crate_name = "user"]
// aux-crate:default_generic_args=default-generic-args.rs
// edition:2021

// @has user/type.BoxedStr.html
// @has - '//*[@class="rust item-decl"]//code' "Box<str>"
pub use default_generic_args::BoxedStr;

// @has user/type.IntMap.html
// @has - '//*[@class="rust item-decl"]//code' "HashMap<i64, u64>"
pub use default_generic_args::IntMap;

// @has user/type.T0.html
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32>"
pub use default_generic_args::T0;

// @has user/type.T1.html
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32, u32>"
pub use default_generic_args::T1;

// @has user/type.T2.html
// @has - '//*[@class="rust item-decl"]//code' "TyPair<i32, K>"
pub use default_generic_args::T2;

// @has user/type.T3.html
// @has - '//*[@class="rust item-decl"]//code' "TyPair<Q>"
pub use default_generic_args::T3;

// @has user/type.C0.html
// @has - '//*[@class="rust item-decl"]//code' "CtPair<43>"
pub use default_generic_args::C0;

// @has user/type.C1.html
// @has - '//*[@class="rust item-decl"]//code' "CtPair<0, 1>"
pub use default_generic_args::C1;

// @has user/type.C2.html
// Test that we normalize constants in this case:
// FIXME: Ideally, we would render `3` here instead of the def-path str of the normalized constant.
// @has - '//*[@class="rust item-decl"]//code' "CtPair<default_generic_args::::C2::{constant#0}>"
pub use default_generic_args::C2;

// @has user/type.R0.html
// @has - '//*[@class="rust item-decl"]//code' "Re<'q>"
pub use default_generic_args::R0;

// @has user/type.R1.html
// @has - '//*[@class="rust item-decl"]//code' "Re<'q>"
pub use default_generic_args::R1;

// @has user/type.R2.html
// Check that we consider regions:
// @has - '//*[@class="rust item-decl"]//code' "Re<'q, &'static ()>"
pub use default_generic_args::R2;

// @has user/type.H0.html
// Check that we handle higher-ranked regions correctly:
// FIXME: Ideally we would also print the *binders* here.
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a>))"
pub use default_generic_args::H0;

// @has user/type.H1.html
// Check that we don't conflate distinct universially quantified regions (#1):
// FIXME: Ideally we would also print the *binders* here.
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, &'b ()>))"
pub use default_generic_args::H1;

// @has user/type.H2.html
// Check that we don't conflate distinct universially quantified regions (#2):
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, &'b ()>))"
pub use default_generic_args::H2;

// @has user/type.P0.html
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
pub use default_generic_args::P0;

// @has user/type.P1.html
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
pub use default_generic_args::P1;

// @has user/type.P2.html
// @has - '//*[@class="rust item-decl"]//code' "Proj<(), ()>"
pub use default_generic_args::P2;

// @has user/type.A0.html
// Ensure that we elide generic arguments that are alpha-equivalent to their respective
// generic parameter (modulo substs) (#1):
// @has - '//*[@class="rust item-decl"]//code' "Alpha"
pub use default_generic_args::A0;

// @has user/type.A1.html
// Ensure that we elide generic arguments that are alpha-equivalent to their respective
// generic parameter (modulo substs) (#1):
// @has - '//*[@class="rust item-decl"]//code' "Alpha"
pub use default_generic_args::A1;

// @has user/type.M0.html
// Test that we don't elide `u64` even if it coincides with `A`'s default precisely because
// `()` is not the default of `B`. Mindlessly eliding `u64` would lead to `M<()>` which is a
// different type (`M<(), u64>` versus `M<u64, ()>`).
// @has - '//*[@class="rust item-decl"]//code' "Multi<u64, ()>"
pub use default_generic_args::M0;

// @has user/type.F.html
// FIXME: Ideally, we would elide `&'a ()` but `'a` is an escaping bound var which we can't reason
// about at the moment since we don't keep track of bound vars.
// @has - '//*[@class="rust item-decl"]//code' "dyn for<'a> Trait<'a, &'a ()>"
pub use default_generic_args::F;
8 changes: 4 additions & 4 deletions tests/rustdoc/inline_cross/dyn_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ pub use dyn_trait::AmbiguousBoundWrappedEarly1;
pub use dyn_trait::AmbiguousBoundWrappedStatic;

// @has user/type.NoBoundsWrappedDefaulted.html
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait, Global>;"
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait>;"
pub use dyn_trait::NoBoundsWrappedDefaulted;
// @has user/type.NoBoundsWrappedEarly.html
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait + 'e, Global>;"
// @has - '//*[@class="rust item-decl"]//code' "Box<dyn Trait + 'e>;"
pub use dyn_trait::NoBoundsWrappedEarly;
// @has user/fn.nbwl.html
// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box<dyn Trait + 'l, Global>)"
// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box<dyn Trait + 'l>)"
pub use dyn_trait::no_bounds_wrapped_late as nbwl;
// @has user/fn.nbwel.html
// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box<dyn Trait + '_, Global>)"
// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box<dyn Trait + '_>)"
// NB: It might seem counterintuitive to display the explicitly elided lifetime `'_` here instead of
// eliding it but this behavior is correct: The default is `'static` here which != `'_`.
pub use dyn_trait::no_bounds_wrapped_elided as nbwel;
Expand Down
4 changes: 2 additions & 2 deletions tests/rustdoc/inline_cross/impl_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
extern crate impl_trait_aux;

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

Expand Down Expand Up @@ -38,7 +38,7 @@ pub use impl_trait_aux::func5;
pub use impl_trait_aux::async_fn;

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

Expand Down
4 changes: 2 additions & 2 deletions tests/rustdoc/normalize-assoc-item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn f2() -> <isize as Trait>::X {
}

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

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

0 comments on commit 6bc50e2

Please sign in to comment.