]> git.proxmox.com Git - rustc.git/blobdiff - src/librustdoc/html/format.rs
Merge 1.70 into proxmox/bookworm
[rustc.git] / src / librustdoc / html / format.rs
index 0e4c5ed6836827752b5fe6de118c02d829d95c01..1b445b8981e1a7dbc8a9e7912e08cd311e290a3e 100644 (file)
@@ -1,13 +1,15 @@
 //! HTML formatting module
 //!
 //! This module contains a large number of `fmt::Display` implementations for
-//! various types in `rustdoc::clean`. These implementations all currently
-//! assume that HTML output is desired, although it may be possible to redesign
-//! them in the future to instead emit any format desired.
+//! various types in `rustdoc::clean`.
+//!
+//! These implementations all emit HTML. As an internal implementation detail,
+//! 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::cell::Cell;
-use std::fmt;
+use std::fmt::{self, Write};
 use std::iter::{self, once};
 
 use rustc_ast as ast;
@@ -19,7 +21,6 @@ use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefId;
 use rustc_metadata::creader::{CStore, LoadedMacro};
 use rustc_middle::ty;
-use rustc_middle::ty::DefIdTree;
 use rustc_middle::ty::TyCtxt;
 use rustc_span::symbol::kw;
 use rustc_span::{sym, Symbol};
@@ -127,7 +128,6 @@ impl Buffer {
     // the fmt::Result return type imposed by fmt::Write (and avoiding the trait
     // import).
     pub(crate) fn write_fmt(&mut self, v: fmt::Arguments<'_>) {
-        use fmt::Write;
         self.buffer.write_fmt(v).unwrap();
     }
 
@@ -136,10 +136,6 @@ impl Buffer {
         self.into_inner()
     }
 
-    pub(crate) fn is_for_html(&self) -> bool {
-        self.for_html
-    }
-
     pub(crate) fn reserve(&mut self, additional: usize) {
         self.buffer.reserve(additional)
     }
@@ -280,8 +276,6 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
     indent: usize,
     ending: Ending,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
-    use fmt::Write;
-
     display_fn(move |f| {
         let mut where_predicates = gens.where_predicates.iter().filter(|pred| {
             !matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty())
@@ -309,13 +303,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
                                 write!(
                                     f,
                                     "for<{:#}> {ty_cx:#}: {generic_bounds:#}",
-                                    comma_sep(bound_params.iter().map(|lt| lt.print()), true)
+                                    comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
                                 )
                             } else {
                                 write!(
                                     f,
                                     "for&lt;{}&gt; {ty_cx}: {generic_bounds}",
-                                    comma_sep(bound_params.iter().map(|lt| lt.print()), true)
+                                    comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
                                 )
                             }
                         }
@@ -355,10 +349,10 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
             let mut br_with_padding = String::with_capacity(6 * indent + 28);
             br_with_padding.push_str("\n");
 
-            let padding_amout =
+            let padding_amount =
                 if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() };
 
-            for _ in 0..padding_amout {
+            for _ in 0..padding_amount {
                 br_with_padding.push_str(" ");
             }
             let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
@@ -773,6 +767,12 @@ pub(crate) fn link_tooltip(did: DefId, fragment: &Option<UrlFragment>, cx: &Cont
         .or_else(|| cache.external_paths.get(&did))
         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 {
@@ -1138,22 +1138,21 @@ fn fmt_type<'cx>(
             //        the ugliness comes from inlining across crates where
             //        everything comes in as a fully resolved QPath (hard to
             //        look at).
-            match href(trait_.def_id(), cx) {
-                Ok((ref url, _, ref path)) if !f.alternate() => {
-                    write!(
-                        f,
-                        "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
-                                    title=\"type {path}::{name}\">{name}</a>{args}",
-                        url = url,
-                        shortty = ItemType::AssocType,
-                        name = assoc.name,
-                        path = join_with_double_colon(path),
-                        args = assoc.args.print(cx),
-                    )?;
-                }
-                _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?,
-            }
-            Ok(())
+            if !f.alternate() && let Ok((url, _, path)) = href(trait_.def_id(), cx) {
+                write!(
+                    f,
+                    "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
+                                title=\"type {path}::{name}\">{name}</a>",
+                    shortty = ItemType::AssocType,
+                    name = assoc.name,
+                    path = join_with_double_colon(&path),
+                )
+            } else {
+                write!(f, "{}", assoc.name)
+            }?;
+
+            // Carry `f.alternate()` into this display w/o branching manually.
+            fmt::Display::fmt(&assoc.args.print(cx), f)
         }
     }
 }
@@ -1307,6 +1306,28 @@ impl clean::BareFunctionDecl {
     }
 }
 
+// Implements Write but only counts the bytes "written".
+struct WriteCounter(usize);
+
+impl std::fmt::Write for WriteCounter {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.0 += s.len();
+        Ok(())
+    }
+}
+
+// Implements Display by emitting the given number of spaces.
+struct Indent(usize);
+
+impl fmt::Display for Indent {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (0..self.0).for_each(|_| {
+            f.write_char(' ').unwrap();
+        });
+        Ok(())
+    }
+}
+
 impl clean::FnDecl {
     pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
         &'a self,
@@ -1346,95 +1367,80 @@ impl clean::FnDecl {
         indent: usize,
         cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
-        display_fn(move |f| self.inner_full_print(header_len, indent, f, cx))
+        display_fn(move |f| {
+            // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
+            let mut counter = WriteCounter(0);
+            write!(&mut counter, "{:#}", display_fn(|f| { self.inner_full_print(None, f, cx) }))
+                .unwrap();
+            // If the text form was over 80 characters wide, we will line-wrap our output.
+            let line_wrapping_indent =
+                if header_len + counter.0 > 80 { Some(indent) } else { None };
+            // Generate the final output. This happens to accept `{:#}` formatting to get textual
+            // output but in practice it is only formatted with `{}` to get HTML output.
+            self.inner_full_print(line_wrapping_indent, f, cx)
+        })
     }
 
     fn inner_full_print(
         &self,
-        header_len: usize,
-        indent: usize,
+        // For None, the declaration will not be line-wrapped. For Some(n),
+        // the declaration will be line-wrapped, with an indent of n spaces.
+        line_wrapping_indent: Option<usize>,
         f: &mut fmt::Formatter<'_>,
         cx: &Context<'_>,
     ) -> fmt::Result {
         let amp = if f.alternate() { "&" } else { "&amp;" };
-        let mut args = Buffer::html();
-        let mut args_plain = Buffer::new();
+
+        write!(f, "(")?;
+        if let Some(n) = line_wrapping_indent {
+            write!(f, "\n{}", Indent(n + 4))?;
+        }
         for (i, input) in self.inputs.values.iter().enumerate() {
+            if i > 0 {
+                match line_wrapping_indent {
+                    None => write!(f, ", ")?,
+                    Some(n) => write!(f, ",\n{}", Indent(n + 4))?,
+                };
+            }
             if let Some(selfty) = input.to_self() {
                 match selfty {
                     clean::SelfValue => {
-                        args.push_str("self");
-                        args_plain.push_str("self");
+                        write!(f, "self")?;
                     }
                     clean::SelfBorrowed(Some(ref lt), mtbl) => {
-                        write!(args, "{}{} {}self", amp, lt.print(), mtbl.print_with_space());
-                        write!(args_plain, "&{} {}self", lt.print(), mtbl.print_with_space());
+                        write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?;
                     }
                     clean::SelfBorrowed(None, mtbl) => {
-                        write!(args, "{}{}self", amp, mtbl.print_with_space());
-                        write!(args_plain, "&{}self", mtbl.print_with_space());
+                        write!(f, "{}{}self", amp, mtbl.print_with_space())?;
                     }
                     clean::SelfExplicit(ref typ) => {
-                        if f.alternate() {
-                            write!(args, "self: {:#}", typ.print(cx));
-                        } else {
-                            write!(args, "self: {}", typ.print(cx));
-                        }
-                        write!(args_plain, "self: {:#}", typ.print(cx));
+                        write!(f, "self: ")?;
+                        fmt::Display::fmt(&typ.print(cx), f)?;
                     }
                 }
             } else {
-                if i > 0 {
-                    args.push_str("\n");
-                }
                 if input.is_const {
-                    args.push_str("const ");
-                    args_plain.push_str("const ");
-                }
-                write!(args, "{}: ", input.name);
-                write!(args_plain, "{}: ", input.name);
-
-                if f.alternate() {
-                    write!(args, "{:#}", input.type_.print(cx));
-                } else {
-                    write!(args, "{}", input.type_.print(cx));
+                    write!(f, "const ")?;
                 }
-                write!(args_plain, "{:#}", input.type_.print(cx));
-            }
-            if i + 1 < self.inputs.values.len() {
-                args.push_str(",");
-                args_plain.push_str(",");
+                write!(f, "{}: ", input.name)?;
+                fmt::Display::fmt(&input.type_.print(cx), f)?;
             }
         }
 
-        let mut args_plain = format!("({})", args_plain.into_inner());
-        let mut args = args.into_inner();
-
         if self.c_variadic {
-            args.push_str(",\n ...");
-            args_plain.push_str(", ...");
+            match line_wrapping_indent {
+                None => write!(f, ", ...")?,
+                Some(n) => write!(f, "\n{}...", Indent(n + 4))?,
+            };
         }
 
-        let arrow_plain = format!("{:#}", self.output.print(cx));
-        let arrow =
-            if f.alternate() { arrow_plain.clone() } else { format!("{}", self.output.print(cx)) };
-
-        let declaration_len = header_len + args_plain.len() + arrow_plain.len();
-        let output = if declaration_len > 80 {
-            let full_pad = format!("\n{}", " ".repeat(indent + 4));
-            let close_pad = format!("\n{}", " ".repeat(indent));
-            format!(
-                "({pad}{args}{close}){arrow}",
-                pad = if self.inputs.values.is_empty() { "" } else { &full_pad },
-                args = args.replace('\n', &full_pad),
-                close = close_pad,
-                arrow = arrow
-            )
-        } else {
-            format!("({args}){arrow}", args = args.replace('\n', " "), arrow = arrow)
+        match line_wrapping_indent {
+            None => write!(f, ")")?,
+            Some(n) => write!(f, "\n{})", Indent(n))?,
         };
 
-        write!(f, "{}", output)
+        fmt::Display::fmt(&self.output.print(cx), f)?;
+        Ok(())
     }
 }
 
@@ -1469,7 +1475,7 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
                 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).to_string();
+                let anchor = anchor(vis_did, last_name, cx);
 
                 let mut s = "pub(in ".to_owned();
                 for seg in &path.data[..path.data.len() - 1] {
@@ -1491,9 +1497,9 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
     tcx: TyCtxt<'tcx>,
     item_did: DefId,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
-    let to_print = match visibility {
-        None => String::new(),
-        Some(ty::Visibility::Public) => "pub ".to_owned(),
+    let to_print: Cow<'static, str> = match visibility {
+        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
@@ -1501,17 +1507,17 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
             let parent_module = find_nearest_parent_module(tcx, item_did);
 
             if vis_did.is_crate_root() {
-                "pub(crate) ".to_owned()
+                "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
-                String::new()
+                "".into()
             } else if parent_module.and_then(|parent| find_nearest_parent_module(tcx, parent))
                 == Some(vis_did)
             {
-                "pub(super) ".to_owned()
+                "pub(super) ".into()
             } else {
-                format!("pub(in {}) ", tcx.def_path_str(vis_did))
+                format!("pub(in {}) ", tcx.def_path_str(vis_did)).into()
             }
         }
     };