about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-09-25 20:35:49 +0000
committerbors <bors@rust-lang.org>2025-09-25 20:35:49 +0000
commit7ac0330c6d684d86d6f86fabe601a3defdc3b234 (patch)
tree5c1a901b7316ab94dc6ad125c5524a23f86bd101 /src
parenteabf390b4ceeb34db9f37e97f435134abbcdea92 (diff)
parentc6d0059fb887c172a9325adae78548c975653572 (diff)
downloadrust-7ac0330c6d684d86d6f86fabe601a3defdc3b234.tar.gz
rust-7ac0330c6d684d86d6f86fabe601a3defdc3b234.zip
Auto merge of #147037 - matthiaskrgr:rollup-xtgqzuu, r=matthiaskrgr
Rollup of 8 pull requests

Successful merges:

 - rust-lang/rust#116882 (rustdoc: hide `#[repr]` if it isn't part of the public ABI)
 - rust-lang/rust#135771 ([rustdoc] Add support for associated items in "jump to def" feature)
 - rust-lang/rust#141032 (avoid violating `slice::from_raw_parts` safety contract in `Vec::extract_if`)
 - rust-lang/rust#142401 (Add proper name mangling for pattern types)
 - rust-lang/rust#146293 (feat: non-panicking `Vec::try_remove`)
 - rust-lang/rust#146859 (BTreeMap: Don't leak allocators when initializing nodes)
 - rust-lang/rust#146924 (Add doc for `NonZero*` const creation)
 - rust-lang/rust#146933 (Make `render_example_with_highlighting` return an `impl fmt::Display`)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rw-r--r--src/doc/rustc/src/symbol-mangling/v0.md28
-rw-r--r--src/doc/rustdoc/src/advanced-features.md20
-rw-r--r--src/librustdoc/clean/types.rs112
-rw-r--r--src/librustdoc/html/highlight.rs250
-rw-r--r--src/librustdoc/html/markdown.rs45
-rw-r--r--src/librustdoc/html/render/mod.rs180
-rw-r--r--src/librustdoc/html/render/print_item.rs8
-rw-r--r--src/librustdoc/html/render/span_map.rs124
-rw-r--r--src/tools/miri/tests/pass/vec.rs10
9 files changed, 431 insertions, 346 deletions
diff --git a/src/doc/rustc/src/symbol-mangling/v0.md b/src/doc/rustc/src/symbol-mangling/v0.md
index 109942518fc..2bcc453a532 100644
--- a/src/doc/rustc/src/symbol-mangling/v0.md
+++ b/src/doc/rustc/src/symbol-mangling/v0.md
@@ -710,6 +710,7 @@ A *placeholder* may occur in circumstances where a type or const value is not re
 [mut-ptr-type]: #mut-ptr-type
 [fn-type]: #fn-type
 [dyn-trait-type]: #dyn-trait-type
+[pattern-type]: #pattern-type
 
 > type → \
 > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *[basic-type]* \
@@ -722,6 +723,7 @@ A *placeholder* may occur in circumstances where a type or const value is not re
 > &nbsp;&nbsp; | *[mut-ptr-type]* \
 > &nbsp;&nbsp; | *[fn-type]* \
 > &nbsp;&nbsp; | *[dyn-trait-type]* \
+> &nbsp;&nbsp; | *[pattern-type]* \
 > &nbsp;&nbsp; | *[path]* \
 > &nbsp;&nbsp; | *[backref]*
 
@@ -830,6 +832,23 @@ Remaining primitives are encoded as a crate production, e.g. `C4f128`.
 [fn-sig]: #fn-sig
 [abi]: #abi
 
+* `W` — A [pattern-type][pattern-tpye] `u32 is 0..100`.
+  > <span id="pattern-type">pattern-type</span> → `W` *[pattern-kind]*
+  >
+  > <span id="pattern-kind">pattern-kind</span> → \
+  > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *[range-pattern-kind]* \
+  > &nbsp;&nbsp; *[or-pattern-kind]*
+  >
+  > <span id="range-pattern-kind">range-pattern-kind</span> → `R` *[const]* *[const]*
+  >
+  > <span id="or-pattern-kind">or-pattern-kind</span> → `O` *[pattern-kind]* `E`
+
+  While or patterns can be nested in theory, in practice this does not happen and they are instead flattened.
+
+  Range patterns have a start and end constant that are both included in the range.
+  The end must be larger than the start (there can be no wraparound). To emulate wraparound,
+  you need to use an or pattern of the two ranges to the upper limit and from the lower limit.
+
 * `D` — A [trait object][reference-trait-object] `dyn Trait<Assoc=X> + Send + 'a`.
 
   > <span id="dyn-trait-type">dyn-trait-type</span> → `D` *[dyn-bounds]* *[lifetime]*
@@ -1139,6 +1158,7 @@ The following is a summary of all of the productions of the symbol grammar.
 > &nbsp;&nbsp; | *[mut-ptr-type]* \
 > &nbsp;&nbsp; | *[fn-type]* \
 > &nbsp;&nbsp; | *[dyn-trait-type]* \
+> &nbsp;&nbsp; | *[pattern-type]* \
 > &nbsp;&nbsp; | *[path]* \
 > &nbsp;&nbsp; | *[backref]*
 >
@@ -1152,6 +1172,14 @@ The following is a summary of all of the productions of the symbol grammar.
 > [mut-ptr-type] → `O` *[type]* \
 > [fn-type] → `F` *[fn-sig]* \
 > [dyn-trait-type] → `D` *[dyn-bounds]* *[lifetime]*
+> [pattern-type] → `W` *[pattern-kind]*
+>
+> [pattern-kind] → \
+> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *[range-pattern-kind]* \
+> &nbsp;&nbsp; *[or-pattern-kind]*
+>
+> [range-pattern-kind] -> `R` *[const]* *[const]* \
+> [or-pattern-kind] -> `O` *[pattern-kind]* `E` \
 >
 > [namespace] → *[lower]* | *[upper]*
 >
diff --git a/src/doc/rustdoc/src/advanced-features.md b/src/doc/rustdoc/src/advanced-features.md
index c02c9aebe7e..f49edb2ac78 100644
--- a/src/doc/rustdoc/src/advanced-features.md
+++ b/src/doc/rustdoc/src/advanced-features.md
@@ -89,20 +89,30 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
 This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL
 to automatically go to the first result.
 
-## `#[repr(transparent)]`: Documenting the transparent representation
+## `#[repr(...)]`: Documenting the representation of a type
+
+Generally, rustdoc only displays the representation of a given type if none of its variants are
+`#[doc(hidden)]` and if all of its fields are public and not `#[doc(hidden)]` since it's likely
+not meant to be considered part of the public ABI otherwise.
+
+Note that there's no way to overwrite that heuristic and force rustdoc to show the representation
+regardless.
+
+### `#[repr(transparent)]`
 
 You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
 in the [Rustonomicon][repr-trans-nomicon].
 
 Since this representation is only considered part of the public ABI if the single field with non-trivial
-size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays
-the attribute if and only if the non-1-ZST field is public or at least one field is public in case all
-fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized.
+size or alignment is public and if the documentation does not state otherwise, rustdoc helpfully displays
+the attribute if and only if the non-1-ZST field is public and not `#[doc(hidden)]` or
+– in case all fields are 1-ZST fields — at least one field is public and not `#[doc(hidden)]`.
+The term *1-ZST* refers to types that are one-aligned and zero-sized.
 
 It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
 if one wishes to declare the representation as private even if the non-1-ZST field is public.
 However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work.
-Therefore, if you would like to do so, you should always write it down in prose independently of whether
+Therefore, if you would like to do so, you should always write that down in prose independently of whether
 you use `cfg_attr` or not.
 
 [repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index bd3f4e9a6f2..ff513c71035 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -794,50 +794,6 @@ impl Item {
         Some(tcx.visibility(def_id))
     }
 
-    /// Get a list of attributes excluding `#[repr]` to display.
-    ///
-    /// Only used by the HTML output-format.
-    fn attributes_without_repr(&self) -> Vec<String> {
-        self.attrs
-            .other_attrs
-            .iter()
-            .filter_map(|attr| match attr {
-                hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
-                    Some(format!("#[unsafe(link_section = \"{name}\")]"))
-                }
-                hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
-                    Some("#[unsafe(no_mangle)]".to_string())
-                }
-                hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
-                    Some(format!("#[unsafe(export_name = \"{name}\")]"))
-                }
-                hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
-                    Some("#[non_exhaustive]".to_string())
-                }
-                _ => None,
-            })
-            .collect()
-    }
-
-    /// Get a list of attributes to display on this item.
-    ///
-    /// Only used by the HTML output-format.
-    pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
-        let mut attrs = self.attributes_without_repr();
-
-        if let Some(repr_attr) = self.repr(tcx, cache) {
-            attrs.push(repr_attr);
-        }
-        attrs
-    }
-
-    /// Returns a stringified `#[repr(...)]` attribute.
-    ///
-    /// Only used by the HTML output-format.
-    pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
-        repr_attributes(tcx, cache, self.def_id()?, self.type_())
-    }
-
     pub fn is_doc_hidden(&self) -> bool {
         self.attrs.is_doc_hidden()
     }
@@ -847,74 +803,6 @@ impl Item {
     }
 }
 
-/// Return a string representing the `#[repr]` attribute if present.
-///
-/// Only used by the HTML output-format.
-pub(crate) fn repr_attributes(
-    tcx: TyCtxt<'_>,
-    cache: &Cache,
-    def_id: DefId,
-    item_type: ItemType,
-) -> Option<String> {
-    use rustc_abi::IntegerType;
-
-    if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
-        return None;
-    }
-    let adt = tcx.adt_def(def_id);
-    let repr = adt.repr();
-    let mut out = Vec::new();
-    if repr.c() {
-        out.push("C");
-    }
-    if repr.transparent() {
-        // Render `repr(transparent)` iff the non-1-ZST field is public or at least one
-        // field is public in case all fields are 1-ZST fields.
-        let render_transparent = cache.document_private
-            || adt
-                .all_fields()
-                .find(|field| {
-                    let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
-                    tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
-                        .is_ok_and(|layout| !layout.is_1zst())
-                })
-                .map_or_else(
-                    || adt.all_fields().any(|field| field.vis.is_public()),
-                    |field| field.vis.is_public(),
-                );
-
-        if render_transparent {
-            out.push("transparent");
-        }
-    }
-    if repr.simd() {
-        out.push("simd");
-    }
-    let pack_s;
-    if let Some(pack) = repr.pack {
-        pack_s = format!("packed({})", pack.bytes());
-        out.push(&pack_s);
-    }
-    let align_s;
-    if let Some(align) = repr.align {
-        align_s = format!("align({})", align.bytes());
-        out.push(&align_s);
-    }
-    let int_s;
-    if let Some(int) = repr.int {
-        int_s = match int {
-            IntegerType::Pointer(is_signed) => {
-                format!("{}size", if is_signed { 'i' } else { 'u' })
-            }
-            IntegerType::Fixed(size, is_signed) => {
-                format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
-            }
-        };
-        out.push(&int_s);
-    }
-    if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
-}
-
 #[derive(Clone, Debug)]
 pub(crate) enum ItemKind {
     ExternCrateItem {
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 0e06361024b..fad15573cde 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -8,6 +8,7 @@
 use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::fmt::{self, Display, Write};
+use std::iter;
 
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind};
@@ -15,8 +16,9 @@ use rustc_span::edition::Edition;
 use rustc_span::symbol::Symbol;
 use rustc_span::{BytePos, DUMMY_SP, Span};
 
-use super::format::{self, write_str};
+use super::format;
 use crate::clean::PrimitiveType;
+use crate::display::Joined as _;
 use crate::html::escape::EscapeBodyText;
 use crate::html::macro_expansion::ExpandedCode;
 use crate::html::render::{Context, LinkFromSrc};
@@ -45,92 +47,72 @@ pub(crate) enum Tooltip {
     CompileFail,
     ShouldPanic,
     Edition(Edition),
-    None,
 }
 
 /// Highlights `src` as an inline example, returning the HTML output.
 pub(crate) fn render_example_with_highlighting(
     src: &str,
-    out: &mut String,
-    tooltip: Tooltip,
+    tooltip: Option<&Tooltip>,
     playground_button: Option<&str>,
     extra_classes: &[String],
-) {
-    write_header(out, "rust-example-rendered", None, tooltip, extra_classes);
-    write_code(out, src, None, None, None);
-    write_footer(out, playground_button);
+) -> impl Display {
+    fmt::from_fn(move |f| {
+        write_header("rust-example-rendered", tooltip, extra_classes).fmt(f)?;
+        write_code(f, src, None, None, None);
+        write_footer(playground_button).fmt(f)
+    })
 }
 
-fn write_header(
-    out: &mut String,
-    class: &str,
-    extra_content: Option<&str>,
-    tooltip: Tooltip,
-    extra_classes: &[String],
-) {
-    write_str(
-        out,
-        format_args!(
+fn write_header(class: &str, tooltip: Option<&Tooltip>, extra_classes: &[String]) -> impl Display {
+    fmt::from_fn(move |f| {
+        write!(
+            f,
             "<div class=\"example-wrap{}\">",
-            match tooltip {
-                Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore",
-                Tooltip::CompileFail => " compile_fail",
-                Tooltip::ShouldPanic => " should_panic",
-                Tooltip::Edition(_) => " edition",
-                Tooltip::None => "",
-            }
-        ),
-    );
-
-    if tooltip != Tooltip::None {
-        let tooltip = fmt::from_fn(|f| match &tooltip {
-            Tooltip::IgnoreAll => f.write_str("This example is not tested"),
-            Tooltip::IgnoreSome(platforms) => {
-                f.write_str("This example is not tested on ")?;
-                match &platforms[..] {
-                    [] => unreachable!(),
-                    [platform] => f.write_str(platform)?,
-                    [first, second] => write!(f, "{first} or {second}")?,
-                    [platforms @ .., last] => {
-                        for platform in platforms {
-                            write!(f, "{platform}, ")?;
+            tooltip
+                .map(|tooltip| match tooltip {
+                    Tooltip::IgnoreAll | Tooltip::IgnoreSome(_) => " ignore",
+                    Tooltip::CompileFail => " compile_fail",
+                    Tooltip::ShouldPanic => " should_panic",
+                    Tooltip::Edition(_) => " edition",
+                })
+                .unwrap_or_default()
+        )?;
+
+        if let Some(tooltip) = tooltip {
+            let tooltip = fmt::from_fn(|f| match tooltip {
+                Tooltip::IgnoreAll => f.write_str("This example is not tested"),
+                Tooltip::IgnoreSome(platforms) => {
+                    f.write_str("This example is not tested on ")?;
+                    match &platforms[..] {
+                        [] => unreachable!(),
+                        [platform] => f.write_str(platform)?,
+                        [first, second] => write!(f, "{first} or {second}")?,
+                        [platforms @ .., last] => {
+                            for platform in platforms {
+                                write!(f, "{platform}, ")?;
+                            }
+                            write!(f, "or {last}")?;
                         }
-                        write!(f, "or {last}")?;
                     }
+                    Ok(())
                 }
-                Ok(())
-            }
-            Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"),
-            Tooltip::ShouldPanic => f.write_str("This example panics"),
-            Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"),
-            Tooltip::None => unreachable!(),
+                Tooltip::CompileFail => f.write_str("This example deliberately fails to compile"),
+                Tooltip::ShouldPanic => f.write_str("This example panics"),
+                Tooltip::Edition(edition) => write!(f, "This example runs with edition {edition}"),
+            });
+
+            write!(f, "<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>")?;
+        }
+
+        let classes = fmt::from_fn(|f| {
+            iter::once("rust")
+                .chain(Some(class).filter(|class| !class.is_empty()))
+                .chain(extra_classes.iter().map(String::as_str))
+                .joined(" ", f)
         });
-        write_str(out, format_args!("<a href=\"#\" class=\"tooltip\" title=\"{tooltip}\">ⓘ</a>"));
-    }
 
-    if let Some(extra) = extra_content {
-        out.push_str(extra);
-    }
-    if class.is_empty() {
-        write_str(
-            out,
-            format_args!(
-                "<pre class=\"rust{}{}\">",
-                if extra_classes.is_empty() { "" } else { " " },
-                extra_classes.join(" ")
-            ),
-        );
-    } else {
-        write_str(
-            out,
-            format_args!(
-                "<pre class=\"rust {class}{}{}\">",
-                if extra_classes.is_empty() { "" } else { " " },
-                extra_classes.join(" ")
-            ),
-        );
-    }
-    write_str(out, format_args!("<code>"));
+        write!(f, "<pre class=\"{classes}\"><code>")
+    })
 }
 
 /// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
@@ -577,8 +559,8 @@ pub(super) fn write_code(
     });
 }
 
-fn write_footer(out: &mut String, playground_button: Option<&str>) {
-    write_str(out, format_args!("</code></pre>{}</div>", playground_button.unwrap_or_default()));
+fn write_footer(playground_button: Option<&str>) -> impl Display {
+    fmt::from_fn(move |f| write!(f, "</code></pre>{}</div>", playground_button.unwrap_or_default()))
 }
 
 /// How a span of text is classified. Mostly corresponds to token kinds.
@@ -1262,6 +1244,64 @@ fn string<W: Write>(
     }
 }
 
+fn generate_link_to_def(
+    out: &mut impl Write,
+    text_s: &str,
+    klass: Class,
+    href_context: &Option<HrefContext<'_, '_>>,
+    def_span: Span,
+    open_tag: bool,
+) -> bool {
+    if let Some(href_context) = href_context
+        && let Some(href) =
+            href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| {
+                let context = href_context.context;
+                // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
+                // one to the documentation page and one to the source definition.
+                // FIXME: currently, external items only generate a link to their documentation,
+                // a link to their definition can be generated using this:
+                // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
+                match href {
+                    LinkFromSrc::Local(span) => {
+                        context.href_from_span_relative(*span, &href_context.current_href)
+                    }
+                    LinkFromSrc::External(def_id) => {
+                        format::href_with_root_path(*def_id, context, Some(href_context.root_path))
+                            .ok()
+                            .map(|(url, _, _)| url)
+                    }
+                    LinkFromSrc::Primitive(prim) => format::href_with_root_path(
+                        PrimitiveType::primitive_locations(context.tcx())[prim],
+                        context,
+                        Some(href_context.root_path),
+                    )
+                    .ok()
+                    .map(|(url, _, _)| url),
+                    LinkFromSrc::Doc(def_id) => {
+                        format::href_with_root_path(*def_id, context, Some(href_context.root_path))
+                            .ok()
+                            .map(|(doc_link, _, _)| doc_link)
+                    }
+                }
+            })
+    {
+        if !open_tag {
+            // We're already inside an element which has the same klass, no need to give it
+            // again.
+            write!(out, "<a href=\"{href}\">{text_s}").unwrap();
+        } else {
+            let klass_s = klass.as_html();
+            if klass_s.is_empty() {
+                write!(out, "<a href=\"{href}\">{text_s}").unwrap();
+            } else {
+                write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
+            }
+        }
+        return true;
+    }
+    false
+}
+
 /// This function writes `text` into `out` with some modifications depending on `klass`:
 ///
 /// * If `klass` is `None`, `text` is written into `out` with no modification.
@@ -1291,10 +1331,14 @@ fn string_without_closing_tag<T: Display>(
         return Some("</span>");
     };
 
+    let mut added_links = false;
     let mut text_s = text.to_string();
     if text_s.contains("::") {
+        let mut span = def_span.with_hi(def_span.lo());
         text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
+            span = span.with_hi(span.hi() + BytePos(t.len() as _));
             match t {
+                "::" => write!(&mut path, "::"),
                 "self" | "Self" => write!(
                     &mut path,
                     "<span class=\"{klass}\">{t}</span>",
@@ -1307,58 +1351,24 @@ fn string_without_closing_tag<T: Display>(
                         klass = Class::KeyWord.as_html(),
                     )
                 }
-                t => write!(&mut path, "{t}"),
+                t => {
+                    if !t.is_empty()
+                        && generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
+                    {
+                        added_links = true;
+                        write!(&mut path, "</a>")
+                    } else {
+                        write!(&mut path, "{t}")
+                    }
+                }
             }
             .expect("Failed to build source HTML path");
+            span = span.with_lo(span.lo() + BytePos(t.len() as _));
             path
         });
     }
 
-    if let Some(href_context) = href_context
-        && let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span)
-        && let Some(href) = {
-            let context = href_context.context;
-            // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
-            // one to the documentation page and one to the source definition.
-            // FIXME: currently, external items only generate a link to their documentation,
-            // a link to their definition can be generated using this:
-            // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
-            match href {
-                LinkFromSrc::Local(span) => {
-                    context.href_from_span_relative(*span, &href_context.current_href)
-                }
-                LinkFromSrc::External(def_id) => {
-                    format::href_with_root_path(*def_id, context, Some(href_context.root_path))
-                        .ok()
-                        .map(|(url, _, _)| url)
-                }
-                LinkFromSrc::Primitive(prim) => format::href_with_root_path(
-                    PrimitiveType::primitive_locations(context.tcx())[prim],
-                    context,
-                    Some(href_context.root_path),
-                )
-                .ok()
-                .map(|(url, _, _)| url),
-                LinkFromSrc::Doc(def_id) => {
-                    format::href_with_root_path(*def_id, context, Some(href_context.root_path))
-                        .ok()
-                        .map(|(doc_link, _, _)| doc_link)
-                }
-            }
-        }
-    {
-        if !open_tag {
-            // We're already inside an element which has the same klass, no need to give it
-            // again.
-            write!(out, "<a href=\"{href}\">{text_s}").unwrap();
-        } else {
-            let klass_s = klass.as_html();
-            if klass_s.is_empty() {
-                write!(out, "<a href=\"{href}\">{text_s}").unwrap();
-            } else {
-                write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
-            }
-        }
+    if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
         return Some("</a>");
     }
     if !open_tag {
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 4addf2c3c96..7065de14c8e 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -321,31 +321,34 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
             ))
         });
 
-        let tooltip = if ignore == Ignore::All {
-            highlight::Tooltip::IgnoreAll
-        } else if let Ignore::Some(platforms) = ignore {
-            highlight::Tooltip::IgnoreSome(platforms)
-        } else if compile_fail {
-            highlight::Tooltip::CompileFail
-        } else if should_panic {
-            highlight::Tooltip::ShouldPanic
-        } else if explicit_edition {
-            highlight::Tooltip::Edition(edition)
-        } else {
-            highlight::Tooltip::None
+        let tooltip = {
+            use highlight::Tooltip::*;
+
+            if ignore == Ignore::All {
+                Some(IgnoreAll)
+            } else if let Ignore::Some(platforms) = ignore {
+                Some(IgnoreSome(platforms))
+            } else if compile_fail {
+                Some(CompileFail)
+            } else if should_panic {
+                Some(ShouldPanic)
+            } else if explicit_edition {
+                Some(Edition(edition))
+            } else {
+                None
+            }
         };
 
         // insert newline to clearly separate it from the
         // previous block so we can shorten the html output
-        let mut s = String::new();
-        s.push('\n');
-
-        highlight::render_example_with_highlighting(
-            &text,
-            &mut s,
-            tooltip,
-            playground_button.as_deref(),
-            &added_classes,
+        let s = format!(
+            "\n{}",
+            highlight::render_example_with_highlighting(
+                &text,
+                tooltip.as_ref(),
+                playground_button.as_deref(),
+                &added_classes,
+            )
         );
         Some(Event::Html(s.into()))
     }
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 6d684449b6d..9a14137a6e8 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -51,7 +51,9 @@ use askama::Template;
 use itertools::Either;
 use rustc_ast::join_path_syms;
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
-use rustc_hir::attrs::{DeprecatedSince, Deprecation};
+use rustc_hir as hir;
+use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
+use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince};
 use rustc_middle::ty::print::PrintTraitRefExt;
@@ -1310,43 +1312,6 @@ fn render_assoc_item(
     })
 }
 
-struct CodeAttribute(String);
-
-fn render_code_attribute(prefix: &str, code_attr: CodeAttribute, w: &mut impl fmt::Write) {
-    write!(
-        w,
-        "<div class=\"code-attribute\">{prefix}{attr}</div>",
-        prefix = prefix,
-        attr = code_attr.0
-    )
-    .unwrap();
-}
-
-// When an attribute is rendered inside a <code> 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,
-    prefix: &str,
-    cx: &Context<'_>,
-) {
-    for attr in it.attributes(cx.tcx(), cx.cache()) {
-        render_code_attribute(prefix, CodeAttribute(attr), w);
-    }
-}
-
-/// used for type aliases to only render their `repr` attribute.
-fn render_repr_attributes_in_code(
-    w: &mut impl fmt::Write,
-    cx: &Context<'_>,
-    def_id: DefId,
-    item_type: ItemType,
-) {
-    if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
-        render_code_attribute("", CodeAttribute(repr), w);
-    }
-}
-
 #[derive(Copy, Clone)]
 enum AssocItemLink<'a> {
     Anchor(Option<&'a str>),
@@ -2959,3 +2924,142 @@ fn render_call_locations<W: fmt::Write>(
 
     w.write_str("</div>")
 }
+
+fn render_attributes_in_code(
+    w: &mut impl fmt::Write,
+    item: &clean::Item,
+    prefix: &str,
+    cx: &Context<'_>,
+) {
+    for attr in &item.attrs.other_attrs {
+        let hir::Attribute::Parsed(kind) = attr else { continue };
+        let attr = match kind {
+            AttributeKind::LinkSection { name, .. } => {
+                Cow::Owned(format!("#[unsafe(link_section = {})]", Escape(&format!("{name:?}"))))
+            }
+            AttributeKind::NoMangle(..) => Cow::Borrowed("#[unsafe(no_mangle)]"),
+            AttributeKind::ExportName { name, .. } => {
+                Cow::Owned(format!("#[unsafe(export_name = {})]", Escape(&format!("{name:?}"))))
+            }
+            AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"),
+            _ => continue,
+        };
+        render_code_attribute(prefix, attr.as_ref(), w);
+    }
+
+    if let Some(def_id) = item.def_id()
+        && let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id)
+    {
+        render_code_attribute(prefix, &repr, w);
+    }
+}
+
+fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
+    if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) {
+        render_code_attribute("", &repr, w);
+    }
+}
+
+fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
+    write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
+}
+
+/// Compute the *public* `#[repr]` of the item given by `DefId`.
+///
+/// Read more about it here:
+/// <https://doc.rust-lang.org/nightly/rustdoc/advanced-features.html#repr-documenting-the-representation-of-a-type>.
+fn repr_attribute<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    cache: &Cache,
+    def_id: DefId,
+) -> Option<Cow<'static, str>> {
+    let adt = match tcx.def_kind(def_id) {
+        DefKind::Struct | DefKind::Enum | DefKind::Union => tcx.adt_def(def_id),
+        _ => return None,
+    };
+    let repr = adt.repr();
+
+    let is_visible = |def_id| cache.document_hidden || !tcx.is_doc_hidden(def_id);
+    let is_public_field = |field: &ty::FieldDef| {
+        (cache.document_private || field.vis.is_public()) && is_visible(field.did)
+    };
+
+    if repr.transparent() {
+        // The transparent repr is public iff the non-1-ZST field is public and visible or
+        // – in case all fields are 1-ZST fields — at least one field is public and visible.
+        let is_public = 'is_public: {
+            // `#[repr(transparent)]` can only be applied to structs and single-variant enums.
+            let var = adt.variant(rustc_abi::FIRST_VARIANT); // the first and only variant
+
+            if !is_visible(var.def_id) {
+                break 'is_public false;
+            }
+
+            // Side note: There can only ever be one or zero non-1-ZST fields.
+            let non_1zst_field = var.fields.iter().find(|field| {
+                let ty = ty::TypingEnv::post_analysis(tcx, field.did)
+                    .as_query_input(tcx.type_of(field.did).instantiate_identity());
+                tcx.layout_of(ty).is_ok_and(|layout| !layout.is_1zst())
+            });
+
+            match non_1zst_field {
+                Some(field) => is_public_field(field),
+                None => var.fields.is_empty() || var.fields.iter().any(is_public_field),
+            }
+        };
+
+        // Since the transparent repr can't have any other reprs or
+        // repr modifiers beside it, we can safely return early here.
+        return is_public.then(|| "#[repr(transparent)]".into());
+    }
+
+    // Fast path which avoids looking through the variants and fields in
+    // the common case of no `#[repr]` or in the case of `#[repr(Rust)]`.
+    // FIXME: This check is not very robust / forward compatible!
+    if !repr.c()
+        && !repr.simd()
+        && repr.int.is_none()
+        && repr.pack.is_none()
+        && repr.align.is_none()
+    {
+        return None;
+    }
+
+    // The repr is public iff all components are public and visible.
+    let is_public = adt
+        .variants()
+        .iter()
+        .all(|variant| is_visible(variant.def_id) && variant.fields.iter().all(is_public_field));
+    if !is_public {
+        return None;
+    }
+
+    let mut result = Vec::<Cow<'_, _>>::new();
+
+    if repr.c() {
+        result.push("C".into());
+    }
+    if repr.simd() {
+        result.push("simd".into());
+    }
+    if let Some(int) = repr.int {
+        let prefix = if int.is_signed() { 'i' } else { 'u' };
+        let int = match int {
+            rustc_abi::IntegerType::Pointer(_) => format!("{prefix}size"),
+            rustc_abi::IntegerType::Fixed(int, _) => {
+                format!("{prefix}{}", int.size().bytes() * 8)
+            }
+        };
+        result.push(int.into());
+    }
+
+    // Render modifiers last.
+    if let Some(pack) = repr.pack {
+        result.push(format!("packed({})", pack.bytes()).into());
+    }
+    if let Some(align) = repr.align {
+        result.push(format!("align({})", align.bytes()).into());
+    }
+
+    (!result.is_empty()).then(|| format!("#[repr({})]", result.join(", ")).into())
+}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index afa438f2596..adfc7481c73 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -21,7 +21,7 @@ use super::{
     collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
     item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
     render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl,
-    render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
+    render_repr_attribute_in_code, render_rightside, render_stability_since_raw,
     render_stability_since_raw_with_extra, write_section_heading,
 };
 use crate::clean;
@@ -1555,7 +1555,7 @@ impl<'clean> DisplayEnum<'clean> {
         wrap_item(w, |w| {
             if is_type_alias {
                 // For now the only attributes we render for type aliases are `repr` attributes.
-                render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
+                render_repr_attribute_in_code(w, cx, self.def_id);
             } else {
                 render_attributes_in_code(w, it, "", cx);
             }
@@ -2017,7 +2017,7 @@ impl<'a> DisplayStruct<'a> {
         wrap_item(w, |w| {
             if is_type_alias {
                 // For now the only attributes we render for type aliases are `repr` attributes.
-                render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
+                render_repr_attribute_in_code(w, cx, self.def_id);
             } else {
                 render_attributes_in_code(w, it, "", cx);
             }
@@ -2371,7 +2371,7 @@ fn render_union(
     fmt::from_fn(move |mut f| {
         if is_type_alias {
             // For now the only attributes we render for type aliases are `repr` attributes.
-            render_repr_attributes_in_code(f, cx, def_id, ItemType::Union);
+            render_repr_attribute_in_code(f, cx, def_id);
         } else {
             render_attributes_in_code(f, it, "", cx);
         }
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 8bc2e0bd957..ef7ce33298d 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -2,11 +2,9 @@ use std::path::{Path, PathBuf};
 
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_hir::def::{DefKind, Res};
-use rustc_hir::def_id::{DefId, LOCAL_CRATE};
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{
-    ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath,
-};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
+use rustc_hir::intravisit::{self, Visitor, VisitorExt};
+use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::TyCtxt;
 use rustc_span::hygiene::MacroKind;
@@ -67,7 +65,7 @@ struct SpanMapVisitor<'tcx> {
 
 impl SpanMapVisitor<'_> {
     /// This function is where we handle `hir::Path` elements and add them into the "span map".
-    fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
+    fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
         match path.res {
             // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
             // Would be nice to support them too alongside the other `DefKind`
@@ -79,24 +77,36 @@ impl SpanMapVisitor<'_> {
                     LinkFromSrc::External(def_id)
                 };
                 // In case the path ends with generics, we remove them from the span.
-                let span = path
-                    .segments
-                    .last()
-                    .map(|last| {
-                        // In `use` statements, the included item is not in the path segments.
-                        // However, it doesn't matter because you can't have generics on `use`
-                        // statements.
-                        if path.span.contains(last.ident.span) {
-                            path.span.with_hi(last.ident.span.hi())
-                        } else {
-                            path.span
-                        }
-                    })
-                    .unwrap_or(path.span);
+                let span = if only_use_last_segment
+                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
+                {
+                    path_span
+                } else {
+                    path.segments
+                        .last()
+                        .map(|last| {
+                            // In `use` statements, the included item is not in the path segments.
+                            // However, it doesn't matter because you can't have generics on `use`
+                            // statements.
+                            if path.span.contains(last.ident.span) {
+                                path.span.with_hi(last.ident.span.hi())
+                            } else {
+                                path.span
+                            }
+                        })
+                        .unwrap_or(path.span)
+                };
                 self.matches.insert(span, link);
             }
             Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
-                self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
+                let path_span = if only_use_last_segment
+                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
+                {
+                    path_span
+                } else {
+                    path.span
+                };
+                self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span)));
             }
             Res::PrimTy(p) => {
                 // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
@@ -189,31 +199,23 @@ impl SpanMapVisitor<'_> {
             self.matches.insert(span, link);
         }
     }
+}
 
-    fn handle_pat(&mut self, p: &Pat<'_>) {
-        let mut check_qpath = |qpath, hir_id| match qpath {
-            QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => {
-                self.infer_id(path.hir_id, Some(hir_id), qpath.span());
-            }
-            QPath::Resolved(_, path) => self.handle_path(path),
-            _ => {}
-        };
-        match p.kind {
-            PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p),
-            PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => {
-                check_qpath(qpath, p.hir_id)
-            }
-            PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => {
-                check_qpath(*qpath, *hir_id)
-            }
-            PatKind::Or(pats) => {
-                for pat in pats {
-                    self.handle_pat(pat);
-                }
-            }
-            _ => {}
+// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
+// panicking.
+fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
+    for (_, node) in tcx.hir_parent_iter(hir_id) {
+        // FIXME: associated type impl items don't have an associated body, so we don't handle
+        // them currently.
+        if let Node::ImplItem(impl_item) = node
+            && matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
+        {
+            return None;
+        } else if let Some((def_id, _)) = node.associated_body() {
+            return Some(def_id);
         }
     }
+    None
 }
 
 impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
@@ -227,12 +229,42 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
         if self.handle_macro(path.span) {
             return;
         }
-        self.handle_path(path);
+        self.handle_path(path, false);
         intravisit::walk_path(self, path);
     }
 
-    fn visit_pat(&mut self, p: &Pat<'tcx>) {
-        self.handle_pat(p);
+    fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
+        match *qpath {
+            QPath::TypeRelative(qself, path) => {
+                if matches!(path.res, Res::Err) {
+                    let tcx = self.tcx;
+                    if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
+                        let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
+                        let path = rustc_hir::Path {
+                            // We change the span to not include parens.
+                            span: path.ident.span,
+                            res: typeck_results.qpath_res(qpath, id),
+                            segments: &[],
+                        };
+                        self.handle_path(&path, false);
+                    }
+                } else {
+                    self.infer_id(path.hir_id, Some(id), path.ident.span);
+                }
+
+                rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
+                self.visit_path_segment(path);
+            }
+            QPath::Resolved(maybe_qself, path) => {
+                self.handle_path(path, true);
+
+                rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
+                if !self.handle_macro(path.span) {
+                    intravisit::walk_path(self, path);
+                }
+            }
+            _ => {}
+        }
     }
 
     fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs
index 3e526813bb4..8b1b1e143b1 100644
--- a/src/tools/miri/tests/pass/vec.rs
+++ b/src/tools/miri/tests/pass/vec.rs
@@ -169,6 +169,15 @@ fn miri_issue_2759() {
     input.replace_range(0..0, "0");
 }
 
+/// This was skirting the edge of UB, let's make sure it remains on the sound side.
+/// Context: <https://github.com/rust-lang/rust/pull/141032>.
+fn extract_if() {
+    let mut v = vec![Box::new(0u64), Box::new(1u64)];
+    for item in v.extract_if(.., |x| **x == 0) {
+        drop(item);
+    }
+}
+
 fn main() {
     assert_eq!(vec_reallocate().len(), 5);
 
@@ -199,4 +208,5 @@ fn main() {
     swap_remove();
     reverse();
     miri_issue_2759();
+    extract_if();
 }