diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index b81fc5a0a718e..ea2aa963eddf3 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -35,10 +35,9 @@ impl ExternalHtml { ) -> Option { let codes = ErrorCodes::from(nightly_build); let ih = load_external_files(in_header, dcx)?; - let bc = load_external_files(before_content, dcx)?; - let m_bc = load_external_files(md_before_content, dcx)?; - let bc = format!( - "{bc}{}", + let bc = { + let mut bc = load_external_files(before_content, dcx)?; + let m_bc = load_external_files(md_before_content, dcx)?; Markdown { content: &m_bc, links: &[], @@ -48,12 +47,13 @@ impl ExternalHtml { playground, heading_offset: HeadingOffset::H2, } - .into_string() - ); - let ac = load_external_files(after_content, dcx)?; - let m_ac = load_external_files(md_after_content, dcx)?; - let ac = format!( - "{ac}{}", + .write_into(&mut bc) + .unwrap(); + bc + }; + let ac = { + let mut ac = load_external_files(after_content, dcx)?; + let m_ac = load_external_files(md_after_content, dcx)?; Markdown { content: &m_ac, links: &[], @@ -63,8 +63,10 @@ impl ExternalHtml { playground, heading_offset: HeadingOffset::H2, } - .into_string() - ); + .write_into(&mut ac) + .unwrap(); + ac + }; Some(ExternalHtml { in_header: ih, before_content: bc, after_content: ac }) } } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index f626e07b000a6..d947836a4615d 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -21,13 +21,14 @@ //! playground: &None, //! heading_offset: HeadingOffset::H2, //! }; -//! let html = md.into_string(); +//! let mut html = String::new(); +//! md.write_into(&mut html).unwrap(); //! // ... something using html //! ``` use std::borrow::Cow; use std::collections::VecDeque; -use std::fmt::Write; +use std::fmt::{self, Write}; use std::iter::Peekable; use std::ops::{ControlFlow, Range}; use std::path::PathBuf; @@ -1327,17 +1328,32 @@ impl LangString { } } +trait WriteSpec { + fn reserve_spec(&mut self, additional: usize); +} + +impl WriteSpec for W { + #[inline] + default fn reserve_spec(&mut self, _additional: usize) {} +} + +impl<'a> WriteSpec for &'a mut String { + #[inline] + fn reserve_spec(&mut self, additional: usize) { + self.reserve(additional); + } +} + impl<'a> Markdown<'a> { - pub fn into_string(self) -> String { + pub fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { // This is actually common enough to special-case if self.content.is_empty() { - return String::new(); + return Ok(()); } - let mut s = String::with_capacity(self.content.len() * 3 / 2); - html::push_html(&mut s, self.into_iter()); + f.reserve_spec(self.content.len() * 3 / 2); - s + html::write_html_fmt(f, self.into_iter()) } fn into_iter(self) -> CodeBlocks<'a, 'a, impl Iterator>> { @@ -1453,19 +1469,20 @@ impl MarkdownWithToc<'_> { (toc.into_toc(), s) } - pub(crate) fn into_string(self) -> String { + + pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { let (toc, s) = self.into_parts(); - format!("{s}", toc = toc.print()) + write!(f, "{s}", toc = toc.print()) } } impl MarkdownItemInfo<'_> { - pub(crate) fn into_string(self) -> String { + pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { let MarkdownItemInfo(md, ids) = self; // This is actually common enough to special-case if md.is_empty() { - return String::new(); + return Ok(()); } let p = Parser::new_ext(md, main_body_opts()).into_offset_iter(); @@ -1475,7 +1492,7 @@ impl MarkdownItemInfo<'_> { _ => event, }); - let mut s = String::with_capacity(md.len() * 3 / 2); + f.reserve_spec(md.len() * 3 / 2); ids.handle_footnotes(|ids, existing_footnotes| { let p = HeadingLinks::new(p, None, ids, HeadingOffset::H1); @@ -1484,10 +1501,8 @@ impl MarkdownItemInfo<'_> { let p = p.filter(|event| { !matches!(event, Event::Start(Tag::Paragraph) | Event::End(TagEnd::Paragraph)) }); - html::push_html(&mut s, p); - }); - - s + html::write_html_fmt(&mut f, p) + }) } } diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 784d0c5d21e78..61fd428746332 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -297,7 +297,8 @@ fn test_lang_string_tokenizer() { fn test_header() { fn t(input: &str, expect: &str) { let mut map = IdMap::new(); - let output = Markdown { + let mut output = String::new(); + Markdown { content: input, links: &[], ids: &mut map, @@ -306,7 +307,8 @@ fn test_header() { playground: &None, heading_offset: HeadingOffset::H2, } - .into_string(); + .write_into(&mut output) + .unwrap(); assert_eq!(output, expect, "original: {}", input); } @@ -348,7 +350,8 @@ fn test_header() { fn test_header_ids_multiple_blocks() { let mut map = IdMap::new(); fn t(map: &mut IdMap, input: &str, expect: &str) { - let output = Markdown { + let mut output = String::new(); + Markdown { content: input, links: &[], ids: map, @@ -357,7 +360,8 @@ fn test_header_ids_multiple_blocks() { playground: &None, heading_offset: HeadingOffset::H2, } - .into_string(); + .write_into(&mut output) + .unwrap(); assert_eq!(output, expect, "original: {}", input); } @@ -466,7 +470,8 @@ fn test_plain_text_summary() { fn test_markdown_html_escape() { fn t(input: &str, expect: &str) { let mut idmap = IdMap::new(); - let output = MarkdownItemInfo(input, &mut idmap).into_string(); + let mut output = String::new(); + MarkdownItemInfo(input, &mut idmap).write_into(&mut output).unwrap(); assert_eq!(output, expect, "original: {}", input); } @@ -496,7 +501,8 @@ fn test_find_testable_code_line() { fn test_ascii_with_prepending_hashtag() { fn t(input: &str, expect: &str) { let mut map = IdMap::new(); - let output = Markdown { + let mut output = String::new(); + Markdown { content: input, links: &[], ids: &mut map, @@ -505,7 +511,8 @@ fn test_ascii_with_prepending_hashtag() { playground: &None, heading_offset: HeadingOffset::H2, } - .into_string(); + .write_into(&mut output) + .unwrap(); assert_eq!(output, expect, "original: {}", input); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index ed58bae70bd46..ba526b5d7a4d1 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -508,22 +508,21 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { the Rustdoc book]({DOC_RUST_LANG_ORG_VERSION}/rustdoc/scraped-examples.html)." )); - let mut ids = IdMap::default(); format!( "
\

About scraped examples

\
\
{}
", - Markdown { + fmt::from_fn(|f| Markdown { content: &content, links: &[], - ids: &mut ids, + ids: &mut IdMap::default(), error_codes: shared.codes, edition: shared.edition(), playground: &shared.playground, heading_offset: HeadingOffset::H1, } - .into_string() + .write_into(f)) ) } @@ -555,20 +554,18 @@ fn render_markdown( heading_offset: HeadingOffset, ) -> impl fmt::Display { fmt::from_fn(move |f| { - write!( - f, - "
{}
", - Markdown { - content: md_text, - links: &links, - ids: &mut cx.id_map.borrow_mut(), - error_codes: cx.shared.codes, - edition: cx.shared.edition(), - playground: &cx.shared.playground, - heading_offset, - } - .into_string() - ) + f.write_str("
")?; + Markdown { + content: md_text, + links: &links, + ids: &mut cx.id_map.borrow_mut(), + error_codes: cx.shared.codes, + edition: cx.shared.edition(), + playground: &cx.shared.playground, + heading_offset, + } + .write_into(&mut *f)?; + f.write_str("
") }) } @@ -752,7 +749,7 @@ fn short_item_info( let mut id_map = cx.id_map.borrow_mut(); let html = MarkdownItemInfo(note, &mut id_map); message.push_str(": "); - message.push_str(&html.into_string()); + html.write_into(&mut message).unwrap(); } extra_info.push(ShortItemInfo::Deprecation { message }); } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 025c135aff2a6..22d37630dac8e 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -3,8 +3,8 @@ html_playground_url = "https://play.rust-lang.org/" )] #![feature(rustc_private)] -#![feature(ascii_char)] #![feature(ascii_char_variants)] +#![feature(ascii_char)] #![feature(assert_matches)] #![feature(box_patterns)] #![feature(debug_closure_helpers)] @@ -13,6 +13,7 @@ #![feature(if_let_guard)] #![feature(impl_trait_in_assoc_type)] #![feature(iter_intersperse)] +#![feature(min_specialization)] #![feature(never_type)] #![feature(round_char_boundary)] #![feature(test)] diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 76eac084907d7..4ca2c104888bb 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -8,7 +8,7 @@ //! //! [docs]: https://doc.rust-lang.org/stable/rustdoc/#using-standalone-markdown-files -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; use std::fs::{File, create_dir_all, read_to_string}; use std::io::prelude::*; use std::path::Path; @@ -77,32 +77,33 @@ pub(crate) fn render_and_write>( } let title = metadata[0]; - let mut ids = IdMap::new(); let error_codes = ErrorCodes::from(options.unstable_features.is_nightly_build()); - let text = if !options.markdown_no_toc { - MarkdownWithToc { - content: text, - links: &[], - ids: &mut ids, - error_codes, - edition, - playground: &playground, - } - .into_string() - } else { - Markdown { - content: text, - links: &[], - ids: &mut ids, - error_codes, - edition, - playground: &playground, - heading_offset: HeadingOffset::H1, + let text = fmt::from_fn(|f| { + if !options.markdown_no_toc { + MarkdownWithToc { + content: text, + links: &[], + ids: &mut IdMap::new(), + error_codes, + edition, + playground: &playground, + } + .write_into(f) + } else { + Markdown { + content: text, + links: &[], + ids: &mut IdMap::new(), + error_codes, + edition, + playground: &playground, + heading_offset: HeadingOffset::H1, + } + .write_into(f) } - .into_string() - }; + }); - let err = write!( + let res = write!( &mut out, r#" @@ -130,15 +131,10 @@ pub(crate) fn render_and_write>( "#, title = Escape(title), - css = css, in_header = options.external_html.in_header, before_content = options.external_html.before_content, - text = text, after_content = options.external_html.after_content, ); - match err { - Err(e) => Err(format!("cannot write to `{output}`: {e}", output = output.display())), - Ok(_) => Ok(()), - } + res.map_err(|e| format!("cannot write to `{output}`: {e}", output = output.display())) }