diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index cc3a60c596ae7..b9c75ff2fcff6 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -84,49 +84,50 @@ impl, U> Clean> for Option { } } +/// Collect all inner modules which are tagged as implementations of +/// primitives. +/// +/// Note that this loop only searches the top-level items of the crate, +/// and this is intentional. If we were to search the entire crate for an +/// item tagged with `#[doc(primitive)]` then we would also have to +/// search the entirety of external modules for items tagged +/// `#[doc(primitive)]`, which is a pretty inefficient process (decoding +/// all that metadata unconditionally). +/// +/// In order to keep the metadata load under control, the +/// `#[doc(primitive)]` feature is explicitly designed to only allow the +/// primitive tags to show up as the top level items in a crate. +/// +/// Also note that this does not attempt to deal with modules tagged +/// duplicately for the same primitive. This is handled later on when +/// rendering by delegating everything to a hash map. +crate fn as_primitive(cx: &DocContext<'_>, res: Res) -> Option<(DefId, PrimitiveType, Attributes)> { + if let Res::Def(DefKind::Mod, def_id) = res { + let attrs = cx.tcx.get_attrs(def_id).clean(cx); + let mut prim = None; + for attr in attrs.lists(sym::doc) { + if let Some(v) = attr.value_str() { + if attr.check_name(sym::primitive) { + prim = PrimitiveType::from_symbol(v); + if prim.is_some() { + break; + } + // FIXME: should warn on unknown primitives? + } + } + } + return prim.map(|p| (def_id, p, attrs)); + } + None +} + impl Clean for CrateNum { fn clean(&self, cx: &DocContext<'_>) -> ExternalCrate { let root = DefId { krate: *self, index: CRATE_DEF_INDEX }; let krate_span = cx.tcx.def_span(root); let krate_src = cx.sess().source_map().span_to_filename(krate_span); - // Collect all inner modules which are tagged as implementations of - // primitives. - // - // Note that this loop only searches the top-level items of the crate, - // and this is intentional. If we were to search the entire crate for an - // item tagged with `#[doc(primitive)]` then we would also have to - // search the entirety of external modules for items tagged - // `#[doc(primitive)]`, which is a pretty inefficient process (decoding - // all that metadata unconditionally). - // - // In order to keep the metadata load under control, the - // `#[doc(primitive)]` feature is explicitly designed to only allow the - // primitive tags to show up as the top level items in a crate. - // - // Also note that this does not attempt to deal with modules tagged - // duplicately for the same primitive. This is handled later on when - // rendering by delegating everything to a hash map. - let as_primitive = |res: Res| { - if let Res::Def(DefKind::Mod, def_id) = res { - let attrs = cx.tcx.get_attrs(def_id).clean(cx); - let mut prim = None; - for attr in attrs.lists(sym::doc) { - if let Some(v) = attr.value_str() { - if attr.check_name(sym::primitive) { - prim = PrimitiveType::from_symbol(v); - if prim.is_some() { - break; - } - // FIXME: should warn on unknown primitives? - } - } - } - return prim.map(|p| (def_id, p, attrs)); - } - None - }; - let primitives = if root.is_local() { + let primitives: Vec<(DefId, PrimitiveType, Attributes)> = if root.is_local() { cx.tcx .hir() .krate() @@ -137,14 +138,14 @@ impl Clean for CrateNum { .filter_map(|&id| { let item = cx.tcx.hir().expect_item(id.id); match item.kind { - hir::ItemKind::Mod(_) => as_primitive(Res::Def( - DefKind::Mod, - cx.tcx.hir().local_def_id(id.id).to_def_id(), - )), + hir::ItemKind::Mod(_) => as_primitive( + cx, + Res::Def(DefKind::Mod, cx.tcx.hir().local_def_id(id.id).to_def_id()), + ), hir::ItemKind::Use(ref path, hir::UseKind::Single) if item.vis.node.is_pub() => { - as_primitive(path.res).map(|(_, prim, attrs)| { + as_primitive(cx, path.res).map(|(_, prim, attrs)| { // Pretend the primitive is local. (cx.tcx.hir().local_def_id(id.id).to_def_id(), prim, attrs) }) @@ -158,7 +159,7 @@ impl Clean for CrateNum { .item_children(root) .iter() .map(|item| item.res) - .filter_map(as_primitive) + .filter_map(|res| as_primitive(cx, res)) .collect() }; diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 89549eae2cb0e..971f7fba3ac68 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1173,7 +1173,7 @@ impl GetDefId for Type { fn def_id(&self) -> Option { match *self { ResolvedPath { did, .. } => Some(did), - Primitive(p) => cache().primitive_locations.get(&p).cloned(), + Primitive(p) => crate::core::primitives().get(&p).cloned(), BorrowedRef { type_: box Generic(..), .. } => { Primitive(PrimitiveType::Reference).def_id() } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index cbd0ca0de6414..96023f5a9555d 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -5,7 +5,8 @@ use rustc_driver::abort_on_err; use rustc_errors::emitter::{Emitter, EmitterWriter}; use rustc_errors::json::JsonEmitter; use rustc_feature::UnstableFeatures; -use rustc_hir::def::{Namespace::TypeNS, Res}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Namespace::TypeNS, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIndex, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::HirId; use rustc_hir::{ @@ -29,9 +30,11 @@ use rustc_span::DUMMY_SP; use std::cell::RefCell; use std::mem; use std::rc::Rc; +use std::sync::Arc; use crate::clean; -use crate::clean::{AttributesExt, MAX_DEF_ID}; +use crate::clean::types::PrimitiveType; +use crate::clean::{as_primitive, AttributesExt, MAX_DEF_ID}; use crate::config::RenderInfo; use crate::config::{Options as RustdocOptions, RenderOptions}; use crate::passes::{self, Condition::*, ConditionalPass}; @@ -39,6 +42,13 @@ use crate::passes::{self, Condition::*, ConditionalPass}; pub use rustc_session::config::{CodegenOptions, DebuggingOptions, Input, Options}; pub use rustc_session::search_paths::SearchPath; +thread_local!(static PRIMITIVES: RefCell>> = + Default::default()); + +crate fn primitives() -> Arc> { + PRIMITIVES.with(|c| c.borrow().clone()) +} + pub type ExternalPaths = FxHashMap, clean::TypeKind)>; pub struct DocContext<'tcx> { @@ -499,6 +509,65 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt }; debug!("crate: {:?}", tcx.hir().krate()); + PRIMITIVES.with(|v| { + let mut tmp = v.borrow_mut(); + let stored_primitives = Arc::make_mut(&mut *tmp); + + let mut externs = Vec::new(); + for &cnum in ctxt.tcx.crates().iter() { + externs.push(cnum); + } + externs.sort_by(|a, b| a.cmp(&b)); + + for krate in externs.iter().chain([LOCAL_CRATE].iter()) { + let root = DefId { krate: *krate, index: CRATE_DEF_INDEX }; + let iter: Vec<(DefId, PrimitiveType)> = if root.is_local() { + ctxt.tcx.hir_crate(*krate).item.module.item_ids.iter().filter_map( + |&id| { + let item = ctxt.tcx.hir().expect_item(id.id); + match item.kind { + hir::ItemKind::Mod(_) => as_primitive( + &ctxt, + Res::Def( + DefKind::Mod, + ctxt.tcx.hir().local_def_id(id.id).to_def_id(), + ), + ) + .map(|(did, prim, _)| (did, prim)), + hir::ItemKind::Use(ref path, hir::UseKind::Single) + if item.vis.node.is_pub() => + { + as_primitive(&ctxt, path.res).map(|(_, prim, _)| { + // Pretend the primitive is local. + ( + ctxt.tcx + .hir() + .local_def_id(id.id) + .to_def_id(), + prim, + ) + }) + } + _ => None, + } + }, + ) + .collect() + } else { + ctxt + .tcx + .item_children(root) + .iter() + .map(|item| item.res) + .filter_map(|res| as_primitive(&ctxt, res).map(|(did, prim, _)| (did, prim))) + .collect() + }; + for (did, prim) in iter { + stored_primitives.insert(prim, did); + } + } + }); + let mut krate = clean::krate(&mut ctxt); if let Some(ref m) = krate.module { diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 99b31473f87a3..f6a1f333316a3 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -11,6 +11,7 @@ use rustc_span::source_map::FileName; use crate::clean::{self, GetDefId}; use crate::config::RenderInfo; +use crate::core::primitives; use crate::fold::DocFolder; use crate::formats::item_type::ItemType; use crate::formats::Impl; @@ -76,9 +77,6 @@ pub struct Cache { /// Cache of where external crate documentation can be found. pub extern_locations: FxHashMap, - /// Cache of where documentation for primitives can be found. - pub primitive_locations: FxHashMap, - // Note that external items for which `doc(hidden)` applies to are shown as // non-reachable while local items aren't. This is because we're reusing // the access levels from the privacy check pass. @@ -182,19 +180,6 @@ impl Cache { cache.external_paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); } - // Cache where all known primitives have their documentation located. - // - // Favor linking to as local extern as possible, so iterate all crates in - // reverse topological order. - for &(_, ref e) in krate.externs.iter().rev() { - for &(def_id, prim, _) in &e.primitives { - cache.primitive_locations.insert(prim, def_id); - } - } - for &(def_id, prim, _) in &krate.primitives { - cache.primitive_locations.insert(prim, def_id); - } - cache.stack.push(krate.name.clone()); krate = cache.fold_crate(krate); @@ -403,9 +388,8 @@ impl DocFolder for Cache { true } ref t => { - let prim_did = t - .primitive_type() - .and_then(|t| self.primitive_locations.get(&t).cloned()); + let prim_did = + t.primitive_type().and_then(|t| primitives().get(&t).cloned()); match prim_did { Some(did) => { self.parent_stack.push(did); @@ -436,9 +420,8 @@ impl DocFolder for Cache { dids.insert(did); } ref t => { - let did = t - .primitive_type() - .and_then(|t| self.primitive_locations.get(&t).cloned()); + let did = + t.primitive_type().and_then(|t| primitives().get(&t).cloned()); if let Some(did) = did { dids.insert(did); diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 699f8c36cba6a..f416fae6e0719 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -15,6 +15,7 @@ use rustc_span::def_id::DefId; use rustc_target::spec::abi::Abi; use crate::clean::{self, PrimitiveType}; +use crate::core::primitives; use crate::formats::cache::cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; @@ -559,10 +560,13 @@ fn primitive_link( prim: clean::PrimitiveType, name: &str, ) -> fmt::Result { - let m = cache(); let mut needs_termination = false; + if !f.alternate() { - match m.primitive_locations.get(&prim) { + let m = cache(); + let primitives = primitives(); + + match primitives.get(&prim) { Some(&def_id) if def_id.is_local() => { let len = CURRENT_DEPTH.with(|s| s.get()); let len = if len == 0 { 0 } else { len - 1 }; @@ -575,13 +579,21 @@ fn primitive_link( needs_termination = true; } Some(&def_id) => { - let loc = match m.extern_locations[&def_id.krate] { - (ref cname, _, ExternalLocation::Remote(ref s)) => Some((cname, s.to_string())), - (ref cname, _, ExternalLocation::Local) => { + let loc = match m.extern_locations.get(&def_id.krate) { + Some((ref cname, _, ExternalLocation::Remote(ref s))) => { + Some((cname.as_str(), s.to_string())) + } + Some((ref cname, _, ExternalLocation::Local)) => { + let len = CURRENT_DEPTH.with(|s| s.get()); + Some((cname.as_str(), "../".repeat(len))) + } + Some((.., ExternalLocation::Unknown)) => None, + None => { + // It means we're in core, since the primitive types aren't "accessible" + // from it so we have to simulate it. let len = CURRENT_DEPTH.with(|s| s.get()); - Some((cname, "../".repeat(len))) + Some(("core", "../".repeat(len))) } - (.., ExternalLocation::Unknown) => None, }; if let Some((cname, root)) = loc { write!( diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 378efa1a1bed7..afe8be039072e 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -13,6 +13,7 @@ use crate::html::render::{plain_summary_line, shorten}; use crate::html::render::{Generic, IndexItem, IndexItemFunctionType, RenderType, TypeWithKind}; /// Indicates where an external crate can be found. +#[derive(Debug)] pub enum ExternalLocation { /// Remote URL root of the external crate Remote(String), diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 5fb2d9f6f917c..9b6df6cd66153 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -66,6 +66,7 @@ use serde::{Serialize, Serializer}; use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, TypeKind}; use crate::config::RenderInfo; use crate::config::RenderOptions; +use crate::core::primitives; use crate::docfs::{DocFS, PathError}; use crate::doctree; use crate::error::Error; @@ -3405,7 +3406,7 @@ fn render_deref_methods( render_assoc_items(w, cx, container_item, did, what, cache); } else { if let Some(prim) = target.primitive_type() { - if let Some(&did) = cache.primitive_locations.get(&prim) { + if let Some(&did) = primitives().get(&prim) { render_assoc_items(w, cx, container_item, did, what, cache); } } @@ -4058,7 +4059,7 @@ fn sidebar_assoc_items(it: &clean::Item) -> String { .def_id() .or(target .primitive_type() - .and_then(|prim| c.primitive_locations.get(&prim).cloned())) + .and_then(|prim| primitives().get(&prim).cloned())) .and_then(|did| c.impls.get(&did)); if let Some(impls) = inner_impl { out.push_str(""); diff --git a/src/test/rustdoc-js/primitive-fn-like-search.js b/src/test/rustdoc-js/primitive-fn-like-search.js new file mode 100644 index 0000000000000..6b052aa4aadb1 --- /dev/null +++ b/src/test/rustdoc-js/primitive-fn-like-search.js @@ -0,0 +1,13 @@ +// exact-check +const QUERY = 'u32 -> u32'; + +const EXPECTED = { + 'others': [ + ], + 'returned': [ + { 'path': 'primitive_fn_like_search', 'name': 'foo' }, + ], + 'in_args': [ + { 'path': 'primitive_fn_like_search', 'name': 'foo' }, + ], +}; diff --git a/src/test/rustdoc-js/primitive-fn-like-search.rs b/src/test/rustdoc-js/primitive-fn-like-search.rs new file mode 100644 index 0000000000000..fc5d7cb3f5931 --- /dev/null +++ b/src/test/rustdoc-js/primitive-fn-like-search.rs @@ -0,0 +1,5 @@ +pub struct Foo; + +pub trait Bar {} + +pub fn foo(a: Foo, b: u32, c: T, d: D) -> u32 {0}