Skip to content

Commit 7ec6df5

Browse files
committed
rustdoc: Fix cross-crate links to reexported items
Cross crate links can target items which are not rendered in the documentation. If the item is reexported at a higher level, the destination of the link (a concatenation of the fully qualified name) may actually lead to nowhere. This fixes this problem by altering rustdoc to emit pages which redirect to the local copy of the reexported structure. cc #14515 Closes #14137
1 parent c5830a9 commit 7ec6df5

File tree

6 files changed

+98
-30
lines changed

6 files changed

+98
-30
lines changed

src/doc/guide-unsafe.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ function is never called.
511511
With the above techniques, we've got a bare-metal executable running some Rust
512512
code. There is a good deal of functionality provided by the standard library,
513513
however, that is necessary to be productive in Rust. If the standard library is
514-
not sufficient, then [libcore](../core/index.html) is designed to be used
514+
not sufficient, then [libcore](core/index.html) is designed to be used
515515
instead.
516516

517517
The core library has very few dependencies and is much more portable than the

src/librustdoc/clean/mod.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,27 @@ impl<'a> Clean<Crate> for visit_ast::RustdocVisitor<'a> {
104104
let id = link::find_crate_id(self.attrs.as_slice(),
105105
t_outputs.out_filestem.as_slice());
106106

107-
// Clean the module, translating the entire libsyntax AST to one that is
107+
// Clean the crate, translating the entire libsyntax AST to one that is
108108
// understood by rustdoc.
109109
let mut module = self.module.clean();
110110

111111
// Collect all inner modules which are tagged as implementations of
112112
// primitives.
113+
//
114+
// Note that this loop only searches the top-level items of the crate,
115+
// and this is intentional. If we were to search the entire crate for an
116+
// item tagged with `#[doc(primitive)]` then we we would also have to
117+
// search the entirety of external modules for items tagged
118+
// `#[doc(primitive)]`, which is a pretty inefficient process (decoding
119+
// all that metadata unconditionally).
120+
//
121+
// In order to keep the metadata load under control, the
122+
// `#[doc(primitive)]` feature is explicitly designed to only allow the
123+
// primitive tags to show up as the top level items in a crate.
124+
//
125+
// Also note that this does not attempt to deal with modules tagged
126+
// duplicately for the same primitive. This is handled later on when
127+
// rendering by delegating everything to a hash map.
113128
let mut primitives = Vec::new();
114129
{
115130
let m = match module.inner {

src/librustdoc/html/format.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -424,14 +424,8 @@ impl fmt::Show for clean::Type {
424424
decl.decl)
425425
}
426426
clean::Tuple(ref typs) => {
427-
try!(f.write("(".as_bytes()));
428-
for (i, typ) in typs.iter().enumerate() {
429-
if i > 0 {
430-
try!(f.write(", ".as_bytes()))
431-
}
432-
try!(write!(f, "{}", *typ));
433-
}
434-
f.write(")".as_bytes())
427+
primitive_link(f, clean::PrimitiveTuple,
428+
format!("({:#})", typs).as_slice())
435429
}
436430
clean::Vector(ref t) => {
437431
primitive_link(f, clean::Slice, format!("[{}]", **t).as_slice())

src/librustdoc/html/layout.rs

+14
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,17 @@ r##"<!DOCTYPE html>
130130
fn nonestr<'a>(s: &'a str) -> &'a str {
131131
if s == "" { "none" } else { s }
132132
}
133+
134+
pub fn redirect(dst: &mut io::Writer, url: &str) -> io::IoResult<()> {
135+
write!(dst,
136+
r##"<!DOCTYPE html>
137+
<html lang="en">
138+
<head>
139+
<meta http-equiv="refresh" content="0;URL={url}">
140+
</head>
141+
<body>
142+
</body>
143+
</html>"##,
144+
url = url,
145+
)
146+
}

src/librustdoc/html/render.rs

+64-19
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ use html::markdown;
7070
pub struct Context {
7171
/// Current hierarchy of components leading down to what's currently being
7272
/// rendered
73-
pub current: Vec<String> ,
73+
pub current: Vec<String>,
7474
/// String representation of how to get back to the root path of the 'doc/'
7575
/// folder in terms of a relative URL.
7676
pub root_path: String,
@@ -90,6 +90,10 @@ pub struct Context {
9090
/// the source files are present in the html rendering, then this will be
9191
/// `true`.
9292
pub include_sources: bool,
93+
/// A flag, which when turned off, will render pages which redirect to the
94+
/// real location of an item. This is used to allow external links to
95+
/// publicly reused items to redirect to the right location.
96+
pub render_redirect_pages: bool,
9397
}
9498

9599
/// Indicates where an external crate can be found.
@@ -227,6 +231,7 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
227231
krate: krate.name.clone(),
228232
},
229233
include_sources: true,
234+
render_redirect_pages: false,
230235
};
231236
try!(mkdir(&cx.dst));
232237

@@ -493,7 +498,17 @@ fn write_shared(cx: &Context,
493498
let dst = cx.dst.join("implementors");
494499
try!(mkdir(&dst));
495500
for (&did, imps) in cache.implementors.iter() {
496-
let &(ref remote_path, remote_item_type) = cache.paths.get(&did);
501+
// Private modules can leak through to this phase of rustdoc, which
502+
// could contain implementations for otherwise private types. In some
503+
// rare cases we could find an implementation for an item which wasn't
504+
// indexed, so we just skip this step in that case.
505+
//
506+
// FIXME: this is a vague explanation for why this can't be a `get`, in
507+
// theory it should be...
508+
let &(ref remote_path, remote_item_type) = match cache.paths.find(&did) {
509+
Some(p) => p,
510+
None => continue,
511+
};
497512

498513
let mut mydst = dst.clone();
499514
for part in remote_path.slice_to(remote_path.len() - 1).iter() {
@@ -823,7 +838,7 @@ impl DocFolder for Cache {
823838
clean::StructItem(..) | clean::EnumItem(..) |
824839
clean::TypedefItem(..) | clean::TraitItem(..) |
825840
clean::FunctionItem(..) | clean::ModuleItem(..) |
826-
clean::ForeignFunctionItem(..) => {
841+
clean::ForeignFunctionItem(..) if !self.privmod => {
827842
// Reexported items mean that the same id can show up twice
828843
// in the rustdoc ast that we're looking at. We know,
829844
// however, that a reexported item doesn't show up in the
@@ -840,7 +855,7 @@ impl DocFolder for Cache {
840855
}
841856
// link variants to their parent enum because pages aren't emitted
842857
// for each variant
843-
clean::VariantItem(..) => {
858+
clean::VariantItem(..) if !self.privmod => {
844859
let mut stack = self.stack.clone();
845860
stack.pop();
846861
self.paths.insert(item.def_id, (stack, item_type::Enum));
@@ -932,14 +947,6 @@ impl DocFolder for Cache {
932947
}
933948
None
934949
}
935-
// Private modules may survive the strip-private pass if
936-
// they contain impls for public types, but those will get
937-
// stripped here
938-
clean::Item { inner: clean::ModuleItem(ref m),
939-
visibility, .. }
940-
if (m.items.len() == 0 &&
941-
item.doc_value().is_none()) ||
942-
visibility != Some(ast::Public) => None,
943950

944951
i => Some(i),
945952
}
@@ -1020,7 +1027,7 @@ impl Context {
10201027
/// The rendering driver uses this closure to queue up more work.
10211028
fn item(&mut self, item: clean::Item,
10221029
f: |&mut Context, clean::Item|) -> io::IoResult<()> {
1023-
fn render(w: io::File, cx: &mut Context, it: &clean::Item,
1030+
fn render(w: io::File, cx: &Context, it: &clean::Item,
10241031
pushname: bool) -> io::IoResult<()> {
10251032
info!("Rendering an item to {}", w.path().display());
10261033
// A little unfortunate that this is done like this, but it sure
@@ -1047,16 +1054,42 @@ impl Context {
10471054
// of the pain by using a buffered writer instead of invoking the
10481055
// write sycall all the time.
10491056
let mut writer = BufferedWriter::new(w);
1050-
try!(layout::render(&mut writer as &mut Writer, &cx.layout, &page,
1051-
&Sidebar{ cx: cx, item: it },
1052-
&Item{ cx: cx, item: it }));
1057+
if !cx.render_redirect_pages {
1058+
try!(layout::render(&mut writer, &cx.layout, &page,
1059+
&Sidebar{ cx: cx, item: it },
1060+
&Item{ cx: cx, item: it }));
1061+
} else {
1062+
let mut url = "../".repeat(cx.current.len());
1063+
match cache_key.get().unwrap().paths.find(&it.def_id) {
1064+
Some(&(ref names, _)) => {
1065+
for name in names.slice_to(names.len() - 1).iter() {
1066+
url.push_str(name.as_slice());
1067+
url.push_str("/");
1068+
}
1069+
url.push_str(item_path(it).as_slice());
1070+
try!(layout::redirect(&mut writer, url.as_slice()));
1071+
}
1072+
None => {}
1073+
}
1074+
}
10531075
writer.flush()
10541076
}
10551077

10561078
match item.inner {
10571079
// modules are special because they add a namespace. We also need to
10581080
// recurse into the items of the module as well.
10591081
clean::ModuleItem(..) => {
1082+
// Private modules may survive the strip-private pass if they
1083+
// contain impls for public types. These modules can also
1084+
// contain items such as publicly reexported structures.
1085+
//
1086+
// External crates will provide links to these structures, so
1087+
// these modules are recursed into, but not rendered normally (a
1088+
// flag on the context).
1089+
if !self.render_redirect_pages {
1090+
self.render_redirect_pages = ignore_private_module(&item);
1091+
}
1092+
10601093
let name = item.name.get_ref().to_string();
10611094
let mut item = Some(item);
10621095
self.recurse(name, |this| {
@@ -1289,8 +1322,9 @@ fn document(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
12891322
fn item_module(w: &mut fmt::Formatter, cx: &Context,
12901323
item: &clean::Item, items: &[clean::Item]) -> fmt::Result {
12911324
try!(document(w, item));
1292-
debug!("{:?}", items);
1293-
let mut indices = Vec::from_fn(items.len(), |i| i);
1325+
let mut indices = range(0, items.len()).filter(|i| {
1326+
!ignore_private_module(&items[*i])
1327+
}).collect::<Vec<uint>>();
12941328

12951329
fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: uint, idx2: uint) -> Ordering {
12961330
if shortty(i1) == shortty(i2) {
@@ -1332,7 +1366,6 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
13321366
}
13331367
}
13341368

1335-
debug!("{:?}", indices);
13361369
indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2));
13371370

13381371
debug!("{:?}", indices);
@@ -1976,6 +2009,8 @@ impl<'a> fmt::Show for Sidebar<'a> {
19762009
fn build_sidebar(m: &clean::Module) -> HashMap<String, Vec<String>> {
19772010
let mut map = HashMap::new();
19782011
for item in m.items.iter() {
2012+
if ignore_private_module(item) { continue }
2013+
19792014
let short = shortty(item).to_static_str();
19802015
let myname = match item.name {
19812016
None => continue,
@@ -2023,3 +2058,13 @@ fn item_primitive(w: &mut fmt::Formatter,
20232058
try!(document(w, it));
20242059
render_methods(w, it)
20252060
}
2061+
2062+
fn ignore_private_module(it: &clean::Item) -> bool {
2063+
match it.inner {
2064+
clean::ModuleItem(ref m) => {
2065+
(m.items.len() == 0 && it.doc_value().is_none()) ||
2066+
it.visibility != Some(ast::Public)
2067+
}
2068+
_ => false,
2069+
}
2070+
}

src/librustdoc/passes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
7070
for_: clean::ResolvedPath{ did, .. },
7171
ref trait_, ..
7272
}) => {
73-
// Impls for stripped don't need to exist
73+
// Impls for stripped types don't need to exist
7474
if self.stripped.contains(&did.node) {
7575
return None;
7676
}

0 commit comments

Comments
 (0)