Skip to content

Commit

Permalink
Auto merge of #100150 - notriddle:notriddle/implementors-js, r=Guilla…
Browse files Browse the repository at this point in the history
…umeGomez

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

The exact amount that this reduces the size of an implementors file depends on whether most of the impls are synthetic or not. For `Send`, it reduces the file from 128K to 112K, while for `Clone` it went from 64K to 44K.
  • Loading branch information
bors committed Aug 9, 2022
2 parents 63e4312 + fc31fce commit 34a6cae
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 22 deletions.
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

0 comments on commit 34a6cae

Please sign in to comment.