Fix broken handling of primitive items

- Fix broken handling of primitive associated items
- Remove fragment hack

  Fixes 83083

- more logging
- Update CrateNum hacks

  The CrateNum has no relation to where in the dependency tree the crate
  is, only when it's loaded. Explicitly special-case core instead of
  assuming it will be the first DefId.

- Update and add tests
- Cache calculation of primitive locations

  This could possibly be avoided by passing a Cache into
  collect_intra_doc_links; but that's a much larger change, and doesn't
  seem valuable other than for this.
This commit is contained in:
Joshua Nelson 2021-07-10 22:25:36 -04:00
parent f78acaee03
commit cb7e527692
16 changed files with 144 additions and 165 deletions

View file

@ -461,60 +461,20 @@ impl Item {
.map_or(&[][..], |v| v.as_slice())
.iter()
.filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
match did {
Some(did) => {
if let Ok((mut href, ..)) = href(*did, cx) {
if let Some(ref fragment) = *fragment {
href.push('#');
href.push_str(fragment);
}
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href,
})
} else {
None
}
}
// FIXME(83083): using fragments as a side-channel for
// primitive names is very unfortunate
None => {
let relative_to = &cx.current;
if let Some(ref fragment) = *fragment {
let url = match cx.cache().extern_locations.get(&self.def_id.krate()) {
Some(&ExternalLocation::Local) => {
if relative_to[0] == "std" {
let depth = relative_to.len() - 1;
"../".repeat(depth)
} else {
let depth = relative_to.len();
format!("{}std/", "../".repeat(depth))
}
}
Some(ExternalLocation::Remote(ref s)) => {
format!("{}/std/", s.trim_end_matches('/'))
}
Some(ExternalLocation::Unknown) | None => {
format!("{}/std/", crate::DOC_RUST_LANG_ORG_CHANNEL)
}
};
// This is a primitive so the url is done "by hand".
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: format!(
"{}primitive.{}.html{}",
url,
&fragment[..tail],
&fragment[tail..]
),
})
} else {
panic!("This isn't a primitive?!");
}
debug!(?did);
if let Ok((mut href, ..)) = href(*did, cx) {
debug!(?href);
if let Some(ref fragment) = *fragment {
href.push('#');
href.push_str(fragment);
}
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href,
})
} else {
None
}
})
.collect()
@ -531,18 +491,10 @@ impl Item {
.get(&self.def_id)
.map_or(&[][..], |v| v.as_slice())
.iter()
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
// FIXME(83083): using fragments as a side-channel for
// primitive names is very unfortunate
if did.is_some() || fragment.is_some() {
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: String::new(),
})
} else {
None
}
.map(|ItemLink { link: s, link_text, .. }| RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: String::new(),
})
.collect()
}
@ -963,7 +915,7 @@ crate struct Attributes {
crate other_attrs: Vec<ast::Attribute>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
/// A link that has not yet been rendered.
///
/// This link will be turned into a rendered link by [`Item::links`].
@ -975,7 +927,7 @@ crate struct ItemLink {
/// This may not be the same as `link` if there was a disambiguator
/// in an intra-doc link (e.g. \[`fn@f`\])
pub(crate) link_text: String,
pub(crate) did: Option<DefId>,
pub(crate) did: DefId,
/// The url fragment to append to the link
pub(crate) fragment: Option<String>,
}
@ -1802,6 +1754,39 @@ impl PrimitiveType {
Never => sym::never,
}
}
/// Returns the DefId of the module with `doc(primitive)` for this primitive type.
/// Panics if there is no such module.
///
/// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
/// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
/// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
/// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
crate fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
PRIMITIVE_LOCATIONS.get_or_init(|| {
let mut primitive_locations = FxHashMap::default();
// NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate.
// This is a degenerate case that I don't plan to support.
for &crate_num in tcx.crates(()) {
let e = ExternalCrate { crate_num };
let crate_name = e.name(tcx);
debug!(?crate_num, ?crate_name);
for &(def_id, prim) in &e.primitives(tcx) {
// HACK: try to link to std instead where possible
if crate_name == sym::core && primitive_locations.get(&prim).is_some() {
continue;
}
primitive_locations.insert(prim, def_id);
}
}
let local_primitives = ExternalCrate { crate_num: LOCAL_CRATE }.primitives(tcx);
for (def_id, prim) in local_primitives {
primitive_locations.insert(prim, def_id);
}
primitive_locations
})
}
}
impl From<ast::IntTy> for PrimitiveType {