Skip to content

rustdoc: use a more compact encoding for implementors/trait.*.js #100150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/librustdoc/html/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl Buffer {
}
}

fn comma_sep<T: fmt::Display>(
pub(crate) fn comma_sep<T: fmt::Display>(
items: impl Iterator<Item = T>,
space_after_comma: bool,
) -> impl fmt::Display {
Expand Down
79 changes: 62 additions & 17 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::ffi::OsStr;
use std::fmt::Write;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{self, BufReader};
Expand All @@ -10,7 +9,8 @@ use std::sync::LazyLock as Lazy;
use itertools::Itertools;
use rustc_data_structures::flock;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use serde::Serialize;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};

use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS};
use crate::clean::Crate;
Expand Down Expand Up @@ -284,25 +284,43 @@ pub(super) fn write_shared(
cx.write_shared(SharedResource::Unversioned { name }, contents, &options.emit)?;
}

fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec<String>, Vec<String>)> {
/// Read a file and return all lines that match the `"{crate}":{data},` format,
/// and return a tuple `(Vec<DataString>, Vec<CrateNameString>)`.
///
/// This forms the payload of files that look like this:
///
/// ```javascript
/// var data = {
/// "{crate1}":{data},
/// "{crate2}":{data}
/// };
/// use_data(data);
/// ```
///
/// The file needs to be formatted so that *only crate data lines start with `"`*.
fn collect(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
let mut ret = Vec::new();
let mut krates = Vec::new();

if path.exists() {
let prefix = format!(r#"{}["{}"]"#, key, krate);
let prefix = format!("\"{}\"", krate);
for line in BufReader::new(File::open(path)?).lines() {
let line = line?;
if !line.starts_with(key) {
if !line.starts_with('"') {
continue;
}
if line.starts_with(&prefix) {
continue;
}
ret.push(line.to_string());
if line.ends_with(",") {
ret.push(line[..line.len() - 1].to_string());
} else {
// No comma (it's the case for the last added crate line)
ret.push(line.to_string());
}
krates.push(
line[key.len() + 2..]
.split('"')
.next()
line.split('"')
.find(|s| !s.is_empty())
.map(|s| s.to_owned())
.unwrap_or_else(String::new),
);
Expand All @@ -311,6 +329,20 @@ pub(super) fn write_shared(
Ok((ret, krates))
}

/// Read a file and return all lines that match the <code>"{crate}":{data},\</code> format,
/// and return a tuple `(Vec<DataString>, Vec<CrateNameString>)`.
///
/// This forms the payload of files that look like this:
///
/// ```javascript
/// var data = JSON.parse('{\
/// "{crate1}":{data},\
/// "{crate2}":{data}\
/// }');
/// use_data(data);
/// ```
///
/// The file needs to be formatted so that *only crate data lines start with `"`*.
fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
let mut ret = Vec::new();
let mut krates = Vec::new();
Expand Down Expand Up @@ -526,13 +558,27 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
},
};

#[derive(Serialize)]
struct Implementor {
text: String,
synthetic: bool,
types: Vec<String>,
}

impl Serialize for Implementor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&self.text)?;
if self.synthetic {
seq.serialize_element(&1)?;
seq.serialize_element(&self.types)?;
}
seq.end()
}
}

let implementors = imps
.iter()
.filter_map(|imp| {
Expand Down Expand Up @@ -563,9 +609,9 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
}

let implementors = format!(
r#"implementors["{}"] = {};"#,
r#""{}":{}"#,
krate.name(cx.tcx()),
serde_json::to_string(&implementors).unwrap()
serde_json::to_string(&implementors).expect("failed serde conversion"),
);

let mut mydst = dst.clone();
Expand All @@ -576,16 +622,15 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));

let (mut all_implementors, _) =
try_err!(collect(&mydst, krate.name(cx.tcx()).as_str(), "implementors"), &mydst);
try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst);
all_implementors.push(implementors);
// Sort the implementors by crate so the file will be generated
// identically even with rustdoc running in parallel.
all_implementors.sort();

let mut v = String::from("(function() {var implementors = {};\n");
for implementor in &all_implementors {
writeln!(v, "{}", *implementor).unwrap();
}
let mut v = String::from("(function() {var implementors = {\n");
v.push_str(&all_implementors.join(",\n"));
v.push_str("\n};");
v.push_str(
"if (window.register_implementors) {\
window.register_implementors(implementors);\
Expand Down
14 changes: 10 additions & 4 deletions src/librustdoc/html/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ function loadCss(cssFileName) {
const synthetic_implementors = document.getElementById("synthetic-implementors-list");
const inlined_types = new Set();

const TEXT_IDX = 0;
const SYNTHETIC_IDX = 1;
const TYPES_IDX = 2;

if (synthetic_implementors) {
// This `inlined_types` variable is used to avoid having the same implementation
// showing up twice. For example "String" in the "Sync" doc page.
Expand Down Expand Up @@ -536,10 +540,12 @@ function loadCss(cssFileName) {

struct_loop:
for (const struct of structs) {
const list = struct.synthetic ? synthetic_implementors : implementors;
const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;

if (struct.synthetic) {
for (const struct_type of struct.types) {
// The types list is only used for synthetic impls.
// If this changes, `main.js` and `write_shared.rs` both need changed.
if (struct[SYNTHETIC_IDX]) {
for (const struct_type of struct[TYPES_IDX]) {
if (inlined_types.has(struct_type)) {
continue struct_loop;
}
Expand All @@ -548,7 +554,7 @@ function loadCss(cssFileName) {
}

const code = document.createElement("h3");
code.innerHTML = struct.text;
code.innerHTML = struct[TEXT_IDX];
addClass(code, "code-header");
addClass(code, "in-band");

Expand Down