From 6da2b7dd8f9e32fe94969ebcf8f8745c2b2108aa Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 1 Dec 2023 11:04:34 -0700 Subject: [PATCH 1/5] rustdoc: do not escape quotes in body text Escaping quote marks is only needed in attributes, not text. ```console $ du -hs doc-old/ doc-new/ 670M doc-old/ 669M doc-new/ ``` --- src/librustdoc/html/escape.rs | 36 +++++++++++++++++++ src/librustdoc/html/highlight.rs | 12 +++++-- .../html/highlight/fixtures/dos_line.html | 2 +- .../html/highlight/fixtures/sample.html | 8 ++--- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index 4a19d0a44c3..ea4b573aeb9 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -38,3 +38,39 @@ impl<'a> fmt::Display for Escape<'a> { Ok(()) } } + +/// Wrapper struct which will emit the HTML-escaped version of the contained +/// string when passed to a format string. +/// +/// This is only safe to use for text nodes. If you need your output to be +/// safely contained in an attribute, use [`Escape`]. If you don't know the +/// difference, use [`Escape`]. +pub(crate) struct EscapeBodyText<'a>(pub &'a str); + +impl<'a> fmt::Display for EscapeBodyText<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + // Because the internet is always right, turns out there's not that many + // characters to escape: http://stackoverflow.com/questions/7381974 + let EscapeBodyText(s) = *self; + let pile_o_bits = s; + let mut last = 0; + for (i, ch) in s.char_indices() { + let s = match ch { + '>' => ">", + '<' => "<", + '&' => "&", + _ => continue, + }; + fmt.write_str(&pile_o_bits[last..i])?; + fmt.write_str(s)?; + // NOTE: we only expect single byte characters here - which is fine as long as we + // only match single byte characters + last = i + 1; + } + + if last < s.len() { + fmt.write_str(&pile_o_bits[last..])?; + } + Ok(()) + } +} diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index b762c8a1ce6..1cdc792a819 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -6,7 +6,7 @@ //! Use the `render_with_highlighting` to highlight some rust code. use crate::clean::PrimitiveType; -use crate::html::escape::Escape; +use crate::html::escape::EscapeBodyText; use crate::html::render::{Context, LinkFromSrc}; use std::collections::VecDeque; @@ -189,7 +189,7 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> { && can_merge(current_class, Some(*parent_class), "") { for (text, class) in self.pending_elems.iter() { - string(self.out, Escape(text), *class, &self.href_context, false); + string(self.out, EscapeBodyText(text), *class, &self.href_context, false); } } else { // We only want to "open" the tag ourselves if we have more than one pending and if the @@ -202,7 +202,13 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> { None }; for (text, class) in self.pending_elems.iter() { - string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none()); + string( + self.out, + EscapeBodyText(text), + *class, + &self.href_context, + close_tag.is_none(), + ); } if let Some(close_tag) = close_tag { exit_span(self.out, close_tag); diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html index 30b50ca7c66..b98e6712590 100644 --- a/src/librustdoc/html/highlight/fixtures/dos_line.html +++ b/src/librustdoc/html/highlight/fixtures/dos_line.html @@ -1,3 +1,3 @@ pub fn foo() { -println!("foo"); +println!("foo"); } diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html index fced2eacd9e..aa735e81597 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.html +++ b/src/librustdoc/html/highlight/fixtures/sample.html @@ -8,12 +8,12 @@ .lifetime { color: #B76514; } .question-mark { color: #ff9011; } -
#![crate_type = "lib"]
+
#![crate_type = "lib"]
 
 use std::path::{Path, PathBuf};
 
-#[cfg(target_os = "linux")]
-#[cfg(target_os = "windows")]
+#[cfg(target_os = "linux")]
+#[cfg(target_os = "windows")]
 fn main() -> () {
     let foo = true && false || true;
     let _: *const () = 0;
@@ -22,7 +22,7 @@
     let _ = *foo;
     mac!(foo, &mut bar);
     assert!(self.length < N && index <= self.length);
-    ::std::env::var("gateau").is_ok();
+    ::std::env::var("gateau").is_ok();
     #[rustfmt::skip]
     let s:std::path::PathBuf = std::path::PathBuf::new();
     let mut s = String::new();

From edabd477f86c48929f12b4c2f5a15d58cde120a5 Mon Sep 17 00:00:00 2001
From: Ralf Jung 
Date: Sun, 3 Dec 2023 11:51:58 +0100
Subject: [PATCH 2/5] interpret: make numeric_intrinsic accessible from Miri

---
 .../src/interpret/intrinsics.rs               | 82 ++++++++-----------
 1 file changed, 36 insertions(+), 46 deletions(-)

diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
index 80e14f5a884..c29f23b913f 100644
--- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs
+++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs
@@ -3,17 +3,22 @@
 //! and miri.
 
 use rustc_hir::def_id::DefId;
-use rustc_middle::mir::{
-    self,
-    interpret::{Allocation, ConstAllocation, GlobalId, InterpResult, PointerArithmetic, Scalar},
-    BinOp, ConstValue, NonDivergingIntrinsic,
-};
 use rustc_middle::ty;
 use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement};
 use rustc_middle::ty::GenericArgsRef;
 use rustc_middle::ty::{Ty, TyCtxt};
+use rustc_middle::{
+    mir::{
+        self,
+        interpret::{
+            Allocation, ConstAllocation, GlobalId, InterpResult, PointerArithmetic, Scalar,
+        },
+        BinOp, ConstValue, NonDivergingIntrinsic,
+    },
+    ty::layout::TyAndLayout,
+};
 use rustc_span::symbol::{sym, Symbol};
-use rustc_target::abi::{Abi, Primitive, Size};
+use rustc_target::abi::Size;
 
 use super::{
     util::ensure_monomorphic_enough, CheckInAllocMsg, ImmTy, InterpCx, Machine, OpTy, PlaceTy,
@@ -22,23 +27,6 @@ use super::{
 
 use crate::fluent_generated as fluent;
 
-fn numeric_intrinsic(name: Symbol, bits: u128, kind: Primitive) -> Scalar {
-    let size = match kind {
-        Primitive::Int(integer, _) => integer.size(),
-        _ => bug!("invalid `{}` argument: {:?}", name, bits),
-    };
-    let extra = 128 - u128::from(size.bits());
-    let bits_out = match name {
-        sym::ctpop => u128::from(bits.count_ones()),
-        sym::ctlz => u128::from(bits.leading_zeros()) - extra,
-        sym::cttz => u128::from((bits << extra).trailing_zeros()) - extra,
-        sym::bswap => (bits << extra).swap_bytes(),
-        sym::bitreverse => (bits << extra).reverse_bits(),
-        _ => bug!("not a numeric intrinsic: {}", name),
-    };
-    Scalar::from_uint(bits_out, size)
-}
-
 /// Directly returns an `Allocation` containing an absolute path representation of the given type.
 pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ConstAllocation<'tcx> {
     let path = crate::util::type_name(tcx, ty);
@@ -179,30 +167,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
             | sym::bswap
             | sym::bitreverse => {
                 let ty = instance_args.type_at(0);
-                let layout_of = self.layout_of(ty)?;
+                let layout = self.layout_of(ty)?;
                 let val = self.read_scalar(&args[0])?;
-                let bits = val.to_bits(layout_of.size)?;
-                let kind = match layout_of.abi {
-                    Abi::Scalar(scalar) => scalar.primitive(),
-                    _ => span_bug!(
-                        self.cur_span(),
-                        "{} called on invalid type {:?}",
-                        intrinsic_name,
-                        ty
-                    ),
-                };
-                let (nonzero, actual_intrinsic_name) = match intrinsic_name {
-                    sym::cttz_nonzero => (true, sym::cttz),
-                    sym::ctlz_nonzero => (true, sym::ctlz),
-                    other => (false, other),
-                };
-                if nonzero && bits == 0 {
-                    throw_ub_custom!(
-                        fluent::const_eval_call_nonzero_intrinsic,
-                        name = intrinsic_name,
-                    );
-                }
-                let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind);
+                let out_val = self.numeric_intrinsic(intrinsic_name, val, layout)?;
                 self.write_scalar(out_val, dest)?;
             }
             sym::saturating_add | sym::saturating_sub => {
@@ -493,6 +460,29 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         }
     }
 
+    pub fn numeric_intrinsic(
+        &self,
+        name: Symbol,
+        val: Scalar,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, Scalar> {
+        assert!(layout.ty.is_integral(), "invalid type for numeric intrinsic: {}", layout.ty);
+        let bits = val.to_bits(layout.size)?;
+        let extra = 128 - u128::from(layout.size.bits());
+        let bits_out = match name {
+            sym::ctpop => u128::from(bits.count_ones()),
+            sym::ctlz_nonzero | sym::cttz_nonzero if bits == 0 => {
+                throw_ub_custom!(fluent::const_eval_call_nonzero_intrinsic, name = name,);
+            }
+            sym::ctlz | sym::ctlz_nonzero => u128::from(bits.leading_zeros()) - extra,
+            sym::cttz | sym::cttz_nonzero => u128::from((bits << extra).trailing_zeros()) - extra,
+            sym::bswap => (bits << extra).swap_bytes(),
+            sym::bitreverse => (bits << extra).reverse_bits(),
+            _ => bug!("not a numeric intrinsic: {}", name),
+        };
+        Ok(Scalar::from_uint(bits_out, layout.size))
+    }
+
     pub fn exact_div(
         &mut self,
         a: &ImmTy<'tcx, M::Provenance>,

From bcfeaabaf3f942401c23e3edefa9de798ab2dc2f Mon Sep 17 00:00:00 2001
From: Ralf Jung 
Date: Mon, 4 Dec 2023 08:16:03 +0100
Subject: [PATCH 3/5] portable-simd: add missing feature gate

---
 library/portable-simd/crates/core_simd/tests/pointers.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/library/portable-simd/crates/core_simd/tests/pointers.rs b/library/portable-simd/crates/core_simd/tests/pointers.rs
index a90ff928ced..b9f32d16e01 100644
--- a/library/portable-simd/crates/core_simd/tests/pointers.rs
+++ b/library/portable-simd/crates/core_simd/tests/pointers.rs
@@ -1,4 +1,4 @@
-#![feature(portable_simd, strict_provenance)]
+#![feature(portable_simd, strict_provenance, exposed_provenance)]
 
 use core_simd::simd::{
     ptr::{SimdConstPtr, SimdMutPtr},

From 1c556bbed4a02a914c152c96c18c04835338ba83 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez 
Date: Mon, 4 Dec 2023 12:12:13 +0100
Subject: [PATCH 4/5] Don't generate the "Fields" heading if there is no field
 displayed

---
 src/librustdoc/html/render/print_item.rs | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 131b1d608e6..ff7ce01e807 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1737,7 +1737,14 @@ fn item_variants(
         w.write_str("");
 
         let heading_and_fields = match &variant_data.kind {
-            clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)),
+            clean::VariantKind::Struct(s) => {
+                // If there is no field to display, no need to add the heading.
+                if s.fields.iter().any(|f| !f.is_doc_hidden()) {
+                    Some(("Fields", &s.fields))
+                } else {
+                    None
+                }
+            }
             clean::VariantKind::Tuple(fields) => {
                 // Documentation on tuple variant fields is rare, so to reduce noise we only emit
                 // the section if at least one field is documented.

From 8e53edb2ece6cc6ab6715fef5b3332ae8ffee8a2 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez 
Date: Mon, 4 Dec 2023 12:13:24 +0100
Subject: [PATCH 5/5] Add regression test for #118195

---
 tests/rustdoc/enum-variant-fields-heading.rs   | 18 ++++++++++++++++++
 .../enum-variant-fields-heading.variants.html  |  3 +++
 2 files changed, 21 insertions(+)
 create mode 100644 tests/rustdoc/enum-variant-fields-heading.rs
 create mode 100644 tests/rustdoc/enum-variant-fields-heading.variants.html

diff --git a/tests/rustdoc/enum-variant-fields-heading.rs b/tests/rustdoc/enum-variant-fields-heading.rs
new file mode 100644
index 00000000000..8a7c99a8735
--- /dev/null
+++ b/tests/rustdoc/enum-variant-fields-heading.rs
@@ -0,0 +1,18 @@
+// This is a regression test for .
+// It ensures that the "Fields" heading is not generated if no field is displayed.
+
+#![crate_name = "foo"]
+
+// @has 'foo/enum.Foo.html'
+// @has - '//*[@id="variant.A"]' 'A'
+// @count - '//*[@id="variant.A.fields"]' 0
+// @has - '//*[@id="variant.B"]' 'B'
+// @count - '//*[@id="variant.B.fields"]' 0
+// @snapshot variants - '//*[@id="main-content"]/*[@class="variants"]'
+
+pub enum Foo {
+    /// A variant with no fields
+    A {},
+    /// A variant with hidden fields
+    B { #[doc(hidden)] a: u8 },
+}
diff --git a/tests/rustdoc/enum-variant-fields-heading.variants.html b/tests/rustdoc/enum-variant-fields-heading.variants.html
new file mode 100644
index 00000000000..bcb36f7cf86
--- /dev/null
+++ b/tests/rustdoc/enum-variant-fields-heading.variants.html
@@ -0,0 +1,3 @@
+
§

A

A variant with no fields

+
§

B

A variant with hidden fields

+
\ No newline at end of file