rustdoc: ensure HTML/JS side implementors don't have dups
This commit is contained in:
parent
a7d6768e3b
commit
20010d7597
@ -3,7 +3,7 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def::CtorKind;
|
use rustc_hir::def::CtorKind;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
@ -790,16 +790,18 @@ fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item
|
|||||||
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All);
|
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All);
|
||||||
|
|
||||||
let cache = cx.cache();
|
let cache = cx.cache();
|
||||||
|
let mut extern_crates = FxHashSet::default();
|
||||||
if let Some(implementors) = cache.implementors.get(&it.item_id.expect_def_id()) {
|
if let Some(implementors) = cache.implementors.get(&it.item_id.expect_def_id()) {
|
||||||
// The DefId is for the first Type found with that name. The bool is
|
// The DefId is for the first Type found with that name. The bool is
|
||||||
// if any Types with the same name but different DefId have been found.
|
// if any Types with the same name but different DefId have been found.
|
||||||
let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default();
|
let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default();
|
||||||
for implementor in implementors {
|
for implementor in implementors {
|
||||||
match implementor.inner_impl().for_ {
|
if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cx.cache()) &&
|
||||||
clean::Type::Path { ref path }
|
!did.is_local() {
|
||||||
| clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. }
|
extern_crates.insert(did.krate);
|
||||||
if !path.is_assoc_ty() =>
|
}
|
||||||
{
|
match implementor.inner_impl().for_.without_borrowed_ref() {
|
||||||
|
clean::Type::Path { ref path } if !path.is_assoc_ty() => {
|
||||||
let did = path.def_id();
|
let did = path.def_id();
|
||||||
let &mut (prev_did, ref mut has_duplicates) =
|
let &mut (prev_did, ref mut has_duplicates) =
|
||||||
implementor_dups.entry(path.last()).or_insert((did, false));
|
implementor_dups.entry(path.last()).or_insert((did, false));
|
||||||
@ -898,20 +900,96 @@ fn trait_item(w: &mut Buffer, cx: &Context<'_>, m: &clean::Item, t: &clean::Item
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include implementors in crates that depend on the current crate.
|
||||||
|
//
|
||||||
|
// This is complicated by the way rustdoc is invoked, which is basically
|
||||||
|
// the same way rustc is invoked: it gets called, one at a time, for each
|
||||||
|
// crate. When building the rustdocs for the current crate, rustdoc can
|
||||||
|
// see crate metadata for its dependencies, but cannot see metadata for its
|
||||||
|
// dependents.
|
||||||
|
//
|
||||||
|
// To make this work, we generate a "hook" at this stage, and our
|
||||||
|
// dependents can "plug in" to it when they build. For simplicity's sake,
|
||||||
|
// it's [JSONP]: a JavaScript file with the data we need (and can parse),
|
||||||
|
// surrounded by a tiny wrapper that the Rust side ignores, but allows the
|
||||||
|
// JavaScript side to include without having to worry about Same Origin
|
||||||
|
// Policy. The code for *that* is in `write_shared.rs`.
|
||||||
|
//
|
||||||
|
// This is further complicated by `#[doc(inline)]`. We want all copies
|
||||||
|
// of an inlined trait to reference the same JS file, to address complex
|
||||||
|
// dependency graphs like this one (lower crates depend on higher crates):
|
||||||
|
//
|
||||||
|
// ```text
|
||||||
|
// --------------------------------------------
|
||||||
|
// | crate A: trait Foo |
|
||||||
|
// --------------------------------------------
|
||||||
|
// | |
|
||||||
|
// -------------------------------- |
|
||||||
|
// | crate B: impl A::Foo for Bar | |
|
||||||
|
// -------------------------------- |
|
||||||
|
// | |
|
||||||
|
// ---------------------------------------------
|
||||||
|
// | crate C: #[doc(inline)] use A::Foo as Baz |
|
||||||
|
// | impl Baz for Quux |
|
||||||
|
// ---------------------------------------------
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Basically, we want `C::Baz` and `A::Foo` to show the same set of
|
||||||
|
// impls, which is easier if they both treat `/implementors/A/trait.Foo.js`
|
||||||
|
// as the Single Source of Truth.
|
||||||
|
//
|
||||||
|
// We also want the `impl Baz for Quux` to be written to
|
||||||
|
// `trait.Foo.js`. However, when we generate plain HTML for `C::Baz`,
|
||||||
|
// we're going to want to generate plain HTML for `impl Baz for Quux` too,
|
||||||
|
// because that'll load faster, and it's better for SEO. And we don't want
|
||||||
|
// the same impl to show up twice on the same page.
|
||||||
|
//
|
||||||
|
// To make this work, the implementors JS file has a structure kinda
|
||||||
|
// like this:
|
||||||
|
//
|
||||||
|
// ```js
|
||||||
|
// JSONP({
|
||||||
|
// "B": {"impl A::Foo for Bar"},
|
||||||
|
// "C": {"impl Baz for Quux"},
|
||||||
|
// });
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// First of all, this means we can rebuild a crate, and it'll replace its own
|
||||||
|
// data if something changes. That is, `rustdoc` is idempotent. The other
|
||||||
|
// advantage is that we can list the crates that get included in the HTML,
|
||||||
|
// and ignore them when doing the JavaScript-based part of rendering.
|
||||||
|
// So C's HTML will have something like this:
|
||||||
|
//
|
||||||
|
// ```html
|
||||||
|
// <script type="text/javascript" src="/implementors/A/trait.Foo.js"
|
||||||
|
// data-ignore-extern-crates="A,B" async></script>
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// And, when the JS runs, anything in data-ignore-extern-crates is known
|
||||||
|
// to already be in the HTML, and will be ignored.
|
||||||
|
//
|
||||||
|
// [JSONP]: https://en.wikipedia.org/wiki/JSONP
|
||||||
let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
|
let mut js_src_path: UrlPartsBuilder = std::iter::repeat("..")
|
||||||
.take(cx.current.len())
|
.take(cx.current.len())
|
||||||
.chain(std::iter::once("implementors"))
|
.chain(std::iter::once("implementors"))
|
||||||
.collect();
|
.collect();
|
||||||
if it.item_id.is_local() {
|
if let Some(did) = it.item_id.as_def_id() &&
|
||||||
js_src_path.extend(cx.current.iter().copied());
|
let get_extern = { || cache.external_paths.get(&did).map(|s| s.0.clone()) } &&
|
||||||
|
let Some(fqp) = cache.exact_paths.get(&did).cloned().or_else(get_extern) {
|
||||||
|
js_src_path.extend(fqp[..fqp.len() - 1].iter().copied());
|
||||||
|
js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap()));
|
||||||
} else {
|
} else {
|
||||||
let (ref path, _) = cache.external_paths[&it.item_id.expect_def_id()];
|
js_src_path.extend(cx.current.iter().copied());
|
||||||
js_src_path.extend(path[..path.len() - 1].iter().copied());
|
|
||||||
}
|
|
||||||
js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap()));
|
js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), it.name.unwrap()));
|
||||||
|
}
|
||||||
|
let extern_crates = extern_crates
|
||||||
|
.into_iter()
|
||||||
|
.map(|cnum| cx.shared.tcx.crate_name(cnum).to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
write!(
|
write!(
|
||||||
w,
|
w,
|
||||||
"<script type=\"text/javascript\" src=\"{src}\" async></script>",
|
"<script type=\"text/javascript\" src=\"{src}\" data-ignore-extern-crates=\"{extern_crates}\" async></script>",
|
||||||
src = js_src_path.finish(),
|
src = js_src_path.finish(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -501,10 +501,13 @@ fn to_json_string(&self) -> String {
|
|||||||
//
|
//
|
||||||
// FIXME: this is a vague explanation for why this can't be a `get`, in
|
// FIXME: this is a vague explanation for why this can't be a `get`, in
|
||||||
// theory it should be...
|
// theory it should be...
|
||||||
let &(ref remote_path, remote_item_type) = match cache.paths.get(&did) {
|
let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
|
||||||
Some(p) => p,
|
Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
|
||||||
|
Some((_, t)) => (p, t),
|
||||||
|
None => continue,
|
||||||
|
},
|
||||||
None => match cache.external_paths.get(&did) {
|
None => match cache.external_paths.get(&did) {
|
||||||
Some(p) => p,
|
Some((p, t)) => (p, t),
|
||||||
None => continue,
|
None => continue,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -756,8 +756,14 @@ function loadCss(cssFileName) {
|
|||||||
const traitName = document.querySelector("h1.fqn > .in-band > .trait").textContent;
|
const traitName = document.querySelector("h1.fqn > .in-band > .trait").textContent;
|
||||||
const baseIdName = "impl-" + traitName + "-";
|
const baseIdName = "impl-" + traitName + "-";
|
||||||
const libs = Object.getOwnPropertyNames(imp);
|
const libs = Object.getOwnPropertyNames(imp);
|
||||||
|
// We don't want to include impls from this JS file, when the HTML already has them.
|
||||||
|
// The current crate should always be ignored. Other crates that should also be
|
||||||
|
// ignored are included in the attribute `data-ignore-extern-crates`.
|
||||||
|
const ignoreExternCrates = document
|
||||||
|
.querySelector("script[data-ignore-extern-crates]")
|
||||||
|
.getAttribute("data-ignore-extern-crates");
|
||||||
for (const lib of libs) {
|
for (const lib of libs) {
|
||||||
if (lib === window.currentCrate) {
|
if (lib === window.currentCrate || ignoreExternCrates.indexOf(lib) !== -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const structs = imp[lib];
|
const structs = imp[lib];
|
||||||
|
@ -18,3 +18,10 @@ assert: "#implementors-list .impl:nth-child(2) > .code-header.in-band"
|
|||||||
goto: file://|DOC_PATH|/test_docs/struct.HasEmptyTraits.html
|
goto: file://|DOC_PATH|/test_docs/struct.HasEmptyTraits.html
|
||||||
compare-elements-position-near-false: ("#impl-EmptyTrait1", "#impl-EmptyTrait2", {"y": 30})
|
compare-elements-position-near-false: ("#impl-EmptyTrait1", "#impl-EmptyTrait2", {"y": 30})
|
||||||
compare-elements-position-near: ("#impl-EmptyTrait3 h3", "#impl-EmptyTrait3 .item-info", {"y": 30})
|
compare-elements-position-near: ("#impl-EmptyTrait3 h3", "#impl-EmptyTrait3 .item-info", {"y": 30})
|
||||||
|
|
||||||
|
// Now check that re-exports work correctly.
|
||||||
|
// There should be exactly one impl shown on both of these pages.
|
||||||
|
goto: file://|DOC_PATH|/lib2/trait.TraitToReexport.html
|
||||||
|
assert-count: ("#implementors-list .impl", 1)
|
||||||
|
goto: file://|DOC_PATH|/implementors/trait.TraitToReexport.html
|
||||||
|
assert-count: ("#implementors-list .impl", 1)
|
@ -9,3 +9,12 @@ fn method() {}
|
|||||||
impl Whatever for Struct {
|
impl Whatever for Struct {
|
||||||
type Foo = u8;
|
type Foo = u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod traits {
|
||||||
|
pub trait TraitToReexport {
|
||||||
|
fn method() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use traits::TraitToReexport;
|
||||||
|
@ -43,6 +43,13 @@ impl implementors::Whatever for Foo {
|
|||||||
type Foo = u32;
|
type Foo = u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use implementors::TraitToReexport;
|
||||||
|
|
||||||
|
pub struct StructToImplOnReexport;
|
||||||
|
|
||||||
|
impl TraitToReexport for StructToImplOnReexport {}
|
||||||
|
|
||||||
pub mod sub_mod {
|
pub mod sub_mod {
|
||||||
/// ```txt
|
/// ```txt
|
||||||
/// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
/// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
Loading…
Reference in New Issue
Block a user