From 8de453a8c6a26e43876def2d757bec40ed9b2767 Mon Sep 17 00:00:00 2001
From: León Orell Valerian Liehr
Date: Tue, 29 Mar 2022 19:30:54 +0200
Subject: rustdoc: discr. required+provided assoc consts+tys
---
src/librustdoc/html/format.rs | 15 ++
src/librustdoc/html/markdown.rs | 6 +-
src/librustdoc/html/render/mod.rs | 247 ++++++++++++++++++++-----------
src/librustdoc/html/render/print_item.rs | 160 ++++++++++----------
src/librustdoc/html/static/js/search.js | 2 +-
5 files changed, 262 insertions(+), 168 deletions(-)
(limited to 'src/librustdoc/html')
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 5c59609d5b8..55b0028180f 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -527,6 +527,21 @@ crate enum HrefError {
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
///
/// This can only happen for non-local items.
+ ///
+ /// # Example
+ ///
+ /// Crate `a` defines a public trait and crate `b` – the target crate that depends on `a` –
+ /// implements it for a local type.
+ /// We document `b` but **not** `a` (we only _build_ the latter – with `rustc`):
+ ///
+ /// ```sh
+ /// rustc a.rs --crate-type=lib
+ /// rustdoc b.rs --crate-type=lib --extern=a=liba.rlib
+ /// ```
+ ///
+ /// Now, the associated items in the trait impl want to link to the corresponding item in the
+ /// trait declaration (see `html::render::assoc_href_attr`) but it's not available since their
+ /// *documentation (was) not built*.
DocumentationNotBuilt,
/// This can only happen for non-local items when `--document-private-items` is not passed.
Private,
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 943c521485b..1ebb41b5933 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -1452,8 +1452,10 @@ fn init_id_map() -> FxHashMap {
map.insert("trait-implementations".to_owned(), 1);
map.insert("synthetic-implementations".to_owned(), 1);
map.insert("blanket-implementations".to_owned(), 1);
- map.insert("associated-types".to_owned(), 1);
- map.insert("associated-const".to_owned(), 1);
+ map.insert("required-associated-types".to_owned(), 1);
+ map.insert("provided-associated-types".to_owned(), 1);
+ map.insert("provided-associated-consts".to_owned(), 1);
+ map.insert("required-associated-consts".to_owned(), 1);
map.insert("required-methods".to_owned(), 1);
map.insert("provided-methods".to_owned(), 1);
map.insert("implementors".to_owned(), 1);
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 12da16527a0..9891c4b676f 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -521,7 +521,7 @@ fn document_short(
let mut summary_html = MarkdownSummaryLine(&s, &item.links(cx)).into_string();
if s.contains('\n') {
- let link = format!(r#" Read more"#, naive_assoc_href(item, link, cx));
+ let link = format!(r#" Read more"#, assoc_href_attr(item, link, cx));
if let Some(idx) = summary_html.rfind("
") {
summary_html.insert_str(idx, &link);
@@ -737,42 +737,82 @@ fn render_impls(
w.write_str(&rendered_impls.join(""));
}
-fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
- use crate::formats::item_type::ItemType::*;
+/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
+fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) -> String {
+ let name = it.name.unwrap();
+ let item_type = it.type_();
- let name = it.name.as_ref().unwrap();
- let ty = match it.type_() {
- Typedef | AssocType => AssocType,
- s => s,
- };
+ let href = match link {
+ AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
+ AssocItemLink::Anchor(None) => Some(format!("#{}.{}", item_type, name)),
+ AssocItemLink::GotoSource(did, provided_methods) => {
+ // We're creating a link from the implementation of an associated item to its
+ // declaration in the trait declaration.
+ let item_type = match item_type {
+ // For historical but not technical reasons, the item type of methods in
+ // trait declarations depends on whether the method is required (`TyMethod`) or
+ // provided (`Method`).
+ ItemType::Method | ItemType::TyMethod => {
+ if provided_methods.contains(&name) {
+ ItemType::Method
+ } else {
+ ItemType::TyMethod
+ }
+ }
+ // For associated types and constants, no such distinction exists.
+ item_type => item_type,
+ };
- let anchor = format!("#{}.{}", ty, name);
- match link {
- AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id),
- AssocItemLink::Anchor(None) => anchor,
- AssocItemLink::GotoSource(did, _) => {
- href(did.expect_def_id(), cx).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor)
+ match href(did.expect_def_id(), cx) {
+ Ok((url, ..)) => Some(format!("{}#{}.{}", url, item_type, name)),
+ // The link is broken since it points to an external crate that wasn't documented.
+ // Do not create any link in such case. This is better than falling back to a
+ // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item
+ // (that used to happen in older versions). Indeed, in most cases this dummy would
+ // coincide with the `id`. However, it would not always do so.
+ // In general, this dummy would be incorrect:
+ // If the type with the trait impl also had an inherent impl with an assoc. item of
+ // the *same* name as this impl item, the dummy would link to that one even though
+ // those two items are distinct!
+ // In this scenario, the actual `id` of this impl item would be
+ // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator).
+ Err(HrefError::DocumentationNotBuilt) => None,
+ Err(_) => Some(format!("#{}.{}", item_type, name)),
+ }
}
- }
+ };
+
+ // If there is no `href` for the reason explained above, simply do not render it which is valid:
+ // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
+ href.map(|href| format!(" href=\"{}\"", href)).unwrap_or_default()
}
fn assoc_const(
w: &mut Buffer,
it: &clean::Item,
ty: &clean::Type,
+ default: Option<&clean::ConstantKind>,
link: AssocItemLink<'_>,
extra: &str,
cx: &Context<'_>,
) {
write!(
w,
- "{}{}const {}: {}",
- extra,
- it.visibility.print_with_space(it.def_id, cx),
- naive_assoc_href(it, link, cx),
- it.name.as_ref().unwrap(),
- ty.print(cx)
+ "{extra}{vis}const {name}: {ty}",
+ extra = extra,
+ vis = it.visibility.print_with_space(it.def_id, cx),
+ href = assoc_href_attr(it, link, cx),
+ name = it.name.as_ref().unwrap(),
+ ty = ty.print(cx),
);
+ if let Some(default) = default {
+ // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the
+ // hood which adds noisy underscores and a type suffix to number literals.
+ // This hurts readability in this context especially when more complex expressions
+ // are involved and it doesn't add much of value.
+ // Find a way to print constants here without all that jazz.
+ write!(w, " = {}", default.value(cx.tcx()).unwrap_or_else(|| default.expr(cx.tcx())));
+ }
}
fn assoc_type(
@@ -787,9 +827,9 @@ fn assoc_type(
) {
write!(
w,
- "{indent}type {name}{generics}",
+ "{indent}type {name}{generics}",
indent = " ".repeat(indent),
- href = naive_assoc_href(it, link, cx),
+ href = assoc_href_attr(it, link, cx),
name = it.name.as_ref().unwrap(),
generics = generics.print(cx),
);
@@ -814,22 +854,6 @@ fn assoc_method(
) {
let header = meth.fn_header(cx.tcx()).expect("Trying to get header from a non-function item");
let name = meth.name.as_ref().unwrap();
- let href = match link {
- AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
- AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
- AssocItemLink::GotoSource(did, provided_methods) => {
- // We're creating a link from an impl-item to the corresponding
- // trait-item and need to map the anchored type accordingly.
- let ty =
- if provided_methods.contains(name) { ItemType::Method } else { ItemType::TyMethod };
-
- match (href(did.expect_def_id(), cx), ty) {
- (Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
- (Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
- (Err(_), ty) => Some(format!("#{}.{}", ty, name)),
- }
- }
- };
let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
// FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
// this condition.
@@ -843,6 +867,7 @@ fn assoc_method(
let unsafety = header.unsafety.print_with_space();
let defaultness = print_default_space(meth.is_default());
let abi = print_abi_with_space(header.abi).to_string();
+ let href = assoc_href_attr(meth, link, cx);
// NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
let generics_len = format!("{:#}", g.print(cx)).len();
@@ -868,7 +893,7 @@ fn assoc_method(
w.reserve(header_len + "{".len() + "".len());
write!(
w,
- "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\
+ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\
{generics}{decl}{notable_traits}{where_clause}",
indent = indent_str,
vis = vis,
@@ -877,8 +902,7 @@ fn assoc_method(
unsafety = unsafety,
defaultness = defaultness,
abi = abi,
- // links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
- href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
+ href = href,
name = name,
generics = g.print(cx),
decl = d.full_print(header_len, indent, header.asyncness, cx),
@@ -968,23 +992,43 @@ fn render_assoc_item(
cx: &Context<'_>,
render_mode: RenderMode,
) {
- match *item.kind {
+ match &*item.kind {
clean::StrippedItem(..) => {}
- clean::TyMethodItem(ref m) => {
+ clean::TyMethodItem(m) => {
assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
}
- clean::MethodItem(ref m, _) => {
+ clean::MethodItem(m, _) => {
assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
}
- clean::AssocConstItem(ref ty, _) => {
- assoc_const(w, item, ty, link, if parent == ItemType::Trait { " " } else { "" }, cx)
- }
- clean::AssocTypeItem(ref generics, ref bounds, ref default) => assoc_type(
+ kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => assoc_const(
+ w,
+ item,
+ ty,
+ match kind {
+ clean::TyAssocConstItem(_) => None,
+ clean::AssocConstItem(_, default) => Some(default),
+ _ => unreachable!(),
+ },
+ link,
+ if parent == ItemType::Trait { " " } else { "" },
+ cx,
+ ),
+ clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type(
w,
item,
generics,
bounds,
- default.as_ref(),
+ None,
+ link,
+ if parent == ItemType::Trait { 4 } else { 0 },
+ cx,
+ ),
+ clean::AssocTypeItem(ref ty, ref bounds) => assoc_type(
+ w,
+ item,
+ &ty.generics,
+ bounds,
+ Some(ty.item_type.as_ref().unwrap_or(&ty.type_)),
link,
if parent == ItemType::Trait { 4 } else { 0 },
cx,
@@ -1205,7 +1249,7 @@ fn render_deref_methods(
.items
.iter()
.find_map(|item| match *item.kind {
- clean::TypedefItem(ref t, true) => Some(match *t {
+ clean::AssocTypeItem(ref t, _) => Some(match *t {
clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_),
_ => (&t.type_, &t.type_),
}),
@@ -1291,7 +1335,7 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
impl_.print(false, cx)
);
for it in &impl_.items {
- if let clean::TypedefItem(ref tydef, _) = *it.kind {
+ if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
out.push_str(" ");
let empty_set = FxHashSet::default();
let src_link =
@@ -1300,7 +1344,7 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
&mut out,
it,
&tydef.generics,
- &[],
+ &[], // intentionally leaving out bounds
Some(&tydef.type_),
src_link,
0,
@@ -1439,7 +1483,7 @@ fn render_impl(
if item_type == ItemType::Method { " method-toggle" } else { "" };
write!(w, "", method_toggle_class);
}
- match *item.kind {
+ match &*item.kind {
clean::MethodItem(..) | clean::TyMethodItem(_) => {
// Only render when the method is not static or we allow static methods
if render_method_item {
@@ -1471,63 +1515,68 @@ fn render_impl(
w.write_str("");
}
}
- clean::TypedefItem(ref tydef, _) => {
- let source_id = format!("{}.{}", ItemType::AssocType, name);
+ kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => {
+ let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
write!(
w,
"",
id, item_type, in_trait_class
);
+ render_rightside(w, cx, item, containing_item, render_mode);
write!(w, "", id);
w.write_str("");
w.write_str("");
}
- clean::AssocConstItem(ref ty, _) => {
+ clean::TyAssocTypeItem(generics, bounds) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
- write!(
- w,
- "",
- id, item_type, in_trait_class
- );
- render_rightside(w, cx, item, containing_item, render_mode);
+ write!(w, "", id, item_type, in_trait_class);
write!(w, "", id);
w.write_str("");
w.write_str("");
}
- clean::AssocTypeItem(ref generics, ref bounds, ref default) => {
+ clean::AssocTypeItem(tydef, _bounds) => {
let source_id = format!("{}.{}", item_type, name);
let id = cx.derive_id(source_id.clone());
- write!(w, "", id, item_type, in_trait_class,);
+ write!(
+ w,
+ "",
+ id, item_type, in_trait_class
+ );
write!(w, "", id);
w.write_str("