Skip to content

Commit cb7e527

Browse files
committed
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.
1 parent f78acae commit cb7e527

File tree

16 files changed

+144
-165
lines changed

16 files changed

+144
-165
lines changed

src/librustdoc/clean/types.rs

+52-67
Original file line numberDiff line numberDiff line change
@@ -461,60 +461,20 @@ impl Item {
461461
.map_or(&[][..], |v| v.as_slice())
462462
.iter()
463463
.filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
464-
match did {
465-
Some(did) => {
466-
if let Ok((mut href, ..)) = href(*did, cx) {
467-
if let Some(ref fragment) = *fragment {
468-
href.push('#');
469-
href.push_str(fragment);
470-
}
471-
Some(RenderedLink {
472-
original_text: s.clone(),
473-
new_text: link_text.clone(),
474-
href,
475-
})
476-
} else {
477-
None
478-
}
479-
}
480-
// FIXME(83083): using fragments as a side-channel for
481-
// primitive names is very unfortunate
482-
None => {
483-
let relative_to = &cx.current;
484-
if let Some(ref fragment) = *fragment {
485-
let url = match cx.cache().extern_locations.get(&self.def_id.krate()) {
486-
Some(&ExternalLocation::Local) => {
487-
if relative_to[0] == "std" {
488-
let depth = relative_to.len() - 1;
489-
"../".repeat(depth)
490-
} else {
491-
let depth = relative_to.len();
492-
format!("{}std/", "../".repeat(depth))
493-
}
494-
}
495-
Some(ExternalLocation::Remote(ref s)) => {
496-
format!("{}/std/", s.trim_end_matches('/'))
497-
}
498-
Some(ExternalLocation::Unknown) | None => {
499-
format!("{}/std/", crate::DOC_RUST_LANG_ORG_CHANNEL)
500-
}
501-
};
502-
// This is a primitive so the url is done "by hand".
503-
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
504-
Some(RenderedLink {
505-
original_text: s.clone(),
506-
new_text: link_text.clone(),
507-
href: format!(
508-
"{}primitive.{}.html{}",
509-
url,
510-
&fragment[..tail],
511-
&fragment[tail..]
512-
),
513-
})
514-
} else {
515-
panic!("This isn't a primitive?!");
516-
}
464+
debug!(?did);
465+
if let Ok((mut href, ..)) = href(*did, cx) {
466+
debug!(?href);
467+
if let Some(ref fragment) = *fragment {
468+
href.push('#');
469+
href.push_str(fragment);
517470
}
471+
Some(RenderedLink {
472+
original_text: s.clone(),
473+
new_text: link_text.clone(),
474+
href,
475+
})
476+
} else {
477+
None
518478
}
519479
})
520480
.collect()
@@ -531,18 +491,10 @@ impl Item {
531491
.get(&self.def_id)
532492
.map_or(&[][..], |v| v.as_slice())
533493
.iter()
534-
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
535-
// FIXME(83083): using fragments as a side-channel for
536-
// primitive names is very unfortunate
537-
if did.is_some() || fragment.is_some() {
538-
Some(RenderedLink {
539-
original_text: s.clone(),
540-
new_text: link_text.clone(),
541-
href: String::new(),
542-
})
543-
} else {
544-
None
545-
}
494+
.map(|ItemLink { link: s, link_text, .. }| RenderedLink {
495+
original_text: s.clone(),
496+
new_text: link_text.clone(),
497+
href: String::new(),
546498
})
547499
.collect()
548500
}
@@ -963,7 +915,7 @@ crate struct Attributes {
963915
crate other_attrs: Vec<ast::Attribute>,
964916
}
965917

966-
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
918+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
967919
/// A link that has not yet been rendered.
968920
///
969921
/// This link will be turned into a rendered link by [`Item::links`].
@@ -975,7 +927,7 @@ crate struct ItemLink {
975927
/// This may not be the same as `link` if there was a disambiguator
976928
/// in an intra-doc link (e.g. \[`fn@f`\])
977929
pub(crate) link_text: String,
978-
pub(crate) did: Option<DefId>,
930+
pub(crate) did: DefId,
979931
/// The url fragment to append to the link
980932
pub(crate) fragment: Option<String>,
981933
}
@@ -1802,6 +1754,39 @@ impl PrimitiveType {
18021754
Never => sym::never,
18031755
}
18041756
}
1757+
1758+
/// Returns the DefId of the module with `doc(primitive)` for this primitive type.
1759+
/// Panics if there is no such module.
1760+
///
1761+
/// This gives precedence to primitives defined in the current crate, and deprioritizes primitives defined in `core`,
1762+
/// but otherwise, if multiple crates define the same primitive, there is no guarantee of which will be picked.
1763+
/// In particular, if a crate depends on both `std` and another crate that also defines `doc(primitive)`, then
1764+
/// it's entirely random whether `std` or the other crate is picked. (no_std crates are usually fine unless multiple dependencies define a primitive.)
1765+
crate fn primitive_locations(tcx: TyCtxt<'_>) -> &FxHashMap<PrimitiveType, DefId> {
1766+
static PRIMITIVE_LOCATIONS: OnceCell<FxHashMap<PrimitiveType, DefId>> = OnceCell::new();
1767+
PRIMITIVE_LOCATIONS.get_or_init(|| {
1768+
let mut primitive_locations = FxHashMap::default();
1769+
// NOTE: technically this misses crates that are only passed with `--extern` and not loaded when checking the crate.
1770+
// This is a degenerate case that I don't plan to support.
1771+
for &crate_num in tcx.crates(()) {
1772+
let e = ExternalCrate { crate_num };
1773+
let crate_name = e.name(tcx);
1774+
debug!(?crate_num, ?crate_name);
1775+
for &(def_id, prim) in &e.primitives(tcx) {
1776+
// HACK: try to link to std instead where possible
1777+
if crate_name == sym::core && primitive_locations.get(&prim).is_some() {
1778+
continue;
1779+
}
1780+
primitive_locations.insert(prim, def_id);
1781+
}
1782+
}
1783+
let local_primitives = ExternalCrate { crate_num: LOCAL_CRATE }.primitives(tcx);
1784+
for (def_id, prim) in local_primitives {
1785+
primitive_locations.insert(prim, def_id);
1786+
}
1787+
primitive_locations
1788+
})
1789+
}
18051790
}
18061791

18071792
impl From<ast::IntTy> for PrimitiveType {

src/librustdoc/formats/cache.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_middle::middle::privacy::AccessLevels;
66
use rustc_middle::ty::TyCtxt;
77
use rustc_span::symbol::sym;
88

9-
use crate::clean::{self, GetDefId, ItemId};
9+
use crate::clean::{self, GetDefId, ItemId, PrimitiveType};
1010
use crate::config::RenderOptions;
1111
use crate::fold::DocFolder;
1212
use crate::formats::item_type::ItemType;
@@ -159,17 +159,16 @@ impl Cache {
159159
self.external_paths.insert(e.def_id(), (vec![name.to_string()], ItemType::Module));
160160
}
161161

162-
// Cache where all known primitives have their documentation located.
163-
//
164-
// Favor linking to as local extern as possible, so iterate all crates in
165-
// reverse topological order.
166-
for &e in krate.externs.iter().rev() {
167-
for &(def_id, prim) in &e.primitives(tcx) {
168-
self.primitive_locations.insert(prim, def_id);
169-
}
170-
}
171-
for &(def_id, prim) in &krate.primitives {
172-
self.primitive_locations.insert(prim, def_id);
162+
// FIXME: avoid this clone (requires implementing Default manually)
163+
self.primitive_locations = PrimitiveType::primitive_locations(tcx).clone();
164+
for (prim, &def_id) in &self.primitive_locations {
165+
let crate_name = tcx.crate_name(def_id.krate);
166+
// Recall that we only allow primitive modules to be at the root-level of the crate.
167+
// If that restriction is ever lifted, this will have to include the relative paths instead.
168+
self.external_paths.insert(
169+
def_id,
170+
(vec![crate_name.to_string(), prim.as_sym().to_string()], ItemType::Primitive),
171+
);
173172
}
174173

175174
krate = CacheBuilder { tcx, cache: self }.fold_crate(krate);

src/librustdoc/html/format.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -509,14 +509,19 @@ crate fn href_with_root_path(
509509
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
510510
}
511511

512-
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
512+
if !did.is_local()
513+
&& !cache.access_levels.is_public(did)
514+
&& !cache.document_private
515+
&& !cache.primitive_locations.values().any(|&id| id == did)
516+
{
513517
return Err(HrefError::Private);
514518
}
515519

516520
let mut is_remote = false;
517521
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
518522
Some(&(ref fqp, shortty)) => (fqp, shortty, {
519523
let module_fqp = to_module_fqp(shortty, fqp);
524+
debug!(?fqp, ?shortty, ?module_fqp);
520525
href_relative_parts(module_fqp, relative_to)
521526
}),
522527
None => {
@@ -548,6 +553,7 @@ crate fn href_with_root_path(
548553
url_parts.insert(0, root);
549554
}
550555
}
556+
debug!(?url_parts);
551557
let last = &fqp.last().unwrap()[..];
552558
let filename;
553559
match shortty {

src/librustdoc/json/conversions.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ impl JsonRenderer<'_> {
3030
.get(&item.def_id)
3131
.into_iter()
3232
.flatten()
33-
.filter_map(|clean::ItemLink { link, did, .. }| {
34-
did.map(|did| (link.clone(), from_item_id(did.into())))
35-
})
33+
.map(|clean::ItemLink { link, did, .. }| (link.clone(), from_item_id((*did).into())))
3634
.collect();
3735
let docs = item.attrs.collapsed_doc_value();
3836
let attrs = item

src/librustdoc/passes/collect_intra_doc_links.rs

+23-36
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_hir::def::{
1414
};
1515
use rustc_hir::def_id::{CrateNum, DefId};
1616
use rustc_middle::ty::TyCtxt;
17-
use rustc_middle::{bug, ty};
17+
use rustc_middle::{bug, span_bug, ty};
1818
use rustc_resolve::ParentScope;
1919
use rustc_session::lint::Lint;
2020
use rustc_span::hygiene::{MacroKind, SyntaxContext};
@@ -98,14 +98,10 @@ impl Res {
9898
}
9999
}
100100

101-
fn def_id(self) -> DefId {
102-
self.opt_def_id().expect("called def_id() on a primitive")
103-
}
104-
105-
fn opt_def_id(self) -> Option<DefId> {
101+
fn def_id(self, tcx: TyCtxt<'_>) -> DefId {
106102
match self {
107-
Res::Def(_, id) => Some(id),
108-
Res::Primitive(_) => None,
103+
Res::Def(_, id) => id,
104+
Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(),
109105
}
110106
}
111107

@@ -237,10 +233,7 @@ enum AnchorFailure {
237233
/// link, Rustdoc disallows having a user-specified anchor.
238234
///
239235
/// Most of the time this is fine, because you can just link to the page of
240-
/// the item if you want to provide your own anchor. For primitives, though,
241-
/// rustdoc uses the anchor as a side channel to know which page to link to;
242-
/// it doesn't show up in the generated link. Ideally, rustdoc would remove
243-
/// this limitation, allowing you to link to subheaders on primitives.
236+
/// the item if you want to provide your own anchor.
244237
RustdocAnchorConflict(Res),
245238
}
246239

@@ -388,7 +381,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
388381
ty::AssocKind::Const => "associatedconstant",
389382
ty::AssocKind::Type => "associatedtype",
390383
};
391-
let fragment = format!("{}#{}.{}", prim_ty.as_sym(), out, item_name);
384+
let fragment = format!("{}.{}", out, item_name);
392385
(Res::Primitive(prim_ty), fragment, Some((kind.as_def_kind(), item.def_id)))
393386
})
394387
})
@@ -475,14 +468,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
475468
return handle_variant(self.cx, res, extra_fragment);
476469
}
477470
// Not a trait item; just return what we found.
478-
Res::Primitive(ty) => {
479-
if extra_fragment.is_some() {
480-
return Err(ErrorKind::AnchorFailure(
481-
AnchorFailure::RustdocAnchorConflict(res),
482-
));
483-
}
484-
return Ok((res, Some(ty.as_sym().to_string())));
485-
}
486471
_ => return Ok((res, extra_fragment.clone())),
487472
}
488473
}
@@ -517,6 +502,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
517502
let (res, fragment, side_channel) =
518503
self.resolve_associated_item(ty_res, item_name, ns, module_id)?;
519504
let result = if extra_fragment.is_some() {
505+
// NOTE: can never be a primitive since `side_channel.is_none()` only when `res`
506+
// is a trait (and the side channel DefId is always an associated item).
520507
let diag_res = side_channel.map_or(res, |(k, r)| Res::Def(k, r));
521508
Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(diag_res)))
522509
} else {
@@ -1152,7 +1139,7 @@ impl LinkCollector<'_, '_> {
11521139
module_id = DefId { krate, index: CRATE_DEF_INDEX };
11531140
}
11541141

1155-
let (mut res, mut fragment) = self.resolve_with_disambiguator_cached(
1142+
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
11561143
ResolutionInfo {
11571144
module_id,
11581145
dis: disambiguator,
@@ -1174,16 +1161,7 @@ impl LinkCollector<'_, '_> {
11741161
if let Some(prim) = resolve_primitive(path_str, TypeNS) {
11751162
// `prim@char`
11761163
if matches!(disambiguator, Some(Disambiguator::Primitive)) {
1177-
if fragment.is_some() {
1178-
anchor_failure(
1179-
self.cx,
1180-
diag_info,
1181-
AnchorFailure::RustdocAnchorConflict(prim),
1182-
);
1183-
return None;
1184-
}
11851164
res = prim;
1186-
fragment = Some(prim.name(self.cx.tcx).to_string());
11871165
} else {
11881166
// `[char]` when a `char` module is in scope
11891167
let candidates = vec![res, prim];
@@ -1303,12 +1281,17 @@ impl LinkCollector<'_, '_> {
13031281
}
13041282
}
13051283

1306-
Some(ItemLink { link: ori_link.link, link_text, did: None, fragment })
1284+
Some(ItemLink {
1285+
link: ori_link.link,
1286+
link_text,
1287+
did: res.def_id(self.cx.tcx),
1288+
fragment,
1289+
})
13071290
}
13081291
Res::Def(kind, id) => {
13091292
verify(kind, id)?;
13101293
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
1311-
Some(ItemLink { link: ori_link.link, link_text, did: Some(id), fragment })
1294+
Some(ItemLink { link: ori_link.link, link_text, did: id, fragment })
13121295
}
13131296
}
13141297
}
@@ -2069,8 +2052,11 @@ fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: A
20692052
diag.span_label(sp, "invalid anchor");
20702053
}
20712054
if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
2072-
diag.note("this restriction may be lifted in a future release");
2073-
diag.note("see https://github.com/rust-lang/rust/issues/83083 for more information");
2055+
if let Some(sp) = sp {
2056+
span_bug!(sp, "anchors should be allowed now");
2057+
} else {
2058+
bug!("anchors should be allowed now");
2059+
}
20742060
}
20752061
});
20762062
}
@@ -2198,10 +2184,11 @@ fn handle_variant(
21982184
use rustc_middle::ty::DefIdTree;
21992185

22002186
if extra_fragment.is_some() {
2187+
// NOTE: `res` can never be a primitive since this function is only called when `tcx.def_kind(res) == DefKind::Variant`.
22012188
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
22022189
}
22032190
cx.tcx
2204-
.parent(res.def_id())
2191+
.parent(res.def_id(cx.tcx))
22052192
.map(|parent| {
22062193
let parent_def = Res::Def(DefKind::Enum, parent);
22072194
let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());

src/test/rustdoc-ui/intra-doc/anchors.rs

-10
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,3 @@ pub fn bar() {}
3737
/// Damn enum's variants: [Enum::A#whatever].
3838
//~^ ERROR `Enum::A#whatever` contains an anchor
3939
pub fn enum_link() {}
40-
41-
/// Primitives?
42-
///
43-
/// [u32#hello]
44-
//~^ ERROR `u32#hello` contains an anchor
45-
pub fn x() {}
46-
47-
/// [prim@usize#x]
48-
//~^ ERROR `prim@usize#x` contains an anchor
49-
pub mod usize {}

0 commit comments

Comments
 (0)