From a192e5d9c2e90798ffcd0bc79527cdc4ad52d949 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Fri, 2 Oct 2020 16:21:43 -0400 Subject: [PATCH] Normalize `::T` for rustdoc - Only run for `QPath::Resolved` with `Some` self parameter (`::T`) - Fall back to the previous behavior if the path can't be resolved - Show what the behavior is if the type can't be normalized - Run `resolve_vars_if_possible` It's not clear whether or not this is necessary. See https://github.com/rust-lang/rust/pull/77616 for more context. - Add a test for cross-crate re-exports - Use the same code for both `hir::Ty` and `Ty` --- src/librustdoc/clean/mod.rs | 57 +++++++++++++---- .../rustdoc/auxiliary/normalize-assoc-item.rs | 12 ++++ src/test/rustdoc/normalize-assoc-item.rs | 63 +++++++++++++++++++ 3 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 src/test/rustdoc/auxiliary/normalize-assoc-item.rs create mode 100644 src/test/rustdoc/normalize-assoc-item.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1667a92d757..b89fa1f7a01 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1290,6 +1290,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &DocContext<'_>) -> Type { hir::TyKind::Path(qpath) => qpath, _ => unreachable!(), }; + match qpath { hir::QPath::Resolved(None, ref path) => { if let Res::Def(DefKind::TyParam, did) = path.res { @@ -1393,6 +1394,12 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &DocContext<'_>) -> Type { resolve_type(cx, path.clean(cx), hir_id) } hir::QPath::Resolved(Some(ref qself), ref p) => { + // Try to normalize `::T` to a type + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + if let Some(normalized_value) = normalize(cx.tcx, ty) { + return normalized_value.clean(cx); + } + let segments = if p.is_global() { &p.segments[1..] } else { &p.segments }; let trait_segments = &segments[..segments.len() - 1]; let trait_path = self::Path { @@ -1410,18 +1417,12 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &DocContext<'_>) -> Type { } } hir::QPath::TypeRelative(ref qself, ref segment) => { - let mut res = Res::Err; - /* - let hir_ty = hir::Ty { - kind: hir::TyKind::Path((*qpath).clone()), - hir_id, - span, - }; - */ let ty = hir_ty_to_ty(cx.tcx, hir_ty); - if let ty::Projection(proj) = ty.kind() { - res = Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id); - } + let res = if let ty::Projection(proj) = ty.kind() { + Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id) + } else { + Res::Err + }; let trait_path = hir::Path { span, res, segments: &[] }; Type::QPath { name: segment.ident.name.clean(cx), @@ -1496,10 +1497,42 @@ impl Clean for hir::Ty<'_> { } } +/// Returns `None` if the type could not be normalized +fn normalize(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + use crate::rustc_trait_selection::infer::TyCtxtInferExt; + use crate::rustc_trait_selection::traits::query::normalize::AtExt; + use rustc_middle::traits::ObligationCause; + use rustc_middle::ty::ParamEnv; + + // Try to normalize `::T` to a type + // FIXME: rustdoc won't be able to perform 'partial' normalization + // until this param env is actually correct + // 'partial': ` as IntoIterator>::IntoIter>` -> `vec::IntoIter` + let param_env = ParamEnv::empty(); + let lifted = ty.lift_to_tcx(tcx).unwrap(); + let normalized = tcx.infer_ctxt().enter(|infcx| { + infcx + .at(&ObligationCause::dummy(), param_env) + .normalize(lifted) + .map(|resolved| infcx.resolve_vars_if_possible(resolved.value)) + }); + match normalized { + Ok(normalized_value) => { + debug!("resolved {:?} to {:?}", ty, normalized_value); + Some(normalized_value) + } + Err(err) => { + debug!("failed to resolve {:?}: {:?}", ty, err); + None + } + } +} + impl<'tcx> Clean for Ty<'tcx> { fn clean(&self, cx: &DocContext<'_>) -> Type { debug!("cleaning type: {:?}", self); - match *self.kind() { + let ty = normalize(cx.tcx, self.lift_to_tcx(cx.tcx).unwrap()).unwrap_or(self); + match *ty.kind() { ty::Never => Never, ty::Bool => Primitive(PrimitiveType::Bool), ty::Char => Primitive(PrimitiveType::Char), diff --git a/src/test/rustdoc/auxiliary/normalize-assoc-item.rs b/src/test/rustdoc/auxiliary/normalize-assoc-item.rs new file mode 100644 index 00000000000..fbd111c3035 --- /dev/null +++ b/src/test/rustdoc/auxiliary/normalize-assoc-item.rs @@ -0,0 +1,12 @@ +#![crate_name = "inner"] +pub trait MyTrait { + type Y; +} + +impl MyTrait for u32 { + type Y = i32; +} + +pub fn foo() -> ::Y { + 0 +} diff --git a/src/test/rustdoc/normalize-assoc-item.rs b/src/test/rustdoc/normalize-assoc-item.rs new file mode 100644 index 00000000000..829f446b7cc --- /dev/null +++ b/src/test/rustdoc/normalize-assoc-item.rs @@ -0,0 +1,63 @@ +// ignore-tidy-linelength +// aux-build:normalize-assoc-item.rs +// build-aux-docs + +pub trait Trait { + type X; +} + +impl Trait for usize { + type X = isize; +} + +// @has 'normalize_assoc_item/fn.f.html' '//pre[@class="rust fn"]' 'pub fn f() -> isize' +pub fn f() -> ::X { + 0 +} + +pub struct S { + // @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, +} + +impl Trait for S { + type X = Box; +} + +pub struct Generic(Inner); + +impl Trait for Generic { + type X = (Inner, Inner::X); +} + +// These can't be normalized because they depend on a generic parameter. +// However the user can choose whether the text should be displayed as `Inner::X` or `::X`. + +// @has 'normalize_assoc_item/struct.Unknown.html' '//pre[@class="rust struct"]' 'pub struct Unknown(pub ::X);' +pub struct Unknown(pub ::X); + +// @has 'normalize_assoc_item/struct.Unknown2.html' '//pre[@class="rust struct"]' 'pub struct Unknown2(pub Inner::X);' +pub struct Unknown2(pub Inner::X); + +trait Lifetimes<'a> { + type Y; +} + +impl<'a> Lifetimes<'a> for usize { + type Y = &'a isize; +} + +// @has 'normalize_assoc_item/fn.g.html' '//pre[@class="rust fn"]' "pub fn g() -> &isize" +pub fn g() -> >::Y { + &0 +} + +// @has 'normalize_assoc_item/constant.A.html' '//pre[@class="rust const"]' "pub const A: &isize" +pub const A: >::Y = &0; + +// test cross-crate re-exports +extern crate inner; +// @has 'normalize_assoc_item/fn.foo.html' '//pre[@class="rust fn"]' "pub fn foo() -> i32" +pub use inner::foo;