diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index fe2b91bf819a1..dfd7414652fa7 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -16,7 +16,7 @@ minifier = "0.3.0" pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -serde_json = { version = "1.0", features = ["preserve_order"] } +serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } smallvec = "1.8.1" tempfile = "3" diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index c4f4d4a6d364e..0ed8921b1e8d7 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -14,11 +14,7 @@ use rustc_span::edition::Edition; use rustc_span::{sym, FileName, Symbol}; use super::print_item::{full_path, item_path, print_item}; -<<<<<<< HEAD -use super::search_index::build_index; use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar}; -======= ->>>>>>> 164844f3f807 (initial implementation of mergable rustdoc cci) use super::write_shared::write_shared; use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath}; use crate::clean::types::ExternalLocation; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index d8f800c93ab0f..d72671fbdabeb 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -29,9 +29,9 @@ pub(crate) mod search_index; mod tests; mod context; +mod ordered_json; mod print_item; pub(crate) mod sidebar; -mod sorted_json; mod sorted_template; mod span_map; mod type_layout; diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/ordered_json.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json.rs rename to src/librustdoc/html/render/ordered_json.rs index e937382f5b0a6..3f76ff659d04e 100644 --- a/src/librustdoc/html/render/sorted_json.rs +++ b/src/librustdoc/html/render/ordered_json.rs @@ -1,72 +1,74 @@ +use std::borrow::Borrow; +use std::fmt; + use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::borrow::Borrow; -use std::fmt; /// Prerenedered json. /// -/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified -/// keys. -/// -/// Must use serde_json with the preserve_order feature. -/// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(from = "Value")] #[serde(into = "Value")] -pub(crate) struct SortedJson(String); +pub(crate) struct OrderedJson(String); -impl SortedJson { +impl OrderedJson { /// If you pass in an array, it will not be sorted. - pub(crate) fn serialize(item: T) -> Self { - SortedJson(serde_json::to_string(&item).unwrap()) + pub(crate) fn serialize(item: T) -> Result { + Ok(OrderedJson(serde_json::to_string(&item)?)) } /// Serializes and sorts - pub(crate) fn array, I: IntoIterator>(items: I) -> Self { + pub(crate) fn array_sorted, I: IntoIterator>( + items: I, + ) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{}]", items)) + OrderedJson(format!("[{}]", items)) } - pub(crate) fn array_unsorted, I: IntoIterator>( + pub(crate) fn array_unsorted, I: IntoIterator>( items: I, ) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); - SortedJson(format!("[{items}]")) + OrderedJson(format!("[{items}]")) } } -impl fmt::Display for SortedJson { +impl fmt::Display for OrderedJson { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + self.0.fmt(f) } } -impl From for SortedJson { +impl From for OrderedJson { fn from(value: Value) -> Self { - SortedJson(serde_json::to_string(&value).unwrap()) + let serialized = + serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); + OrderedJson(serialized) } } -impl From for Value { - fn from(json: SortedJson) -> Self { - serde_json::from_str(&json.0).unwrap() +impl From for Value { + fn from(json: OrderedJson) -> Self { + serde_json::from_str(&json.0).expect("OrderedJson should always store valid JSON") } } /// For use in JSON.parse('{...}'). /// -/// JSON.parse supposedly loads faster than raw JS source, +/// Assumes we are going to be wrapped in single quoted strings. +/// +/// JSON.parse loads faster than raw JS source, /// so this is used for large objects. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct EscapedJson(SortedJson); +pub(crate) struct EscapedJson(OrderedJson); -impl From for EscapedJson { - fn from(json: SortedJson) -> Self { +impl From for EscapedJson { + fn from(json: OrderedJson) -> Self { EscapedJson(json) } } @@ -77,7 +79,7 @@ impl fmt::Display for EscapedJson { // for JSON content. // We need to escape double quotes for the JSON let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); - write!(f, "{}", json) + json.fmt(f) } } diff --git a/src/librustdoc/html/render/sorted_json/tests.rs b/src/librustdoc/html/render/ordered_json/tests.rs similarity index 55% rename from src/librustdoc/html/render/sorted_json/tests.rs rename to src/librustdoc/html/render/ordered_json/tests.rs index 1e72c6f614c38..e0fe6446b9aff 100644 --- a/src/librustdoc/html/render/sorted_json/tests.rs +++ b/src/librustdoc/html/render/ordered_json/tests.rs @@ -1,90 +1,90 @@ -use super::super::sorted_json::*; +use super::super::ordered_json::*; -fn check(json: SortedJson, serialized: &str) { +fn check(json: OrderedJson, serialized: &str) { assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = json.to_string(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); let json = serde_json::to_string(&json).unwrap(); - let json: SortedJson = serde_json::from_str(&json).unwrap(); + let json: OrderedJson = serde_json::from_str(&json).unwrap(); assert_eq!(json.to_string(), serialized); assert_eq!(serde_json::to_string(&json).unwrap(), serialized); } -// Test this basic are needed because we are testing that our Display impl + serialize impl don't -// nest everything in extra level of string. We also are testing round trip. +// Make sure there is no extra level of string, plus number of escapes. #[test] fn escape_json_number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), "3"); } #[test] fn escape_json_single_quote() { - let json = SortedJson::serialize("he's"); + let json = OrderedJson::serialize("he's").unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\'s""#); } #[test] fn escape_json_array() { - let json = SortedJson::serialize([1, 2, 3]); + let json = OrderedJson::serialize([1, 2, 3]).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#"[1,2,3]"#); } #[test] fn escape_json_string() { - let json = SortedJson::serialize(r#"he"llo"#); + let json = OrderedJson::serialize(r#"he"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\"llo""#); } #[test] fn escape_json_string_escaped() { - let json = SortedJson::serialize(r#"he\"llo"#); + let json = OrderedJson::serialize(r#"he\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#); } #[test] fn escape_json_string_escaped_escaped() { - let json = SortedJson::serialize(r#"he\\"llo"#); + let json = OrderedJson::serialize(r#"he\\"llo"#).unwrap(); let json = EscapedJson::from(json); assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#); } +// Testing round trip + making sure there is no extra level of string #[test] fn number() { - let json = SortedJson::serialize(3); + let json = OrderedJson::serialize(3).unwrap(); let serialized = "3"; check(json, serialized); } #[test] fn boolean() { - let json = SortedJson::serialize(true); + let json = OrderedJson::serialize(true).unwrap(); let serialized = "true"; check(json, serialized); } #[test] fn string() { - let json = SortedJson::serialize("he\"llo"); + let json = OrderedJson::serialize("he\"llo").unwrap(); let serialized = r#""he\"llo""#; check(json, serialized); } #[test] fn serialize_array() { - let json = SortedJson::serialize([3, 1, 2]); + let json = OrderedJson::serialize([3, 1, 2]).unwrap(); let serialized = "[3,1,2]"; check(json, serialized); } @@ -93,18 +93,19 @@ fn serialize_array() { fn sorted_array() { let items = ["c", "a", "b"]; let serialized = r#"["a","b","c"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_sorted(items); check(json, serialized); } #[test] fn nested_array() { - let a = SortedJson::serialize(3); - let b = SortedJson::serialize(2); - let c = SortedJson::serialize(1); - let d = SortedJson::serialize([1, 3, 2]); - let json = SortedJson::array([a, b, c, d]); + let a = OrderedJson::serialize(3).unwrap(); + let b = OrderedJson::serialize(2).unwrap(); + let c = OrderedJson::serialize(1).unwrap(); + let d = OrderedJson::serialize([1, 3, 2]).unwrap(); + let json = OrderedJson::array_sorted([a, b, c, d]); let serialized = r#"[1,2,3,[1,3,2]]"#; check(json, serialized); } @@ -113,7 +114,8 @@ fn nested_array() { fn array_unsorted() { let items = ["c", "a", "b"]; let serialized = r#"["c","a","b"]"#; - let items: Vec = items.into_iter().map(SortedJson::serialize).collect(); - let json = SortedJson::array_unsorted(items); + let items: Vec = + items.into_iter().map(OrderedJson::serialize).collect::, _>>().unwrap(); + let json = OrderedJson::array_unsorted(items); check(json, serialized); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 2fa645ccf05ff..9f195da43c609 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -18,7 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; use crate::html::markdown::short_markdown_summary; -use crate::html::render::sorted_json::SortedJson; +use crate::html::render::ordered_json::OrderedJson; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; /// The serialized search description sharded version @@ -47,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re /// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept /// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features pub(crate) struct SerializedSearchIndex { - pub(crate) index: SortedJson, + pub(crate) index: OrderedJson, pub(crate) desc: Vec<(usize, String)>, } @@ -693,9 +693,9 @@ pub(crate) fn build_index<'tcx>( desc_index, empty_desc, }; - let index = SortedJson::array_unsorted([ - SortedJson::serialize(crate_name.as_str()), - SortedJson::serialize(data), + let index = OrderedJson::array_unsorted([ + OrderedJson::serialize(crate_name.as_str()).unwrap(), + OrderedJson::serialize(data).unwrap(), ]); SerializedSearchIndex { index, desc } } diff --git a/src/librustdoc/html/render/sorted_template.rs b/src/librustdoc/html/render/sorted_template.rs index 8e0a2ee0fd4d9..1dc70408f013d 100644 --- a/src/librustdoc/html/render/sorted_template.rs +++ b/src/librustdoc/html/render/sorted_template.rs @@ -1,8 +1,9 @@ use std::collections::BTreeSet; -use std::fmt; +use std::fmt::{self, Write as _}; use std::marker::PhantomData; use std::str::FromStr; +use itertools::{Itertools as _, Position}; use serde::{Deserialize, Serialize}; /// Append-only templates for sorted, deduplicated lists of items. @@ -13,7 +14,7 @@ pub(crate) struct SortedTemplate { format: PhantomData, before: String, after: String, - contents: BTreeSet, + fragments: BTreeSet, } /// Written to last line of file to specify the location of each fragment @@ -22,82 +23,88 @@ struct Offset { /// Index of the first byte in the template start: usize, /// The length of each fragment in the encoded template, including the separator - delta: Vec, + fragment_lengths: Vec, } impl SortedTemplate { /// Generate this template from arbitary text. /// Will insert wherever the substring `magic` can be found. /// Errors if it does not appear exactly once. - pub(crate) fn magic(template: &str, magic: &str) -> Result { - let mut split = template.split(magic); - let before = split.next().ok_or(Error)?; - let after = split.next().ok_or(Error)?; + pub(crate) fn from_template(template: &str, delimiter: &str) -> Result { + let mut split = template.split(delimiter); + let before = split.next().ok_or(Error("delimiter should appear at least once"))?; + let after = split.next().ok_or(Error("delimiter should appear at least once"))?; + // not `split_once` because we want to check for too many occurrences if split.next().is_some() { - return Err(Error); + return Err(Error("delimiter should appear at most once")); } - Ok(Self::before_after(before, after)) + Ok(Self::from_before_after(before, after)) } - /// Template will insert contents between `before` and `after` - pub(crate) fn before_after(before: S, after: T) -> Self { + /// Template will insert fragments between `before` and `after` + pub(crate) fn from_before_after(before: S, after: T) -> Self { let before = before.to_string(); let after = after.to_string(); - SortedTemplate { format: PhantomData, before, after, contents: Default::default() } + SortedTemplate { format: PhantomData, before, after, fragments: Default::default() } } } -impl SortedTemplate { +impl SortedTemplate { /// Adds this text to the template pub(crate) fn append(&mut self, insert: String) { - self.contents.insert(insert); + self.fragments.insert(insert); } } impl fmt::Display for SortedTemplate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut delta = Vec::default(); + fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fragment_lengths = Vec::default(); write!(f, "{}", self.before)?; - let contents: Vec<_> = self.contents.iter().collect(); - let mut sep = ""; - for content in contents { - delta.push(sep.len() + content.len()); - write!(f, "{}{}", sep, content)?; - sep = F::SEPARATOR; + for (p, fragment) in self.fragments.iter().with_position() { + let mut f = DeltaWriter { inner: &mut f, delta: 0 }; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + write!(f, "{}{}", sep, fragment)?; + fragment_lengths.push(f.delta); } - let offset = Offset { start: self.before.len(), delta }; + let offset = Offset { start: self.before.len(), fragment_lengths }; let offset = serde_json::to_string(&offset).unwrap(); - write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?; - Ok(()) + write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END) } } -fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> { - s.is_char_boundary(index).then(|| s.split_at(index)) -} - impl FromStr for SortedTemplate { type Err = Error; fn from_str(s: &str) -> Result { - let (s, offset) = s.rsplit_once("\n").ok_or(Error)?; - let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?; - let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?; - let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?; - let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?; - let mut contents = BTreeSet::default(); - let mut sep = ""; - for &index in offset.delta.iter() { - let (content, rest) = checked_split_at(s, index).ok_or(Error)?; + let (s, offset) = s + .rsplit_once("\n") + .ok_or(Error("invalid format: should have a newline on the last line"))?; + let offset = offset + .strip_prefix(F::COMMENT_START) + .ok_or(Error("last line expected to start with a comment"))?; + let offset = offset + .strip_suffix(F::COMMENT_END) + .ok_or(Error("last line expected to end with a comment"))?; + let offset: Offset = serde_json::from_str(&offset).map_err(|_| { + Error("could not find insertion location descriptor object on last line") + })?; + let (before, mut s) = + s.split_at_checked(offset.start).ok_or(Error("invalid start: out of bounds"))?; + let mut fragments = BTreeSet::default(); + for (p, &index) in offset.fragment_lengths.iter().with_position() { + let (fragment, rest) = + s.split_at_checked(index).ok_or(Error("invalid fragment length: out of bounds"))?; s = rest; - let content = content.strip_prefix(sep).ok_or(Error)?; - contents.insert(content.to_string()); - sep = F::SEPARATOR; + let sep = if matches!(p, Position::First | Position::Only) { "" } else { F::SEPARATOR }; + let fragment = fragment + .strip_prefix(sep) + .ok_or(Error("invalid fragment length: expected to find separator here"))?; + fragments.insert(fragment.to_string()); } Ok(SortedTemplate { format: PhantomData, before: before.to_string(), after: s.to_string(), - contents, + fragments, }) } } @@ -127,11 +134,24 @@ impl FileFormat for Js { } #[derive(Debug, Clone)] -pub(crate) struct Error; +pub(crate) struct Error(&'static str); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid template") + write!(f, "invalid template: {}", self.0) + } +} + +struct DeltaWriter { + inner: W, + delta: usize, +} + +impl fmt::Write for DeltaWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.inner.write_str(s)?; + self.delta += s.len(); + Ok(()) } } diff --git a/src/librustdoc/html/render/sorted_template/tests.rs b/src/librustdoc/html/render/sorted_template/tests.rs index 04553f65a2154..db057463005c9 100644 --- a/src/librustdoc/html/render/sorted_template/tests.rs +++ b/src/librustdoc/html/render/sorted_template/tests.rs @@ -1,6 +1,7 @@ -use super::super::sorted_template::*; use std::str::FromStr; +use super::super::sorted_template::*; + fn is_comment_js(s: &str) -> bool { s.starts_with("//") } @@ -13,7 +14,7 @@ fn is_comment_html(s: &str) -> bool { #[test] fn html_from_empty() { let inserts = ["

hello

", "

kind

", "

hello

", "

world

"]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -29,7 +30,7 @@ fn html_page() { let inserts = ["

hello

", "

kind

", "

world

"]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); for insert in inserts { template.append(insert.to_string()); } @@ -43,7 +44,7 @@ fn html_page() { #[test] fn js_from_empty() { let inserts = ["1", "2", "2", "2", "3", "1"]; - let mut template = SortedTemplate::::before_after("", ""); + let mut template = SortedTemplate::::from_before_after("", ""); for insert in inserts { template.append(insert.to_string()); } @@ -56,7 +57,7 @@ fn js_from_empty() { #[test] fn js_empty_array() { - let template = SortedTemplate::::before_after("[", "]"); + let template = SortedTemplate::::from_before_after("[", "]"); let template = format!("{template}"); let (template, end) = template.rsplit_once("\n").unwrap(); assert_eq!(template, format!("[]")); @@ -67,7 +68,7 @@ fn js_empty_array() { #[test] fn js_number_array() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -81,7 +82,7 @@ fn js_number_array() { #[test] fn magic_js_number_array() { let inserts = ["1", "1"]; - let mut template = SortedTemplate::::magic("[#]", "#").unwrap(); + let mut template = SortedTemplate::::from_template("[#]", "#").unwrap(); for insert in inserts { template.append(insert.to_string()); } @@ -95,7 +96,7 @@ fn magic_js_number_array() { #[test] fn round_trip_js() { let inserts = ["1", "2", "3"]; - let mut template = SortedTemplate::::before_after("[", "]"); + let mut template = SortedTemplate::::from_before_after("[", "]"); for insert in inserts { template.append(insert.to_string()); } @@ -114,7 +115,7 @@ fn round_trip_html() { let inserts = ["

hello

", "

kind

", "

world

", "

kind

"]; let before = ""; let after = ""; - let mut template = SortedTemplate::::before_after(before, after); + let mut template = SortedTemplate::::from_before_after(before, after); template.append(inserts[0].to_string()); template.append(inserts[1].to_string()); let template = format!("{template}"); @@ -129,7 +130,7 @@ fn round_trip_html() { #[test] fn blank_js() { let inserts = ["1", "2", "3"]; - let template = SortedTemplate::::before_after("", ""); + let template = SortedTemplate::::from_before_after("", ""); let template = format!("{template}"); let (t, _) = template.rsplit_once("\n").unwrap(); assert_eq!(t, ""); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 7554d945b200b..8d37cbacb58c8 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -1,4 +1,4 @@ -//! Rustdoc writes out two kinds of shared files: +//! Rustdoc writes aut two kinds of shared files: //! - Static files, which are embedded in the rustdoc binary and are written with a //! filename that includes a hash of their contents. These will always have a new //! URL if the contents change, so they are safe to cache with the @@ -13,18 +13,16 @@ //! --resource-suffix flag and are emitted when --emit-type is empty (default) //! or contains "invocation-specific". -use std::any::Any; use std::cell::RefCell; use std::ffi::OsString; use std::fs::File; -use std::io::BufWriter; -use std::io::Write as _; +use std::io::{self, BufWriter, Write as _}; use std::iter::once; use std::marker::PhantomData; use std::path::{Component, Path, PathBuf}; use std::rc::{Rc, Weak}; use std::str::FromStr; -use std::{fmt, fs, io}; +use std::{fmt, fs}; use indexmap::IndexMap; use itertools::Itertools; @@ -35,8 +33,9 @@ use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::Symbol; +use serde::de::DeserializeOwned; use serde::ser::SerializeSeq; -use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; @@ -48,9 +47,8 @@ use crate::formats::item_type::ItemType; use crate::formats::Impl; use crate::html::format::Buffer; use crate::html::layout; -use crate::html::render::search_index::build_index; -use crate::html::render::search_index::SerializedSearchIndex; -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; +use crate::html::render::search_index::{build_index, SerializedSearchIndex}; use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate}; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::static_files::{self, suffix_path}; @@ -76,7 +74,7 @@ pub(crate) fn write_shared( let crate_name = krate.name(cx.tcx()); let crate_name = crate_name.as_str(); // rand - let crate_name_json = SortedJson::serialize(crate_name); // "rand" + let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand" let external_crates = hack_get_external_crate_names(&cx.dst)?; let info = CrateInfo { src_files_js: SourcesPart::get(cx, &crate_name_json)?, @@ -171,9 +169,9 @@ fn write_search_desc( search_desc: &[(usize, String)], ) -> Result<(), Error> { let crate_name = krate.name(cx.tcx()).to_string(); - let encoded_crate_name = SortedJson::serialize(&crate_name); + let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap(); let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]); - if Path::new(&path).exists() { + if path.exists() { try_err!(fs::remove_dir_all(&path), &path); } for (i, (_, part)) in search_desc.iter().enumerate() { @@ -182,7 +180,7 @@ fn write_search_desc( &cx.shared.resource_suffix, ); let path = path.join(filename); - let part = SortedJson::serialize(&part); + let part = OrderedJson::serialize(&part).unwrap(); let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})"); create_parents(&path)?; try_err!(fs::write(&path, part), &path); @@ -201,20 +199,6 @@ struct CrateInfo { type_impl: PartsAndLocations, } -impl CrateInfo { - /// Gets a reference to the cross-crate information parts for `T` - fn get(&self) -> &PartsAndLocations { - (&self.src_files_js as &dyn Any) - .downcast_ref() - .or_else(|| (&self.search_index_js as &dyn Any).downcast_ref()) - .or_else(|| (&self.all_crates as &dyn Any).downcast_ref()) - .or_else(|| (&self.crates_index as &dyn Any).downcast_ref()) - .or_else(|| (&self.trait_impl as &dyn Any).downcast_ref()) - .or_else(|| (&self.type_impl as &dyn Any).downcast_ref()) - .expect("this should be an exhaustive list of `CciPart`s") - } -} - /// Paths (relative to the doc root) and their pre-merge contents #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(transparent)] @@ -263,6 +247,7 @@ impl fmt::Display for Part { trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static { /// Identifies the file format of the cross-crate information type FileFormat: sorted_template::FileFormat; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations; } #[derive(Serialize, Deserialize, Clone, Default, Debug)] @@ -270,11 +255,14 @@ struct SearchIndex; type SearchIndexPart = Part; impl CciPart for SearchIndexPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.search_index_js + } } impl SearchIndexPart { fn blank_template() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var searchIndex = new Map(JSON.parse('[", r"]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; @@ -283,7 +271,7 @@ else if (window.initSearch) window.initSearch(searchIndex);", } fn get( - search_index: SortedJson, + search_index: OrderedJson, resource_suffix: &str, ) -> Result, Error> { let path = suffix_path("search-index.js", resource_suffix); @@ -294,17 +282,20 @@ else if (window.initSearch) window.initSearch(searchIndex);", #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct AllCrates; -type AllCratesPart = Part; +type AllCratesPart = Part; impl CciPart for AllCratesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.all_crates + } } impl AllCratesPart { fn blank_template() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after("window.ALL_CRATES = [", "];") + SortedTemplate::from_before_after("window.ALL_CRATES = [", "];") } - fn get(crate_name_json: SortedJson) -> Result, Error> { + fn get(crate_name_json: OrderedJson) -> Result, Error> { // external hack_get_external_crate_names not needed here, because // there's no way that we write the search index but not crates.js let path = PathBuf::from("crates.js"); @@ -339,6 +330,9 @@ struct CratesIndex; type CratesIndexPart = Part; impl CciPart for CratesIndexPart { type FileFormat = sorted_template::Html; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.crates_index + } } impl CratesIndexPart { @@ -354,10 +348,11 @@ impl CratesIndexPart { }; let layout = &cx.shared.layout; let style_files = &cx.shared.style_files; - const MAGIC: &str = "\u{FFFC}"; // users are being naughty if they have this - let content = format!("

List of all crates

    {MAGIC}
"); + const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this + let content = + format!("

List of all crates

    {DELIMITER}
"); let template = layout::render(layout, &page, "", content, &style_files); - match SortedTemplate::magic(&template, MAGIC) { + match SortedTemplate::from_template(&template, DELIMITER) { Ok(template) => template, Err(e) => panic!( "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}" @@ -385,6 +380,9 @@ struct Sources; type SourcesPart = Part; impl CciPart for SourcesPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.src_files_js + } } impl SourcesPart { @@ -392,14 +390,14 @@ impl SourcesPart { // This needs to be `var`, not `const`. // This variable needs declared in the current global scope so that if // src-script.js loads first, it can pick it up. - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"var srcIndex = new Map(JSON.parse('[", r"]')); createSrcSidebar();", ) } - fn get(cx: &Context<'_>, crate_name: &SortedJson) -> Result, Error> { + fn get(cx: &Context<'_>, crate_name: &OrderedJson) -> Result, Error> { let hierarchy = Rc::new(Hierarchy::default()); cx.shared .local_sources @@ -408,7 +406,7 @@ createSrcSidebar();", .for_each(|source| hierarchy.add_path(source)); let path = suffix_path("src-files.js", &cx.shared.resource_suffix); let hierarchy = hierarchy.to_json_string(); - let part = SortedJson::array_unsorted([crate_name, &hierarchy]); + let part = OrderedJson::array_unsorted([crate_name, &hierarchy]); let part = EscapedJson::from(part); Ok(PartsAndLocations::with(path, part)) } @@ -428,21 +426,23 @@ impl Hierarchy { Self { elem, parent: Rc::downgrade(parent), ..Self::default() } } - fn to_json_string(&self) -> SortedJson { + fn to_json_string(&self) -> OrderedJson { let subs = self.children.borrow(); let files = self.elems.borrow(); - let name = SortedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")); + let name = OrderedJson::serialize(self.elem.to_str().expect("invalid osstring conversion")) + .unwrap(); let mut out = Vec::from([name]); if !subs.is_empty() || !files.is_empty() { let subs = subs.iter().map(|(_, s)| s.to_json_string()); - out.push(SortedJson::array(subs)); + out.push(OrderedJson::array_sorted(subs)); } if !files.is_empty() { - let files = - files.iter().map(|s| SortedJson::serialize(s.to_str().expect("invalid osstring"))); - out.push(SortedJson::array(files)); + let files = files + .iter() + .map(|s| OrderedJson::serialize(s.to_str().expect("invalid osstring")).unwrap()); + out.push(OrderedJson::array_sorted(files)); } - SortedJson::array_unsorted(out) + OrderedJson::array_unsorted(out) } fn add_path(self: &Rc, path: &Path) { @@ -481,14 +481,17 @@ impl Hierarchy { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TypeAlias; -type TypeAliasPart = Part; +type TypeAliasPart = Part; impl CciPart for TypeAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.type_impl + } } impl TypeAliasPart { fn blank_template() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var type_impls = Object.fromEntries([", r"]); @@ -504,7 +507,7 @@ impl TypeAliasPart { fn get( cx: &mut Context<'_>, krate: &Crate, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &Rc::clone(&cx.shared).cache; let mut path_parts = PartsAndLocations::default(); @@ -594,9 +597,10 @@ impl TypeAliasPart { aliased_type.target_fqp[aliased_type.target_fqp.len() - 1] )); - let part = - SortedJson::array(impls.iter().map(SortedJson::serialize).collect::>()); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + let part = OrderedJson::array_sorted( + impls.iter().map(OrderedJson::serialize).collect::, _>>().unwrap(), + ); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -604,14 +608,17 @@ impl TypeAliasPart { #[derive(Serialize, Deserialize, Clone, Default, Debug)] struct TraitAlias; -type TraitAliasPart = Part; +type TraitAliasPart = Part; impl CciPart for TraitAliasPart { type FileFormat = sorted_template::Js; + fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations { + &crate_info.trait_impl + } } impl TraitAliasPart { fn blank_template() -> SortedTemplate<::FileFormat> { - SortedTemplate::before_after( + SortedTemplate::from_before_after( r"(function() { var implementors = Object.fromEntries([", r"]); @@ -626,7 +633,7 @@ impl TraitAliasPart { fn get( cx: &mut Context<'_>, - crate_name_json: &SortedJson, + crate_name_json: &OrderedJson, ) -> Result, Error> { let cache = &cx.shared.cache; let mut path_parts = PartsAndLocations::default(); @@ -688,10 +695,14 @@ impl TraitAliasPart { } path.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); - let part = SortedJson::array( - implementors.iter().map(SortedJson::serialize).collect::>(), + let part = OrderedJson::array_sorted( + implementors + .iter() + .map(OrderedJson::serialize) + .collect::, _>>() + .unwrap(), ); - path_parts.push(path, SortedJson::array_unsorted([crate_name_json, &part])); + path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part])); } Ok(path_parts) } @@ -864,13 +875,15 @@ fn get_path_parts( crates_info: &[CrateInfo], ) -> FxHashMap> { let mut templates: FxHashMap> = FxHashMap::default(); - crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each( - |(path, part)| { + crates_info + .iter() + .map(|crate_info| T::from_crate_info(crate_info).parts.iter()) + .flatten() + .for_each(|(path, part)| { let path = dst.join(&path); let part = part.to_string(); templates.entry(path).or_default().push(part); - }, - ); + }); templates } diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs index 48af0e8618c71..8dc0f6d3fbb3e 100644 --- a/src/librustdoc/html/render/write_shared/tests.rs +++ b/src/librustdoc/html/render/write_shared/tests.rs @@ -1,4 +1,4 @@ -use crate::html::render::sorted_json::{EscapedJson, SortedJson}; +use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::sorted_template::{Html, SortedTemplate}; use crate::html::render::write_shared::*; @@ -26,13 +26,13 @@ fn sources_template() { r"var srcIndex = new Map(JSON.parse('[]')); createSrcSidebar();" ); - template.append(EscapedJson::from(SortedJson::serialize("u")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("u").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u"]')); createSrcSidebar();"# ); - template.append(EscapedJson::from(SortedJson::serialize("v")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("v").unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r#"var srcIndex = new Map(JSON.parse('["u","v"]')); @@ -42,7 +42,8 @@ createSrcSidebar();"# #[test] fn sources_parts() { - let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap(); + let parts = + SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap(); assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js")); assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#); } @@ -51,15 +52,15 @@ fn sources_parts() { fn all_crates_template() { let mut template = AllCratesPart::blank_template(); assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];"); - template.append(EscapedJson::from(SortedJson::serialize("b")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("b").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#); - template.append(EscapedJson::from(SortedJson::serialize("a")).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize("a").unwrap()).to_string()); assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#); } #[test] fn all_crates_parts() { - let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap(); + let parts = AllCratesPart::get(OrderedJson::serialize("crate").unwrap()).unwrap(); assert_eq!(&parts.parts[0].0, Path::new("crates.js")); assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#); } @@ -73,14 +74,14 @@ fn search_index_template() { if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2]]')); if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; else if (window.initSearch) window.initSearch(searchIndex);" ); - template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string()); + template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string()); assert_eq!( but_last_line(&template.to_string()), r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]')); @@ -119,7 +120,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -131,7 +132,7 @@ fn trait_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -159,7 +160,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["a"]).to_string()); + template.append(OrderedJson::serialize(["a"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -171,7 +172,7 @@ fn type_alias_template() { } })()"#, ); - template.append(SortedJson::serialize(["b"]).to_string()); + template.append(OrderedJson::serialize(["b"]).unwrap().to_string()); assert_eq!( but_last_line(&template.to_string()), r#"(function() { @@ -189,7 +190,7 @@ fn type_alias_template() { fn read_template_test() { let path = tempfile::TempDir::new().unwrap(); let path = path.path().join("file.html"); - let make_blank = || SortedTemplate::::before_after("
", "
"); + let make_blank = || SortedTemplate::::from_before_after("
", "
"); let template = read_template_or_blank(make_blank, &path).unwrap(); assert_eq!(but_last_line(&template.to_string()), "
"); diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index a691f61acb69e..1f15605d8beed 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1,11 +1,5 @@ // ignore-tidy-filelength -use crate::read2::{read2_abbreviated, Truncated}; -use crate::util::{add_dylib_path, copy_dir_all, dylib_env_var, logv, static_regex, PathBufExt}; -use colored::Colorize; -use miropt_test_tools::{files_for_miropt_test, MiroptTest, MiroptTestFile}; -use regex::{Captures, Regex}; -use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ffi::{OsStr, OsString}; use std::fs::{self, create_dir_all, File, OpenOptions};