about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-07-03 19:59:09 +0000
committerbors <bors@rust-lang.org>2025-07-03 19:59:09 +0000
commitda58c051315268a197ce280f6ba07bbd03c66535 (patch)
treed4e8490322e7fae28ddacd47d4fa6861af392fc2
parent48aee7e383503c234cce4206dee9f19f57edb617 (diff)
parent9092fbbee1080a2aae02120f823012a48df68f84 (diff)
downloadrust-da58c051315268a197ce280f6ba07bbd03c66535.tar.gz
rust-da58c051315268a197ce280f6ba07bbd03c66535.zip
Auto merge of #143390 - GuillaumeGomez:rollup-2avj41s, r=GuillaumeGomez
Rollup of 5 pull requests

Successful merges:

 - rust-lang/rust#141831 (rustdoc: fix attrs of locally reexported foreign items)
 - rust-lang/rust#143364 (don't include `.md` in title)
 - rust-lang/rust#143369 (Various refactorings to the metadata loader)
 - rust-lang/rust#143379 (Post {beta,stable}-accepted notifications to compiler/bootstrap backport zulip channels on `{beta,stable}-accepted` label application)
 - rust-lang/rust#143384 (Update browser-ui-test version to `0.21.1`)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_metadata/src/creader.rs109
-rw-r--r--compiler/rustc_metadata/src/locator.rs120
-rw-r--r--src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version2
-rw-r--r--src/doc/rustc/src/SUMMARY.md2
-rw-r--r--src/librustdoc/clean/mod.rs19
-rw-r--r--src/librustdoc/visit_ast.rs20
-rw-r--r--src/tools/rustdoc-gui/tester.js2
-rw-r--r--tests/rustdoc/reexport/extern-135092.rs26
-rw-r--r--triagebot.toml109
9 files changed, 279 insertions, 130 deletions
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 2c882b84c58..e65c7a68426 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -24,8 +24,7 @@ use rustc_middle::ty::data_structures::IndexSet;
 use rustc_middle::ty::{TyCtxt, TyCtxtFeed};
 use rustc_proc_macro::bridge::client::ProcMacro;
 use rustc_session::config::{
-    self, CrateType, ExtendedTargetModifierInfo, ExternLocation, OptionsTargetModifiers,
-    TargetModifier,
+    CrateType, ExtendedTargetModifierInfo, ExternLocation, OptionsTargetModifiers, TargetModifier,
 };
 use rustc_session::cstore::{CrateDepKind, CrateSource, ExternCrate, ExternCrateSource};
 use rustc_session::lint::{self, BuiltinLintDiag};
@@ -33,11 +32,11 @@ use rustc_session::output::validate_crate_name;
 use rustc_session::search_paths::PathKind;
 use rustc_span::edition::Edition;
 use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
-use rustc_target::spec::{PanicStrategy, Target, TargetTuple};
+use rustc_target::spec::{PanicStrategy, Target};
 use tracing::{debug, info, trace};
 
 use crate::errors;
-use crate::locator::{CrateError, CrateLocator, CratePaths};
+use crate::locator::{CrateError, CrateLocator, CratePaths, CrateRejections};
 use crate::rmeta::{
     CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob, TargetModifiers,
 };
@@ -684,61 +683,67 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
     fn load_proc_macro<'b>(
         &self,
         locator: &mut CrateLocator<'b>,
+        crate_rejections: &mut CrateRejections,
         path_kind: PathKind,
         host_hash: Option<Svh>,
     ) -> Result<Option<(LoadResult, Option<Library>)>, CrateError>
     where
         'a: 'b,
     {
-        // Use a new crate locator so trying to load a proc macro doesn't affect the error
-        // message we emit
-        let mut proc_macro_locator = locator.clone();
-
-        // Try to load a proc macro
-        proc_macro_locator.is_proc_macro = true;
-
-        // Load the proc macro crate for the target
-        let (locator, target_result) = if self.sess.opts.unstable_opts.dual_proc_macros {
-            proc_macro_locator.reset();
-            let result = match self.load(&mut proc_macro_locator)? {
-                Some(LoadResult::Previous(cnum)) => {
-                    return Ok(Some((LoadResult::Previous(cnum), None)));
-                }
-                Some(LoadResult::Loaded(library)) => Some(LoadResult::Loaded(library)),
-                None => return Ok(None),
-            };
-            locator.hash = host_hash;
-            // Use the locator when looking for the host proc macro crate, as that is required
-            // so we want it to affect the error message
-            (locator, result)
-        } else {
-            (&mut proc_macro_locator, None)
-        };
+        if self.sess.opts.unstable_opts.dual_proc_macros {
+            // Use a new crate locator and crate rejections so trying to load a proc macro doesn't
+            // affect the error message we emit
+            let mut proc_macro_locator = locator.clone();
+
+            // Try to load a proc macro
+            proc_macro_locator.for_target_proc_macro(self.sess, path_kind);
+
+            // Load the proc macro crate for the target
+            let target_result =
+                match self.load(&mut proc_macro_locator, &mut CrateRejections::default())? {
+                    Some(LoadResult::Previous(cnum)) => {
+                        return Ok(Some((LoadResult::Previous(cnum), None)));
+                    }
+                    Some(LoadResult::Loaded(library)) => Some(LoadResult::Loaded(library)),
+                    None => return Ok(None),
+                };
 
-        // Load the proc macro crate for the host
+            // Use the existing crate_rejections as we want the error message to be affected by
+            // loading the host proc macro.
+            *crate_rejections = CrateRejections::default();
 
-        locator.reset();
-        locator.is_proc_macro = true;
-        locator.target = &self.sess.host;
-        locator.tuple = TargetTuple::from_tuple(config::host_tuple());
-        locator.filesearch = self.sess.host_filesearch();
-        locator.path_kind = path_kind;
+            // Load the proc macro crate for the host
+            locator.for_proc_macro(self.sess, path_kind);
 
-        let Some(host_result) = self.load(locator)? else {
-            return Ok(None);
-        };
+            locator.hash = host_hash;
+
+            let Some(host_result) = self.load(locator, crate_rejections)? else {
+                return Ok(None);
+            };
 
-        Ok(Some(if self.sess.opts.unstable_opts.dual_proc_macros {
             let host_result = match host_result {
                 LoadResult::Previous(..) => {
                     panic!("host and target proc macros must be loaded in lock-step")
                 }
                 LoadResult::Loaded(library) => library,
             };
-            (target_result.unwrap(), Some(host_result))
+            Ok(Some((target_result.unwrap(), Some(host_result))))
         } else {
-            (host_result, None)
-        }))
+            // Use a new crate locator and crate rejections so trying to load a proc macro doesn't
+            // affect the error message we emit
+            let mut proc_macro_locator = locator.clone();
+
+            // Load the proc macro crate for the host
+            proc_macro_locator.for_proc_macro(self.sess, path_kind);
+
+            let Some(host_result) =
+                self.load(&mut proc_macro_locator, &mut CrateRejections::default())?
+            else {
+                return Ok(None);
+            };
+
+            Ok(Some((host_result, None)))
+        }
     }
 
     fn resolve_crate(
@@ -799,15 +804,21 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
                 extra_filename,
                 path_kind,
             );
+            let mut crate_rejections = CrateRejections::default();
 
-            match self.load(&mut locator)? {
+            match self.load(&mut locator, &mut crate_rejections)? {
                 Some(res) => (res, None),
                 None => {
                     info!("falling back to loading proc_macro");
                     dep_kind = CrateDepKind::MacrosOnly;
-                    match self.load_proc_macro(&mut locator, path_kind, host_hash)? {
+                    match self.load_proc_macro(
+                        &mut locator,
+                        &mut crate_rejections,
+                        path_kind,
+                        host_hash,
+                    )? {
                         Some(res) => res,
-                        None => return Err(locator.into_error(dep_root.cloned())),
+                        None => return Err(locator.into_error(crate_rejections, dep_root.cloned())),
                     }
                 }
             }
@@ -837,8 +848,12 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
         }
     }
 
-    fn load(&self, locator: &mut CrateLocator<'_>) -> Result<Option<LoadResult>, CrateError> {
-        let Some(library) = locator.maybe_load_library_crate()? else {
+    fn load(
+        &self,
+        locator: &CrateLocator<'_>,
+        crate_rejections: &mut CrateRejections,
+    ) -> Result<Option<LoadResult>, CrateError> {
+        let Some(library) = locator.maybe_load_library_crate(crate_rejections)? else {
             return Ok(None);
         };
 
diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs
index 82254f0a381..c30cfd1fcf7 100644
--- a/compiler/rustc_metadata/src/locator.rs
+++ b/compiler/rustc_metadata/src/locator.rs
@@ -224,11 +224,11 @@ use rustc_data_structures::owned_slice::{OwnedSlice, slice_owned};
 use rustc_data_structures::svh::Svh;
 use rustc_errors::{DiagArgValue, IntoDiagArg};
 use rustc_fs_util::try_canonicalize;
-use rustc_session::Session;
 use rustc_session::cstore::CrateSource;
 use rustc_session::filesearch::FileSearch;
 use rustc_session::search_paths::PathKind;
 use rustc_session::utils::CanonicalizedPath;
+use rustc_session::{Session, config};
 use rustc_span::{Span, Symbol};
 use rustc_target::spec::{Target, TargetTuple};
 use tempfile::Builder as TempFileBuilder;
@@ -251,14 +251,11 @@ pub(crate) struct CrateLocator<'a> {
     exact_paths: Vec<CanonicalizedPath>,
     pub hash: Option<Svh>,
     extra_filename: Option<&'a str>,
-    pub target: &'a Target,
-    pub tuple: TargetTuple,
-    pub filesearch: &'a FileSearch,
-    pub is_proc_macro: bool,
-
-    pub path_kind: PathKind,
-    // Mutable in-progress state or output.
-    crate_rejections: CrateRejections,
+    target: &'a Target,
+    tuple: TargetTuple,
+    filesearch: &'a FileSearch,
+    is_proc_macro: bool,
+    path_kind: PathKind,
 }
 
 #[derive(Clone, Debug)]
@@ -346,34 +343,46 @@ impl<'a> CrateLocator<'a> {
             filesearch: sess.target_filesearch(),
             path_kind,
             is_proc_macro: false,
-            crate_rejections: CrateRejections::default(),
         }
     }
 
-    pub(crate) fn reset(&mut self) {
-        self.crate_rejections.via_hash.clear();
-        self.crate_rejections.via_triple.clear();
-        self.crate_rejections.via_kind.clear();
-        self.crate_rejections.via_version.clear();
-        self.crate_rejections.via_filename.clear();
-        self.crate_rejections.via_invalid.clear();
+    pub(crate) fn for_proc_macro(&mut self, sess: &'a Session, path_kind: PathKind) {
+        self.is_proc_macro = true;
+        self.target = &sess.host;
+        self.tuple = TargetTuple::from_tuple(config::host_tuple());
+        self.filesearch = sess.host_filesearch();
+        self.path_kind = path_kind;
     }
 
-    pub(crate) fn maybe_load_library_crate(&mut self) -> Result<Option<Library>, CrateError> {
+    pub(crate) fn for_target_proc_macro(&mut self, sess: &'a Session, path_kind: PathKind) {
+        self.is_proc_macro = true;
+        self.target = &sess.target;
+        self.tuple = sess.opts.target_triple.clone();
+        self.filesearch = sess.target_filesearch();
+        self.path_kind = path_kind;
+    }
+
+    pub(crate) fn maybe_load_library_crate(
+        &self,
+        crate_rejections: &mut CrateRejections,
+    ) -> Result<Option<Library>, CrateError> {
         if !self.exact_paths.is_empty() {
-            return self.find_commandline_library();
+            return self.find_commandline_library(crate_rejections);
         }
         let mut seen_paths = FxHashSet::default();
         if let Some(extra_filename) = self.extra_filename {
-            if let library @ Some(_) = self.find_library_crate(extra_filename, &mut seen_paths)? {
+            if let library @ Some(_) =
+                self.find_library_crate(crate_rejections, extra_filename, &mut seen_paths)?
+            {
                 return Ok(library);
             }
         }
-        self.find_library_crate("", &mut seen_paths)
+        self.find_library_crate(crate_rejections, "", &mut seen_paths)
     }
 
     fn find_library_crate(
-        &mut self,
+        &self,
+        crate_rejections: &mut CrateRejections,
         extra_prefix: &str,
         seen_paths: &mut FxHashSet<PathBuf>,
     ) -> Result<Option<Library>, CrateError> {
@@ -465,7 +474,7 @@ impl<'a> CrateLocator<'a> {
                 .flatten()
             {
                 for (_, spf) in static_matches {
-                    self.crate_rejections.via_kind.push(CrateMismatch {
+                    crate_rejections.via_kind.push(CrateMismatch {
                         path: spf.path.to_path_buf(),
                         got: "static".to_string(),
                     });
@@ -483,7 +492,9 @@ impl<'a> CrateLocator<'a> {
         // search is being performed for.
         let mut libraries = FxIndexMap::default();
         for (_hash, (rlibs, rmetas, dylibs, interfaces)) in candidates {
-            if let Some((svh, lib)) = self.extract_lib(rlibs, rmetas, dylibs, interfaces)? {
+            if let Some((svh, lib)) =
+                self.extract_lib(crate_rejections, rlibs, rmetas, dylibs, interfaces)?
+            {
                 libraries.insert(svh, lib);
             }
         }
@@ -495,13 +506,11 @@ impl<'a> CrateLocator<'a> {
             0 => Ok(None),
             1 => Ok(Some(libraries.into_iter().next().unwrap().1)),
             _ => {
-                let mut libraries: Vec<_> = libraries.into_values().collect();
-
-                libraries.sort_by_cached_key(|lib| lib.source.paths().next().unwrap().clone());
-                let candidates = libraries
-                    .iter()
+                let mut candidates: Vec<PathBuf> = libraries
+                    .into_values()
                     .map(|lib| lib.source.paths().next().unwrap().clone())
-                    .collect::<Vec<_>>();
+                    .collect();
+                candidates.sort();
 
                 Err(CrateError::MultipleCandidates(
                     self.crate_name,
@@ -514,7 +523,8 @@ impl<'a> CrateLocator<'a> {
     }
 
     fn extract_lib(
-        &mut self,
+        &self,
+        crate_rejections: &mut CrateRejections,
         rlibs: FxIndexMap<PathBuf, PathKind>,
         rmetas: FxIndexMap<PathBuf, PathKind>,
         dylibs: FxIndexMap<PathBuf, PathKind>,
@@ -526,10 +536,11 @@ impl<'a> CrateLocator<'a> {
         // Make sure there's at most one rlib and at most one dylib.
         //
         // See comment in `extract_one` below.
-        let rmeta = self.extract_one(rmetas, CrateFlavor::Rmeta, &mut slot)?;
-        let rlib = self.extract_one(rlibs, CrateFlavor::Rlib, &mut slot)?;
-        let sdylib_interface = self.extract_one(interfaces, CrateFlavor::SDylib, &mut slot)?;
-        let dylib = self.extract_one(dylibs, CrateFlavor::Dylib, &mut slot)?;
+        let rmeta = self.extract_one(crate_rejections, rmetas, CrateFlavor::Rmeta, &mut slot)?;
+        let rlib = self.extract_one(crate_rejections, rlibs, CrateFlavor::Rlib, &mut slot)?;
+        let sdylib_interface =
+            self.extract_one(crate_rejections, interfaces, CrateFlavor::SDylib, &mut slot)?;
+        let dylib = self.extract_one(crate_rejections, dylibs, CrateFlavor::Dylib, &mut slot)?;
 
         if sdylib_interface.is_some() && dylib.is_none() {
             return Err(CrateError::FullMetadataNotFound(self.crate_name, CrateFlavor::SDylib));
@@ -563,7 +574,8 @@ impl<'a> CrateLocator<'a> {
     //
     // The `PathBuf` in `slot` will only be used for diagnostic purposes.
     fn extract_one(
-        &mut self,
+        &self,
+        crate_rejections: &mut CrateRejections,
         m: FxIndexMap<PathBuf, PathKind>,
         flavor: CrateFlavor,
         slot: &mut Option<(Svh, MetadataBlob, PathBuf, CrateFlavor)>,
@@ -605,7 +617,7 @@ impl<'a> CrateLocator<'a> {
                 Some(self.crate_name),
             ) {
                 Ok(blob) => {
-                    if let Some(h) = self.crate_matches(&blob, &lib) {
+                    if let Some(h) = self.crate_matches(crate_rejections, &blob, &lib) {
                         (h, blob)
                     } else {
                         info!("metadata mismatch");
@@ -620,7 +632,7 @@ impl<'a> CrateLocator<'a> {
                         "Rejecting via version: expected {} got {}",
                         expected_version, found_version
                     );
-                    self.crate_rejections
+                    crate_rejections
                         .via_version
                         .push(CrateMismatch { path: lib, got: found_version });
                     continue;
@@ -635,7 +647,7 @@ impl<'a> CrateLocator<'a> {
                     // The file was present and created by the same compiler version, but we
                     // couldn't load it for some reason. Give a hard error instead of silently
                     // ignoring it, but only if we would have given an error anyway.
-                    self.crate_rejections.via_invalid.push(CrateMismatch { path: lib, got: err });
+                    crate_rejections.via_invalid.push(CrateMismatch { path: lib, got: err });
                     continue;
                 }
                 Err(err @ MetadataError::NotPresent(_)) => {
@@ -713,7 +725,12 @@ impl<'a> CrateLocator<'a> {
         }
     }
 
-    fn crate_matches(&mut self, metadata: &MetadataBlob, libpath: &Path) -> Option<Svh> {
+    fn crate_matches(
+        &self,
+        crate_rejections: &mut CrateRejections,
+        metadata: &MetadataBlob,
+        libpath: &Path,
+    ) -> Option<Svh> {
         let header = metadata.get_header();
         if header.is_proc_macro_crate != self.is_proc_macro {
             info!(
@@ -730,7 +747,7 @@ impl<'a> CrateLocator<'a> {
 
         if header.triple != self.tuple {
             info!("Rejecting via crate triple: expected {} got {}", self.tuple, header.triple);
-            self.crate_rejections.via_triple.push(CrateMismatch {
+            crate_rejections.via_triple.push(CrateMismatch {
                 path: libpath.to_path_buf(),
                 got: header.triple.to_string(),
             });
@@ -741,7 +758,7 @@ impl<'a> CrateLocator<'a> {
         if let Some(expected_hash) = self.hash {
             if hash != expected_hash {
                 info!("Rejecting via hash: expected {} got {}", expected_hash, hash);
-                self.crate_rejections
+                crate_rejections
                     .via_hash
                     .push(CrateMismatch { path: libpath.to_path_buf(), got: hash.to_string() });
                 return None;
@@ -751,7 +768,10 @@ impl<'a> CrateLocator<'a> {
         Some(hash)
     }
 
-    fn find_commandline_library(&mut self) -> Result<Option<Library>, CrateError> {
+    fn find_commandline_library(
+        &self,
+        crate_rejections: &mut CrateRejections,
+    ) -> Result<Option<Library>, CrateError> {
         // First, filter out all libraries that look suspicious. We only accept
         // files which actually exist that have the correct naming scheme for
         // rlibs/dylibs.
@@ -796,24 +816,28 @@ impl<'a> CrateLocator<'a> {
                 dylibs.insert(loc_canon.clone(), PathKind::ExternFlag);
                 continue;
             }
-            self.crate_rejections
+            crate_rejections
                 .via_filename
                 .push(CrateMismatch { path: loc_orig.clone(), got: String::new() });
         }
 
         // Extract the dylib/rlib/rmeta triple.
-        self.extract_lib(rlibs, rmetas, dylibs, sdylib_interfaces)
+        self.extract_lib(crate_rejections, rlibs, rmetas, dylibs, sdylib_interfaces)
             .map(|opt| opt.map(|(_, lib)| lib))
     }
 
-    pub(crate) fn into_error(self, dep_root: Option<CratePaths>) -> CrateError {
+    pub(crate) fn into_error(
+        self,
+        crate_rejections: CrateRejections,
+        dep_root: Option<CratePaths>,
+    ) -> CrateError {
         CrateError::LocatorCombined(Box::new(CombinedLocatorError {
             crate_name: self.crate_name,
             dep_root,
             triple: self.tuple,
             dll_prefix: self.target.dll_prefix.to_string(),
             dll_suffix: self.target.dll_suffix.to_string(),
-            crate_rejections: self.crate_rejections,
+            crate_rejections,
         }))
     }
 }
@@ -993,7 +1017,7 @@ struct CrateMismatch {
 }
 
 #[derive(Clone, Debug, Default)]
-struct CrateRejections {
+pub(crate) struct CrateRejections {
     via_hash: Vec<CrateMismatch>,
     via_triple: Vec<CrateMismatch>,
     via_kind: Vec<CrateMismatch>,
diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
index f8d54d44557..b9f8e558df4 100644
--- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
+++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version
@@ -1 +1 @@
-0.20.7
\ No newline at end of file
+0.21.1
\ No newline at end of file
diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md
index 201a5503079..9fe4c218121 100644
--- a/src/doc/rustc/src/SUMMARY.md
+++ b/src/doc/rustc/src/SUMMARY.md
@@ -130,7 +130,7 @@
     - [\*-win7-windows-msvc](platform-support/win7-windows-msvc.md)
     - [x86_64-fortanix-unknown-sgx](platform-support/x86_64-fortanix-unknown-sgx.md)
     - [x86_64-pc-cygwin](platform-support/x86_64-pc-cygwin.md)
-    - [x86_64-unknown-linux-none.md](platform-support/x86_64-unknown-linux-none.md)
+    - [x86_64-unknown-linux-none](platform-support/x86_64-unknown-linux-none.md)
     - [x86_64-unknown-none](platform-support/x86_64-unknown-none.md)
     - [xtensa-\*-none-elf](platform-support/xtensa.md)
     - [\*-nuttx-\*](platform-support/nuttx.md)
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 93d0e76b56f..2ab827b3ace 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -66,8 +66,8 @@ use crate::visit_ast::Module as DocModule;
 pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<'tcx>) -> Item {
     let mut items: Vec<Item> = vec![];
     let mut inserted = FxHashSet::default();
-    items.extend(doc.foreigns.iter().map(|(item, renamed)| {
-        let item = clean_maybe_renamed_foreign_item(cx, item, *renamed);
+    items.extend(doc.foreigns.iter().map(|(item, renamed, import_id)| {
+        let item = clean_maybe_renamed_foreign_item(cx, item, *renamed, *import_id);
         if let Some(name) = item.name
             && (cx.render_options.document_hidden || !item.is_doc_hidden())
         {
@@ -89,7 +89,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext<
         Some(item)
     }));
 
-    // Split up imports from all other items.
+    // Split up glob imports from all other items.
     //
     // 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
@@ -2762,7 +2762,6 @@ fn clean_maybe_renamed_item<'tcx>(
     import_id: Option<LocalDefId>,
 ) -> Vec<Item> {
     use hir::ItemKind;
-
     fn get_name(
         cx: &DocContext<'_>,
         item: &hir::Item<'_>,
@@ -2974,6 +2973,7 @@ fn clean_extern_crate<'tcx>(
         && !cx.is_json_output();
 
     let krate_owner_def_id = krate.owner_id.def_id;
+
     if please_inline
         && let Some(items) = inline::try_inline(
             cx,
@@ -3135,6 +3135,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
     cx: &mut DocContext<'tcx>,
     item: &hir::ForeignItem<'tcx>,
     renamed: Option<Symbol>,
+    import_id: Option<LocalDefId>,
 ) -> Item {
     let def_id = item.owner_id.to_def_id();
     cx.with_param_env(def_id, |cx| {
@@ -3150,11 +3151,13 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
             hir::ForeignItemKind::Type => ForeignTypeItem,
         };
 
-        Item::from_def_id_and_parts(
-            item.owner_id.def_id.to_def_id(),
-            Some(renamed.unwrap_or(item.ident.name)),
-            kind,
+        generate_item_with_correct_attrs(
             cx,
+            kind,
+            item.owner_id.def_id.to_def_id(),
+            item.ident.name,
+            import_id,
+            renamed,
         )
     })
 }
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 5b52e785b8f..2889bfae7b0 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -38,9 +38,18 @@ pub(crate) struct Module<'hir> {
         (LocalDefId, Option<Symbol>),
         (&'hir hir::Item<'hir>, Option<Symbol>, Option<LocalDefId>),
     >,
-    /// Same as for `items`.
+
+    /// (def_id, renamed) -> (res, local_import_id)
+    ///
+    /// `inlined_foreigns` only contains `extern` items
+    /// that are cross-crate inlined.
+    ///
+    /// Locally inlined `extern` items are
+    /// stored in `foreigns` with the `import_id` set,
+    /// analogous to how `items` is.
     pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), (Res, LocalDefId)>,
-    pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>)>,
+    /// (item, renamed, import_id)
+    pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>, Option<LocalDefId>)>,
 }
 
 impl Module<'_> {
@@ -327,7 +336,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             }
             Node::ForeignItem(it) if !glob => {
                 let prev = mem::replace(&mut self.inlining, true);
-                self.visit_foreign_item_inner(it, renamed);
+                self.visit_foreign_item_inner(it, renamed, Some(def_id));
                 self.inlining = prev;
                 true
             }
@@ -432,7 +441,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             hir::ItemKind::ForeignMod { items, .. } => {
                 for item in items {
                     let item = tcx.hir_foreign_item(item.id);
-                    self.visit_foreign_item_inner(item, None);
+                    self.visit_foreign_item_inner(item, None, None);
                 }
             }
             // If we're inlining, skip private items.
@@ -527,10 +536,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
         &mut self,
         item: &'tcx hir::ForeignItem<'_>,
         renamed: Option<Symbol>,
+        import_id: Option<LocalDefId>,
     ) {
         // If inlining we only want to include public functions.
         if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
-            self.modules.last_mut().unwrap().foreigns.push((item, renamed));
+            self.modules.last_mut().unwrap().foreigns.push((item, renamed, import_id));
         }
     }
 
diff --git a/src/tools/rustdoc-gui/tester.js b/src/tools/rustdoc-gui/tester.js
index ff87b76aa45..9dc32f335a8 100644
--- a/src/tools/rustdoc-gui/tester.js
+++ b/src/tools/rustdoc-gui/tester.js
@@ -196,7 +196,7 @@ async function main(argv) {
         // This is more convenient that setting fields one by one.
         const args = [
             "--variable", "DOC_PATH", opts["doc_folder"].split("\\").join("/"),
-            "--enable-fail-on-js-error", "--allow-file-access-from-files",
+            "--allow-file-access-from-files",
         ];
         if (opts["debug"]) {
             debug = true;
diff --git a/tests/rustdoc/reexport/extern-135092.rs b/tests/rustdoc/reexport/extern-135092.rs
new file mode 100644
index 00000000000..fb5c71d56d5
--- /dev/null
+++ b/tests/rustdoc/reexport/extern-135092.rs
@@ -0,0 +1,26 @@
+// Test to make sure reexports of extern items are combined
+// <https://github.com/rust-lang/rust/issues/135092>
+
+#![crate_name = "foo"]
+
+mod native {
+    extern "C" {
+        /// bar.
+        pub fn bar();
+    }
+
+    /// baz.
+    pub fn baz() {}
+}
+
+//@ has 'foo/fn.bar.html'
+//@ has - '//div[@class="docblock"]' 'bar.'
+//@ has - '//div[@class="docblock"]' 'foo'
+/// foo
+pub use native::bar;
+
+//@ has 'foo/fn.baz.html'
+//@ has - '//div[@class="docblock"]' 'baz.'
+//@ has - '//div[@class="docblock"]' 'foo'
+/// foo
+pub use native::baz;
diff --git a/triagebot.toml b/triagebot.toml
index 6385528e7b6..64a8a7c46b4 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -1,6 +1,11 @@
 # This file's format is documented at
 # https://forge.rust-lang.org/triagebot/pr-assignment.html#configuration
 
+
+# ------------------------------------------------------------------------------
+# Labels
+# ------------------------------------------------------------------------------
+
 [relabel]
 allow-unauthenticated = [
     "A-*",
@@ -44,6 +49,11 @@ remove_labels = ["S-waiting-on-author"]
 # Those labels are added when PR author requests a review from an assignee
 add_labels = ["S-waiting-on-review"]
 
+
+# ------------------------------------------------------------------------------
+# Ping groups
+# ------------------------------------------------------------------------------
+
 [ping.windows]
 message = """\
 Hey Windows Group! This bug has been identified as a good "Windows candidate".
@@ -153,6 +163,11 @@ Hi relnotes-interest-group, this issue/PR could use some help in reviewing /
 adjusting release notes. Could you take a look if available? Thanks <3
 """
 
+
+# ------------------------------------------------------------------------------
+# Autolabels
+# ------------------------------------------------------------------------------
+
 [prioritize]
 label = "I-prioritize"
 
@@ -582,6 +597,11 @@ trigger_files = [
     "compiler/rustc_codegen_llvm",
 ]
 
+
+# ------------------------------------------------------------------------------
+# Prioritization and team nominations
+# ------------------------------------------------------------------------------
+
 [notify-zulip."I-prioritize"]
 zulip_stream = 245100 # #t-compiler/prioritization/alerts
 topic = "#{number} {title}"
@@ -598,6 +618,21 @@ message_on_remove = "Issue #{number}'s prioritization request has been removed."
 message_on_close = "Issue #{number} has been closed while requested for prioritization."
 message_on_reopen = "Issue #{number} has been reopened."
 
+[notify-zulip."I-types-nominated"]
+zulip_stream = 326866 # #T-types/nominated
+topic = "#{number}: {title}"
+message_on_add = """\
+@*T-types* issue #{number} "{title}" has been nominated for team discussion.
+"""
+message_on_remove = "Issue #{number}'s nomination has been removed. Thanks all for participating!"
+message_on_close = "Issue #{number} has been closed. Thanks for participating!"
+message_on_reopen = "Issue #{number} has been reopened. Pinging @*T-types*."
+
+
+# ------------------------------------------------------------------------------
+# Zulip notifications
+# ------------------------------------------------------------------------------
+
 [notify-zulip."beta-nominated".rustdoc]
 required_labels = ["T-rustdoc"]
 zulip_stream = 266220 # #t-rustdoc
@@ -661,15 +696,6 @@ message_on_remove = "PR #{number}'s stable-acceptance has been **removed**."
 message_on_close = "PR #{number} has been closed. Thanks for participating!"
 message_on_reopen = "PR #{number} has been reopened. Pinging @*T-rustdoc*."
 
-[notify-zulip."I-types-nominated"]
-zulip_stream = 326866 # #T-types/nominated
-topic = "#{number}: {title}"
-message_on_add = """\
-@*T-types* issue #{number} "{title}" has been nominated for team discussion.
-"""
-message_on_remove = "Issue #{number}'s nomination has been removed. Thanks all for participating!"
-message_on_close = "Issue #{number} has been closed. Thanks for participating!"
-message_on_reopen = "Issue #{number} has been reopened. Pinging @*T-types*."
 
 [notify-zulip."beta-nominated".compiler]
 required_labels = ["T-compiler"]
@@ -688,6 +714,13 @@ don't know
 ]
 message_on_remove = "PR #{number}'s beta-nomination has been removed."
 
+[notify-zulip."beta-accepted".compiler]
+required_labels = ["T-compiler"]
+zulip_stream = 474880 # #t-compiler/backports
+# Put it in the same thread as beta-nominated.
+topic = "#{number}: beta-nominated"
+message_on_add = "PR #{number} has been **accepted** for **beta** backport."
+
 [notify-zulip."stable-nominated".compiler]
 required_labels = ["T-compiler"]
 zulip_stream = 474880 # #t-compiler/backports
@@ -706,6 +739,14 @@ don't know
 ]
 message_on_remove = "PR #{number}'s stable-nomination has been removed."
 
+[notify-zulip."stable-accepted".compiler]
+required_labels = ["T-compiler"]
+zulip_stream = 474880 # #t-compiler/backports
+# Put it in the same thread as stable-nominated.
+topic = "#{number}: stable-nominated"
+message_on_add = "PR #{number} has been **accepted** for **stable** backport."
+
+
 [notify-zulip."beta-nominated".bootstrap]
 required_labels = ["T-bootstrap"]
 zulip_stream = 507486 # #t-infra/bootstrap/backports
@@ -723,6 +764,13 @@ don't know
 ]
 message_on_remove = "PR #{number}'s beta-nomination has been removed."
 
+[notify-zulip."beta-accepted".bootstrap]
+required_labels = ["T-bootstrap"]
+zulip_stream = 507486 # #t-infra/bootstrap/backports
+# Put it in the same thread as beta-nominated.
+topic = "#{number}: beta-nominated"
+message_on_add = "PR #{number} has been **accepted** for **beta** backport."
+
 [notify-zulip."stable-nominated".bootstrap]
 required_labels = ["T-bootstrap"]
 zulip_stream = 507486 # #t-infra/bootstrap/backports
@@ -741,6 +789,14 @@ don't know
 ]
 message_on_remove = "PR #{number}'s stable-nomination has been removed."
 
+[notify-zulip."stable-accepted".bootstrap]
+required_labels = ["T-bootstrap"]
+zulip_stream = 507486 # #t-infra/bootstrap/backports
+# Put it in the same thread as stable-nominated.
+topic = "#{number}: stable-nominated"
+message_on_add = "PR #{number} has been **accepted** for **stable** backport."
+
+
 [notify-zulip."A-edition-2021"]
 required_labels = ["C-bug"]
 zulip_stream = 268952 # #edition
@@ -757,17 +813,10 @@ message_on_add = """\
 Issue #{number} "{title}" has been added.
 """
 
-[no-merges]
-exclude_titles = ["Rollup of", "subtree update", "Subtree update"]
-labels = ["has-merge-commits", "S-waiting-on-author"]
-
-[github-releases]
-format = "rustc"
-project-name = "Rust"
-changelog-path = "RELEASES.md"
-changelog-branch = "master"
 
-[shortcut]
+# ------------------------------------------------------------------------------
+# Mentions
+# ------------------------------------------------------------------------------
 
 [mentions."triagebot.toml"]
 message = "`triagebot.toml` has been modified, there may have been changes to the review queue."
@@ -1201,6 +1250,11 @@ cc = ["@m-ou-se"]
 [mentions."compiler/rustc_ast_lowering/src/format.rs"]
 cc = ["@m-ou-se"]
 
+
+# ------------------------------------------------------------------------------
+# PR assignments
+# ------------------------------------------------------------------------------
+
 [assign]
 warn_non_default_branch.enable = true
 contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html"
@@ -1442,6 +1496,23 @@ compiletest = [
 
 [pr-tracking]
 
+
+# ------------------------------------------------------------------------------
+# Misc
+# ------------------------------------------------------------------------------
+
+[no-merges]
+exclude_titles = ["Rollup of", "subtree update", "Subtree update"]
+labels = ["has-merge-commits", "S-waiting-on-author"]
+
+[github-releases]
+format = "rustc"
+project-name = "Rust"
+changelog-path = "RELEASES.md"
+changelog-branch = "master"
+
+[shortcut]
+
 # Enable issue transfers within the org
 # Documentation at: https://forge.rust-lang.org/triagebot/transfer.html
 [transfer]