about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-07-10 19:56:08 +0000
committerbors <bors@rust-lang.org>2025-07-10 19:56:08 +0000
commit2a023bf80a6fbd6a06d5460a34eb247b986286ed (patch)
treed5ca6e0ecb9a1b89ddd3fc5cb328f67a51a181fe /src
parenta9f2aad0454ef1a06de6588d012517b534540765 (diff)
parentb5d1b92a8c7241cc6989e3b5d0246654c4065679 (diff)
downloadrust-2a023bf80a6fbd6a06d5460a34eb247b986286ed.tar.gz
rust-2a023bf80a6fbd6a06d5460a34eb247b986286ed.zip
Auto merge of #143746 - matthiaskrgr:rollup-yaojj7t, r=matthiaskrgr
Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#143446 (use `--dynamic-list` for exporting executable symbols)
 - rust-lang/rust#143590 (Fix weird rustdoc output when single and glob reexport conflict on a name)
 - rust-lang/rust#143599 (emit `.att_syntax` when global/naked asm use that option)
 - rust-lang/rust#143615 (Fix handling of no_std targets in `doc::Std` step)
 - rust-lang/rust#143632 (fix: correct parameter names in LLVMRustBuildMinNum and LLVMRustBuildMaxNum FFI declarations)
 - rust-lang/rust#143640 (Constify `Fn*` traits)
 - rust-lang/rust#143651 (Win: Use exceptions with empty data for SEH panic exception copies instead of a new panic)
 - rust-lang/rust#143660 (Disable docs for `compiler-builtins` and `sysroot`)
 - rust-lang/rust#143665 ([rustdoc-json] Add tests for `#[doc(hidden)]` handling of items.)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs30
-rw-r--r--src/bootstrap/src/core/build_steps/doc.rs10
-rw-r--r--src/bootstrap/src/core/builder/mod.rs9
-rw-r--r--src/bootstrap/src/core/builder/tests.rs169
-rw-r--r--src/bootstrap/src/utils/tests/mod.rs28
-rw-r--r--src/librustdoc/clean/inline.rs10
-rw-r--r--src/librustdoc/clean/mod.rs50
-rw-r--r--src/librustdoc/visit_ast.rs64
8 files changed, 258 insertions, 112 deletions
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 05965ecc170..1282a89a5e4 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -449,26 +449,24 @@ fn copy_self_contained_objects(
     target_deps
 }
 
-/// Resolves standard library crates for `Std::run_make` for any build kind (like check, build, clippy, etc.).
+/// Resolves standard library crates for `Std::run_make` for any build kind (like check, doc,
+/// build, clippy, etc.).
 pub fn std_crates_for_run_make(run: &RunConfig<'_>) -> Vec<String> {
-    // FIXME: Extend builder tests to cover the `crates` field of `Std` instances.
-    if cfg!(test) {
-        return vec![];
-    }
-
-    let has_alias = run.paths.iter().any(|set| set.assert_single_path().path.ends_with("library"));
+    let mut crates = run.make_run_crates(builder::Alias::Library);
+
+    // For no_std targets, we only want to check core and alloc
+    // Regardless of core/alloc being selected explicitly or via the "library" default alias,
+    // we only want to keep these two crates.
+    // The set of no_std crates should be kept in sync with what `Builder::std_cargo` does.
+    // Note: an alternative design would be to return an enum from this function (Default vs Subset)
+    // of crates. However, several steps currently pass `-p <package>` even if all crates are
+    // selected, because Cargo behaves differently in that case. To keep that behavior without
+    // making further changes, we pre-filter the no-std crates here.
     let target_is_no_std = run.builder.no_std(run.target).unwrap_or(false);
-
-    // For no_std targets, do not add any additional crates to the compilation other than what `compile::std_cargo` already adds for no_std targets.
     if target_is_no_std {
-        vec![]
-    }
-    // If the paths include "library", build the entire standard library.
-    else if has_alias {
-        run.make_run_crates(builder::Alias::Library)
-    } else {
-        run.cargo_crates_in_set()
+        crates.retain(|c| c == "core" || c == "alloc");
     }
+    crates
 }
 
 /// Tries to find LLVM's `compiler-rt` source directory, for building `library/profiler_builtins`.
diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs
index f7c4c5ad0bb..37418f640ac 100644
--- a/src/bootstrap/src/core/build_steps/doc.rs
+++ b/src/bootstrap/src/core/build_steps/doc.rs
@@ -665,7 +665,11 @@ impl Step for Std {
     }
 
     fn metadata(&self) -> Option<StepMetadata> {
-        Some(StepMetadata::doc("std", self.target).stage(self.stage))
+        Some(
+            StepMetadata::doc("std", self.target)
+                .stage(self.stage)
+                .with_metadata(format!("crates=[{}]", self.crates.join(","))),
+        )
     }
 }
 
@@ -739,10 +743,6 @@ fn doc_std(
     }
 
     for krate in requested_crates {
-        if krate == "sysroot" {
-            // The sysroot crate is an implementation detail, don't include it in public docs.
-            continue;
-        }
         cargo.arg("-p").arg(krate);
     }
 
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index 7464327fde9..4d606953d99 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -146,6 +146,8 @@ pub struct StepMetadata {
     target: TargetSelection,
     built_by: Option<Compiler>,
     stage: Option<u32>,
+    /// Additional opaque string printed in the metadata
+    metadata: Option<String>,
 }
 
 impl StepMetadata {
@@ -170,7 +172,7 @@ impl StepMetadata {
     }
 
     fn new(name: &'static str, target: TargetSelection, kind: Kind) -> Self {
-        Self { name, kind, target, built_by: None, stage: None }
+        Self { name, kind, target, built_by: None, stage: None, metadata: None }
     }
 
     pub fn built_by(mut self, compiler: Compiler) -> Self {
@@ -183,6 +185,11 @@ impl StepMetadata {
         self
     }
 
+    pub fn with_metadata(mut self, metadata: String) -> Self {
+        self.metadata = Some(metadata);
+        self
+    }
+
     pub fn get_stage(&self) -> Option<u32> {
         self.stage.or(self
             .built_by
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index bbcb58fca14..68d1885b0a7 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -637,8 +637,8 @@ mod snapshot {
 
     use crate::core::build_steps::{compile, dist, doc, test, tool};
     use crate::core::builder::tests::{
-        TEST_TRIPLE_1, TEST_TRIPLE_2, TEST_TRIPLE_3, configure, configure_with_args, first,
-        host_target, render_steps, run_build,
+        RenderConfig, TEST_TRIPLE_1, TEST_TRIPLE_2, TEST_TRIPLE_3, configure, configure_with_args,
+        first, host_target, render_steps, run_build,
     };
     use crate::core::builder::{Builder, Kind, StepDescription, StepMetadata};
     use crate::core::config::TargetSelection;
@@ -994,7 +994,7 @@ mod snapshot {
         [build] llvm <host>
         [build] rustc 0 <host> -> rustc 1 <host>
         [build] rustdoc 0 <host>
-        [doc] std 1 <host>
+        [doc] std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         ");
     }
 
@@ -1043,12 +1043,12 @@ mod snapshot {
         [build] rustc 1 <host> -> std 1 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1075,12 +1075,12 @@ mod snapshot {
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustc 1 <host> -> WasmComponentLd 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1112,15 +1112,15 @@ mod snapshot {
         [build] rustc 1 <host> -> std 1 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
+        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
         [dist] docs <target1>
-        [doc] std 2 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <host> crates=[]
+        [doc] std 2 <target1> crates=[]
         [dist] mingw <host>
         [dist] mingw <target1>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
@@ -1149,14 +1149,14 @@ mod snapshot {
         [build] rustc 1 <host> -> std 1 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 1 <host> -> std 1 <target1>
         [build] rustc 2 <host> -> std 2 <target1>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
-        [doc] std 2 <host>
+        [doc] std 2 <host> crates=[]
         [dist] mingw <host>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
         [dist] rustc <host>
@@ -1186,8 +1186,8 @@ mod snapshot {
         [build] rustc 1 <host> -> std 1 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
+        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 1 <host> -> std 1 <target1>
@@ -1195,8 +1195,8 @@ mod snapshot {
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <host>
         [dist] docs <target1>
-        [doc] std 2 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <host> crates=[]
+        [doc] std 2 <target1> crates=[]
         [dist] mingw <host>
         [dist] mingw <target1>
         [build] rustc 0 <host> -> GenerateCopyright 1 <host>
@@ -1228,11 +1228,11 @@ mod snapshot {
         [build] rustc 1 <host> -> std 1 <host>
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <target1>
-        [doc] std 2 <target1>
+        [doc] std 2 <target1> crates=[]
         [dist] mingw <target1>
         [build] rustc 2 <host> -> std 2 <target1>
         [dist] rustc 2 <host> -> std 2 <target1>
@@ -1260,14 +1260,14 @@ mod snapshot {
         [build] rustc 1 <host> -> rustc 2 <host>
         [build] rustc 1 <host> -> WasmComponentLd 2 <host>
         [build] rustdoc 1 <host>
-        [doc] std 2 <target1>
+        [doc] std 2 <target1> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
         [build] rustc 2 <host> -> std 2 <host>
         [build] rustc 1 <host> -> std 1 <target1>
         [build] rustc 2 <host> -> std 2 <target1>
         [build] rustc 0 <host> -> LintDocs 1 <host>
         [build] rustc 0 <host> -> RustInstaller 1 <host>
         [dist] docs <target1>
-        [doc] std 2 <target1>
+        [doc] std 2 <target1> crates=[]
         [dist] mingw <target1>
         [build] llvm <target1>
         [build] rustc 1 <host> -> rustc 2 <target1>
@@ -1575,6 +1575,80 @@ mod snapshot {
         steps.assert_contains(StepMetadata::test("CrateLibrustc", host));
         steps.assert_contains_fuzzy(StepMetadata::build("rustc", host));
     }
+
+    #[test]
+    fn doc_library() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("doc")
+                .path("library")
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustdoc 0 <host>
+        [doc] std 1 <host> crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,std,sysroot,test,unwind]
+        ");
+    }
+
+    #[test]
+    fn doc_core() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("doc")
+                .path("core")
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustdoc 0 <host>
+        [doc] std 1 <host> crates=[core]
+        ");
+    }
+
+    #[test]
+    fn doc_core_no_std_target() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("doc")
+                .path("core")
+                .override_target_no_std(&host_target())
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustdoc 0 <host>
+        [doc] std 1 <host> crates=[core]
+        ");
+    }
+
+    #[test]
+    fn doc_library_no_std_target() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("doc")
+                .path("library")
+                .override_target_no_std(&host_target())
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustdoc 0 <host>
+        [doc] std 1 <host> crates=[alloc,core]
+        ");
+    }
+
+    #[test]
+    fn doc_library_no_std_target_cross_compile() {
+        let ctx = TestCtx::new();
+        insta::assert_snapshot!(
+            ctx.config("doc")
+                .path("library")
+                .targets(&[TEST_TRIPLE_1])
+                .override_target_no_std(TEST_TRIPLE_1)
+                .render_steps(), @r"
+        [build] llvm <host>
+        [build] rustc 0 <host> -> rustc 1 <host>
+        [build] rustdoc 0 <host>
+        [doc] std 1 <target1> crates=[alloc,core]
+        ");
+    }
 }
 
 struct ExecutedSteps {
@@ -1583,7 +1657,11 @@ struct ExecutedSteps {
 
 impl ExecutedSteps {
     fn render(&self) -> String {
-        render_steps(&self.steps)
+        self.render_with(RenderConfig::default())
+    }
+
+    fn render_with(&self, config: RenderConfig) -> String {
+        render_steps(&self.steps, config)
     }
 
     #[track_caller]
@@ -1592,7 +1670,7 @@ impl ExecutedSteps {
         if !self.contains(&metadata) {
             panic!(
                 "Metadata `{}` ({metadata:?}) not found in executed steps:\n{}",
-                render_metadata(&metadata),
+                render_metadata(&metadata, &RenderConfig::default()),
                 self.render()
             );
         }
@@ -1607,7 +1685,7 @@ impl ExecutedSteps {
         if !self.contains_fuzzy(&metadata) {
             panic!(
                 "Metadata `{}` ({metadata:?}) not found in executed steps:\n{}",
-                render_metadata(&metadata),
+                render_metadata(&metadata, &RenderConfig::default()),
                 self.render()
             );
         }
@@ -1619,7 +1697,7 @@ impl ExecutedSteps {
         if self.contains(&metadata) {
             panic!(
                 "Metadata `{}` ({metadata:?}) found in executed steps (it should not be there):\n{}",
-                render_metadata(&metadata),
+                render_metadata(&metadata, &RenderConfig::default()),
                 self.render()
             );
         }
@@ -1641,7 +1719,7 @@ impl ExecutedSteps {
 }
 
 fn fuzzy_metadata_eq(executed: &StepMetadata, to_match: &StepMetadata) -> bool {
-    let StepMetadata { name, kind, target, built_by: _, stage: _ } = executed;
+    let StepMetadata { name, kind, target, built_by: _, stage: _, metadata } = executed;
     *name == to_match.name && *kind == to_match.kind && *target == to_match.target
 }
 
@@ -1672,6 +1750,16 @@ impl ConfigBuilder {
     }
 }
 
+struct RenderConfig {
+    normalize_host: bool,
+}
+
+impl Default for RenderConfig {
+    fn default() -> Self {
+        Self { normalize_host: true }
+    }
+}
+
 /// Renders the executed bootstrap steps for usage in snapshot tests with insta.
 /// Only renders certain important steps.
 /// Each value in `steps` should be a tuple of (Step, step output).
@@ -1679,7 +1767,7 @@ impl ConfigBuilder {
 /// The arrow in the rendered output (`X -> Y`) means `X builds Y`.
 /// This is similar to the output printed by bootstrap to stdout, but here it is
 /// generated purely for the purpose of tests.
-fn render_steps(steps: &[ExecutedStep]) -> String {
+fn render_steps(steps: &[ExecutedStep], config: RenderConfig) -> String {
     steps
         .iter()
         .filter_map(|step| {
@@ -1689,32 +1777,35 @@ fn render_steps(steps: &[ExecutedStep]) -> String {
                 return None;
             };
 
-            Some(render_metadata(&metadata))
+            Some(render_metadata(&metadata, &config))
         })
         .collect::<Vec<_>>()
         .join("\n")
 }
 
-fn render_metadata(metadata: &StepMetadata) -> String {
+fn render_metadata(metadata: &StepMetadata, config: &RenderConfig) -> String {
     let mut record = format!("[{}] ", metadata.kind.as_str());
     if let Some(compiler) = metadata.built_by {
-        write!(record, "{} -> ", render_compiler(compiler));
+        write!(record, "{} -> ", render_compiler(compiler, config));
     }
     let stage = metadata.get_stage().map(|stage| format!("{stage} ")).unwrap_or_default();
-    write!(record, "{} {stage}<{}>", metadata.name, normalize_target(metadata.target));
+    write!(record, "{} {stage}<{}>", metadata.name, normalize_target(metadata.target, config));
+    if let Some(metadata) = &metadata.metadata {
+        write!(record, " {metadata}");
+    }
     record
 }
 
-fn normalize_target(target: TargetSelection) -> String {
-    target
-        .to_string()
-        .replace(&host_target(), "host")
-        .replace(TEST_TRIPLE_1, "target1")
-        .replace(TEST_TRIPLE_2, "target2")
+fn normalize_target(target: TargetSelection, config: &RenderConfig) -> String {
+    let mut target = target.to_string();
+    if config.normalize_host {
+        target = target.replace(&host_target(), "host");
+    }
+    target.replace(TEST_TRIPLE_1, "target1").replace(TEST_TRIPLE_2, "target2")
 }
 
-fn render_compiler(compiler: Compiler) -> String {
-    format!("rustc {} <{}>", compiler.stage, normalize_target(compiler.host))
+fn render_compiler(compiler: Compiler, config: &RenderConfig) -> String {
+    format!("rustc {} <{}>", compiler.stage, normalize_target(compiler.host, config))
 }
 
 fn host_target() -> String {
diff --git a/src/bootstrap/src/utils/tests/mod.rs b/src/bootstrap/src/utils/tests/mod.rs
index 5b568c1df5b..ec87e71e0b6 100644
--- a/src/bootstrap/src/utils/tests/mod.rs
+++ b/src/bootstrap/src/utils/tests/mod.rs
@@ -48,17 +48,30 @@ impl ConfigBuilder {
     }
 
     pub fn path(mut self, path: &str) -> Self {
-        self.args.push(path.to_string());
-        self
+        self.arg(path)
     }
 
     pub fn paths(mut self, paths: &[&str]) -> Self {
-        for path in paths {
-            self = self.path(path);
+        self.args(paths)
+    }
+
+    pub fn arg(mut self, arg: &str) -> Self {
+        self.args.push(arg.to_string());
+        self
+    }
+
+    pub fn args(mut self, args: &[&str]) -> Self {
+        for arg in args {
+            self = self.arg(arg);
         }
         self
     }
 
+    /// Set the specified target to be treated as a no_std target.
+    pub fn override_target_no_std(mut self, target: &str) -> Self {
+        self.args(&["--set", &format!("target.{target}.no-std=true")])
+    }
+
     pub fn hosts(mut self, targets: &[&str]) -> Self {
         self.args.push("--host".to_string());
         self.args.push(targets.join(","));
@@ -77,13 +90,6 @@ impl ConfigBuilder {
         self
     }
 
-    pub fn args(mut self, args: &[&str]) -> Self {
-        for arg in args {
-            self.args.push(arg.to_string());
-        }
-        self
-    }
-
     pub fn create_config(mut self) -> Config {
         // Run in dry-check, otherwise the test would be too slow
         self.args.push("--dry-run".to_string());
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 96199cb972a..37b012d6d0d 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -152,8 +152,14 @@ pub(crate) fn try_inline(
     };
 
     cx.inlined.insert(did.into());
-    let mut item =
-        crate::clean::generate_item_with_correct_attrs(cx, kind, did, name, import_def_id, None);
+    let mut item = crate::clean::generate_item_with_correct_attrs(
+        cx,
+        kind,
+        did,
+        name,
+        import_def_id.as_slice(),
+        None,
+    );
     // The visibility needs to reflect the one from the reexport and not from the "source" DefId.
     item.inner.inline_stmt_id = import_def_id;
     ret.push(item);
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index c2f3da18cd3..e6f7aef02c0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -94,12 +94,12 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
     // This covers the case where somebody does an import which should pull in an item,
     // but there's already an item with the same namespace and same name. Rust gives
     // priority to the not-imported one, so we should, too.
-    items.extend(doc.items.values().flat_map(|(item, renamed, import_id)| {
+    items.extend(doc.items.values().flat_map(|(item, renamed, import_ids)| {
         // First, lower everything other than glob imports.
         if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) {
             return Vec::new();
         }
-        let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id);
+        let v = clean_maybe_renamed_item(cx, item, *renamed, import_ids);
         for item in &v {
             if let Some(name) = item.name
                 && (cx.render_options.document_hidden || !item.is_doc_hidden())
@@ -162,7 +162,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
         kind,
         doc.def_id.to_def_id(),
         doc.name,
-        doc.import_id,
+        doc.import_id.as_slice(),
         doc.renamed,
     )
 }
@@ -182,22 +182,29 @@ fn generate_item_with_correct_attrs(
     kind: ItemKind,
     def_id: DefId,
     name: Symbol,
-    import_id: Option<LocalDefId>,
+    import_ids: &[LocalDefId],
     renamed: Option<Symbol>,
 ) -> Item {
     let target_attrs = inline::load_attrs(cx, def_id);
-    let attrs = if let Some(import_id) = import_id {
-        // glob reexports are treated the same as `#[doc(inline)]` items.
-        //
-        // For glob re-exports the item may or may not exist to be re-exported (potentially the cfgs
-        // on the path up until the glob can be removed, and only cfgs on the globbed item itself
-        // matter), for non-inlined re-exports see #85043.
-        let is_inline = hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
-            .get_word_attr(sym::inline)
-            .is_some()
-            || (is_glob_import(cx.tcx, import_id)
-                && (cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id)));
-        let mut attrs = get_all_import_attributes(cx, import_id, def_id, is_inline);
+    let attrs = if !import_ids.is_empty() {
+        let mut attrs = Vec::with_capacity(import_ids.len());
+        let mut is_inline = false;
+
+        for import_id in import_ids.iter().copied() {
+            // glob reexports are treated the same as `#[doc(inline)]` items.
+            //
+            // For glob re-exports the item may or may not exist to be re-exported (potentially the
+            // cfgs on the path up until the glob can be removed, and only cfgs on the globbed item
+            // itself matter), for non-inlined re-exports see #85043.
+            let import_is_inline =
+                hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
+                    .get_word_attr(sym::inline)
+                    .is_some()
+                    || (is_glob_import(cx.tcx, import_id)
+                        && (cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id)));
+            attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline));
+            is_inline = is_inline || import_is_inline;
+        }
         add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None);
         attrs
     } else {
@@ -216,7 +223,8 @@ fn generate_item_with_correct_attrs(
 
     let name = renamed.or(Some(name));
     let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg);
-    item.inner.inline_stmt_id = import_id;
+    // FIXME (GuillaumeGomez): Should we also make `inline_stmt_id` a `Vec` instead of an `Option`?
+    item.inner.inline_stmt_id = import_ids.first().copied();
     item
 }
 
@@ -2754,7 +2762,7 @@ fn clean_maybe_renamed_item<'tcx>(
     cx: &mut DocContext<'tcx>,
     item: &hir::Item<'tcx>,
     renamed: Option<Symbol>,
-    import_id: Option<LocalDefId>,
+    import_ids: &[LocalDefId],
 ) -> Vec<Item> {
     use hir::ItemKind;
     fn get_name(
@@ -2825,7 +2833,7 @@ fn clean_maybe_renamed_item<'tcx>(
                     })),
                     item.owner_id.def_id.to_def_id(),
                     name,
-                    import_id,
+                    import_ids,
                     renamed,
                 ));
                 return ret;
@@ -2880,7 +2888,7 @@ fn clean_maybe_renamed_item<'tcx>(
             kind,
             item.owner_id.def_id.to_def_id(),
             name,
-            import_id,
+            import_ids,
             renamed,
         )]
     })
@@ -3151,7 +3159,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
             kind,
             item.owner_id.def_id.to_def_id(),
             item.ident.name,
-            import_id,
+            import_id.as_slice(),
             renamed,
         )
     })
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 2889bfae7b0..2784d7c761a 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -32,11 +32,29 @@ pub(crate) struct Module<'hir> {
     pub(crate) def_id: LocalDefId,
     pub(crate) renamed: Option<Symbol>,
     pub(crate) import_id: Option<LocalDefId>,
-    /// The key is the item `ItemId` and the value is: (item, renamed, import_id).
+    /// The key is the item `ItemId` and the value is: (item, renamed, Vec<import_id>).
     /// We use `FxIndexMap` to keep the insert order.
+    ///
+    /// `import_id` needs to be a `Vec` because we live in a dark world where you can have code
+    /// like:
+    ///
+    /// ```
+    /// mod raw {
+    ///     pub fn foo() {}
+    /// }
+    ///
+    /// /// Foobar
+    /// pub use raw::foo;
+    ///
+    /// pub use raw::*;
+    /// ```
+    ///
+    /// So in this case, we don't want to have two items but just one with attributes from all
+    /// non-glob imports to be merged. Glob imports attributes are always ignored, whether they're
+    /// shadowed or not.
     pub(crate) items: FxIndexMap<
         (LocalDefId, Option<Symbol>),
-        (&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
+        (&'hir hir::Item<'hir>, Option<Symbol>, Vec<LocalDefId>),
     >,
 
     /// (def_id, renamed) -> (res, local_import_id)
@@ -154,7 +172,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             {
                 let item = self.cx.tcx.hir_expect_item(local_def_id);
                 let (ident, _, _) = item.expect_macro();
-                top_level_module.items.insert((local_def_id, Some(ident.name)), (item, None, None));
+                top_level_module
+                    .items
+                    .insert((local_def_id, Some(ident.name)), (item, None, Vec::new()));
             }
         }
 
@@ -236,7 +256,6 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
     ) -> bool {
         debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
 
-        let glob = renamed.is_none();
         if renamed == Some(kw::Underscore) {
             // We never inline `_` reexports.
             return false;
@@ -261,6 +280,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             return false;
         }
 
+        let is_glob = renamed.is_none();
         let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
         let Some(res_did) = ori_res_did.as_local() else {
             // For cross-crate impl inlining we need to know whether items are
@@ -268,7 +288,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             // made reachable by cross-crate inlining which we're checking here.
             // (this is done here because we need to know this upfront).
             crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
-            if is_hidden || glob {
+            if is_hidden || is_glob {
                 return false;
             }
             // We store inlined foreign items otherwise, it'd mean that the `use` item would be kept
@@ -316,10 +336,10 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             // Bang macros are handled a bit on their because of how they are handled by the
             // compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
             // `#[doc(inline)]`, then we don't inline it.
-            Node::Item(_) if is_bang_macro && !please_inline && renamed.is_some() && is_hidden => {
+            Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
                 return false;
             }
-            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if glob => {
+            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
                 let prev = mem::replace(&mut self.inlining, true);
                 for &i in m.item_ids {
                     let i = tcx.hir_item(i);
@@ -328,13 +348,13 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
                 self.inlining = prev;
                 true
             }
-            Node::Item(it) if !glob => {
+            Node::Item(it) if !is_glob => {
                 let prev = mem::replace(&mut self.inlining, true);
                 self.visit_item_inner(it, renamed, Some(def_id));
                 self.inlining = prev;
                 true
             }
-            Node::ForeignItem(it) if !glob => {
+            Node::ForeignItem(it) if !is_glob => {
                 let prev = mem::replace(&mut self.inlining, true);
                 self.visit_foreign_item_inner(it, renamed, Some(def_id));
                 self.inlining = prev;
@@ -378,8 +398,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
     fn add_to_current_mod(
         &mut self,
         item: &'tcx hir::Item<'_>,
-        renamed: Option<Symbol>,
-        parent_id: Option<LocalDefId>,
+        mut renamed: Option<Symbol>,
+        import_id: Option<LocalDefId>,
     ) {
         if self.is_importable_from_parent
             // If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export`
@@ -392,11 +412,21 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
                 _ => false,
             }
         {
-            self.modules
-                .last_mut()
-                .unwrap()
-                .items
-                .insert((item.owner_id.def_id, renamed), (item, renamed, parent_id));
+            if renamed == item.kind.ident().map(|ident| ident.name) {
+                renamed = None;
+            }
+            let key = (item.owner_id.def_id, renamed);
+            if let Some(import_id) = import_id {
+                self.modules
+                    .last_mut()
+                    .unwrap()
+                    .items
+                    .entry(key)
+                    .and_modify(|v| v.2.push(import_id))
+                    .or_insert_with(|| (item, renamed, vec![import_id]));
+            } else {
+                self.modules.last_mut().unwrap().items.insert(key, (item, renamed, Vec::new()));
+            }
         }
     }
 
@@ -468,7 +498,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
                             _ => false,
                         });
                         let ident = match kind {
-                            hir::UseKind::Single(ident) => Some(renamed.unwrap_or(ident.name)),
+                            hir::UseKind::Single(ident) => Some(ident.name),
                             hir::UseKind::Glob => None,
                             hir::UseKind::ListStem => unreachable!(),
                         };