Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit eb2635e

Browse files
committedFeb 2, 2025·
librustdoc: create a helper for separating elements of an iterator instead of implementing it multiple times
1 parent 8239a37 commit eb2635e

File tree

5 files changed

+200
-203
lines changed

5 files changed

+200
-203
lines changed
 

‎src/librustdoc/clean/cfg.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_span::Span;
1414
use rustc_span::symbol::{Symbol, sym};
1515

1616
use crate::html::escape::Escape;
17+
use crate::joined::Joined as _;
1718

1819
#[cfg(test)]
1920
mod tests;
@@ -396,6 +397,8 @@ impl Display<'_> {
396397
sub_cfgs: &[Cfg],
397398
separator: &str,
398399
) -> fmt::Result {
400+
use fmt::Display as _;
401+
399402
let short_longhand = self.1.is_long() && {
400403
let all_crate_features =
401404
sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
@@ -414,20 +417,29 @@ impl Display<'_> {
414417
}
415418
};
416419

417-
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
418-
if i != 0 {
419-
fmt.write_str(separator)?;
420-
}
421-
if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) {
422-
if self.1.is_html() {
423-
write!(fmt, "<code>{feat}</code>")?;
424-
} else {
425-
write!(fmt, "`{feat}`")?;
426-
}
427-
} else {
428-
write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
429-
}
430-
}
420+
fmt::from_fn(|f| {
421+
sub_cfgs
422+
.iter()
423+
.map(|sub_cfg| {
424+
fmt::from_fn(move |fmt| {
425+
if let Cfg::Cfg(_, Some(feat)) = sub_cfg
426+
&& short_longhand
427+
{
428+
if self.1.is_html() {
429+
write!(fmt, "<code>{feat}</code>")?;
430+
} else {
431+
write!(fmt, "`{feat}`")?;
432+
}
433+
} else {
434+
write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
435+
}
436+
Ok(())
437+
})
438+
})
439+
.joined(separator, f)
440+
})
441+
.fmt(fmt)?;
442+
431443
Ok(())
432444
}
433445
}
@@ -439,11 +451,20 @@ impl fmt::Display for Display<'_> {
439451
Cfg::Any(ref sub_cfgs) => {
440452
let separator =
441453
if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
442-
for (i, sub_cfg) in sub_cfgs.iter().enumerate() {
443-
fmt.write_str(if i == 0 { "neither " } else { separator })?;
444-
write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?;
445-
}
446-
Ok(())
454+
fmt.write_str("neither ")?;
455+
456+
sub_cfgs
457+
.iter()
458+
.map(|sub_cfg| {
459+
fmt::from_fn(|fmt| {
460+
write_with_opt_paren(
461+
fmt,
462+
!sub_cfg.is_all(),
463+
Display(sub_cfg, self.1),
464+
)
465+
})
466+
})
467+
.joined(separator, fmt)
447468
}
448469
ref simple @ Cfg::Cfg(..) => write!(fmt, "non-{}", Display(simple, self.1)),
449470
ref c => write!(fmt, "not ({})", Display(c, self.1)),

‎src/librustdoc/html/format.rs

Lines changed: 103 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
//! not be used external to this module.
99
1010
use std::borrow::Cow;
11-
use std::cell::Cell;
1211
use std::cmp::Ordering;
1312
use std::fmt::{self, Display, Write};
1413
use std::iter::{self, once};
1514

16-
use itertools::Itertools;
15+
use itertools::Either;
1716
use rustc_abi::ExternAbi;
1817
use rustc_attr_parsing::{ConstStability, StabilityLevel, StableSince};
1918
use rustc_data_structures::captures::Captures;
@@ -35,6 +34,7 @@ use crate::formats::cache::Cache;
3534
use crate::formats::item_type::ItemType;
3635
use crate::html::escape::{Escape, EscapeBodyText};
3736
use crate::html::render::Context;
37+
use crate::joined::Joined as _;
3838
use crate::passes::collect_intra_doc_links::UrlFragment;
3939

4040
pub(crate) trait Print {
@@ -146,36 +146,18 @@ impl Buffer {
146146
}
147147
}
148148

149-
pub(crate) fn comma_sep<T: Display>(
150-
items: impl Iterator<Item = T>,
151-
space_after_comma: bool,
152-
) -> impl Display {
153-
let items = Cell::new(Some(items));
154-
fmt::from_fn(move |f| {
155-
for (i, item) in items.take().unwrap().enumerate() {
156-
if i != 0 {
157-
write!(f, ",{}", if space_after_comma { " " } else { "" })?;
158-
}
159-
item.fmt(f)?;
160-
}
161-
Ok(())
162-
})
163-
}
164-
165149
pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
166150
bounds: &'a [clean::GenericBound],
167151
cx: &'a Context<'tcx>,
168152
) -> impl Display + 'a + Captures<'tcx> {
169153
fmt::from_fn(move |f| {
170154
let mut bounds_dup = FxHashSet::default();
171155

172-
for (i, bound) in bounds.iter().filter(|b| bounds_dup.insert(*b)).enumerate() {
173-
if i > 0 {
174-
f.write_str(" + ")?;
175-
}
176-
bound.print(cx).fmt(f)?;
177-
}
178-
Ok(())
156+
bounds
157+
.iter()
158+
.filter(move |b| bounds_dup.insert(*b))
159+
.map(|bound| bound.print(cx))
160+
.joined(" + ", f)
179161
})
180162
}
181163

@@ -190,12 +172,7 @@ impl clean::GenericParamDef {
190172

191173
if !outlives.is_empty() {
192174
f.write_str(": ")?;
193-
for (i, lt) in outlives.iter().enumerate() {
194-
if i != 0 {
195-
f.write_str(" + ")?;
196-
}
197-
write!(f, "{}", lt.print())?;
198-
}
175+
outlives.iter().map(|lt| lt.print()).joined(" + ", f)?;
199176
}
200177

201178
Ok(())
@@ -245,10 +222,12 @@ impl clean::Generics {
245222
return Ok(());
246223
}
247224

225+
let real_params =
226+
fmt::from_fn(|f| real_params.clone().map(|g| g.print(cx)).joined(", ", f));
248227
if f.alternate() {
249-
write!(f, "<{:#}>", comma_sep(real_params.map(|g| g.print(cx)), true))
228+
write!(f, "<{:#}>", real_params)
250229
} else {
251-
write!(f, "&lt;{}&gt;", comma_sep(real_params.map(|g| g.print(cx)), true))
230+
write!(f, "&lt;{}&gt;", real_params)
252231
}
253232
})
254233
}
@@ -260,6 +239,42 @@ pub(crate) enum Ending {
260239
NoNewline,
261240
}
262241

242+
fn print_where_predicate<'a, 'tcx: 'a>(
243+
predicate: &'a clean::WherePredicate,
244+
cx: &'a Context<'tcx>,
245+
) -> impl Display + 'a + Captures<'tcx> {
246+
fmt::from_fn(move |f| {
247+
match predicate {
248+
clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
249+
print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
250+
ty.print(cx).fmt(f)?;
251+
f.write_str(":")?;
252+
if !bounds.is_empty() {
253+
f.write_str(" ")?;
254+
print_generic_bounds(bounds, cx).fmt(f)?;
255+
}
256+
Ok(())
257+
}
258+
clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
259+
// We don't need to check `alternate` since we can be certain that neither
260+
// the lifetime nor the bounds contain any characters which need escaping.
261+
write!(f, "{}:", lifetime.print())?;
262+
if !bounds.is_empty() {
263+
write!(f, " {}", print_generic_bounds(bounds, cx))?;
264+
}
265+
Ok(())
266+
}
267+
clean::WherePredicate::EqPredicate { lhs, rhs } => {
268+
if f.alternate() {
269+
write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
270+
} else {
271+
write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
272+
}
273+
}
274+
}
275+
})
276+
}
277+
263278
/// * The Generics from which to emit a where-clause.
264279
/// * The number of spaces to indent each line with.
265280
/// * Whether the where-clause needs to add a comma and newline after the last bound.
@@ -270,55 +285,26 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
270285
ending: Ending,
271286
) -> impl Display + 'a + Captures<'tcx> {
272287
fmt::from_fn(move |f| {
273-
let mut where_predicates = gens
274-
.where_predicates
275-
.iter()
276-
.map(|pred| {
277-
fmt::from_fn(move |f| {
278-
if f.alternate() {
279-
f.write_str(" ")?;
280-
} else {
281-
f.write_str("\n")?;
282-
}
288+
if gens.where_predicates.is_empty() {
289+
return Ok(());
290+
}
283291

284-
match pred {
285-
clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
286-
print_higher_ranked_params_with_space(bound_params, cx, "for")
287-
.fmt(f)?;
288-
ty.print(cx).fmt(f)?;
289-
f.write_str(":")?;
290-
if !bounds.is_empty() {
291-
f.write_str(" ")?;
292-
print_generic_bounds(bounds, cx).fmt(f)?;
293-
}
294-
Ok(())
295-
}
296-
clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
297-
// We don't need to check `alternate` since we can be certain that neither
298-
// the lifetime nor the bounds contain any characters which need escaping.
299-
write!(f, "{}:", lifetime.print())?;
300-
if !bounds.is_empty() {
301-
write!(f, " {}", print_generic_bounds(bounds, cx))?;
302-
}
303-
Ok(())
304-
}
305-
clean::WherePredicate::EqPredicate { lhs, rhs } => {
306-
if f.alternate() {
307-
write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
308-
} else {
309-
write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
310-
}
292+
let where_preds = fmt::from_fn(|f| {
293+
gens.where_predicates
294+
.iter()
295+
.map(|predicate| {
296+
fmt::from_fn(|f| {
297+
if f.alternate() {
298+
f.write_str(" ")?;
299+
} else {
300+
f.write_str("\n")?;
311301
}
312-
}
302+
print_where_predicate(predicate, cx).fmt(f)
303+
})
313304
})
314-
})
315-
.peekable();
316-
317-
if where_predicates.peek().is_none() {
318-
return Ok(());
319-
}
305+
.joined(",", f)
306+
});
320307

321-
let where_preds = comma_sep(where_predicates, false);
322308
let clause = if f.alternate() {
323309
if ending == Ending::Newline {
324310
format!(" where{where_preds},")
@@ -415,12 +401,7 @@ impl clean::GenericBound {
415401
} else {
416402
f.write_str("use&lt;")?;
417403
}
418-
for (i, arg) in args.iter().enumerate() {
419-
if i > 0 {
420-
write!(f, ", ")?;
421-
}
422-
arg.fmt(f)?;
423-
}
404+
args.iter().joined(", ", f)?;
424405
if f.alternate() { f.write_str(">") } else { f.write_str("&gt;") }
425406
}
426407
})
@@ -438,29 +419,18 @@ impl clean::GenericArgs {
438419
} else {
439420
f.write_str("&lt;")?;
440421
}
441-
let mut comma = false;
442-
for arg in args.iter() {
443-
if comma {
444-
f.write_str(", ")?;
445-
}
446-
comma = true;
447-
if f.alternate() {
448-
write!(f, "{:#}", arg.print(cx))?;
449-
} else {
450-
write!(f, "{}", arg.print(cx))?;
451-
}
452-
}
453-
for constraint in constraints.iter() {
454-
if comma {
455-
f.write_str(", ")?;
456-
}
457-
comma = true;
458-
if f.alternate() {
459-
write!(f, "{:#}", constraint.print(cx))?;
460-
} else {
461-
write!(f, "{}", constraint.print(cx))?;
462-
}
463-
}
422+
423+
[Either::Left(args), Either::Right(constraints)]
424+
.into_iter()
425+
.flat_map(Either::factor_into_iter)
426+
.map(|either| {
427+
either.map_either(
428+
|arg| arg.print(cx),
429+
|constraint| constraint.print(cx),
430+
)
431+
})
432+
.joined(", ", f)?;
433+
464434
if f.alternate() {
465435
f.write_str(">")?;
466436
} else {
@@ -470,14 +440,7 @@ impl clean::GenericArgs {
470440
}
471441
clean::GenericArgs::Parenthesized { inputs, output } => {
472442
f.write_str("(")?;
473-
let mut comma = false;
474-
for ty in inputs.iter() {
475-
if comma {
476-
f.write_str(", ")?;
477-
}
478-
comma = true;
479-
ty.print(cx).fmt(f)?;
480-
}
443+
inputs.iter().map(|ty| ty.print(cx)).joined(", ", f)?;
481444
f.write_str(")")?;
482445
if let Some(ref ty) = *output {
483446
if f.alternate() {
@@ -524,6 +487,7 @@ pub(crate) enum HrefError {
524487
// Panics if `syms` is empty.
525488
pub(crate) fn join_with_double_colon(syms: &[Symbol]) -> String {
526489
let mut s = String::with_capacity(estimate_item_path_byte_length(syms.len()));
490+
// NOTE: using `Joined::joined` here causes a noticeable perf regression
527491
s.push_str(syms[0].as_str());
528492
for sym in &syms[1..] {
529493
s.push_str("::");
@@ -572,20 +536,20 @@ fn generate_macro_def_id_path(
572536
}
573537

574538
if let Some(last) = path.last_mut() {
575-
*last = Symbol::intern(&format!("macro.{}.html", last.as_str()));
539+
*last = Symbol::intern(&format!("macro.{last}.html"));
576540
}
577541

578542
let url = match cache.extern_locations[&def_id.krate] {
579543
ExternalLocation::Remote(ref s) => {
580544
// `ExternalLocation::Remote` always end with a `/`.
581-
format!("{s}{path}", path = path.iter().map(|p| p.as_str()).join("/"))
545+
format!("{s}{path}", path = fmt::from_fn(|f| path.iter().joined("/", f)))
582546
}
583547
ExternalLocation::Local => {
584548
// `root_path` always end with a `/`.
585549
format!(
586550
"{root_path}{path}",
587551
root_path = root_path.unwrap_or(""),
588-
path = path.iter().map(|p| p.as_str()).join("/")
552+
path = fmt::from_fn(|f| path.iter().joined("/", f))
589553
)
590554
}
591555
ExternalLocation::Unknown => {
@@ -682,9 +646,8 @@ fn make_href(
682646
url_parts.push("index.html");
683647
}
684648
_ => {
685-
let prefix = shortty.as_str();
686649
let last = fqp.last().unwrap();
687-
url_parts.push_fmt(format_args!("{prefix}.{last}.html"));
650+
url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
688651
}
689652
}
690653
Ok((url_parts.finish(), shortty, fqp.to_vec()))
@@ -950,12 +913,7 @@ fn tybounds<'a, 'tcx: 'a>(
950913
cx: &'a Context<'tcx>,
951914
) -> impl Display + 'a + Captures<'tcx> {
952915
fmt::from_fn(move |f| {
953-
for (i, bound) in bounds.iter().enumerate() {
954-
if i > 0 {
955-
write!(f, " + ")?;
956-
}
957-
bound.print(cx).fmt(f)?;
958-
}
916+
bounds.iter().map(|bound| bound.print(cx)).joined(" + ", f)?;
959917
if let Some(lt) = lt {
960918
// We don't need to check `alternate` since we can be certain that
961919
// the lifetime doesn't contain any characters which need escaping.
@@ -974,7 +932,7 @@ fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>(
974932
if !params.is_empty() {
975933
f.write_str(keyword)?;
976934
f.write_str(if f.alternate() { "<" } else { "&lt;" })?;
977-
comma_sep(params.iter().map(|lt| lt.print(cx)), true).fmt(f)?;
935+
params.iter().map(|lt| lt.print(cx)).joined(", ", f)?;
978936
f.write_str(if f.alternate() { "> " } else { "&gt; " })?;
979937
}
980938
Ok(())
@@ -1025,9 +983,7 @@ fn fmt_type(
1025983
clean::Primitive(clean::PrimitiveType::Never) => {
1026984
primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
1027985
}
1028-
clean::Primitive(prim) => {
1029-
primitive_link(f, prim, format_args!("{}", prim.as_sym().as_str()), cx)
1030-
}
986+
clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
1031987
clean::BareFunction(ref decl) => {
1032988
print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
1033989
decl.safety.print_with_space().fmt(f)?;
@@ -1067,18 +1023,16 @@ fn fmt_type(
10671023
primitive_link(
10681024
f,
10691025
PrimitiveType::Tuple,
1070-
format_args!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")),
1026+
format_args!(
1027+
"({})",
1028+
fmt::from_fn(|f| generic_names.iter().joined(", ", f))
1029+
),
10711030
cx,
10721031
)
10731032
} else {
1074-
write!(f, "(")?;
1075-
for (i, item) in many.iter().enumerate() {
1076-
if i != 0 {
1077-
write!(f, ", ")?;
1078-
}
1079-
item.print(cx).fmt(f)?;
1080-
}
1081-
write!(f, ")")
1033+
f.write_str("(")?;
1034+
many.iter().map(|item| item.print(cx)).joined(", ", f)?;
1035+
f.write_str(")")
10821036
}
10831037
}
10841038
},
@@ -1407,14 +1361,15 @@ impl clean::Arguments {
14071361
cx: &'a Context<'tcx>,
14081362
) -> impl Display + 'a + Captures<'tcx> {
14091363
fmt::from_fn(move |f| {
1410-
for (i, input) in self.values.iter().enumerate() {
1411-
write!(f, "{}: ", input.name)?;
1412-
input.type_.print(cx).fmt(f)?;
1413-
if i + 1 < self.values.len() {
1414-
write!(f, ", ")?;
1415-
}
1416-
}
1417-
Ok(())
1364+
self.values
1365+
.iter()
1366+
.map(|input| {
1367+
fmt::from_fn(|f| {
1368+
write!(f, "{}: ", input.name)?;
1369+
input.type_.print(cx).fmt(f)
1370+
})
1371+
})
1372+
.joined(", ", f)
14181373
})
14191374
}
14201375
}
@@ -1723,12 +1678,7 @@ impl clean::ImportSource {
17231678
}
17241679
let name = self.path.last();
17251680
if let hir::def::Res::PrimTy(p) = self.path.res {
1726-
primitive_link(
1727-
f,
1728-
PrimitiveType::from(p),
1729-
format_args!("{}", name.as_str()),
1730-
cx,
1731-
)?;
1681+
primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
17321682
} else {
17331683
f.write_str(name.as_str())?;
17341684
}

‎src/librustdoc/html/render/print_item.rs

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cmp::Ordering;
22
use std::fmt;
3+
use std::fmt::{Display, Write};
34

45
use itertools::Itertools;
56
use rinja::Template;
@@ -36,6 +37,7 @@ use crate::html::format::{
3637
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
3738
use crate::html::render::{document_full, document_item_info};
3839
use crate::html::url_parts_builder::UrlPartsBuilder;
40+
use crate::joined::Joined as _;
3941

4042
/// Generates a Rinja template struct for rendering items with common methods.
4143
///
@@ -290,7 +292,7 @@ fn should_hide_fields(n_fields: usize) -> bool {
290292
n_fields > 12
291293
}
292294

293-
fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) {
295+
fn toggle_open(mut w: impl fmt::Write, text: impl Display) {
294296
write!(
295297
w,
296298
"<details class=\"toggle type-contents-toggle\">\
@@ -305,7 +307,7 @@ fn toggle_close(mut w: impl fmt::Write) {
305307
w.write_str("</details>").unwrap();
306308
}
307309

308-
trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + fmt::Display {
310+
trait ItemTemplate<'a, 'cx: 'a>: rinja::Template + Display {
309311
fn item_and_cx(&self) -> (&'a clean::Item, &'a Context<'cx>);
310312
}
311313

@@ -519,13 +521,9 @@ fn extra_info_tags<'a, 'tcx: 'a>(
519521
item: &'a clean::Item,
520522
parent: &'a clean::Item,
521523
import_def_id: Option<DefId>,
522-
) -> impl fmt::Display + 'a + Captures<'tcx> {
524+
) -> impl Display + 'a + Captures<'tcx> {
523525
fmt::from_fn(move |f| {
524-
fn tag_html<'a>(
525-
class: &'a str,
526-
title: &'a str,
527-
contents: &'a str,
528-
) -> impl fmt::Display + 'a {
526+
fn tag_html<'a>(class: &'a str, title: &'a str, contents: &'a str) -> impl Display + 'a {
529527
fmt::from_fn(move |f| {
530528
write!(
531529
f,
@@ -1374,7 +1372,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
13741372
);
13751373

13761374
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
1377-
fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
1375+
fn render_union<'b>(&'b self) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
13781376
fmt::from_fn(move |f| {
13791377
let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx);
13801378
write!(f, "{v}")
@@ -1384,7 +1382,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
13841382
fn document_field<'b>(
13851383
&'b self,
13861384
field: &'a clean::Item,
1387-
) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
1385+
) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
13881386
fmt::from_fn(move |f| {
13891387
let v = document(self.cx, field, Some(self.it), HeadingOffset::H3);
13901388
write!(f, "{v}")
@@ -1398,7 +1396,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
13981396
fn print_ty<'b>(
13991397
&'b self,
14001398
ty: &'a clean::Type,
1401-
) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
1399+
) -> impl Display + Captures<'a> + 'b + Captures<'cx> {
14021400
fmt::from_fn(move |f| {
14031401
let v = ty.print(self.cx);
14041402
write!(f, "{v}")
@@ -1425,7 +1423,7 @@ fn item_union(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, s: &clean::Uni
14251423
fn print_tuple_struct_fields<'a, 'cx: 'a>(
14261424
cx: &'a Context<'cx>,
14271425
s: &'a [clean::Item],
1428-
) -> impl fmt::Display + 'a + Captures<'cx> {
1426+
) -> impl Display + 'a + Captures<'cx> {
14291427
fmt::from_fn(|f| {
14301428
if !s.is_empty()
14311429
&& s.iter().all(|field| {
@@ -1435,17 +1433,15 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
14351433
return f.write_str("<span class=\"comment\">/* private fields */</span>");
14361434
}
14371435

1438-
for (i, ty) in s.iter().enumerate() {
1439-
if i > 0 {
1440-
f.write_str(", ")?;
1441-
}
1442-
match ty.kind {
1443-
clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?,
1444-
clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?,
1445-
_ => unreachable!(),
1446-
}
1447-
}
1448-
Ok(())
1436+
s.iter()
1437+
.map(|ty| {
1438+
fmt::from_fn(|f| match ty.kind {
1439+
clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"),
1440+
clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx)),
1441+
_ => unreachable!(),
1442+
})
1443+
})
1444+
.joined(", ", f)
14491445
})
14501446
}
14511447

@@ -2068,12 +2064,12 @@ fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>)
20682064
bounds.push_str(": ");
20692065
}
20702066
}
2071-
for (i, p) in t_bounds.iter().enumerate() {
2072-
if i > 0 {
2073-
bounds.push_str(inter_str);
2074-
}
2075-
bounds.push_str(&p.print(cx).to_string());
2076-
}
2067+
write!(
2068+
bounds,
2069+
"{}",
2070+
fmt::from_fn(|f| t_bounds.iter().map(|p| p.print(cx)).joined(inter_str, f))
2071+
)
2072+
.unwrap();
20772073
bounds
20782074
}
20792075

@@ -2150,7 +2146,7 @@ fn render_union<'a, 'cx: 'a>(
21502146
g: Option<&'a clean::Generics>,
21512147
fields: &'a [clean::Item],
21522148
cx: &'a Context<'cx>,
2153-
) -> impl fmt::Display + 'a + Captures<'cx> {
2149+
) -> impl Display + 'a + Captures<'cx> {
21542150
fmt::from_fn(move |mut f| {
21552151
write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
21562152

@@ -2330,7 +2326,7 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str {
23302326
if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
23312327
}
23322328

2333-
fn document_non_exhaustive(item: &clean::Item) -> impl fmt::Display + '_ {
2329+
fn document_non_exhaustive(item: &clean::Item) -> impl Display + '_ {
23342330
fmt::from_fn(|f| {
23352331
if item.is_non_exhaustive() {
23362332
write!(

‎src/librustdoc/joined.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use std::fmt::{self, Display, Formatter};
2+
3+
pub(crate) trait Joined: IntoIterator {
4+
/// Takes an iterator over elements that implement [`Display`], and format them into `f`, separated by `sep`.
5+
///
6+
/// This is similar to [`Itertools::format`](itertools::Itertools::format), but instead of returning an implementation of `Display`,
7+
/// it formats directly into a [`Formatter`].
8+
///
9+
/// The performance of `joined` is slightly better than `format`, since it doesn't need to use a `Cell` to keep track of whether [`fmt`](Display::fmt)
10+
/// was already called (`joined`'s API doesn't allow it be called more than once).
11+
fn joined(self, sep: &str, f: &mut Formatter<'_>) -> fmt::Result;
12+
}
13+
14+
impl<I, T> Joined for I
15+
where
16+
I: IntoIterator<Item = T>,
17+
T: Display,
18+
{
19+
fn joined(self, sep: &str, f: &mut Formatter<'_>) -> fmt::Result {
20+
let mut iter = self.into_iter();
21+
let Some(first) = iter.next() else { return Ok(()) };
22+
first.fmt(f)?;
23+
while let Some(item) = iter.next() {
24+
f.write_str(sep)?;
25+
item.fmt(f)?;
26+
}
27+
Ok(())
28+
}
29+
}

‎src/librustdoc/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ mod fold;
113113
mod formats;
114114
// used by the error-index generator, so it needs to be public
115115
pub mod html;
116+
mod joined;
116117
mod json;
117118
pub(crate) mod lint;
118119
mod markdown;

0 commit comments

Comments
 (0)
Please sign in to comment.