diff --git a/src/librustdoc/display.rs b/src/librustdoc/display.rs index 550be1ae89588..e571ffd43dff3 100644 --- a/src/librustdoc/display.rs +++ b/src/librustdoc/display.rs @@ -1,6 +1,7 @@ //! Various utilities for working with [`fmt::Display`] implementations. use std::fmt::{self, Display, Formatter, FormattingOptions}; +use std::io; pub(crate) trait Joined: IntoIterator { /// Takes an iterator over elements that implement [`Display`], and format them into `f`, separated by `sep`. @@ -128,3 +129,37 @@ impl WithOpts { }) } } + +pub(crate) struct IoWriteToFmt<'a, 'b> { + f: &'a mut Formatter<'b>, +} + +impl<'a, 'b> IoWriteToFmt<'a, 'b> { + pub(crate) fn new(f: &'a mut Formatter<'b>) -> Self { + Self { f } + } +} + +impl io::Write for IoWriteToFmt<'_, '_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let Ok(s) = str::from_utf8(buf) else { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid utf8")); + }; + self.f.write_str(s).map_err(|e| io::Error::new(io::ErrorKind::Other, e)).map(|_| buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { + self.f.write_fmt(args).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + +pub(crate) fn fmt_json( + f: &mut Formatter<'_>, + value: &T, +) -> serde_json::Result<()> { + serde_json::to_writer(IoWriteToFmt::new(f), value) +} diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 481aa392007c8..dbd4c0006c18a 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -12,7 +12,7 @@ use std::process::{self, Command, Stdio}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use std::{panic, str}; +use std::{fmt, panic, str}; pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder}; pub(crate) use markdown::test as test_markdown; @@ -34,6 +34,7 @@ use tracing::debug; use self::rust::HirCollector; use crate::config::{Options as RustdocOptions, OutputFormat}; +use crate::display::fmt_json; use crate::html::markdown::{ErrorCodes, Ignore, LangString, MdRelLine}; use crate::lint::init_lints; @@ -309,7 +310,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions .unwrap_or("warn") .to_string(); let uext = UnusedExterns { lint_level, unused_extern_names }; - let unused_extern_json = serde_json::to_string(&uext).unwrap(); + let unused_extern_json = fmt::from_fn(|f| Ok(fmt_json(f, &uext).unwrap())); eprintln!("{unused_extern_json}"); } } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4c06d0da47013..301ad6110a64d 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -22,6 +22,7 @@ use crate::clean::types::ExternalLocation; use crate::clean::utils::has_doc_flag; use crate::clean::{self, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions, ShouldMerge}; +use crate::display::fmt_json; use crate::docfs::{DocFS, PathError}; use crate::error::Error; use crate::formats::FormatRenderer; @@ -826,7 +827,10 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { }; let items = self.build_sidebar_items(module); let js_dst = self.dst.join(format!("sidebar-items{}.js", self.shared.resource_suffix)); - let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap()); + let v = format!( + "window.SIDEBAR_ITEMS = {};", + fmt::from_fn(|f| Ok(fmt_json(f, &items).unwrap())) + ); self.shared.fs.write(js_dst, v)?; } Ok(()) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 871ed53bd3380..f5f01278cfdcf 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -67,7 +67,7 @@ pub(crate) use self::context::*; pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources}; pub(crate) use self::write_shared::*; use crate::clean::{self, ItemId, RenderedLink}; -use crate::display::{Joined as _, MaybeDisplay as _}; +use crate::display::{Joined as _, MaybeDisplay as _, fmt_json}; use crate::error::Error; use crate::formats::Impl; use crate::formats::cache::Cache; @@ -1730,10 +1730,14 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { (format!("{:#}", print_type(ty, cx)), out) } -fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { +fn notable_traits_json<'a>( + f: &mut fmt::Formatter<'_>, + tys: impl Iterator, + cx: &Context<'_>, +) { let mut mp = tys.map(|ty| notable_traits_decl(ty, cx)).collect::>(); mp.sort_unstable_keys(); - serde_json::to_string(&mp).expect("serialize (string, string) -> json object cannot fail") + fmt_json(f, &mp).expect("serialize (string, string) -> json object cannot fail"); } #[derive(Clone, Copy, Debug)] diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index fa7c9e75fdac4..77d85a03a0e8f 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -269,11 +269,9 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp // Render notable-traits.js used for all methods in this module. let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); if !types_with_notable_traits.is_empty() { - write!( - buf, - r#""#, - notable_traits_json(types_with_notable_traits.iter(), cx), - )?; + write!(buf, r#"")?; types_with_notable_traits.clear(); } Ok(()) diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 66d8b667a4cad..c45226e4cd27c 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -1,7 +1,7 @@ //! Calculates information used for the --show-coverage flag. use std::collections::BTreeMap; -use std::ops; +use std::{fmt, ops}; use rustc_hir as hir; use rustc_lint::builtin::MISSING_DOCS; @@ -13,6 +13,7 @@ use tracing::debug; use crate::clean; use crate::core::DocContext; +use crate::display::fmt_json; use crate::html::markdown::{ErrorCodes, find_testable_code}; use crate::passes::Pass; use crate::passes::check_doc_test_visibility::{Tests, should_have_doc_example}; @@ -119,15 +120,17 @@ fn limit_filename_len(filename: String) -> String { } impl CoverageCalculator<'_, '_> { - fn to_json(&self) -> String { - serde_json::to_string( - &self + fn to_json(&self) -> impl fmt::Display { + fmt::from_fn(|f| { + let map = &self .items .iter() .map(|(k, v)| (k.prefer_local().to_string(), v)) - .collect::>(), - ) - .expect("failed to convert JSON data to string") + .collect::>(); + + fmt_json(f, &map).expect("failed to convert JSON data to string"); + Ok(()) + }) } fn print_results(&self) {