diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index b6a73602a322f..0217b3818d9d3 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -7,7 +7,6 @@ //! some of them support an alternate format that emits text, but that should //! not be used external to this module. -use std::borrow::Cow; use std::cmp::Ordering; use std::fmt::{self, Display, Write}; use std::iter::{self, once}; @@ -37,115 +36,6 @@ use crate::html::render::Context; use crate::joined::Joined as _; use crate::passes::collect_intra_doc_links::UrlFragment; -pub(crate) trait Print { - fn print(self, buffer: &mut Buffer); -} - -impl Print for F -where - F: FnOnce(&mut Buffer), -{ - fn print(self, buffer: &mut Buffer) { - (self)(buffer) - } -} - -impl Print for String { - fn print(self, buffer: &mut Buffer) { - buffer.write_str(&self); - } -} - -impl Print for &'_ str { - fn print(self, buffer: &mut Buffer) { - buffer.write_str(self); - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Buffer { - for_html: bool, - buffer: String, -} - -impl core::fmt::Write for Buffer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buffer.write_str(s) - } - - #[inline] - fn write_char(&mut self, c: char) -> fmt::Result { - self.buffer.write_char(c) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { - self.buffer.write_fmt(args) - } -} - -impl Buffer { - pub(crate) fn empty_from(v: &Buffer) -> Buffer { - Buffer { for_html: v.for_html, buffer: String::new() } - } - - pub(crate) fn html() -> Buffer { - Buffer { for_html: true, buffer: String::new() } - } - - pub(crate) fn new() -> Buffer { - Buffer { for_html: false, buffer: String::new() } - } - - pub(crate) fn is_empty(&self) -> bool { - self.buffer.is_empty() - } - - pub(crate) fn into_inner(self) -> String { - self.buffer - } - - pub(crate) fn push(&mut self, c: char) { - self.buffer.push(c); - } - - pub(crate) fn push_str(&mut self, s: &str) { - self.buffer.push_str(s); - } - - pub(crate) fn push_buffer(&mut self, other: Buffer) { - self.buffer.push_str(&other.buffer); - } - - // Intended for consumption by write! and writeln! (std::fmt) but without - // the fmt::Result return type imposed by fmt::Write (and avoiding the trait - // import). - pub(crate) fn write_str(&mut self, s: &str) { - self.buffer.push_str(s); - } - - // Intended for consumption by write! and writeln! (std::fmt) but without - // the fmt::Result return type imposed by fmt::Write (and avoiding the trait - // import). - pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) { - self.buffer.write_fmt(v).unwrap(); - } - - pub(crate) fn to_display(mut self, t: T) -> String { - t.print(&mut self); - self.into_inner() - } - - pub(crate) fn reserve(&mut self, additional: usize) { - self.buffer.reserve(additional) - } - - pub(crate) fn len(&self) -> usize { - self.buffer.len() - } -} - pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( bounds: &'a [clean::GenericBound], cx: &'a Context<'tcx>, @@ -772,27 +662,29 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option, cx: &Cont else { return String::new(); }; - let mut buf = Buffer::new(); let fqp = if *shortty == ItemType::Primitive { // primitives are documented in a crate, but not actually part of it &fqp[fqp.len() - 1..] } else { fqp }; - if let &Some(UrlFragment::Item(id)) = fragment { - write!(buf, "{} ", cx.tcx().def_descr(id)); - for component in fqp { - write!(buf, "{component}::"); - } - write!(buf, "{}", cx.tcx().item_name(id)); - } else if !fqp.is_empty() { - let mut fqp_it = fqp.iter(); - write!(buf, "{shortty} {}", fqp_it.next().unwrap()); - for component in fqp_it { - write!(buf, "::{component}"); + fmt::from_fn(|f| { + if let &Some(UrlFragment::Item(id)) = fragment { + write!(f, "{} ", cx.tcx().def_descr(id))?; + for component in fqp { + write!(f, "{component}::")?; + } + write!(f, "{}", cx.tcx().item_name(id))?; + } else if !fqp.is_empty() { + let mut fqp_it = fqp.iter(); + write!(f, "{shortty} {}", fqp_it.next().unwrap())?; + for component in fqp_it { + write!(f, "::{component}")?; + } } - } - buf.into_inner() + Ok(()) + }) + .to_string() } /// Used to render a [`clean::Path`]. @@ -954,7 +846,7 @@ pub(crate) fn anchor<'a: 'cx, 'cx>( text = EscapeBodyText(text.as_str()), ) } else { - f.write_str(text.as_str()) + write!(f, "{text}") } }) } @@ -1533,53 +1425,51 @@ impl clean::FnDecl { } pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( - item: &clean::Item, + item: &'a clean::Item, cx: &'a Context<'tcx>, ) -> impl Display + 'a + Captures<'tcx> { - use std::fmt::Write as _; - let vis: Cow<'static, str> = match item.visibility(cx.tcx()) { - None => "".into(), - Some(ty::Visibility::Public) => "pub ".into(), - Some(ty::Visibility::Restricted(vis_did)) => { - // FIXME(camelid): This may not work correctly if `item_did` is a module. - // However, rustdoc currently never displays a module's - // visibility, so it shouldn't matter. - let parent_module = find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id()); - - if vis_did.is_crate_root() { - "pub(crate) ".into() - } else if parent_module == Some(vis_did) { - // `pub(in foo)` where `foo` is the parent module - // is the same as no visibility modifier - "".into() - } else if parent_module.and_then(|parent| find_nearest_parent_module(cx.tcx(), parent)) - == Some(vis_did) - { - "pub(super) ".into() - } else { - let path = cx.tcx().def_path(vis_did); - debug!("path={path:?}"); - // modified from `resolved_path()` to work with `DefPathData` - let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); - let anchor = anchor(vis_did, last_name, cx); - - let mut s = "pub(in ".to_owned(); - for seg in &path.data[..path.data.len() - 1] { - let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap()); - } - let _ = write!(s, "{anchor}) "); - s.into() - } - } - }; - let is_doc_hidden = item.is_doc_hidden(); fmt::from_fn(move |f| { if is_doc_hidden { f.write_str("#[doc(hidden)] ")?; } - f.write_str(&vis) + match item.visibility(cx.tcx()) { + None => Ok(()), + Some(ty::Visibility::Public) => f.write_str("pub "), + Some(ty::Visibility::Restricted(vis_did)) => { + // FIXME(camelid): This may not work correctly if `item_did` is a module. + // However, rustdoc currently never displays a module's + // visibility, so it shouldn't matter. + let parent_module = + find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id()); + + if vis_did.is_crate_root() { + f.write_str("pub(crate) ") + } else if parent_module == Some(vis_did) { + // `pub(in foo)` where `foo` is the parent module + // is the same as no visibility modifier + Ok(()) + } else if parent_module + .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent)) + == Some(vis_did) + { + f.write_str("pub(super) ") + } else { + let path = cx.tcx().def_path(vis_did); + debug!("path={path:?}"); + // modified from `resolved_path()` to work with `DefPathData` + let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); + let anchor = anchor(vis_did, last_name, cx); + + f.write_str("pub(in ")?; + for seg in &path.data[..path.data.len() - 1] { + write!(f, "{}::", seg.data.get_opt_name().unwrap())?; + } + write!(f, "{anchor}) ") + } + } + } }) } diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 7b2aee4b4a5d7..0f63b47380c2e 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -6,15 +6,16 @@ //! Use the `render_with_highlighting` to highlight some rust code. use std::collections::VecDeque; -use std::fmt::{Display, Write}; +use std::fmt::{self, Display, Write}; +use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, LiteralKind, TokenKind}; use rustc_span::edition::Edition; use rustc_span::symbol::Symbol; use rustc_span::{BytePos, DUMMY_SP, Span}; -use super::format::{self, Buffer}; +use super::format; use crate::clean::PrimitiveType; use crate::html::escape::EscapeBodyText; use crate::html::render::{Context, LinkFromSrc}; @@ -46,66 +47,67 @@ pub(crate) enum Tooltip { } /// Highlights `src` as an inline example, returning the HTML output. -pub(crate) fn render_example_with_highlighting( - src: &str, - out: &mut Buffer, +pub(crate) fn render_example_with_highlighting<'a>( + src: &'a str, tooltip: Tooltip, - playground_button: Option<&str>, - extra_classes: &[String], -) { - write_header(out, "rust-example-rendered", None, tooltip, extra_classes); - write_code(out, src, None, None); - write_footer(out, playground_button); + playground_button: Option<&'a str>, + extra_classes: &'a [String], +) -> impl Display + 'a { + fmt::from_fn(move |f| { + write_header("rust-example-rendered", tooltip, extra_classes).fmt(f)?; + write_code(src, None, None).fmt(f)?; + write_footer(playground_button).fmt(f)?; + Ok(()) + }) } -fn write_header( - out: &mut Buffer, - class: &str, - extra_content: Option, +fn write_header<'a>( + class: &'a str, tooltip: Tooltip, - extra_classes: &[String], -) { - write!(out, "
", match tooltip { - Tooltip::Ignore => " ignore", - Tooltip::CompileFail => " compile_fail", - Tooltip::ShouldPanic => " should_panic", - Tooltip::Edition(_) => " edition", - Tooltip::None => "", - },); - - if tooltip != Tooltip::None { - let edition_code; - write!(out, "", match tooltip { - Tooltip::Ignore => "This example is not tested", - Tooltip::CompileFail => "This example deliberately fails to compile", - Tooltip::ShouldPanic => "This example panics", - Tooltip::Edition(edition) => { - edition_code = format!("This example runs with edition {edition}"); - &edition_code - } - Tooltip::None => unreachable!(), - },); - } + extra_classes: &'a [String], +) -> impl Display + 'a { + fmt::from_fn(move |f| { + write!(f, "
", match tooltip { + Tooltip::Ignore => " ignore", + Tooltip::CompileFail => " compile_fail", + Tooltip::ShouldPanic => " should_panic", + Tooltip::Edition(_) => " edition", + Tooltip::None => "", + })?; + + if tooltip != Tooltip::None { + let edition_code; + write!(f, "", match tooltip { + Tooltip::Ignore => "This example is not tested", + Tooltip::CompileFail => "This example deliberately fails to compile", + Tooltip::ShouldPanic => "This example panics", + Tooltip::Edition(edition) => { + edition_code = format!("This example runs with edition {edition}"); + &edition_code + } + Tooltip::None => unreachable!(), + })?; + } - if let Some(extra) = extra_content { - out.push_buffer(extra); - } - if class.is_empty() { - write!( - out, - "
",
-            if extra_classes.is_empty() { "" } else { " " },
-            extra_classes.join(" "),
-        );
-    } else {
-        write!(
-            out,
-            "
",
-            if extra_classes.is_empty() { "" } else { " " },
-            extra_classes.join(" "),
-        );
-    }
-    write!(out, "");
+        if class.is_empty() {
+            write!(
+                f,
+                "
",
+                if extra_classes.is_empty() { "" } else { " " },
+                extra_classes.join(" "),
+            )?;
+        } else {
+            write!(
+                f,
+                "
",
+                if extra_classes.is_empty() { "" } else { " " },
+                extra_classes.join(" "),
+            )?;
+        }
+        write!(f, "")?;
+
+        Ok(())
+    })
 }
 
 /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
@@ -141,7 +143,7 @@ struct TokenHandler<'a, 'tcx, F: Write> {
     /// We need to keep the `Class` for each element because it could contain a `Span` which is
     /// used to generate links.
     pending_elems: Vec<(&'a str, Option)>,
-    href_context: Option>,
+    href_context: Option<&'a HrefContext<'a, 'tcx>>,
 }
 
 impl TokenHandler<'_, '_, F> {
@@ -174,7 +176,7 @@ impl TokenHandler<'_, '_, F> {
             && can_merge(current_class, Some(*parent_class), "")
         {
             for (text, class) in self.pending_elems.iter() {
-                string(self.out, EscapeBodyText(text), *class, &self.href_context, false);
+                string(self.out, EscapeBodyText(text), *class, self.href_context, false);
             }
         } else {
             // We only want to "open" the tag ourselves if we have more than one pending and if the
@@ -185,7 +187,7 @@ impl TokenHandler<'_, '_, F> {
                 // a wrapping `span`.
                 && !matches!(current_class, Class::PreludeTy(_))
             {
-                Some(enter_span(self.out, current_class, &self.href_context))
+                Some(enter_span(self.out, current_class, self.href_context))
             } else {
                 None
             };
@@ -194,7 +196,7 @@ impl TokenHandler<'_, '_, F> {
                     self.out,
                     EscapeBodyText(text),
                     *class,
-                    &self.href_context,
+                    self.href_context,
                     close_tag.is_none(),
                 );
             }
@@ -229,93 +231,98 @@ impl Drop for TokenHandler<'_, '_, F> {
 /// item definition.
 ///
 /// More explanations about spans and how we use them here are provided in the
-pub(super) fn write_code(
-    out: &mut impl Write,
-    src: &str,
-    href_context: Option>,
-    decoration_info: Option<&DecorationInfo>,
-) {
-    // This replace allows to fix how the code source with DOS backline characters is displayed.
-    let src = src.replace("\r\n", "\n");
-    let mut token_handler = TokenHandler {
-        out,
-        closing_tags: Vec::new(),
-        pending_exit_span: None,
-        current_class: None,
-        pending_elems: Vec::new(),
-        href_context,
-    };
+pub(super) fn write_code<'a, 'tcx>(
+    src: &'a str,
+    href_context: Option>,
+    decoration_info: Option<&'a DecorationInfo>,
+) -> impl Display + 'a + Captures<'tcx> {
+    fmt::from_fn(move |mut f| {
+        // This replace allows to fix how the code source with DOS backline characters is displayed.
+        let src = src.replace("\r\n", "\n");
+        let mut token_handler = TokenHandler {
+            out: &mut f,
+            closing_tags: Vec::new(),
+            pending_exit_span: None,
+            current_class: None,
+            pending_elems: Vec::new(),
+            href_context: href_context.as_ref(),
+        };
 
-    Classifier::new(
-        &src,
-        token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
-        decoration_info,
-    )
-    .highlight(&mut |highlight| {
-        match highlight {
-            Highlight::Token { text, class } => {
-                // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
-                // need to close the ``.
-                let need_current_class_update = if let Some(pending) =
-                    token_handler.pending_exit_span
-                    && !can_merge(Some(pending), class, text)
-                {
-                    token_handler.handle_exit_span();
-                    true
-                // If the two `Class` are different, time to flush the current content and start
-                // a new one.
-                } else if !can_merge(token_handler.current_class, class, text) {
-                    token_handler.write_pending_elems(token_handler.current_class);
-                    true
-                } else {
-                    token_handler.current_class.is_none()
-                };
+        Classifier::new(
+            &src,
+            token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
+            decoration_info,
+        )
+        .highlight(&mut |highlight| {
+            match highlight {
+                Highlight::Token { text, class } => {
+                    // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
+                    // need to close the ``.
+                    let need_current_class_update = if let Some(pending) =
+                        token_handler.pending_exit_span
+                        && !can_merge(Some(pending), class, text)
+                    {
+                        token_handler.handle_exit_span();
+                        true
+                    // If the two `Class` are different, time to flush the current content and start
+                    // a new one.
+                    } else if !can_merge(token_handler.current_class, class, text) {
+                        token_handler.write_pending_elems(token_handler.current_class);
+                        true
+                    } else {
+                        token_handler.current_class.is_none()
+                    };
 
-                if need_current_class_update {
-                    token_handler.current_class = class.map(Class::dummy);
+                    if need_current_class_update {
+                        token_handler.current_class = class.map(Class::dummy);
+                    }
+                    token_handler.pending_elems.push((text, class));
                 }
-                token_handler.pending_elems.push((text, class));
-            }
-            Highlight::EnterSpan { class } => {
-                let mut should_add = true;
-                if let Some(pending_exit_span) = token_handler.pending_exit_span {
-                    if class.is_equal_to(pending_exit_span) {
-                        should_add = false;
+                Highlight::EnterSpan { class } => {
+                    let mut should_add = true;
+                    if let Some(pending_exit_span) = token_handler.pending_exit_span {
+                        if class.is_equal_to(pending_exit_span) {
+                            should_add = false;
+                        } else {
+                            token_handler.handle_exit_span();
+                        }
                     } else {
-                        token_handler.handle_exit_span();
+                        // We flush everything just in case...
+                        if token_handler.write_pending_elems(token_handler.current_class) {
+                            token_handler.current_class = None;
+                        }
                     }
-                } else {
-                    // We flush everything just in case...
-                    if token_handler.write_pending_elems(token_handler.current_class) {
-                        token_handler.current_class = None;
+                    if should_add {
+                        let closing_tag =
+                            enter_span(token_handler.out, class, token_handler.href_context);
+                        token_handler.closing_tags.push((closing_tag, class));
                     }
+
+                    token_handler.current_class = None;
+                    token_handler.pending_exit_span = None;
                 }
-                if should_add {
-                    let closing_tag =
-                        enter_span(token_handler.out, class, &token_handler.href_context);
-                    token_handler.closing_tags.push((closing_tag, class));
+                Highlight::ExitSpan => {
+                    token_handler.current_class = None;
+                    token_handler.pending_exit_span = Some(
+                        token_handler
+                            .closing_tags
+                            .last()
+                            .as_ref()
+                            .expect("ExitSpan without EnterSpan")
+                            .1,
+                    );
                 }
+            };
+        });
 
-                token_handler.current_class = None;
-                token_handler.pending_exit_span = None;
-            }
-            Highlight::ExitSpan => {
-                token_handler.current_class = None;
-                token_handler.pending_exit_span = Some(
-                    token_handler
-                        .closing_tags
-                        .last()
-                        .as_ref()
-                        .expect("ExitSpan without EnterSpan")
-                        .1,
-                );
-            }
-        };
-    });
+        Ok(())
+    })
 }
 
-fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
-    writeln!(out, "
{}
", playground_button.unwrap_or_default()); +fn write_footer(playground_button: Option<&str>) -> impl Display + '_ { + fmt::from_fn(move |f| { + writeln!(f, "{}
", playground_button.unwrap_or_default()) + }) } /// How a span of text is classified. Mostly corresponds to token kinds. @@ -902,7 +909,7 @@ impl<'src> Classifier<'src> { fn enter_span( out: &mut impl Write, klass: Class, - href_context: &Option>, + href_context: Option<&HrefContext<'_, '_>>, ) -> &'static str { string_without_closing_tag(out, "", Some(klass), href_context, true).expect( "internal error: enter_span was called with Some(klass) but did not return a \ @@ -935,7 +942,7 @@ fn string( out: &mut impl Write, text: T, klass: Option, - href_context: &Option>, + href_context: Option<&HrefContext<'_, '_>>, open_tag: bool, ) { if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag) @@ -957,7 +964,7 @@ fn string_without_closing_tag( out: &mut impl Write, text: T, klass: Option, - href_context: &Option>, + href_context: Option<&HrefContext<'_, '_>>, open_tag: bool, ) -> Option<&'static str> { let Some(klass) = klass else { diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index fccbb98f80ff3..564dfa778ab4e 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -3,7 +3,6 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_span::create_default_session_globals_then; use super::{DecorationInfo, write_code}; -use crate::html::format::Buffer; const STYLE: &str = r#"