From fe19eaccc12ae20444518ea8af072cd5203dad0c 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/mod.rs | 12 +- src/librustdoc/clean/utils.rs | 132 ++++++++++++++---- tests/rustdoc/const-generics/add-impl.rs | 2 +- .../auxiliary/default-generic-args.rs | 45 ++++++ .../inline_cross/default-generic-args.rs | 106 ++++++++++++++ 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 +- 9 files changed, 274 insertions(+), 41 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/mod.rs b/src/librustdoc/clean/mod.rs index f2447b877ca80..445f86a0b7064 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -475,8 +475,9 @@ fn projection_to_path_segment<'tcx>( ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>, cx: &mut DocContext<'tcx>, ) -> PathSegment { - let item = cx.tcx.associated_item(ty.skip_binder().def_id); - let generics = cx.tcx.generics_of(ty.skip_binder().def_id); + let def_id = ty.skip_binder().def_id; + let item = cx.tcx.associated_item(def_id); + let generics = cx.tcx.generics_of(def_id); PathSegment { name: item.name, args: GenericArgs::AngleBracketed { @@ -484,7 +485,7 @@ fn projection_to_path_segment<'tcx>( cx, ty.map_bound(|ty| &ty.args[generics.parent_count..]), false, - None, + def_id, ) .into(), bindings: Default::default(), @@ -2200,18 +2201,19 @@ pub(crate) fn clean_middle_ty<'tcx>( } ty::Alias(ty::Inherent, alias_ty) => { + let def_id = alias_ty.def_id; let alias_ty = bound_ty.rebind(alias_ty); let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None); Type::QPath(Box::new(QPathData { assoc: PathSegment { - name: cx.tcx.associated_item(alias_ty.skip_binder().def_id).name, + name: cx.tcx.associated_item(def_id).name, args: GenericArgs::AngleBracketed { args: ty_args_to_args( cx, alias_ty.map_bound(|ty| ty.args.as_slice()), true, - None, + def_id, ) .into(), bindings: Default::default(), diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index c5302570489d9..dea7bfaf7e22f 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -17,6 +17,7 @@ use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE}; use rustc_metadata::rendered_const; use rustc_middle::mir; use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt}; +use rustc_middle::ty::{TypeVisitable, TypeVisitableExt}; use rustc_span::symbol::{kw, sym, Symbol}; use std::fmt::Write as _; use std::mem; @@ -76,44 +77,123 @@ 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, + owner: DefId, ) -> 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 + if ty_args.skip_binder().is_empty() { + // Fast path which avoids executing the query `generics_of`. + return Vec::new(); + } + + let params = &cx.tcx.generics_of(owner).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(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(ty_args.rebind(ty), default) { + 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, + Some(crate::clean::ContainerTy::Regular { + ty: owner, + args: ty_args, has_self, arg: index, }), - ))), + ))) + } + GenericArgKind::Const(ct) => { // FIXME(effects): this relies on the host effect being called `host`, which users could also name // their const generics. // FIXME(effects): this causes `host = true` and `host = false` generics to also be emitted. - GenericArgKind::Const(ct) if let ty::ConstKind::Param(p) = ct.kind() && p.name == sym::host => None, - GenericArgKind::Const(ct) => { - Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx)))) + if let ty::ConstKind::Param(p) = ct.kind() + && p.name == sym::host + { + return None; } + + if !elision_has_failed_once_before + && 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(ty_args.rebind(ct), default) { + 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. +/// +/// This uses a very conservative approach for performance and correctness reasons, meaning for +/// several classes of terms it claims that they cannot be elided even if they theoretically could. +/// This is absolutely fine since it mostly concerns edge cases. +fn can_elide_generic_arg<'tcx, Term>( + actual: ty::Binder<'tcx, Term>, + default: ty::Binder<'tcx, Term>, +) -> bool +where + Term: Eq + TypeVisitable>, +{ + // In practice, we shouldn't have any inference variables at this point. + // However to be safe, we bail out if we do happen to stumble upon them. + if actual.has_infer() || default.has_infer() { + return false; + } + + // Since we don't properly keep track of bound variables in rustdoc (yet), we don't attempt to + // make any sense out of escaping bound variables. We simply don't have enough context and it + // would be incorrect to try to do so anyway. + if actual.has_escaping_bound_vars() || default.has_escaping_bound_vars() { + return false; + } + + // Theoretically we could now check if either term contains (non-escaping) late-bound regions or + // projections, relate the two using an `InferCtxt` and check if the resulting obligations hold. + // Having projections means that the terms can potentially be further normalized thereby possibly + // revealing that they are equal after all. Regarding late-bound regions, they could to be + // liberated allowing us to consider more types to be equal by ignoring the names of binders + // (e.g., `for<'a> TYPE<'a>` and `for<'b> TYPE<'b>`). + // + // However, we are mostly interested in “reeliding” generic args, i.e., eliding generic args that + // were originally elided by the user and later filled in by the compiler contrary to eliding + // arbitrary generic arguments if they happen to semantically coincide with the default (of course, + // we cannot possibly distinguish these two cases). Therefore and for performance reasons, it + // suffices to only perform a syntactic / structural check by comparing the memory addresses of + // the interned arguments. + actual.skip_binder() == default.skip_binder() } fn external_generic_args<'tcx>( @@ -123,7 +203,7 @@ fn external_generic_args<'tcx>( bindings: ThinVec, ty_args: ty::Binder<'tcx, GenericArgsRef<'tcx>>, ) -> GenericArgs { - let args = ty_args_to_args(cx, ty_args.map_bound(|args| &args[..]), has_self, Some(did)); + let args = ty_args_to_args(cx, ty_args.map_bound(|args| &args[..]), has_self, did); if cx.tcx.fn_trait_kind_from_def_id(did).is_some() { let ty = ty_args 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..06990698f5791 --- /dev/null +++ b/tests/rustdoc/inline_cross/default-generic-args.rs @@ -0,0 +1,106 @@ +#![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 +// FIXME: Add a comment here. +// @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: +// @has - '//*[@class="rust item-decl"]//code' "fn(_: for<'a> 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): +// @has - '//*[@class="rust item-decl"]//code' "for<'b> fn(_: for<'a> 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' "for<'a> fn(_: for<'b> fn(_: Re<'a, &'b ()>))" +pub use default_generic_args::H2; + +// @has user/type.P0.html +// FIXME: Add comment here. +// @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<(), bool>" +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 9b22026e49064..3a2f5d160045c 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; @@ -34,6 +34,6 @@ pub use impl_trait_aux::func4; pub use impl_trait_aux::func5; // @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 7261dfa7dd912..e3184b646bf5e 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-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,