Skip to content

Commit eb27b61

Browse files
authored
Rollup merge of #106466 - clubby789:relative-module-fix, r=notriddle
Fix rustdoc source code rendering for `#[path = "../path/to/mod.rs"]` links Fixes #103517 While generating the location for modules source HTML to be saved at, a `..` path component appeared to be translated to `/up/`. Additionally, while generating the navigation sidebar, `..` path components were ignored. This means that (as in the issue above), a *real* directory structure of: ``` sys/ unix/ mod.rs <-- contains #![path = "../unix/mod.rs] cmath.rs ``` was rendered as: ``` sys/ unix/ mod.rs unix/ cmath.rs <-- links to sys/unix/unix/cmath.rs.html, 404 ``` While the *files* were stored as ``` sys/ unix/ mod.rs.html up/ unix/ cmath.rs.html ```
2 parents 72d650f + d5d1c57 commit eb27b61

File tree

5 files changed

+109
-41
lines changed

5 files changed

+109
-41
lines changed

src/librustdoc/html/render/context.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ impl<'tcx> Context<'tcx> {
309309

310310
pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> {
311311
let mut root = self.root_path();
312-
let mut path = String::new();
312+
let mut path: String;
313313
let cnum = span.cnum(self.sess());
314314

315315
// We can safely ignore synthetic `SourceFile`s.
@@ -340,10 +340,24 @@ impl<'tcx> Context<'tcx> {
340340
ExternalLocation::Unknown => return None,
341341
};
342342

343-
sources::clean_path(&src_root, file, false, |component| {
344-
path.push_str(&component.to_string_lossy());
343+
let href = RefCell::new(PathBuf::new());
344+
sources::clean_path(
345+
&src_root,
346+
file,
347+
|component| {
348+
href.borrow_mut().push(component);
349+
},
350+
|| {
351+
href.borrow_mut().pop();
352+
},
353+
);
354+
355+
path = href.into_inner().to_string_lossy().to_string();
356+
357+
if let Some(c) = path.as_bytes().last() && *c != b'/' {
345358
path.push('/');
346-
});
359+
}
360+
347361
let mut fname = file.file_name().expect("source has no filename").to_os_string();
348362
fname.push(".html");
349363
path.push_str(&fname.to_string_lossy());

src/librustdoc/html/render/write_shared.rs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::cell::RefCell;
12
use std::fs::{self, File};
23
use std::io::prelude::*;
34
use std::io::{self, BufReader};
45
use std::path::{Component, Path};
5-
use std::rc::Rc;
6+
use std::rc::{Rc, Weak};
67

78
use itertools::Itertools;
89
use rustc_data_structures::flock;
@@ -184,23 +185,26 @@ pub(super) fn write_shared(
184185

185186
use std::ffi::OsString;
186187

187-
#[derive(Debug)]
188+
#[derive(Debug, Default)]
188189
struct Hierarchy {
190+
parent: Weak<Self>,
189191
elem: OsString,
190-
children: FxHashMap<OsString, Hierarchy>,
191-
elems: FxHashSet<OsString>,
192+
children: RefCell<FxHashMap<OsString, Rc<Self>>>,
193+
elems: RefCell<FxHashSet<OsString>>,
192194
}
193195

194196
impl Hierarchy {
195-
fn new(elem: OsString) -> Hierarchy {
196-
Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
197+
fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
198+
Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
197199
}
198200

199201
fn to_json_string(&self) -> String {
200-
let mut subs: Vec<&Hierarchy> = self.children.values().collect();
202+
let borrow = self.children.borrow();
203+
let mut subs: Vec<_> = borrow.values().collect();
201204
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
202205
let mut files = self
203206
.elems
207+
.borrow()
204208
.iter()
205209
.map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
206210
.collect::<Vec<_>>();
@@ -220,36 +224,52 @@ pub(super) fn write_shared(
220224
files = files
221225
)
222226
}
223-
}
224227

225-
if cx.include_sources {
226-
let mut hierarchy = Hierarchy::new(OsString::new());
227-
for source in cx
228-
.shared
229-
.local_sources
230-
.iter()
231-
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
232-
{
233-
let mut h = &mut hierarchy;
234-
let mut elems = source
228+
fn add_path(self: &Rc<Self>, path: &Path) {
229+
let mut h = Rc::clone(&self);
230+
let mut elems = path
235231
.components()
236232
.filter_map(|s| match s {
237233
Component::Normal(s) => Some(s.to_owned()),
234+
Component::ParentDir => Some(OsString::from("..")),
238235
_ => None,
239236
})
240237
.peekable();
241238
loop {
242239
let cur_elem = elems.next().expect("empty file path");
240+
if cur_elem == ".." {
241+
if let Some(parent) = h.parent.upgrade() {
242+
h = parent;
243+
}
244+
continue;
245+
}
243246
if elems.peek().is_none() {
244-
h.elems.insert(cur_elem);
247+
h.elems.borrow_mut().insert(cur_elem);
245248
break;
246249
} else {
247-
let e = cur_elem.clone();
248-
h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
250+
let entry = Rc::clone(
251+
h.children
252+
.borrow_mut()
253+
.entry(cur_elem.clone())
254+
.or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
255+
);
256+
h = entry;
249257
}
250258
}
251259
}
260+
}
252261

262+
if cx.include_sources {
263+
let hierarchy = Rc::new(Hierarchy::default());
264+
for source in cx
265+
.shared
266+
.local_sources
267+
.iter()
268+
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
269+
{
270+
hierarchy.add_path(source);
271+
}
272+
let hierarchy = Rc::try_unwrap(hierarchy).unwrap();
253273
let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
254274
let make_sources = || {
255275
let (mut all_sources, _krates) =

src/librustdoc/html/sources.rs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_middle::ty::TyCtxt;
1313
use rustc_session::Session;
1414
use rustc_span::source_map::FileName;
1515

16+
use std::cell::RefCell;
1617
use std::ffi::OsStr;
1718
use std::fs;
1819
use std::path::{Component, Path, PathBuf};
@@ -72,12 +73,22 @@ impl LocalSourcesCollector<'_, '_> {
7273
return;
7374
}
7475

75-
let mut href = String::new();
76-
clean_path(self.src_root, &p, false, |component| {
77-
href.push_str(&component.to_string_lossy());
78-
href.push('/');
79-
});
76+
let href = RefCell::new(PathBuf::new());
77+
clean_path(
78+
&self.src_root,
79+
&p,
80+
|component| {
81+
href.borrow_mut().push(component);
82+
},
83+
|| {
84+
href.borrow_mut().pop();
85+
},
86+
);
8087

88+
let mut href = href.into_inner().to_string_lossy().to_string();
89+
if let Some(c) = href.as_bytes().last() && *c != b'/' {
90+
href.push('/');
91+
}
8192
let mut src_fname = p.file_name().expect("source has no filename").to_os_string();
8293
src_fname.push(".html");
8394
href.push_str(&src_fname.to_string_lossy());
@@ -180,13 +191,28 @@ impl SourceCollector<'_, '_> {
180191

181192
let shared = Rc::clone(&self.cx.shared);
182193
// Create the intermediate directories
183-
let mut cur = self.dst.clone();
184-
let mut root_path = String::from("../../");
185-
clean_path(&shared.src_root, &p, false, |component| {
186-
cur.push(component);
187-
root_path.push_str("../");
188-
});
194+
let cur = RefCell::new(PathBuf::new());
195+
let root_path = RefCell::new(PathBuf::new());
196+
197+
clean_path(
198+
&shared.src_root,
199+
&p,
200+
|component| {
201+
cur.borrow_mut().push(component);
202+
root_path.borrow_mut().push("..");
203+
},
204+
|| {
205+
cur.borrow_mut().pop();
206+
root_path.borrow_mut().pop();
207+
},
208+
);
189209

210+
let root_path = PathBuf::from("../../").join(root_path.into_inner());
211+
let mut root_path = root_path.to_string_lossy();
212+
if let Some(c) = root_path.as_bytes().last() && *c != b'/' {
213+
root_path += "/";
214+
}
215+
let mut cur = self.dst.join(cur.into_inner());
190216
shared.ensure_dir(&cur)?;
191217

192218
let src_fname = p.file_name().expect("source has no filename").to_os_string();
@@ -232,24 +258,26 @@ impl SourceCollector<'_, '_> {
232258
/// Takes a path to a source file and cleans the path to it. This canonicalizes
233259
/// things like ".." to components which preserve the "top down" hierarchy of a
234260
/// static HTML tree. Each component in the cleaned path will be passed as an
235-
/// argument to `f`. The very last component of the path (ie the file name) will
236-
/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
237-
pub(crate) fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
261+
/// argument to `f`. The very last component of the path (ie the file name) is ignored.
262+
/// If a `..` is encountered, the `parent` closure will be called to allow the callee to
263+
/// handle it.
264+
pub(crate) fn clean_path<F, P>(src_root: &Path, p: &Path, mut f: F, mut parent: P)
238265
where
239266
F: FnMut(&OsStr),
267+
P: FnMut(),
240268
{
241269
// make it relative, if possible
242270
let p = p.strip_prefix(src_root).unwrap_or(p);
243271

244272
let mut iter = p.components().peekable();
245273

246274
while let Some(c) = iter.next() {
247-
if !keep_filename && iter.peek().is_none() {
275+
if iter.peek().is_none() {
248276
break;
249277
}
250278

251279
match c {
252-
Component::ParentDir => f("up".as_ref()),
280+
Component::ParentDir => parent(),
253281
Component::Normal(c) => f(c),
254282
_ => continue,
255283
}

src/test/rustdoc/src-links.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
#[path = "src-links/mod.rs"]
88
pub mod qux;
99

10+
// @has src/foo/src-links.rs.html
11+
// @has foo/fizz/index.html '//a/@href' '../src/foo/src-links/fizz.rs.html'
12+
#[path = "src-links/../src-links/fizz.rs"]
13+
pub mod fizz;
14+
1015
// @has foo/bar/index.html '//a/@href' '../../src/foo/src-links.rs.html'
1116
pub mod bar {
1217

src/test/rustdoc/src-links/fizz.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub struct Buzz;

0 commit comments

Comments
 (0)