mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-29 02:03:53 +00:00
rustdoc: migrate item_union
to an Askama template
This commit is contained in:
parent
19f9f658d6
commit
c325fda0bf
@ -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)
|
||||
}
|
||||
|
@ -660,7 +660,7 @@ fn short_item_info(
|
||||
// "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
|
||||
pub(crate) fn render_impls(
|
||||
cx: &mut Context<'_>,
|
||||
w: &mut Buffer,
|
||||
mut w: impl Write,
|
||||
impls: &[&Impl],
|
||||
containing_item: &clean::Item,
|
||||
toggle_open_by_default: bool,
|
||||
@ -672,7 +672,7 @@ pub(crate) fn render_impls(
|
||||
let did = i.trait_did().unwrap();
|
||||
let provided_trait_methods = i.inner_impl().provided_trait_methods(tcx);
|
||||
let assoc_link = AssocItemLink::GotoSource(did.into(), &provided_trait_methods);
|
||||
let mut buffer = if w.is_for_html() { Buffer::html() } else { Buffer::new() };
|
||||
let mut buffer = Buffer::new();
|
||||
render_impl(
|
||||
&mut buffer,
|
||||
cx,
|
||||
@ -693,7 +693,7 @@ pub(crate) fn render_impls(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
rendered_impls.sort();
|
||||
w.write_str(&rendered_impls.join(""));
|
||||
w.write_str(&rendered_impls.join("")).unwrap();
|
||||
}
|
||||
|
||||
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
|
||||
@ -1080,61 +1080,68 @@ impl<'a> AssocItemLink<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_impl_section_heading(w: &mut Buffer, title: &str, id: &str) {
|
||||
fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) {
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"{id}\" class=\"small-section-header\">\
|
||||
{title}\
|
||||
<a href=\"#{id}\" class=\"anchor\">§</a>\
|
||||
</h2>"
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn render_all_impls(
|
||||
w: &mut Buffer,
|
||||
mut w: impl Write,
|
||||
cx: &mut Context<'_>,
|
||||
containing_item: &clean::Item,
|
||||
concrete: &[&Impl],
|
||||
synthetic: &[&Impl],
|
||||
blanket_impl: &[&Impl],
|
||||
) {
|
||||
let mut impls = Buffer::empty_from(w);
|
||||
let mut impls = Buffer::html();
|
||||
render_impls(cx, &mut impls, concrete, containing_item, true);
|
||||
let impls = impls.into_inner();
|
||||
if !impls.is_empty() {
|
||||
write_impl_section_heading(w, "Trait Implementations", "trait-implementations");
|
||||
write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls);
|
||||
write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations");
|
||||
write!(w, "<div id=\"trait-implementations-list\">{}</div>", impls).unwrap();
|
||||
}
|
||||
|
||||
if !synthetic.is_empty() {
|
||||
write_impl_section_heading(w, "Auto Trait Implementations", "synthetic-implementations");
|
||||
w.write_str("<div id=\"synthetic-implementations-list\">");
|
||||
render_impls(cx, w, synthetic, containing_item, false);
|
||||
w.write_str("</div>");
|
||||
write_impl_section_heading(
|
||||
&mut w,
|
||||
"Auto Trait Implementations",
|
||||
"synthetic-implementations",
|
||||
);
|
||||
w.write_str("<div id=\"synthetic-implementations-list\">").unwrap();
|
||||
render_impls(cx, &mut w, synthetic, containing_item, false);
|
||||
w.write_str("</div>").unwrap();
|
||||
}
|
||||
|
||||
if !blanket_impl.is_empty() {
|
||||
write_impl_section_heading(w, "Blanket Implementations", "blanket-implementations");
|
||||
w.write_str("<div id=\"blanket-implementations-list\">");
|
||||
render_impls(cx, w, blanket_impl, containing_item, false);
|
||||
w.write_str("</div>");
|
||||
write_impl_section_heading(&mut w, "Blanket Implementations", "blanket-implementations");
|
||||
w.write_str("<div id=\"blanket-implementations-list\">").unwrap();
|
||||
render_impls(cx, &mut w, blanket_impl, containing_item, false);
|
||||
w.write_str("</div>").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_assoc_items(
|
||||
w: &mut Buffer,
|
||||
cx: &mut Context<'_>,
|
||||
containing_item: &clean::Item,
|
||||
fn render_assoc_items<'a, 'cx: 'a>(
|
||||
cx: &'a mut Context<'cx>,
|
||||
containing_item: &'a clean::Item,
|
||||
it: DefId,
|
||||
what: AssocItemRender<'_>,
|
||||
) {
|
||||
what: AssocItemRender<'a>,
|
||||
) -> impl fmt::Display + 'a + Captures<'cx> {
|
||||
let mut derefs = DefIdSet::default();
|
||||
derefs.insert(it);
|
||||
render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
|
||||
display_fn(move |f| {
|
||||
render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn render_assoc_items_inner(
|
||||
w: &mut Buffer,
|
||||
mut w: &mut dyn fmt::Write,
|
||||
cx: &mut Context<'_>,
|
||||
containing_item: &clean::Item,
|
||||
it: DefId,
|
||||
@ -1147,7 +1154,7 @@ fn render_assoc_items_inner(
|
||||
let Some(v) = cache.impls.get(&it) else { return };
|
||||
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
|
||||
if !non_trait.is_empty() {
|
||||
let mut tmp_buf = Buffer::empty_from(w);
|
||||
let mut tmp_buf = Buffer::html();
|
||||
let (render_mode, id) = match what {
|
||||
AssocItemRender::All => {
|
||||
write_impl_section_heading(&mut tmp_buf, "Implementations", "implementations");
|
||||
@ -1171,7 +1178,7 @@ fn render_assoc_items_inner(
|
||||
(RenderMode::ForDeref { mut_: deref_mut_ }, cx.derive_id(id))
|
||||
}
|
||||
};
|
||||
let mut impls_buf = Buffer::empty_from(w);
|
||||
let mut impls_buf = Buffer::html();
|
||||
for i in &non_trait {
|
||||
render_impl(
|
||||
&mut impls_buf,
|
||||
@ -1191,10 +1198,10 @@ fn render_assoc_items_inner(
|
||||
);
|
||||
}
|
||||
if !impls_buf.is_empty() {
|
||||
w.push_buffer(tmp_buf);
|
||||
write!(w, "<div id=\"{}\">", id);
|
||||
w.push_buffer(impls_buf);
|
||||
w.write_str("</div>");
|
||||
write!(w, "{}", tmp_buf.into_inner()).unwrap();
|
||||
write!(w, "<div id=\"{}\">", id).unwrap();
|
||||
write!(w, "{}", impls_buf.into_inner()).unwrap();
|
||||
w.write_str("</div>").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1204,7 +1211,7 @@ fn render_assoc_items_inner(
|
||||
if let Some(impl_) = deref_impl {
|
||||
let has_deref_mut =
|
||||
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
|
||||
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
|
||||
render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs);
|
||||
}
|
||||
|
||||
// If we were already one level into rendering deref methods, we don't want to render
|
||||
@ -1223,7 +1230,7 @@ fn render_assoc_items_inner(
|
||||
}
|
||||
|
||||
fn render_deref_methods(
|
||||
w: &mut Buffer,
|
||||
mut w: impl Write,
|
||||
cx: &mut Context<'_>,
|
||||
impl_: &Impl,
|
||||
container_item: &clean::Item,
|
||||
@ -1255,10 +1262,10 @@ fn render_deref_methods(
|
||||
return;
|
||||
}
|
||||
}
|
||||
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
|
||||
render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
|
||||
} else if let Some(prim) = target.primitive_type() {
|
||||
if let Some(&did) = cache.primitive_locations.get(&prim) {
|
||||
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
|
||||
render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ fn should_hide_fields(n_fields: usize) -> bool {
|
||||
n_fields > 12
|
||||
}
|
||||
|
||||
fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
|
||||
fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) {
|
||||
write!(
|
||||
w,
|
||||
"<details class=\"toggle type-contents-toggle\">\
|
||||
@ -210,11 +210,12 @@ fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
|
||||
<span>Show {}</span>\
|
||||
</summary>",
|
||||
text
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn toggle_close(w: &mut Buffer) {
|
||||
w.write_str("</details>");
|
||||
fn toggle_close(mut w: impl fmt::Write) {
|
||||
w.write_str("</details>").unwrap();
|
||||
}
|
||||
|
||||
fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
|
||||
@ -580,7 +581,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
let must_implement_one_of_functions = tcx.trait_def(t.def_id).must_implement_one_of.clone();
|
||||
|
||||
// Output the trait definition
|
||||
wrap_item(w, |w| {
|
||||
wrap_item(w, |mut w| {
|
||||
write!(
|
||||
w,
|
||||
"{attrs}{}{}{}trait {}{}{}",
|
||||
@ -610,7 +611,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
if should_hide_fields(count_types) {
|
||||
toggle = true;
|
||||
toggle_open(
|
||||
w,
|
||||
&mut w,
|
||||
format_args!("{} associated items", count_types + count_consts + count_methods),
|
||||
);
|
||||
}
|
||||
@ -634,7 +635,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
if !toggle && should_hide_fields(count_types + count_consts) {
|
||||
toggle = true;
|
||||
toggle_open(
|
||||
w,
|
||||
&mut w,
|
||||
format_args!(
|
||||
"{} associated constant{} and {} method{}",
|
||||
count_consts,
|
||||
@ -662,7 +663,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
if !toggle && should_hide_fields(count_methods) {
|
||||
toggle = true;
|
||||
toggle_open(w, format_args!("{} methods", count_methods));
|
||||
toggle_open(&mut w, format_args!("{} methods", count_methods));
|
||||
}
|
||||
if count_consts != 0 && count_methods != 0 {
|
||||
w.write_str("\n");
|
||||
@ -710,7 +711,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
}
|
||||
if toggle {
|
||||
toggle_close(w);
|
||||
toggle_close(&mut w);
|
||||
}
|
||||
w.write_str("}");
|
||||
}
|
||||
@ -847,7 +848,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
|
||||
}
|
||||
|
||||
// If there are methods directly on this trait object, render them here.
|
||||
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All);
|
||||
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All));
|
||||
|
||||
let cloned_shared = Rc::clone(&cx.shared);
|
||||
let cache = &cloned_shared.cache;
|
||||
@ -1074,7 +1075,7 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &
|
||||
// won't be visible anywhere in the docs. It would be nice to also show
|
||||
// associated items from the aliased type (see discussion in #32077), but
|
||||
// we need #14072 to make sense of the generics.
|
||||
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
|
||||
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
|
||||
}
|
||||
|
||||
fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) {
|
||||
@ -1096,7 +1097,7 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl
|
||||
// won't be visible anywhere in the docs. It would be nice to also show
|
||||
// associated items from the aliased type (see discussion in #32077), but
|
||||
// we need #14072 to make sense of the generics.
|
||||
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
|
||||
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
|
||||
}
|
||||
|
||||
fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) {
|
||||
@ -1124,54 +1125,102 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
|
||||
// won't be visible anywhere in the docs. It would be nice to also show
|
||||
// associated items from the aliased type (see discussion in #32077), but
|
||||
// we need #14072 to make sense of the generics.
|
||||
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
|
||||
document_type_layout(w, cx, def_id);
|
||||
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
|
||||
write!(w, "{}", document_type_layout(cx, def_id));
|
||||
}
|
||||
|
||||
fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) {
|
||||
wrap_item(w, |w| {
|
||||
write!(w, "{}", render_attributes_in_pre(it, ""));
|
||||
render_union(w, it, Some(&s.generics), &s.fields, cx);
|
||||
});
|
||||
#[derive(Template)]
|
||||
#[template(path = "item_union.html")]
|
||||
struct ItemUnion<'a, 'cx> {
|
||||
cx: std::cell::RefCell<&'a mut Context<'cx>>,
|
||||
it: &'a clean::Item,
|
||||
s: &'a clean::Union,
|
||||
}
|
||||
|
||||
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
|
||||
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
|
||||
fn render_assoc_items<'b>(
|
||||
&'b self,
|
||||
) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
|
||||
display_fn(move |f| {
|
||||
let def_id = self.it.item_id.expect_def_id();
|
||||
let mut cx = self.cx.borrow_mut();
|
||||
let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All);
|
||||
write!(f, "{v}")
|
||||
})
|
||||
}
|
||||
fn document_type_layout<'b>(
|
||||
&'b self,
|
||||
) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
|
||||
display_fn(move |f| {
|
||||
let def_id = self.it.item_id.expect_def_id();
|
||||
let cx = self.cx.borrow_mut();
|
||||
let v = document_type_layout(*cx, def_id);
|
||||
write!(f, "{v}")
|
||||
})
|
||||
}
|
||||
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 render_attributes_in_pre<'b>(
|
||||
&'b self,
|
||||
) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
|
||||
display_fn(move |f| {
|
||||
let v = render_attributes_in_pre(self.it, "");
|
||||
write!(f, "{v}")
|
||||
})
|
||||
}
|
||||
fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> {
|
||||
display_fn(move |f| {
|
||||
let mut cx = self.cx.borrow_mut();
|
||||
let v = document(*cx, self.it, None, HeadingOffset::H2);
|
||||
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<String> {
|
||||
let cx = self.cx.borrow();
|
||||
field.stability_class(cx.tcx())
|
||||
}
|
||||
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}")
|
||||
})
|
||||
}
|
||||
|
||||
let mut fields = s
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|f| match *f.kind {
|
||||
clean::StructFieldItem(ref ty) => Some((f, ty)),
|
||||
_ => None,
|
||||
})
|
||||
.peekable();
|
||||
if fields.peek().is_some() {
|
||||
write!(
|
||||
w,
|
||||
"<h2 id=\"fields\" class=\"fields small-section-header\">\
|
||||
Fields<a href=\"#fields\" class=\"anchor\">§</a>\
|
||||
</h2>"
|
||||
);
|
||||
for (field, ty) in fields {
|
||||
let name = field.name.expect("union field name");
|
||||
let id = format!("{}.{}", ItemType::StructField, name);
|
||||
write!(
|
||||
w,
|
||||
"<span id=\"{id}\" class=\"{shortty} small-section-header\">\
|
||||
<a href=\"#{id}\" class=\"anchor field\">§</a>\
|
||||
<code>{name}: {ty}</code>\
|
||||
</span>",
|
||||
shortty = ItemType::StructField,
|
||||
ty = ty.print(cx),
|
||||
);
|
||||
if let Some(stability_class) = field.stability_class(cx.tcx()) {
|
||||
write!(w, "<span class=\"stab {stability_class}\"></span>");
|
||||
}
|
||||
write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3));
|
||||
fn fields_iter(
|
||||
&self,
|
||||
) -> std::iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> {
|
||||
self.s
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|f| match *f.kind {
|
||||
clean::StructFieldItem(ref ty) => Some((f, ty)),
|
||||
_ => None,
|
||||
})
|
||||
.peekable()
|
||||
}
|
||||
}
|
||||
let def_id = it.item_id.expect_def_id();
|
||||
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
|
||||
document_type_layout(w, cx, def_id);
|
||||
|
||||
ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap();
|
||||
}
|
||||
|
||||
fn print_tuple_struct_fields<'a, 'cx: 'a>(
|
||||
@ -1196,7 +1245,7 @@ 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| {
|
||||
wrap_item(w, |mut w| {
|
||||
write!(
|
||||
w,
|
||||
"{attrs}{}enum {}{}",
|
||||
@ -1217,7 +1266,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
|
||||
w.write_str("{\n");
|
||||
let toggle = should_hide_fields(count_variants);
|
||||
if toggle {
|
||||
toggle_open(w, format_args!("{} variants", count_variants));
|
||||
toggle_open(&mut w, format_args!("{} variants", count_variants));
|
||||
}
|
||||
for v in e.variants() {
|
||||
w.write_str(" ");
|
||||
@ -1242,7 +1291,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
|
||||
w.write_str(" // some variants omitted\n");
|
||||
}
|
||||
if toggle {
|
||||
toggle_close(w);
|
||||
toggle_close(&mut w);
|
||||
}
|
||||
w.write_str("}");
|
||||
}
|
||||
@ -1255,11 +1304,12 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
|
||||
w,
|
||||
"<h2 id=\"variants\" class=\"variants small-section-header\">\
|
||||
Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\
|
||||
</h2>",
|
||||
document_non_exhaustive_header(it)
|
||||
</h2>\
|
||||
{}\
|
||||
<div class=\"variants\">",
|
||||
document_non_exhaustive_header(it),
|
||||
document_non_exhaustive(it)
|
||||
);
|
||||
document_non_exhaustive(w, it);
|
||||
write!(w, "<div class=\"variants\">");
|
||||
for variant in e.variants() {
|
||||
let id = cx.derive_id(format!("{}.{}", ItemType::Variant, variant.name.unwrap()));
|
||||
write!(
|
||||
@ -1304,9 +1354,10 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
|
||||
write!(
|
||||
w,
|
||||
"<div class=\"sub-variant\" id=\"{variant_id}\">\
|
||||
<h4>{heading}</h4>",
|
||||
<h4>{heading}</h4>\
|
||||
{}",
|
||||
document_non_exhaustive(variant)
|
||||
);
|
||||
document_non_exhaustive(w, variant);
|
||||
for field in fields {
|
||||
match *field.kind {
|
||||
clean::StrippedItem(box clean::StructFieldItem(_)) => {}
|
||||
@ -1343,8 +1394,8 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
|
||||
write!(w, "</div>");
|
||||
}
|
||||
let def_id = it.item_id.expect_def_id();
|
||||
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
|
||||
document_type_layout(w, cx, def_id);
|
||||
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
|
||||
write!(w, "{}", document_type_layout(cx, def_id));
|
||||
}
|
||||
|
||||
fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) {
|
||||
@ -1382,7 +1433,7 @@ fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
|
||||
let def_id = it.item_id.expect_def_id();
|
||||
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
|
||||
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
|
||||
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
|
||||
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
|
||||
} else {
|
||||
// We handle the "reference" primitive type on its own because we only want to list
|
||||
// implementations on generic types.
|
||||
@ -1463,11 +1514,12 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
|
||||
w,
|
||||
"<h2 id=\"fields\" class=\"fields small-section-header\">\
|
||||
{}{}<a href=\"#fields\" class=\"anchor\">§</a>\
|
||||
</h2>",
|
||||
</h2>\
|
||||
{}",
|
||||
if s.ctor_kind.is_none() { "Fields" } else { "Tuple Fields" },
|
||||
document_non_exhaustive_header(it)
|
||||
document_non_exhaustive_header(it),
|
||||
document_non_exhaustive(it)
|
||||
);
|
||||
document_non_exhaustive(w, 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());
|
||||
@ -1486,8 +1538,8 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
|
||||
}
|
||||
}
|
||||
let def_id = it.item_id.expect_def_id();
|
||||
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
|
||||
document_type_layout(w, cx, def_id);
|
||||
write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All));
|
||||
write!(w, "{}", document_type_layout(cx, def_id));
|
||||
}
|
||||
|
||||
fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) {
|
||||
@ -1519,7 +1571,7 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
|
||||
|
||||
write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
|
||||
|
||||
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
|
||||
write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All))
|
||||
}
|
||||
|
||||
fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
|
||||
@ -1660,64 +1712,69 @@ fn render_implementor(
|
||||
);
|
||||
}
|
||||
|
||||
fn render_union(
|
||||
w: &mut Buffer,
|
||||
it: &clean::Item,
|
||||
g: Option<&clean::Generics>,
|
||||
fields: &[clean::Item],
|
||||
cx: &Context<'_>,
|
||||
) {
|
||||
let tcx = cx.tcx();
|
||||
write!(
|
||||
w,
|
||||
"{}union {}",
|
||||
visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
|
||||
it.name.unwrap(),
|
||||
);
|
||||
fn render_union<'a, 'cx: 'a>(
|
||||
it: &'a clean::Item,
|
||||
g: Option<&'a clean::Generics>,
|
||||
fields: &'a [clean::Item],
|
||||
cx: &'a Context<'cx>,
|
||||
) -> impl fmt::Display + 'a + Captures<'cx> {
|
||||
display_fn(move |mut f| {
|
||||
let tcx = cx.tcx();
|
||||
write!(
|
||||
f,
|
||||
"{}union {}",
|
||||
visibility_print_with_space(it.visibility(tcx), it.item_id, cx),
|
||||
it.name.unwrap(),
|
||||
)?;
|
||||
|
||||
let where_displayed = g
|
||||
.map(|g| {
|
||||
write!(w, "{}", g.print(cx));
|
||||
print_where_clause_and_check(w, g, cx)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let where_displayed = g
|
||||
.map(|g| {
|
||||
let mut buf = Buffer::html();
|
||||
write!(buf, "{}", g.print(cx));
|
||||
let where_displayed = print_where_clause_and_check(&mut buf, g, cx);
|
||||
write!(f, "{buf}", buf = buf.into_inner()).unwrap();
|
||||
where_displayed
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
// If there wasn't a `where` clause, we add a whitespace.
|
||||
if !where_displayed {
|
||||
w.write_str(" ");
|
||||
}
|
||||
|
||||
write!(w, "{{\n");
|
||||
let count_fields =
|
||||
fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
|
||||
let toggle = should_hide_fields(count_fields);
|
||||
if toggle {
|
||||
toggle_open(w, format_args!("{} fields", count_fields));
|
||||
}
|
||||
|
||||
for field in fields {
|
||||
if let clean::StructFieldItem(ref ty) = *field.kind {
|
||||
write!(
|
||||
w,
|
||||
" {}{}: {},\n",
|
||||
visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
|
||||
field.name.unwrap(),
|
||||
ty.print(cx)
|
||||
);
|
||||
// If there wasn't a `where` clause, we add a whitespace.
|
||||
if !where_displayed {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
}
|
||||
|
||||
if it.has_stripped_entries().unwrap() {
|
||||
write!(w, " /* private fields */\n");
|
||||
}
|
||||
if toggle {
|
||||
toggle_close(w);
|
||||
}
|
||||
w.write_str("}");
|
||||
write!(f, "{{\n")?;
|
||||
let count_fields =
|
||||
fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count();
|
||||
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",
|
||||
visibility_print_with_space(field.visibility(tcx), field.item_id, cx),
|
||||
field.name.unwrap(),
|
||||
ty.print(cx)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if it.has_stripped_entries().unwrap() {
|
||||
write!(f, " /* private fields */\n")?;
|
||||
}
|
||||
if toggle {
|
||||
toggle_close(&mut f);
|
||||
}
|
||||
f.write_str("}").unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn render_struct(
|
||||
w: &mut Buffer,
|
||||
mut w: &mut Buffer,
|
||||
it: &clean::Item,
|
||||
g: Option<&clean::Generics>,
|
||||
ty: Option<CtorKind>,
|
||||
@ -1752,7 +1809,7 @@ fn render_struct(
|
||||
let has_visible_fields = count_fields > 0;
|
||||
let toggle = should_hide_fields(count_fields);
|
||||
if toggle {
|
||||
toggle_open(w, format_args!("{} fields", count_fields));
|
||||
toggle_open(&mut w, format_args!("{} fields", count_fields));
|
||||
}
|
||||
for field in fields {
|
||||
if let clean::StructFieldItem(ref ty) = *field.kind {
|
||||
@ -1776,7 +1833,7 @@ fn render_struct(
|
||||
write!(w, " /* private fields */ ");
|
||||
}
|
||||
if toggle {
|
||||
toggle_close(w);
|
||||
toggle_close(&mut w);
|
||||
}
|
||||
w.write_str("}");
|
||||
}
|
||||
@ -1822,161 +1879,169 @@ fn document_non_exhaustive_header(item: &clean::Item) -> &str {
|
||||
if item.is_non_exhaustive() { " (Non-exhaustive)" } else { "" }
|
||||
}
|
||||
|
||||
fn document_non_exhaustive(w: &mut Buffer, item: &clean::Item) {
|
||||
if item.is_non_exhaustive() {
|
||||
write!(
|
||||
w,
|
||||
"<details class=\"toggle non-exhaustive\">\
|
||||
<summary class=\"hideme\"><span>{}</span></summary>\
|
||||
<div class=\"docblock\">",
|
||||
{
|
||||
if item.is_struct() {
|
||||
"This struct is marked as non-exhaustive"
|
||||
} else if item.is_enum() {
|
||||
"This enum is marked as non-exhaustive"
|
||||
} else if item.is_variant() {
|
||||
"This variant is marked as non-exhaustive"
|
||||
} else {
|
||||
"This type is marked as non-exhaustive"
|
||||
fn document_non_exhaustive<'a>(item: &'a clean::Item) -> impl fmt::Display + 'a {
|
||||
display_fn(|f| {
|
||||
if item.is_non_exhaustive() {
|
||||
write!(
|
||||
f,
|
||||
"<details class=\"toggle non-exhaustive\">\
|
||||
<summary class=\"hideme\"><span>{}</span></summary>\
|
||||
<div class=\"docblock\">",
|
||||
{
|
||||
if item.is_struct() {
|
||||
"This struct is marked as non-exhaustive"
|
||||
} else if item.is_enum() {
|
||||
"This enum is marked as non-exhaustive"
|
||||
} else if item.is_variant() {
|
||||
"This variant is marked as non-exhaustive"
|
||||
} else {
|
||||
"This type is marked as non-exhaustive"
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
if item.is_struct() {
|
||||
f.write_str(
|
||||
"Non-exhaustive structs could have additional fields added in future. \
|
||||
Therefore, non-exhaustive structs cannot be constructed in external crates \
|
||||
using the traditional <code>Struct { .. }</code> syntax; cannot be \
|
||||
matched against without a wildcard <code>..</code>; and \
|
||||
struct update syntax will not work.",
|
||||
)?;
|
||||
} else if item.is_enum() {
|
||||
f.write_str(
|
||||
"Non-exhaustive enums could have additional variants added in future. \
|
||||
Therefore, when matching against variants of non-exhaustive enums, an \
|
||||
extra wildcard arm must be added to account for any future variants.",
|
||||
)?;
|
||||
} else if item.is_variant() {
|
||||
f.write_str(
|
||||
"Non-exhaustive enum variants could have additional fields added in future. \
|
||||
Therefore, non-exhaustive enum variants cannot be constructed in external \
|
||||
crates and cannot be matched against.",
|
||||
)?;
|
||||
} else {
|
||||
f.write_str(
|
||||
"This type will require a wildcard arm in any match statements or constructors.",
|
||||
)?;
|
||||
}
|
||||
);
|
||||
|
||||
if item.is_struct() {
|
||||
w.write_str(
|
||||
"Non-exhaustive structs could have additional fields added in future. \
|
||||
Therefore, non-exhaustive structs cannot be constructed in external crates \
|
||||
using the traditional <code>Struct { .. }</code> syntax; cannot be \
|
||||
matched against without a wildcard <code>..</code>; and \
|
||||
struct update syntax will not work.",
|
||||
);
|
||||
} else if item.is_enum() {
|
||||
w.write_str(
|
||||
"Non-exhaustive enums could have additional variants added in future. \
|
||||
Therefore, when matching against variants of non-exhaustive enums, an \
|
||||
extra wildcard arm must be added to account for any future variants.",
|
||||
);
|
||||
} else if item.is_variant() {
|
||||
w.write_str(
|
||||
"Non-exhaustive enum variants could have additional fields added in future. \
|
||||
Therefore, non-exhaustive enum variants cannot be constructed in external \
|
||||
crates and cannot be matched against.",
|
||||
);
|
||||
} else {
|
||||
w.write_str(
|
||||
"This type will require a wildcard arm in any match statements or constructors.",
|
||||
);
|
||||
f.write_str("</div></details>")?;
|
||||
}
|
||||
|
||||
w.write_str("</div></details>");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
|
||||
fn write_size_of_layout(w: &mut Buffer, layout: &LayoutS, tag_size: u64) {
|
||||
fn document_type_layout<'a, 'cx: 'a>(
|
||||
cx: &'a Context<'cx>,
|
||||
ty_def_id: DefId,
|
||||
) -> impl fmt::Display + 'a + Captures<'cx> {
|
||||
fn write_size_of_layout(mut w: impl fmt::Write, layout: &LayoutS, tag_size: u64) {
|
||||
if layout.abi.is_unsized() {
|
||||
write!(w, "(unsized)");
|
||||
write!(w, "(unsized)").unwrap();
|
||||
} else {
|
||||
let size = layout.size.bytes() - tag_size;
|
||||
write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" },);
|
||||
write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" }).unwrap();
|
||||
if layout.abi.is_uninhabited() {
|
||||
write!(
|
||||
w,
|
||||
" (<a href=\"https://doc.rust-lang.org/stable/reference/glossary.html#uninhabited\">uninhabited</a>)"
|
||||
);
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !cx.shared.show_type_layout {
|
||||
return;
|
||||
}
|
||||
display_fn(move |mut f| {
|
||||
if !cx.shared.show_type_layout {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(
|
||||
w,
|
||||
"<h2 id=\"layout\" class=\"small-section-header\"> \
|
||||
Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>"
|
||||
);
|
||||
writeln!(w, "<div class=\"docblock\">");
|
||||
writeln!(
|
||||
f,
|
||||
"<h2 id=\"layout\" class=\"small-section-header\"> \
|
||||
Layout<a href=\"#layout\" class=\"anchor\">§</a></h2>"
|
||||
)?;
|
||||
writeln!(f, "<div class=\"docblock\">")?;
|
||||
|
||||
let tcx = cx.tcx();
|
||||
let param_env = tcx.param_env(ty_def_id);
|
||||
let ty = tcx.type_of(ty_def_id).subst_identity();
|
||||
match tcx.layout_of(param_env.and(ty)) {
|
||||
Ok(ty_layout) => {
|
||||
writeln!(
|
||||
w,
|
||||
"<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \
|
||||
<strong>completely unstable</strong> and may even differ between compilations. \
|
||||
The only exception is types with certain <code>repr(...)</code> attributes. \
|
||||
Please see the Rust Reference’s \
|
||||
<a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \
|
||||
chapter for details on type layout guarantees.</p></div>"
|
||||
);
|
||||
w.write_str("<p><strong>Size:</strong> ");
|
||||
write_size_of_layout(w, &ty_layout.layout.0, 0);
|
||||
writeln!(w, "</p>");
|
||||
if let Variants::Multiple { variants, tag, tag_encoding, .. } =
|
||||
&ty_layout.layout.variants()
|
||||
{
|
||||
if !variants.is_empty() {
|
||||
w.write_str(
|
||||
"<p><strong>Size for each variant:</strong></p>\
|
||||
<ul>",
|
||||
);
|
||||
let tcx = cx.tcx();
|
||||
let param_env = tcx.param_env(ty_def_id);
|
||||
let ty = tcx.type_of(ty_def_id).subst_identity();
|
||||
match tcx.layout_of(param_env.and(ty)) {
|
||||
Ok(ty_layout) => {
|
||||
writeln!(
|
||||
f,
|
||||
"<div class=\"warning\"><p><strong>Note:</strong> Most layout information is \
|
||||
<strong>completely unstable</strong> and may even differ between compilations. \
|
||||
The only exception is types with certain <code>repr(...)</code> attributes. \
|
||||
Please see the Rust Reference’s \
|
||||
<a href=\"https://doc.rust-lang.org/reference/type-layout.html\">“Type Layout”</a> \
|
||||
chapter for details on type layout guarantees.</p></div>"
|
||||
)?;
|
||||
f.write_str("<p><strong>Size:</strong> ")?;
|
||||
write_size_of_layout(&mut f, &ty_layout.layout.0, 0);
|
||||
writeln!(f, "</p>")?;
|
||||
if let Variants::Multiple { variants, tag, tag_encoding, .. } =
|
||||
&ty_layout.layout.variants()
|
||||
{
|
||||
if !variants.is_empty() {
|
||||
f.write_str(
|
||||
"<p><strong>Size for each variant:</strong></p>\
|
||||
<ul>",
|
||||
)?;
|
||||
|
||||
let Adt(adt, _) = ty_layout.ty.kind() else {
|
||||
span_bug!(tcx.def_span(ty_def_id), "not an adt")
|
||||
};
|
||||
let Adt(adt, _) = ty_layout.ty.kind() else {
|
||||
span_bug!(tcx.def_span(ty_def_id), "not an adt")
|
||||
};
|
||||
|
||||
let tag_size = if let TagEncoding::Niche { .. } = tag_encoding {
|
||||
0
|
||||
} else if let Primitive::Int(i, _) = tag.primitive() {
|
||||
i.size().bytes()
|
||||
} else {
|
||||
span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int")
|
||||
};
|
||||
let tag_size = if let TagEncoding::Niche { .. } = tag_encoding {
|
||||
0
|
||||
} else if let Primitive::Int(i, _) = tag.primitive() {
|
||||
i.size().bytes()
|
||||
} else {
|
||||
span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int")
|
||||
};
|
||||
|
||||
for (index, layout) in variants.iter_enumerated() {
|
||||
let name = adt.variant(index).name;
|
||||
write!(w, "<li><code>{name}</code>: ");
|
||||
write_size_of_layout(w, layout, tag_size);
|
||||
writeln!(w, "</li>");
|
||||
for (index, layout) in variants.iter_enumerated() {
|
||||
let name = adt.variant(index).name;
|
||||
write!(&mut f, "<li><code>{name}</code>: ")?;
|
||||
write_size_of_layout(&mut f, layout, tag_size);
|
||||
writeln!(&mut f, "</li>")?;
|
||||
}
|
||||
f.write_str("</ul>")?;
|
||||
}
|
||||
w.write_str("</ul>");
|
||||
}
|
||||
}
|
||||
// This kind of layout error can occur with valid code, e.g. if you try to
|
||||
// get the layout of a generic type such as `Vec<T>`.
|
||||
Err(LayoutError::Unknown(_)) => {
|
||||
writeln!(
|
||||
f,
|
||||
"<p><strong>Note:</strong> Unable to compute type layout, \
|
||||
possibly due to this type having generic parameters. \
|
||||
Layout can only be computed for concrete, fully-instantiated types.</p>"
|
||||
)?;
|
||||
}
|
||||
// This kind of error probably can't happen with valid code, but we don't
|
||||
// want to panic and prevent the docs from building, so we just let the
|
||||
// user know that we couldn't compute the layout.
|
||||
Err(LayoutError::SizeOverflow(_)) => {
|
||||
writeln!(
|
||||
f,
|
||||
"<p><strong>Note:</strong> Encountered an error during type layout; \
|
||||
the type was too big.</p>"
|
||||
)?;
|
||||
}
|
||||
Err(LayoutError::NormalizationFailure(_, _)) => {
|
||||
writeln!(
|
||||
f,
|
||||
"<p><strong>Note:</strong> Encountered an error during type layout; \
|
||||
the type failed to be normalized.</p>"
|
||||
)?;
|
||||
}
|
||||
}
|
||||
// This kind of layout error can occur with valid code, e.g. if you try to
|
||||
// get the layout of a generic type such as `Vec<T>`.
|
||||
Err(LayoutError::Unknown(_)) => {
|
||||
writeln!(
|
||||
w,
|
||||
"<p><strong>Note:</strong> Unable to compute type layout, \
|
||||
possibly due to this type having generic parameters. \
|
||||
Layout can only be computed for concrete, fully-instantiated types.</p>"
|
||||
);
|
||||
}
|
||||
// This kind of error probably can't happen with valid code, but we don't
|
||||
// want to panic and prevent the docs from building, so we just let the
|
||||
// user know that we couldn't compute the layout.
|
||||
Err(LayoutError::SizeOverflow(_)) => {
|
||||
writeln!(
|
||||
w,
|
||||
"<p><strong>Note:</strong> Encountered an error during type layout; \
|
||||
the type was too big.</p>"
|
||||
);
|
||||
}
|
||||
Err(LayoutError::NormalizationFailure(_, _)) => {
|
||||
writeln!(
|
||||
w,
|
||||
"<p><strong>Note:</strong> Encountered an error during type layout; \
|
||||
the type failed to be normalized.</p>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(w, "</div>");
|
||||
writeln!(f, "</div>")
|
||||
})
|
||||
}
|
||||
|
||||
fn pluralize(count: usize) -> &'static str {
|
||||
|
23
src/librustdoc/html/templates/item_union.html
Normal file
23
src/librustdoc/html/templates/item_union.html
Normal file
@ -0,0 +1,23 @@
|
||||
<pre class="rust item-decl"><code>
|
||||
{{ self.render_attributes_in_pre() | safe }}
|
||||
{{ self.render_union() | safe }}
|
||||
</code></pre>
|
||||
{{ self.document() | safe }}
|
||||
{% if self.fields_iter().peek().is_some() %}
|
||||
<h2 id="fields" class="fields small-section-header">
|
||||
Fields<a href="#fields" class="anchor">§</a>
|
||||
</h2>
|
||||
{% for (field, ty) in self.fields_iter() %}
|
||||
{% let name = field.name.expect("union field name") %}
|
||||
<span id="structfield.{{ name }}" class="{{ ItemType::StructField }} small-section-header">
|
||||
<a href="#structfield.{{ name }}" class="anchor field">§</a>
|
||||
<code>{{ name }}: {{ self.print_ty(ty) | safe }}</code>
|
||||
</span>
|
||||
{% if let Some(stability_class) = self.stability_field(field) %}
|
||||
<span class="stab {{ stability_class }}"></span>
|
||||
{% endif %}
|
||||
{{ self.document_field(field) | safe }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{{ self.render_assoc_items() | safe }}
|
||||
{{ self.document_type_layout() | safe }}
|
Loading…
Reference in New Issue
Block a user