Skip to content

Commit 940fb08

Browse files
committed
Move HTML-specific attribute rendering code into HTML rendering module
These functions used to be shared with the JSON backend but nowadays they no longer are. Moveover, HTML-escape the contents of `link_section` and `export_name` and remove redundant attribute-related tests.
1 parent 7d22152 commit 940fb08

File tree

8 files changed

+176
-213
lines changed

8 files changed

+176
-213
lines changed

src/librustdoc/clean/types.rs

Lines changed: 0 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::borrow::Cow;
21
use std::hash::Hash;
32
use std::path::PathBuf;
43
use std::sync::{Arc, OnceLock as OnceCell};
@@ -773,38 +772,6 @@ impl Item {
773772
Some(tcx.visibility(def_id))
774773
}
775774

776-
// FIXME(fmease): Move into html/ mod, this is for HTML output only.
777-
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
778-
let mut attrs: Vec<_> = self
779-
.attrs
780-
.other_attrs
781-
.iter()
782-
.filter_map(|attr| match attr {
783-
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
784-
Some(format!("#[unsafe(link_section = \"{name}\")]"))
785-
}
786-
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
787-
Some("#[unsafe(no_mangle)]".to_string())
788-
}
789-
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
790-
Some(format!("#[unsafe(export_name = \"{name}\")]"))
791-
}
792-
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
793-
Some("#[non_exhaustive]".to_string())
794-
}
795-
_ => None,
796-
})
797-
.collect();
798-
799-
if let Some(def_id) = self.def_id()
800-
&& let Some(attr) = repr_attribute(tcx, cache, def_id)
801-
{
802-
attrs.push(attr);
803-
}
804-
805-
attrs
806-
}
807-
808775
pub fn is_doc_hidden(&self) -> bool {
809776
self.attrs.is_doc_hidden()
810777
}
@@ -814,109 +781,6 @@ impl Item {
814781
}
815782
}
816783

817-
/// Compute the *public* `#[repr]` of the item given by `DefId`.
818-
///
819-
/// Read more about it here:
820-
/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
821-
///
822-
/// For use in the HTML output only.
823-
// FIXME(fmease): Move into html/ mod
824-
pub(crate) fn repr_attribute<'tcx>(
825-
tcx: TyCtxt<'tcx>,
826-
cache: &Cache,
827-
def_id: DefId,
828-
) -> Option<String> {
829-
let adt = match tcx.def_kind(def_id) {
830-
DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id),
831-
_ => return None,
832-
};
833-
let repr = adt.repr();
834-
835-
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
836-
let is_public_field = |field: &ty::FieldDef| {
837-
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
838-
};
839-
840-
if repr.transparent() {
841-
// The transparent repr is public iff the non-1-ZST field is public and visible or
842-
// – in case all fields are 1-ZST fields — at least one field is public and visible.
843-
let is_public = 'is_public: {
844-
// `#[repr(transparent)]` can only be applied to structs and single-variant enums.
845-
let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
846-
847-
if !is_visible(var.def_id) {
848-
break 'is_public false;
849-
}
850-
851-
// Side note: There can only ever be one or zero non-1-ZST fields.
852-
let non_1zst_field = var.fields.iter().find(|field| {
853-
let ty = ty::TypingEnv::post_analysis(tcx, field.did)
854-
.as_query_input(tcx.type_of(field.did).instantiate_identity());
855-
tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
856-
});
857-
858-
match non_1zst_field {
859-
Some(field) => is_public_field(field),
860-
None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
861-
}
862-
};
863-
864-
// Since the transparent repr can't have any other reprs or
865-
// repr modifiers beside it, we can safely return early here.
866-
return is_public.then(|| "#[repr(transparent)]".into());
867-
}
868-
869-
// Fast path which avoids looking through the variants and fields in
870-
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
871-
// FIXME: This check is not very robust / forward compatible!
872-
if !repr.c()
873-
&& !repr.simd()
874-
&& repr.int.is_none()
875-
&& repr.pack.is_none()
876-
&& repr.align.is_none()
877-
{
878-
return None;
879-
}
880-
881-
// The repr is public iff all components are public and visible.
882-
let is_public = adt
883-
.variants()
884-
.iter()
885-
.all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
886-
if !is_public {
887-
return None;
888-
}
889-
890-
let mut result = Vec::<Cow<'_, _>>::new();
891-
892-
if repr.c() {
893-
result.push("C".into());
894-
}
895-
if repr.simd() {
896-
result.push("simd".into());
897-
}
898-
if let Some(int) = repr.int {
899-
let prefix = if int.is_signed() { 'i' } else { 'u' };
900-
let int = match int {
901-
rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
902-
rustc_abi::IntegerType::Fixed(int, _) => {
903-
format!("{prefix}{}", int.size().bytes() * 8)
904-
}
905-
};
906-
result.push(int.into());
907-
}
908-
909-
// Render modifiers last.
910-
if let Some(pack) = repr.pack {
911-
result.push(format!("packed({})", pack.bytes()).into());
912-
}
913-
if let Some(align) = repr.align {
914-
result.push(format!("align({})", align.bytes()).into());
915-
}
916-
917-
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")))
918-
}
919-
920784
#[derive(Clone, Debug)]
921785
pub(crate) enum ItemKind {
922786
ExternCrateItem {

src/librustdoc/html/render/mod.rs

Lines changed: 148 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ use askama::Template;
5151
use itertools::Either;
5252
use rustc_ast::join_path_syms;
5353
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
54-
use rustc_hir::attrs::{DeprecatedSince, Deprecation};
54+
use rustc_hir as hir;
55+
use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
56+
use rustc_hir::def::DefKind;
5557
use rustc_hir::def_id::{DefId, DefIdSet};
5658
use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince};
5759
use rustc_middle::ty::print::PrintTraitRefExt;
@@ -1310,38 +1312,6 @@ fn render_assoc_item(
13101312
})
13111313
}
13121314

1313-
struct CodeAttribute(String);
1314-
1315-
fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) {
1316-
write!(
1317-
w,
1318-
"<div class=\"code-attribute\">{prefix}{attr}</div>",
1319-
prefix = prefix,
1320-
attr = code_attr.0
1321-
)
1322-
.unwrap();
1323-
}
1324-
1325-
// When an attribute is rendered inside a <code> tag, it is formatted using
1326-
// a div to produce a newline after it.
1327-
fn render_attributes_in_code(
1328-
w: &mut impl fmt::Write,
1329-
it: &clean::Item,
1330-
prefix: &str,
1331-
cx: &Context<'_>,
1332-
) {
1333-
for attr in it.attributes(cx.tcx(), cx.cache()) {
1334-
render_code_attribute(prefix, CodeAttribute(attr), w);
1335-
}
1336-
}
1337-
1338-
/// used for type aliases to only render their `repr` attribute.
1339-
fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
1340-
if let Some(repr) = clean::repr_attribute(cx.tcx(), cx.cache(), def_id) {
1341-
render_code_attribute("", CodeAttribute(repr), w);
1342-
}
1343-
}
1344-
13451315
#[derive(Copy, Clone)]
13461316
enum AssocItemLink<'a> {
13471317
Anchor(Option<&'a str>),
@@ -2927,3 +2897,148 @@ fn render_call_locations<W: fmt::Write>(
29272897

29282898
w.write_str("</div>")
29292899
}
2900+
2901+
fn render_attributes_in_code(
2902+
w: &mut impl fmt::Write,
2903+
it: &clean::Item,
2904+
prefix: &str,
2905+
cx: &Context<'_>,
2906+
) {
2907+
for attr in &it.attrs.other_attrs {
2908+
render_code_attribute(
2909+
prefix,
2910+
match attr {
2911+
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
2912+
Cow::Owned(format!("#[unsafe(link_section = \"{}\")]", Escape(name.as_str())))
2913+
}
2914+
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
2915+
Cow::Borrowed("#[unsafe(no_mangle)]")
2916+
}
2917+
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
2918+
Cow::Owned(format!("#[unsafe(export_name = \"{}\")]", Escape(name.as_str())))
2919+
}
2920+
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
2921+
Cow::Borrowed("#[non_exhaustive]")
2922+
}
2923+
_ => continue,
2924+
}
2925+
.as_ref(),
2926+
w,
2927+
);
2928+
}
2929+
2930+
if let Some(def_id) = it.def_id()
2931+
&& let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id)
2932+
{
2933+
render_code_attribute(prefix, &repr, w);
2934+
}
2935+
}
2936+
2937+
fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
2938+
if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) {
2939+
render_code_attribute("", &repr, w);
2940+
}
2941+
}
2942+
2943+
fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
2944+
write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
2945+
}
2946+
2947+
fn repr_attribute<'tcx>(
2948+
tcx: TyCtxt<'tcx>,
2949+
cache: &Cache,
2950+
def_id: DefId,
2951+
) -> Option<Cow<'static, str>> {
2952+
// Read more about private vs. public `#[repr]` here:
2953+
// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
2954+
2955+
let adt = match tcx.def_kind(def_id) {
2956+
DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id),
2957+
_ => return None,
2958+
};
2959+
let repr = adt.repr();
2960+
2961+
let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
2962+
let is_public_field = |field: &ty::FieldDef| {
2963+
(cache.document_private || field.vis.is_public()) && is_visible(field.did)
2964+
};
2965+
2966+
if repr.transparent() {
2967+
// The transparent repr is public iff the non-1-ZST field is public and visible or
2968+
// – in case all fields are 1-ZST fields — at least one field is public and visible.
2969+
let is_public = 'is_public: {
2970+
// `#[repr(transparent)]` can only be applied to structs and single-variant enums.
2971+
let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
2972+
2973+
if !is_visible(var.def_id) {
2974+
break 'is_public false;
2975+
}
2976+
2977+
// Side note: There can only ever be one or zero non-1-ZST fields.
2978+
let non_1zst_field = var.fields.iter().find(|field| {
2979+
let ty = ty::TypingEnv::post_analysis(tcx, field.did)
2980+
.as_query_input(tcx.type_of(field.did).instantiate_identity());
2981+
tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
2982+
});
2983+
2984+
match non_1zst_field {
2985+
Some(field) => is_public_field(field),
2986+
None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
2987+
}
2988+
};
2989+
2990+
// Since the transparent repr can't have any other reprs or
2991+
// repr modifiers beside it, we can safely return early here.
2992+
return is_public.then(|| "#[repr(transparent)]".into());
2993+
}
2994+
2995+
// Fast path which avoids looking through the variants and fields in
2996+
// the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
2997+
// FIXME: This check is not very robust / forward compatible!
2998+
if !repr.c()
2999+
&& !repr.simd()
3000+
&& repr.int.is_none()
3001+
&& repr.pack.is_none()
3002+
&& repr.align.is_none()
3003+
{
3004+
return None;
3005+
}
3006+
3007+
// The repr is public iff all components are public and visible.
3008+
let is_public = adt
3009+
.variants()
3010+
.iter()
3011+
.all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
3012+
if !is_public {
3013+
return None;
3014+
}
3015+
3016+
let mut result = Vec::<Cow<'_, _>>::new();
3017+
3018+
if repr.c() {
3019+
result.push("C".into());
3020+
}
3021+
if repr.simd() {
3022+
result.push("simd".into());
3023+
}
3024+
if let Some(int) = repr.int {
3025+
let prefix = if int.is_signed() { 'i' } else { 'u' };
3026+
let int = match int {
3027+
rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
3028+
rustc_abi::IntegerType::Fixed(int, _) => {
3029+
format!("{prefix}{}", int.size().bytes() * 8)
3030+
}
3031+
};
3032+
result.push(int.into());
3033+
}
3034+
3035+
// Render modifiers last.
3036+
if let Some(pack) = repr.pack {
3037+
result.push(format!("packed({})", pack.bytes()).into());
3038+
}
3039+
if let Some(align) = repr.align {
3040+
result.push(format!("align({})", align.bytes()).into());
3041+
}
3042+
3043+
(!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into())
3044+
}

tests/rustdoc/attribute-rendering.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.

tests/rustdoc/attributes.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ pub extern "C" fn f() {}
99
#[unsafe(export_name = "bar")]
1010
pub extern "C" fn g() {}
1111

12+
//@ has foo/fn.escape.html '//*[@class="code-attribute"]' \
13+
// '#[unsafe(export_name = "<script>alert()</script>")]'
14+
#[unsafe(export_name = "<script>alert()</script>")]
15+
pub extern "C" fn escape() {}
16+
1217
//@ has foo/fn.example.html '//*[@class="code-attribute"]' '#[unsafe(link_section = ".text")]'
1318
#[unsafe(link_section = ".text")]
1419
pub extern "C" fn example() {}
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
// Ensure that we render attributes on inlined cross-crate re-exported items.
2+
13
//@ aux-crate:attributes=attributes.rs
24
//@ edition:2021
35
#![crate_name = "user"]
46

5-
//@ has 'user/struct.NonExhaustive.html'
6-
//@ has - '//*[@class="rust item-decl"]//*[@class="code-attribute"]' '#[non_exhaustive]'
7+
//@ has 'user/fn.no_mangle.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
8+
pub use attributes::no_mangle;
9+
10+
//@ has 'user/fn.link_section.html' '//pre[@class="rust item-decl"]' \
11+
// '#[unsafe(link_section = ".here")]'
12+
pub use attributes::link_section;
13+
14+
//@ has 'user/fn.export_name.html' '//pre[@class="rust item-decl"]' \
15+
// '#[unsafe(export_name = "exonym")]'
16+
pub use attributes::export_name;
17+
18+
//@ has 'user/struct.NonExhaustive.html' '//pre[@class="rust item-decl"]' '#[non_exhaustive]'
719
pub use attributes::NonExhaustive;

0 commit comments

Comments
 (0)