diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e01341acf438e..75e6a746b8052 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -871,7 +871,7 @@ fn assoc_method( write!(w, "{}", render_attributes_in_pre(meth, indent_str, cx)); (4, indent_str, Ending::NoNewline) } else { - render_attributes_in_code(w, meth, cx); + write!(w, "{}", render_attributes_in_code(meth, tcx)); (0, "", Ending::Newline) }; w.reserve(header_len + "{".len() + "".len()); @@ -1075,10 +1075,16 @@ fn render_attributes_in_pre<'a, 'tcx: 'a>( // When an attribute is rendered inside a tag, it is formatted using // a div to produce a newline after it. -fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) { - for attr in it.attributes(cx.tcx(), cx.cache(), false) { - write!(w, "
{attr}
").unwrap(); - } +fn render_attributes_in_code<'a, 'tcx>( + it: &'a clean::Item, + tcx: TyCtxt<'tcx>, +) -> impl fmt::Display + Captures<'a> + Captures<'tcx> { + display_fn(move |f| { + for a in it.attributes(tcx, false) { + write!(f, "
{}
", a)?; + } + Ok(()) + }) } #[derive(Copy, Clone)] diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f6432dc61ae06..112b155db3eeb 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -42,6 +42,10 @@ use crate::html::{highlight, static_files}; use askama::Template; use itertools::Itertools; +trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display { + fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>); +} + /// Generates an Askama template struct for rendering items with common methods. /// /// Usage: @@ -126,6 +130,16 @@ macro_rules! item_template_methods { } item_template_methods!($($rest)*); }; + (render_attributes_in_code $($rest:tt)*) => { + fn render_attributes_in_code<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (it, cx) = self.item_and_mut_cx(); + let v = render_attributes_in_code(it, cx.tcx()); + write!(f, "{v}") + }) + } + item_template_methods!($($rest)*); + }; (render_assoc_items $($rest:tt)*) => { fn render_assoc_items<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { display_fn(move |f| { @@ -322,10 +336,6 @@ fn toggle_close(mut w: impl fmt::Write) { w.write_str("").unwrap(); } -trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display { - fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>); -} - fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) { write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); @@ -1321,67 +1331,68 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c write!(w, "{}", document_type_layout(cx, def_id)); } -fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { - item_template!( - #[template(path = "item_union.html")] - struct ItemUnion<'a, 'cx> { - cx: RefCell<&'a mut Context<'cx>>, - it: &'a clean::Item, - s: &'a clean::Union, - }, - methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items] - ); +// Only to be used by the `item_union()` function +item_template!( + #[template(path = "item_union.html")] + struct ItemUnion<'a, 'cx> { + cx: RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Union, + }, + methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items] +); + +impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { + fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow_mut(); + let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, *cx); + write!(f, "{v}") + }) + } - impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { - fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let cx = self.cx.borrow_mut(); - let v = render_union(self.it, Some(&self.s.generics), &self.s.fields, *cx); - write!(f, "{v}") - }) - } + fn document_field<'b>( + &'b self, + field: &'a clean::Item, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, field, Some(self.it), HeadingOffset::H3); + write!(f, "{v}") + }) + } - fn document_field<'b>( - &'b self, - field: &'a clean::Item, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let mut cx = self.cx.borrow_mut(); - let v = document(*cx, field, Some(self.it), HeadingOffset::H3); - write!(f, "{v}") - }) - } + fn stability_field(&self, field: &clean::Item) -> Option { + let cx = self.cx.borrow(); + field.stability_class(cx.tcx()) + } - fn stability_field(&self, field: &clean::Item) -> Option { + fn print_ty<'b>( + &'b self, + ty: &'a clean::Type, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { let cx = self.cx.borrow(); - field.stability_class(cx.tcx()) - } + let v = ty.print(*cx); + write!(f, "{v}") + }) + } - fn print_ty<'b>( - &'b self, - ty: &'a clean::Type, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let cx = self.cx.borrow(); - let v = ty.print(*cx); - write!(f, "{v}") + fn fields_iter( + &self, + ) -> std::iter::Peekable> { + self.s + .fields + .iter() + .filter_map(|f| match *f.kind { + clean::StructFieldItem(ref ty) => Some((f, ty)), + _ => None, }) - } - - fn fields_iter( - &self, - ) -> std::iter::Peekable> { - self.s - .fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, - }) - .peekable() - } + .peekable() } +} +fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { ItemUnion { cx: RefCell::new(cx), it, s }.render_into(w).unwrap(); } @@ -1413,26 +1424,56 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::Enum) { let tcx = cx.tcx(); let count_variants = e.variants().count(); - wrap_item(w, |w| { - render_attributes_in_code(w, it, cx); + wrap_item(w, |mut w| { write!( w, - "{}enum {}{}", + "{attrs}{}enum {}{}", visibility_print_with_space(it.visibility(tcx), it.item_id, cx), it.name.unwrap(), e.generics.print(cx), + attrs = render_attributes_in_code(it, tcx), ); - render_enum_fields( - w, - cx, - Some(&e.generics), - &e.variants, - count_variants, - e.has_stripped_entries(), - it.is_non_exhaustive(), - it.def_id().unwrap(), - ); + let variants_stripped = e.has_stripped_entries(); + if count_variants == 0 && !variants_stripped { + w.write_str("{}"); + } else { + w.write_str("{\n"); + let toggle = should_hide_fields(count_variants); + if toggle { + toggle_open(&mut w, format_args!("{} variants", count_variants)); + } + for v in e.variants() { + w.write_str(" "); + let name = v.name.unwrap(); + match *v.kind { + // FIXME(#101337): Show discriminant + clean::VariantItem(ref var) => match var.kind { + clean::VariantKind::CLike => write!(w, "{}", name), + clean::VariantKind::Tuple(ref s) => { + write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); + } + clean::VariantKind::Struct(ref s) => { + write!( + w, + "{}", + render_struct(v, None, None, &s.fields, " ", false, cx) + ); + } + }, + _ => unreachable!(), + } + w.write_str(",\n"); + } + + if variants_stripped && !it.is_non_exhaustive() { + w.write_str(" // some variants omitted\n"); + } + if toggle { + toggle_close(&mut w); + } + w.write_str("}"); + } }); write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); @@ -1733,11 +1774,11 @@ fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Ite fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &clean::Constant) { wrap_item(w, |w| { let tcx = cx.tcx(); - render_attributes_in_code(w, it, cx); write!( w, - "{vis}const {name}{generics}: {typ}{where_clause}", + "{attrs}{vis}const {name}: {typ}", + attrs = render_attributes_in_code(it, tcx), vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), name = it.name.unwrap(), generics = c.generics.print(cx), @@ -1780,72 +1821,94 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } -fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { - wrap_item(w, |w| { - render_attributes_in_code(w, it, cx); - render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx); - }); - - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); +// Only to be used by the `item_struct()` function +item_template!( + #[template(path = "item_struct.html")] + struct ItemStruct<'a, 'cx> { + cx: RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Struct, + should_render_fields: bool, + }, + methods = [render_attributes_in_code, document, render_assoc_items, document_type_layout] +); + +impl<'a, 'cx: 'a> ItemStruct<'a, 'cx> { + fn new( + cx: std::cell::RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Struct, + ) -> Self { + let should_render_fields = matches!(s.ctor_kind, None | Some(CtorKind::Fn)) + && struct_field_items(s).peekable().peek().is_some(); + Self { cx, it, s, should_render_fields } + } - item_fields(w, cx, it, &s.fields, s.ctor_kind); + fn render_struct<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let cx = self.cx.borrow(); + let v = render_struct( + self.it, + Some(&self.s.generics), + self.s.ctor_kind, + &self.s.fields, + "", + true, + *cx, + ); + write!(f, "{v}") + }) + } - let def_id = it.item_id.expect_def_id(); - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); - write!(w, "{}", document_type_layout(cx, def_id)); -} + fn struct_field_items_iter<'b>( + &'b self, + ) -> impl Iterator> + Captures<'a> + 'b + Captures<'cx> { + struct_field_items(self.s).enumerate().map(|(index, (item, ty))| { + let mut cx = self.cx.borrow_mut(); + let name = item.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); + let id = cx.derive_id(format!("{}.{}", ItemType::StructField, name)); + let ty = ty.print(*cx).to_string(); + ItemStructField { item, name, id, ty } + }) + } -fn item_fields( - w: &mut Buffer, - cx: &mut Context<'_>, - it: &clean::Item, - fields: &Vec, - ctor_kind: Option, -) { - let mut fields = fields - .iter() - .filter_map(|f| match *f.kind { - clean::StructFieldItem(ref ty) => Some((f, ty)), - _ => None, + fn document_field<'b>( + &'b self, + field: &'b clean::Item, + ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let mut cx = self.cx.borrow_mut(); + let v = document(*cx, field, Some(self.it), HeadingOffset::H3); + write!(f, "{v}") }) - .peekable(); - if let None | Some(CtorKind::Fn) = ctor_kind { - if fields.peek().is_some() { - write!( - w, - "

\ - {}{}§\ -

\ - {}", - if ctor_kind.is_none() { "Fields" } else { "Tuple Fields" }, - document_non_exhaustive_header(it), - document_non_exhaustive(it) - ); - for (index, (field, ty)) in fields.enumerate() { - let field_name = - field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); - let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); - write!( - w, - "\ - §\ - {field_name}: {ty}\ - ", - item_type = ItemType::StructField, - ty = ty.print(cx) - ); - write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3)); - } - } } } +// Only to be used by the `ItemStruct` struct +struct ItemStructField<'a> { + item: &'a clean::Item, + name: String, + id: String, + ty: String, +} + +fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) { + ItemStruct::new(std::cell::RefCell::new(cx), it, s).render_into(w).unwrap(); +} + +fn struct_field_items(s: &clean::Struct) -> impl Iterator { + s.fields.iter().filter_map(|item| match *item.kind { + clean::StructFieldItem(ref ty) => Some((item, ty)), + _ => None, + }) +} + fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { wrap_item(w, |buffer| { - render_attributes_in_code(buffer, it, cx); write!( buffer, - "{vis}static {mutability}{name}: {typ}", + "{attrs}{vis}static {mutability}{name}: {typ}", + attrs = render_attributes_in_code(it, cx.tcx()), vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), mutability = s.mutability.print_with_space(), name = it.name.unwrap(), @@ -1860,12 +1923,12 @@ fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) { wrap_item(w, |buffer| { buffer.write_str("extern {\n").unwrap(); - render_attributes_in_code(buffer, it, cx); write!( buffer, - " {}type {};\n}}", + "{attrs} {}type {};\n}}", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), it.name.unwrap(), + attrs = render_attributes_in_code(it, cx.tcx()), ) .unwrap(); }); @@ -2044,7 +2107,7 @@ fn render_union<'a, 'cx: 'a>( f.write_str(" ")?; } - write!(f, "{{\n")?; + f.write_str("{\n")?; let count_fields = fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count(); let toggle = should_hide_fields(count_fields); @@ -2065,7 +2128,7 @@ fn render_union<'a, 'cx: 'a>( } if it.has_stripped_entries().unwrap() { - write!(f, " /* private fields */\n")?; + f.write_str(" /* private fields */\n")?; } if toggle { toggle_close(&mut f); @@ -2075,109 +2138,86 @@ fn render_union<'a, 'cx: 'a>( }) } -fn render_struct( - w: &mut Buffer, - it: &clean::Item, - g: Option<&clean::Generics>, +fn render_struct<'a, 'cx: 'a>( + it: &'a clean::Item, + g: Option<&'a clean::Generics>, ty: Option, - fields: &[clean::Item], - tab: &str, + fields: &'a [clean::Item], + tab: &'a str, structhead: bool, - cx: &Context<'_>, -) { - let tcx = cx.tcx(); - write!( - w, - "{}{}{}", - visibility_print_with_space(it.visibility(tcx), it.item_id, cx), - if structhead { "struct " } else { "" }, - it.name.unwrap() - ); - if let Some(g) = g { - write!(w, "{}", g.print(cx)) - } - render_struct_fields( - w, - g, - ty, - fields, - tab, - structhead, - it.has_stripped_entries().unwrap_or(false), - cx, - ) -} + cx: &'a Context<'cx>, +) -> impl fmt::Display + 'a + Captures<'cx> { + display_fn(move |mut f| { + let tcx = cx.tcx(); + write!( + f, + "{}{}{}", + visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + if structhead { "struct " } else { "" }, + it.name.unwrap() + )?; -fn render_struct_fields( - mut w: &mut Buffer, - g: Option<&clean::Generics>, - ty: Option, - fields: &[clean::Item], - tab: &str, - structhead: bool, - has_stripped_entries: bool, - cx: &Context<'_>, -) { - let tcx = cx.tcx(); - match ty { - None => { - let where_displayed = - g.map(|g| print_where_clause_and_check(w, g, cx)).unwrap_or(false); + if let Some(g) = g { + write!(f, "{}", g.print(cx))?; + } - // If there wasn't a `where` clause, we add a whitespace. - if !where_displayed { - w.write_str(" {"); - } else { - w.write_str("{"); - } - let count_fields = - fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count(); - let has_visible_fields = count_fields > 0; - let toggle = should_hide_fields(count_fields); - if toggle { - toggle_open(&mut w, format_args!("{count_fields} fields")); - } - for field in fields { - if let clean::StructFieldItem(ref ty) = *field.kind { - write!( - w, - "\n{tab} {vis}{name}: {ty},", - vis = visibility_print_with_space(field.visibility(tcx), field.item_id, cx), - name = field.name.unwrap(), - ty = ty.print(cx), - ); + match ty { + None => { + let mut buf = Buffer::html(); + let where_displayed = + g.map(|g| print_where_clause_and_check(&mut buf, g, cx)).unwrap_or(false); + + // If there wasn't a `where` clause, we add a whitespace. + write!(f, "{}{}{{", buf.into_inner(), if !where_displayed { " " } else { "" })?; + + let count_fields = fields + .iter() + .filter(|item| matches!(*item.kind, clean::StructFieldItem(..))) + .count(); + let has_visible_fields = count_fields > 0; + let toggle = should_hide_fields(count_fields); + if toggle { + toggle_open(&mut f, format_args!("{} fields", count_fields)); + } + for field in fields { + if let clean::StructFieldItem(ref ty) = *field.kind { + write!( + f, + "\n{} {}{}: {},", + tab, + visibility_print_with_space(field.visibility(tcx), field.item_id, cx), + field.name.unwrap(), + ty.print(cx), + )?; + } } - } - if has_visible_fields { - if has_stripped_entries { - write!(w, "\n{tab} /* private fields */"); + if has_visible_fields { + if it.has_stripped_entries().unwrap() { + write!(f, "\n{} /* private fields */", tab)?; + } + write!(f, "\n{}", tab)?; + } else if it.has_stripped_entries().unwrap() { + f.write_str(" /* private fields */ ")?; } - write!(w, "\n{tab}"); - } else if has_stripped_entries { - write!(w, " /* private fields */ "); - } - if toggle { - toggle_close(&mut w); + if toggle { + toggle_close(&mut f); + } + f.write_str("}")?; } - w.write_str("}"); - } - Some(CtorKind::Fn) => { - w.write_str("("); - if fields.iter().all(|field| { - matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) - }) { - write!(w, "/* private fields */"); - } else { + Some(CtorKind::Fn) => { + f.write_str("(")?; for (i, field) in fields.iter().enumerate() { if i > 0 { - w.write_str(", "); + f.write_str(", ")?; } match *field.kind { - clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"), + clean::StrippedItem(box clean::StructFieldItem(..)) => { + f.write_str("_")?; + } clean::StructFieldItem(ref ty) => { write!( - w, + f, "{}{}", visibility_print_with_space( field.visibility(tcx), @@ -2185,29 +2225,30 @@ fn render_struct_fields( cx ), ty.print(cx), - ) + )?; } _ => unreachable!(), } } + f.write_str(")")?; + if let Some(g) = g { + write!(f, "{}", print_where_clause(g, cx, 0, Ending::NoNewline))?; + } + // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct. + if structhead { + f.write_str(";")?; + } } - w.write_str(")"); - if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline)); - } - // We only want a ";" when we are displaying a tuple struct, not a variant tuple struct. - if structhead { - w.write_str(";"); - } - } - Some(CtorKind::Const) => { - // Needed for PhantomData. - if let Some(g) = g { - write!(w, "{}", print_where_clause(g, cx, 0, Ending::NoNewline)); + Some(CtorKind::Const) => { + // Needed for PhantomData. + if let Some(g) = g { + write!(f, "{}", print_where_clause(g, cx, 0, Ending::NoNewline))?; + } + f.write_str(";")?; } - w.write_str(";"); } - } + Ok(()) + }) } fn document_non_exhaustive_header(item: &clean::Item) -> &str { diff --git a/src/librustdoc/html/templates/item_struct.html b/src/librustdoc/html/templates/item_struct.html new file mode 100644 index 0000000000000..22d7d1f66ceb5 --- /dev/null +++ b/src/librustdoc/html/templates/item_struct.html @@ -0,0 +1,26 @@ +

+    {{ self.render_attributes_in_code() | safe }}
+    {{ self.render_struct() | safe }}
+
+{{ self.document() | safe }} +{% if self.should_render_fields %} +

+ {% if self.s.ctor_kind.is_none() %} + Fields + {% else %} + Tuple Fields + {% endif %} + {{ self::document_non_exhaustive_header(self.it) | safe }} + § +

+ {{ self::document_non_exhaustive(self.it) | safe }} + {% for field in self.struct_field_items_iter() %} + + § + {{ field.name|safe }}: {{~ field.ty|safe }} + + {{ self.document_field(field.item) | safe }} + {% endfor %} +{% endif %} +{{ self.render_assoc_items() | safe }} +{{ self.document_type_layout() | safe }}