diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2023-10-30 17:33:14 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-30 17:33:14 +0100 |
| commit | b9dce53d4ac718ccde1c406f82fea70573b27628 (patch) | |
| tree | 2e16253a89691623659230ce206252cec4c375b1 /src | |
| parent | 6d1fc53cf4f61b0dce5fc84cebae3b9fb8a45436 (diff) | |
| parent | 58a80c85b9323e59e1b7e744d6782997a3dcb5ce (diff) | |
| download | rust-b9dce53d4ac718ccde1c406f82fea70573b27628.tar.gz rust-b9dce53d4ac718ccde1c406f82fea70573b27628.zip | |
Rollup merge of #112463 - fmease:rustdoc-elide-x-crate-def-gen-args, r=GuillaumeGomez
rustdoc: elide cross-crate default generic arguments 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 #80379. Also helps with #44306. Follow-up to #103885, #107637. r? ``@ghost``
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustdoc/clean/mod.rs | 12 | ||||
| -rw-r--r-- | src/librustdoc/clean/utils.rs | 132 |
2 files changed, 113 insertions, 31 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4d4c102e69e..e6e2d60f2e5 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 c5302570489..dea7bfaf7e2 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<DefId>, + owner: 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 + 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<TyCtxt<'tcx>>, +{ + // 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<TypeBinding>, 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 |
