about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeón Orell Valerian Liehr <me@fmease.dev>2023-03-29 03:24:35 +0200
committerLeón Orell Valerian Liehr <me@fmease.dev>2023-05-04 16:59:11 +0200
commit61e1eda6db042413cf1794407fd10b7edc90059d (patch)
tree411915ab2c3c7c06c0862ab1f67a019c5b8b0c07
parent46ec3486116dacbd4fcfa1d92943de7fcc17921a (diff)
downloadrust-61e1eda6db042413cf1794407fd10b7edc90059d.tar.gz
rust-61e1eda6db042413cf1794407fd10b7edc90059d.zip
IAT: Rustdoc integration
-rw-r--r--src/librustdoc/clean/auto_trait.rs5
-rw-r--r--src/librustdoc/clean/inline.rs7
-rw-r--r--src/librustdoc/clean/mod.rs60
-rw-r--r--src/librustdoc/clean/types.rs4
-rw-r--r--src/librustdoc/html/format.rs46
-rw-r--r--src/librustdoc/html/render/mod.rs4
-rw-r--r--src/librustdoc/json/conversions.rs2
-rw-r--r--src/rustdoc-json-types/lib.rs8
-rw-r--r--src/tools/jsondoclint/src/validator.rs4
-rw-r--r--tests/rustdoc/inherent-projections.rs38
-rw-r--r--tests/rustdoc/intra-doc/inherent-associated-types.rs45
11 files changed, 183 insertions, 40 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index fb32b6ef1d3..baf2b0a8585 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -556,7 +556,10 @@ where
                 WherePredicate::EqPredicate { lhs, rhs, bound_params } => {
                     match *lhs {
                         Type::QPath(box QPathData {
-                            ref assoc, ref self_type, ref trait_, ..
+                            ref assoc,
+                            ref self_type,
+                            trait_: Some(ref trait_),
+                            ..
                         }) => {
                             let ty = &*self_type;
                             let mut new_trait = trait_.clone();
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 951f54e9366..c852f9cca2b 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -706,7 +706,12 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean:
 
     g.where_predicates.retain(|pred| match pred {
         clean::WherePredicate::BoundPredicate {
-            ty: clean::QPath(box clean::QPathData { self_type: clean::Generic(ref s), trait_, .. }),
+            ty:
+                clean::QPath(box clean::QPathData {
+                    self_type: clean::Generic(ref s),
+                    trait_: Some(trait_),
+                    ..
+                }),
             bounds,
             ..
         } => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did),
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 72b60d95b8c..657f3c9ec45 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -441,7 +441,7 @@ fn clean_projection<'tcx>(
         assoc: projection_to_path_segment(ty, cx),
         should_show_cast,
         self_type,
-        trait_,
+        trait_: Some(trait_),
     }))
 }
 
@@ -1330,7 +1330,13 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                 let mut bounds: Vec<GenericBound> = Vec::new();
                 generics.where_predicates.retain_mut(|pred| match *pred {
                     WherePredicate::BoundPredicate {
-                        ty: QPath(box QPathData { ref assoc, ref self_type, ref trait_, .. }),
+                        ty:
+                            QPath(box QPathData {
+                                ref assoc,
+                                ref self_type,
+                                trait_: Some(ref trait_),
+                                ..
+                            }),
                         bounds: ref mut pred_bounds,
                         ..
                     } => {
@@ -1492,25 +1498,30 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
                 assoc: clean_path_segment(p.segments.last().expect("segments were empty"), cx),
                 should_show_cast,
                 self_type,
-                trait_,
+                trait_: Some(trait_),
             }))
         }
         hir::QPath::TypeRelative(qself, segment) => {
             let ty = hir_ty_to_ty(cx.tcx, hir_ty);
-            let res = match ty.kind() {
+            let self_type = clean_ty(qself, cx);
+
+            let (trait_, should_show_cast) = match ty.kind() {
                 ty::Alias(ty::Projection, proj) => {
-                    Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id)
+                    let res = Res::Def(DefKind::Trait, proj.trait_ref(cx.tcx).def_id);
+                    let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx);
+                    register_res(cx, trait_.res);
+                    let self_def_id = res.opt_def_id();
+                    let should_show_cast =
+                        compute_should_show_cast(self_def_id, &trait_, &self_type);
+
+                    (Some(trait_), should_show_cast)
                 }
+                ty::Alias(ty::Inherent, _) => (None, false),
                 // Rustdoc handles `ty::Error`s by turning them into `Type::Infer`s.
                 ty::Error(_) => return Type::Infer,
-                // Otherwise, this is an inherent associated type.
-                _ => return clean_middle_ty(ty::Binder::dummy(ty), cx, None),
+                _ => bug!("clean: expected associated type, found `{ty:?}`"),
             };
-            let trait_ = clean_path(&hir::Path { span, res, segments: &[] }, cx);
-            register_res(cx, trait_.res);
-            let self_def_id = res.opt_def_id();
-            let self_type = clean_ty(qself, cx);
-            let should_show_cast = compute_should_show_cast(self_def_id, &trait_, &self_type);
+
             Type::QPath(Box::new(QPathData {
                 assoc: clean_path_segment(segment, cx),
                 should_show_cast,
@@ -1836,9 +1847,28 @@ pub(crate) fn clean_middle_ty<'tcx>(
             clean_projection(bound_ty.rebind(*data), cx, parent_def_id)
         }
 
-        // FIXME(fmease): Clean inherent projections properly. This requires making the trait ref in
-        // `QPathData` optional or alternatively adding a new `clean::Type` variant.
-        ty::Alias(ty::Inherent, _data) => Type::Infer,
+        ty::Alias(ty::Inherent, alias_ty) => {
+            let alias_ty = bound_ty.rebind(alias_ty);
+            let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None);
+
+            Type::QPath(Box::new(QPathData {
+                assoc: PathSegment {
+                    name: cx.tcx.associated_item(alias_ty.skip_binder().def_id).name,
+                    args: GenericArgs::AngleBracketed {
+                        args: substs_to_args(
+                            cx,
+                            alias_ty.map_bound(|ty| ty.substs.as_slice()),
+                            true,
+                        )
+                        .into(),
+                        bindings: Default::default(),
+                    },
+                },
+                should_show_cast: false,
+                self_type,
+                trait_: None,
+            }))
+        }
 
         ty::Param(ref p) => {
             if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) {
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 7371b44465b..38664c3e359 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1660,7 +1660,7 @@ impl Type {
 
     pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
         if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self {
-            Some((self_type, trait_.def_id(), assoc.clone()))
+            Some((self_type, trait_.as_ref()?.def_id(), assoc.clone()))
         } else {
             None
         }
@@ -1704,7 +1704,7 @@ pub(crate) struct QPathData {
     pub self_type: Type,
     /// FIXME: compute this field on demand.
     pub should_show_cast: bool,
-    pub trait_: Path,
+    pub trait_: Option<Path>,
 }
 
 /// A primitive (aka, builtin) type.
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 1c6810bdaf9..d963d6092c4 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -1116,14 +1116,17 @@ fn fmt_type<'cx>(
             ref trait_,
             should_show_cast,
         }) => {
+            // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
+            // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
+
             if f.alternate() {
-                if should_show_cast {
+                if let Some(trait_) = trait_ && should_show_cast {
                     write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))?
                 } else {
                     write!(f, "{:#}::", self_type.print(cx))?
                 }
             } else {
-                if should_show_cast {
+                if let Some(trait_) = trait_ && should_show_cast {
                     write!(f, "&lt;{} as {}&gt;::", self_type.print(cx), trait_.print(cx))?
                 } else {
                     write!(f, "{}::", self_type.print(cx))?
@@ -1139,15 +1142,36 @@ fn fmt_type<'cx>(
             //        the ugliness comes from inlining across crates where
             //        everything comes in as a fully resolved QPath (hard to
             //        look at).
-            if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) {
-                write!(
-                    f,
-                    "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
-                                title=\"type {path}::{name}\">{name}</a>",
-                    shortty = ItemType::AssocType,
-                    name = assoc.name,
-                    path = join_with_double_colon(&path),
-                )
+            if !f.alternate() {
+                // FIXME(inherent_associated_types): We always link to the very first associated
+                // type (in respect to source order) that bears the given name (`assoc.name`) and that is
+                // affiliated with the computed `DefId`. This is obviously incorrect when we have
+                // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself
+                // through here and map it to the corresponding HTML ID that was generated by
+                // `render::Context::derive_id` when the impl blocks were rendered.
+                // There is no such mapping unfortunately.
+                // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking
+                // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()`
+                // considering privacy, `doc(hidden)`, etc.
+                // I don't feel like that right now :cold_sweat:.
+
+                let parent_href = match trait_ {
+                    Some(trait_) => href(trait_.def_id(), cx).ok(),
+                    None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
+                };
+
+                if let Some((url, _, path)) = parent_href {
+                    write!(
+                        f,
+                        "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
+                                    title=\"type {path}::{name}\">{name}</a>",
+                        shortty = ItemType::AssocType,
+                        name = assoc.name,
+                        path = join_with_double_colon(&path),
+                    )
+                } else {
+                    write!(f, "{}", assoc.name)
+                }
             } else {
                 write!(f, "{}", assoc.name)
             }?;
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index e09c6480060..d6773169639 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2202,7 +2202,9 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec<String> {
             }
             clean::Type::QPath(box clean::QPathData { self_type, trait_, .. }) => {
                 work.push_back(self_type);
-                process_path(trait_.def_id());
+                if let Some(trait_) = trait_ {
+                    process_path(trait_.def_id());
+                }
             }
             _ => {}
         }
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index b5bebb70593..b1cef20b434 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -574,7 +574,7 @@ impl FromWithTcx<clean::Type> for Type {
                 name: assoc.name.to_string(),
                 args: Box::new(assoc.args.into_tcx(tcx)),
                 self_type: Box::new(self_type.into_tcx(tcx)),
-                trait_: trait_.into_tcx(tcx),
+                trait_: trait_.map(|trait_| trait_.into_tcx(tcx)),
             },
         }
     }
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index 3cf8ceed620..3556834071f 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
 
 /// rustdoc format-version.
-pub const FORMAT_VERSION: u32 = 24;
+pub const FORMAT_VERSION: u32 = 25;
 
 /// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information
 /// about the language items in the local crate, as well as info about external items to allow
@@ -581,13 +581,15 @@ pub enum Type {
         #[serde(rename = "type")]
         type_: Box<Type>,
     },
-    /// `<Type as Trait>::Name` or associated types like `T::Item` where `T: Iterator`
+    /// Associated types like `<Type as Trait>::Name` and `T::Item` where
+    /// `T: Iterator` or inherent associated types like `Struct::Name`.
     QualifiedPath {
         name: String,
         args: Box<GenericArgs>,
         self_type: Box<Type>,
+        /// `None` iff this is an *inherent* associated type.
         #[serde(rename = "trait")]
-        trait_: Path,
+        trait_: Option<Path>,
     },
 }
 
diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs
index a1f675a3b40..bf8a64acf08 100644
--- a/src/tools/jsondoclint/src/validator.rs
+++ b/src/tools/jsondoclint/src/validator.rs
@@ -273,7 +273,9 @@ impl<'a> Validator<'a> {
             Type::QualifiedPath { name: _, args, self_type, trait_ } => {
                 self.check_generic_args(&**args);
                 self.check_type(&**self_type);
-                self.check_path(trait_, PathKind::Trait);
+                if let Some(trait_) = trait_ {
+                    self.check_path(trait_, PathKind::Trait);
+                }
             }
         }
     }
diff --git a/tests/rustdoc/inherent-projections.rs b/tests/rustdoc/inherent-projections.rs
index bdd881ab456..9bda0acaf83 100644
--- a/tests/rustdoc/inherent-projections.rs
+++ b/tests/rustdoc/inherent-projections.rs
@@ -1,10 +1,9 @@
 #![feature(inherent_associated_types)]
 #![allow(incomplete_features)]
 
-// FIXME(fmease): Properly render inherent projections.
-
-// @has inherent_projections/fn.create.html
-// @has - '//pre[@class="rust item-decl"]' "create() -> _"
+// @has 'inherent_projections/fn.create.html'
+// @has - '//pre[@class="rust item-decl"]' "create() -> Owner::Metadata"
+// @has - '//pre[@class="rust item-decl"]//a[@class="associatedtype"]/@href' 'struct.Owner.html#associatedtype.Metadata'
 pub fn create() -> Owner::Metadata {}
 
 pub struct Owner;
@@ -12,3 +11,34 @@ pub struct Owner;
 impl Owner {
     pub type Metadata = ();
 }
+
+// Make sure we handle bound vars correctly.
+// @has 'inherent_projections/type.User.html' '//pre[@class="rust item-decl"]' "for<'a> fn(_: Carrier<'a>::Focus)"
+pub type User = for<'a> fn(Carrier<'a>::Focus);
+
+pub struct Carrier<'a>(&'a ());
+
+impl<'a> Carrier<'a> {
+    pub type Focus = &'a mut i32;
+}
+
+////////////////////////////////////////
+
+// FIXME(inherent_associated_types): Below we link to `Proj` but we should link to `Proj-1`.
+// The current test checks for the buggy behavior for demonstration purposes.
+
+// @has 'inherent_projections/type.Test.html'
+// @has - '//pre[@class="rust item-decl"]' "Parametrized<i32>"
+// @has - '//pre[@class="rust item-decl"]//a[@class="associatedtype"]/@href' 'struct.Parametrized.html#associatedtype.Proj'
+// @!has - '//pre[@class="rust item-decl"]//a[@class="associatedtype"]/@href' 'struct.Parametrized.html#associatedtype.Proj-1'
+pub type Test = Parametrized<i32>::Proj;
+
+pub struct Parametrized<T>(T);
+
+impl Parametrized<bool> {
+    pub type Proj = ();
+}
+
+impl Parametrized<i32> {
+    pub type Proj = String;
+}
diff --git a/tests/rustdoc/intra-doc/inherent-associated-types.rs b/tests/rustdoc/intra-doc/inherent-associated-types.rs
new file mode 100644
index 00000000000..2b28d2ae60b
--- /dev/null
+++ b/tests/rustdoc/intra-doc/inherent-associated-types.rs
@@ -0,0 +1,45 @@
+#![feature(inherent_associated_types)]
+
+#![allow(incomplete_features)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+// @has inherent_associated_types/index.html
+
+// @has - '//a/@href' 'enum.Simple.html#associatedtype.Type'
+//! [`Simple::Type`]
+
+pub enum Simple {}
+
+impl Simple {
+    pub type Type = ();
+}
+
+////////////////////////////////////////
+
+// @has 'inherent_associated_types/type.Test0.html' '//a/@href' \
+//          'struct.Parametrized.html#associatedtype.Proj'
+/// [`Parametrized<bool>::Proj`]
+pub type Test0 = ();
+
+// FIXME(inherent_associated_types): The intra-doc link below should point to `Proj-1` not `Proj`.
+// The current test checks for the buggy behavior for demonstration purposes.
+// The same bug happens for inherent associated functions and constants (see #85960, #93398).
+//
+// Further, at some point we should reject the intra-doc link `Parametrized::Proj`.
+// It currently links to `Parametrized<bool>::Proj`.
+
+// @has 'inherent_associated_types/type.Test1.html'
+// @has - '//a/@href' 'struct.Parametrized.html#associatedtype.Proj'
+// @!has - '//a/@href' 'struct.Parametrized.html#associatedtype.Proj-1'
+/// [`Parametrized<i32>::Proj`]
+pub type Test1 = ();
+
+pub struct Parametrized<T>(T);
+
+impl Parametrized<bool> {
+    pub type Proj = ();
+}
+
+impl Parametrized<i32> {
+    pub type Proj = String;
+}