From ac18bb3662fb51886c7fe4ffecc84ea2b784455b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 9 Jun 2023 15:30:08 +0200 Subject: [PATCH] rustdoc: elide cross-crate default generic arguments --- src/librustdoc/clean/utils.rs | 141 +++++++++++++++--- tests/rustdoc/const-generics/add-impl.rs | 2 +- .../auxiliary/default-generic-args.rs | 45 ++++++ .../inline_cross/default-generic-args.rs | 108 ++++++++++++++ tests/rustdoc/inline_cross/dyn_trait.rs | 8 +- tests/rustdoc/inline_cross/impl_trait.rs | 4 +- tests/rustdoc/normalize-assoc-item.rs | 4 +- tests/rustdoc/where-clause-order.rs | 2 +- 8 files changed, 283 insertions(+), 31 deletions(-) create mode 100644 tests/rustdoc/inline_cross/auxiliary/default-generic-args.rs create mode 100644 tests/rustdoc/inline_cross/default-generic-args.rs diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 8388f722a7f1a..25d6783a0d7ca 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -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; @@ -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, ) -> Vec { - 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>>( + 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>( diff --git a/tests/rustdoc/const-generics/add-impl.rs b/tests/rustdoc/const-generics/add-impl.rs index 195e47bc8ba80..df490d2c636ee 100644 --- a/tests/rustdoc/const-generics/add-impl.rs +++ b/tests/rustdoc/const-generics/add-impl.rs @@ -7,7 +7,7 @@ pub struct Simd { inner: T, } -// @has foo/struct.Simd.html '//div[@id="trait-implementations-list"]//h3[@class="code-header"]' 'impl Add> for Simd' +// @has foo/struct.Simd.html '//div[@id="trait-implementations-list"]//h3[@class="code-header"]' 'impl Add for Simd' impl Add for Simd { type Output = Self; diff --git a/tests/rustdoc/inline_cross/auxiliary/default-generic-args.rs b/tests/rustdoc/inline_cross/auxiliary/default-generic-args.rs new file mode 100644 index 0000000000000..1e31f18927e9d --- /dev/null +++ b/tests/rustdoc/inline_cross/auxiliary/default-generic-args.rs @@ -0,0 +1,45 @@ +pub type BoxedStr = Box; +pub type IntMap = std::collections::HashMap; + +pub struct TyPair(T, U); + +pub type T0 = TyPair; +pub type T1 = TyPair; +pub type T2 = TyPair; +pub type T3 = TyPair; + +pub struct CtPair; + +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::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 fn(&'any ())>(T); + +pub type A0 = Alpha; +pub type A1 = Alpha fn(&'arbitrary ())>; + +pub struct Multi(A, B); + +pub type M0 = Multi; + +pub trait Trait<'a, T = &'a ()> {} + +pub type F = dyn for<'a> Trait<'a>; diff --git a/tests/rustdoc/inline_cross/default-generic-args.rs b/tests/rustdoc/inline_cross/default-generic-args.rs new file mode 100644 index 0000000000000..702b8e8189132 --- /dev/null +++ b/tests/rustdoc/inline_cross/default-generic-args.rs @@ -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" +pub use default_generic_args::BoxedStr; + +// @has user/type.IntMap.html +// @has - '//*[@class="rust item-decl"]//code' "HashMap" +pub use default_generic_args::IntMap; + +// @has user/type.T0.html +// @has - '//*[@class="rust item-decl"]//code' "TyPair" +pub use default_generic_args::T0; + +// @has user/type.T1.html +// @has - '//*[@class="rust item-decl"]//code' "TyPair" +pub use default_generic_args::T1; + +// @has user/type.T2.html +// @has - '//*[@class="rust item-decl"]//code' "TyPair" +pub use default_generic_args::T2; + +// @has user/type.T3.html +// @has - '//*[@class="rust item-decl"]//code' "TyPair" +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" +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`). +// @has - '//*[@class="rust item-decl"]//code' "Multi" +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; diff --git a/tests/rustdoc/inline_cross/dyn_trait.rs b/tests/rustdoc/inline_cross/dyn_trait.rs index 679972f035ad4..9871be79ca367 100644 --- a/tests/rustdoc/inline_cross/dyn_trait.rs +++ b/tests/rustdoc/inline_cross/dyn_trait.rs @@ -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;" +// @has - '//*[@class="rust item-decl"]//code' "Box;" pub use dyn_trait::NoBoundsWrappedDefaulted; // @has user/type.NoBoundsWrappedEarly.html -// @has - '//*[@class="rust item-decl"]//code' "Box;" +// @has - '//*[@class="rust item-decl"]//code' "Box;" pub use dyn_trait::NoBoundsWrappedEarly; // @has user/fn.nbwl.html -// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box)" +// @has - '//pre[@class="rust item-decl"]' "nbwl<'l>(_: Box)" pub use dyn_trait::no_bounds_wrapped_late as nbwl; // @has user/fn.nbwel.html -// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box)" +// @has - '//pre[@class="rust item-decl"]' "nbwel(_: Box)" // 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; diff --git a/tests/rustdoc/inline_cross/impl_trait.rs b/tests/rustdoc/inline_cross/impl_trait.rs index b6a1552bc00ca..99471ed074fa9 100644 --- a/tests/rustdoc/inline_cross/impl_trait.rs +++ b/tests/rustdoc/inline_cross/impl_trait.rs @@ -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> + 'a)" +// @has - '//pre[@class="rust item-decl"]' "pub fn func<'a>(_x: impl Clone + Into> + 'a)" // @!has - '//pre[@class="rust item-decl"]' 'where' pub use impl_trait_aux::func; @@ -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> + 'a)" +// @has - '//*[@id="method.method"]//h4[@class="code-header"]' "pub fn method<'a>(_x: impl Clone + Into> + 'a)" // @!has - '//*[@id="method.method"]//h4[@class="code-header"]' 'where' pub use impl_trait_aux::Foo; diff --git a/tests/rustdoc/normalize-assoc-item.rs b/tests/rustdoc/normalize-assoc-item.rs index c6fd5e1101ef5..d39e1b15a4cbe 100644 --- a/tests/rustdoc/normalize-assoc-item.rs +++ b/tests/rustdoc/normalize-assoc-item.rs @@ -30,7 +30,7 @@ pub fn f2() -> ::X { } pub struct S { - // @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.box_me_up"]' 'box_me_up: Box' + // @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.box_me_up"]' 'box_me_up: Box' pub box_me_up: ::X, // @has 'normalize_assoc_item/struct.S.html' '//span[@id="structfield.generic"]' 'generic: (usize, isize)' pub generic: as Trait>::X, @@ -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() -> IntoIter" +// @has 'normalize_assoc_item/fn.h.html' '//pre[@class="rust item-decl"]' "pub fn h() -> IntoIter" pub fn h() -> as IntoIterator>::IntoIter { vec![].into_iter() } diff --git a/tests/rustdoc/where-clause-order.rs b/tests/rustdoc/where-clause-order.rs index b10f8f6856e8c..ce5034e35c105 100644 --- a/tests/rustdoc/where-clause-order.rs +++ b/tests/rustdoc/where-clause-order.rs @@ -7,7 +7,7 @@ where } // @has 'foo/trait.SomeTrait.html' -// @has - "//*[@id='impl-SomeTrait%3C(A,+B,+C,+D,+E)%3E-for-(A,+B,+C,+D,+E)']/h3" "impl SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E)where A: PartialOrd + PartialEq, B: PartialOrd + PartialEq, C: PartialOrd + PartialEq, D: PartialOrd + PartialEq, E: PartialOrd + PartialEq + ?Sized, " +// @has - "//*[@id='impl-SomeTrait-for-(A,+B,+C,+D,+E)']/h3" "impl SomeTrait for (A, B, C, D, E)where A: PartialOrd + PartialEq, B: PartialOrd + PartialEq, C: PartialOrd + PartialEq, D: PartialOrd + PartialEq, E: PartialOrd + PartialEq + ?Sized, " impl SomeTrait<(A, B, C, D, E)> for (A, B, C, D, E) where A: PartialOrd + PartialEq,