Skip to content

Commit 64fe43c

Browse files
jyn514GuillaumeGomez
authored andcommitted
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 b1de9bd commit 64fe43c

File tree

16 files changed

+140
-161
lines changed

16 files changed

+140
-161
lines changed

src/librustdoc/clean/types.rs

Lines changed: 52 additions & 67 deletions
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.clone(), 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.clone(), 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
}
@@ -962,7 +914,7 @@ crate struct Attributes {
962914
crate other_attrs: Vec<ast::Attribute>,
963915
}
964916

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

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

src/librustdoc/formats/cache.rs

Lines changed: 11 additions & 12 deletions
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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,14 +495,19 @@ crate fn href_with_root_path(
495495
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
496496
}
497497

498-
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
498+
if !did.is_local()
499+
&& !cache.access_levels.is_public(did)
500+
&& !cache.document_private
501+
&& !cache.primitive_locations.values().any(|&id| id == did)
502+
{
499503
return Err(HrefError::Private);
500504
}
501505

502506
let mut is_remote = false;
503507
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
504508
Some(&(ref fqp, shortty)) => (fqp, shortty, {
505509
let module_fqp = to_module_fqp(shortty, fqp);
510+
debug!(?fqp, ?shortty, ?module_fqp);
506511
href_relative_parts(module_fqp, relative_to)
507512
}),
508513
None => {
@@ -534,6 +539,7 @@ crate fn href_with_root_path(
534539
url_parts.insert(0, root);
535540
}
536541
}
542+
debug!(?url_parts);
537543
let last = &fqp.last().unwrap()[..];
538544
let filename;
539545
match shortty {

src/librustdoc/json/conversions.rs

Lines changed: 1 addition & 3 deletions
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

Lines changed: 19 additions & 32 deletions
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};
@@ -95,14 +95,10 @@ impl Res {
9595
}
9696
}
9797

98-
fn def_id(self) -> DefId {
99-
self.opt_def_id().expect("called def_id() on a primitive")
100-
}
101-
102-
fn opt_def_id(self) -> Option<DefId> {
98+
fn def_id(self, tcx: TyCtxt<'_>) -> DefId {
10399
match self {
104-
Res::Def(_, id) => Some(id),
105-
Res::Primitive(_) => None,
100+
Res::Def(_, id) => id,
101+
Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(),
106102
}
107103
}
108104

@@ -385,7 +381,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
385381
ty::AssocKind::Const => "associatedconstant",
386382
ty::AssocKind::Type => "associatedtype",
387383
};
388-
let fragment = format!("{}#{}.{}", prim_ty.as_sym(), out, item_name);
384+
let fragment = format!("{}.{}", out, item_name);
389385
(Res::Primitive(prim_ty), fragment, Some((kind.as_def_kind(), item.def_id)))
390386
})
391387
})
@@ -472,14 +468,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
472468
return handle_variant(self.cx, res, extra_fragment);
473469
}
474470
// Not a trait item; just return what we found.
475-
Res::Primitive(ty) => {
476-
if extra_fragment.is_some() {
477-
return Err(ErrorKind::AnchorFailure(
478-
AnchorFailure::RustdocAnchorConflict(res),
479-
));
480-
}
481-
return Ok((res, Some(ty.as_sym().to_string())));
482-
}
483471
_ => return Ok((res, extra_fragment.clone())),
484472
}
485473
}
@@ -1149,7 +1137,7 @@ impl LinkCollector<'_, '_> {
11491137
module_id = DefId { krate, index: CRATE_DEF_INDEX };
11501138
}
11511139

1152-
let (mut res, mut fragment) = self.resolve_with_disambiguator_cached(
1140+
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
11531141
ResolutionInfo {
11541142
module_id,
11551143
dis: disambiguator,
@@ -1171,16 +1159,7 @@ impl LinkCollector<'_, '_> {
11711159
if let Some(prim) = resolve_primitive(path_str, TypeNS) {
11721160
// `prim@char`
11731161
if matches!(disambiguator, Some(Disambiguator::Primitive)) {
1174-
if fragment.is_some() {
1175-
anchor_failure(
1176-
self.cx,
1177-
diag_info,
1178-
AnchorFailure::RustdocAnchorConflict(prim),
1179-
);
1180-
return None;
1181-
}
11821162
res = prim;
1183-
fragment = Some(prim.name(self.cx.tcx).to_string());
11841163
} else {
11851164
// `[char]` when a `char` module is in scope
11861165
let candidates = vec![res, prim];
@@ -1300,12 +1279,17 @@ impl LinkCollector<'_, '_> {
13001279
}
13011280
}
13021281

1303-
Some(ItemLink { link: ori_link.link, link_text, did: None, fragment })
1282+
Some(ItemLink {
1283+
link: ori_link.link,
1284+
link_text,
1285+
did: res.def_id(self.cx.tcx),
1286+
fragment,
1287+
})
13041288
}
13051289
Res::Def(kind, id) => {
13061290
verify(kind, id)?;
13071291
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
1308-
Some(ItemLink { link: ori_link.link, link_text, did: Some(id), fragment })
1292+
Some(ItemLink { link: ori_link.link, link_text, did: id, fragment })
13091293
}
13101294
}
13111295
}
@@ -2066,8 +2050,11 @@ fn anchor_failure(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, failure: A
20662050
diag.span_label(sp, "invalid anchor");
20672051
}
20682052
if let AnchorFailure::RustdocAnchorConflict(Res::Primitive(_)) = failure {
2069-
diag.note("this restriction may be lifted in a future release");
2070-
diag.note("see https://github.com/rust-lang/rust/issues/83083 for more information");
2053+
if let Some(sp) = sp {
2054+
span_bug!(sp, "anchors should be allowed now");
2055+
} else {
2056+
bug!("anchors should be allowed now");
2057+
}
20712058
}
20722059
});
20732060
}
@@ -2198,7 +2185,7 @@ fn handle_variant(
21982185
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
21992186
}
22002187
cx.tcx
2201-
.parent(res.def_id())
2188+
.parent(res.def_id(cx.tcx))
22022189
.map(|parent| {
22032190
let parent_def = Res::Def(DefKind::Enum, parent);
22042191
let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());

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

Lines changed: 0 additions & 10 deletions
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)