From 93da2e2d27bff59da10a91adec5febda720f8be3 Mon Sep 17 00:00:00 2001 From: Kornel Date: Thu, 3 Jul 2025 11:02:55 +0100 Subject: [PATCH 1/5] Avoid redundant lookup in CrateLoader::existing_match --- compiler/rustc_metadata/src/creader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index e65c7a684260d..5967eb936dfe0 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -533,7 +533,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { // We're also sure to compare *paths*, not actual byte slices. The // `source` stores paths which are normalized which may be different // from the strings on the command line. - let source = self.cstore.get_crate_data(cnum).cdata.source(); + let source = data.source(); if let Some(entry) = self.sess.opts.externs.get(name.as_str()) { // Only use `--extern crate_name=path` here, not `--extern crate_name`. if let Some(mut files) = entry.files() { From f6f94663860c032360cf78ecafcc407394bcf8f8 Mon Sep 17 00:00:00 2001 From: Kornel Date: Thu, 3 Jul 2025 13:41:54 +0100 Subject: [PATCH 2/5] Clarify update_extern_crate --- compiler/rustc_metadata/src/rmeta/decoder.rs | 8 +++++-- .../src/rmeta/decoder/cstore_impl.rs | 24 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index e6aedc61338bd..00c97a2f738e5 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1937,9 +1937,13 @@ impl CrateMetadata { self.root.decode_target_modifiers(&self.blob).collect() } - pub(crate) fn update_extern_crate(&mut self, new_extern_crate: ExternCrate) -> bool { + /// Keep `new_extern_crate` if it looks better in diagnostics + pub(crate) fn update_extern_crate_diagnostics( + &mut self, + new_extern_crate: ExternCrate, + ) -> bool { let update = - Some(new_extern_crate.rank()) > self.extern_crate.as_ref().map(ExternCrate::rank); + self.extern_crate.as_ref().is_none_or(|old| old.rank() < new_extern_crate.rank()); if update { self.extern_crate = Some(new_extern_crate); } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 57a672c45f7bc..989741c004a17 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -627,14 +627,32 @@ impl CStore { } } - pub(crate) fn update_extern_crate(&mut self, cnum: CrateNum, extern_crate: ExternCrate) { + /// Track how an extern crate has been loaded. Called after resolving an import in the local crate. + pub(crate) fn update_extern_crate( + &mut self, + cnum: CrateNum, + extern_crate: ExternCrate, + ) { + debug_assert_eq!( + extern_crate.dependency_of, LOCAL_CRATE, + "this function should not be called on transitive dependencies" + ); + self.update_transitive_extern_crate_diagnostics(cnum, extern_crate); + } + + /// `CrateMetadata` uses `ExternCrate` only for diagnostics + fn update_transitive_extern_crate_diagnostics( + &mut self, + cnum: CrateNum, + extern_crate: ExternCrate, + ) { let cmeta = self.get_crate_data_mut(cnum); - if cmeta.update_extern_crate(extern_crate) { + if cmeta.update_extern_crate_diagnostics(extern_crate) { // Propagate the extern crate info to dependencies if it was updated. let extern_crate = ExternCrate { dependency_of: cnum, ..extern_crate }; let dependencies = mem::take(&mut cmeta.dependencies); for &dep_cnum in &dependencies { - self.update_extern_crate(dep_cnum, extern_crate); + self.update_transitive_extern_crate_diagnostics(dep_cnum, extern_crate); } self.get_crate_data_mut(cnum).dependencies = dependencies; } From 57e649f5c73a642b947cb4d71f0917e102db42a0 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 15 Jul 2025 10:48:17 +0100 Subject: [PATCH 3/5] Save names of used extern crates Tracks association between `self.sess.opts.externs` (aliases in `--extern alias=rlib`) and resolved `CrateNum` Intended to allow Rustdoc match the aliases in `--extern-html-root-url` Force-injected extern crates aren't included, since they're meant for the linker only --- compiler/rustc_metadata/src/creader.rs | 15 +++++++++++++++ .../src/rmeta/decoder/cstore_impl.rs | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 5967eb936dfe0..75fcc9eeb2148 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -12,6 +12,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::owned_slice::OwnedSlice; use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::{self, FreezeReadGuard, FreezeWriteGuard}; +use rustc_data_structures::unord::UnordMap; use rustc_errors::DiagCtxtHandle; use rustc_expand::base::SyntaxExtension; use rustc_fs_util::try_canonicalize; @@ -68,6 +69,9 @@ pub struct CStore { /// This crate has a `#[alloc_error_handler]` item. has_alloc_error_handler: bool, + /// Names that were used to load the crates via `extern crate` or paths. + resolved_externs: UnordMap, + /// Unused externs of the crate unused_externs: Vec, } @@ -268,6 +272,14 @@ impl CStore { self.metas[cnum] = Some(Box::new(data)); } + /// Save the name used to resolve the extern crate in the local crate + /// + /// The name isn't always the crate's own name, because `sess.opts.externs` can assign it another name. + /// It's also not always the same as the `DefId`'s symbol due to renames `extern crate resolved_name as defid_name`. + pub(crate) fn set_resolved_extern_crate_name(&mut self, name: Symbol, extern_crate: CrateNum) { + self.resolved_externs.insert(name, extern_crate); + } + pub(crate) fn iter_crate_data(&self) -> impl Iterator { self.metas .iter_enumerated() @@ -494,6 +506,7 @@ impl CStore { alloc_error_handler_kind: None, has_global_allocator: false, has_alloc_error_handler: false, + resolved_externs: UnordMap::default(), unused_externs: Vec::new(), } } @@ -1302,6 +1315,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { let path_len = definitions.def_path(def_id).data.len(); self.cstore.update_extern_crate( cnum, + name, ExternCrate { src: ExternCrateSource::Extern(def_id.to_def_id()), span: item.span, @@ -1320,6 +1334,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { self.cstore.update_extern_crate( cnum, + name, ExternCrate { src: ExternCrateSource::Path, span, diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 989741c004a17..9415e420eed54 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -628,15 +628,20 @@ impl CStore { } /// Track how an extern crate has been loaded. Called after resolving an import in the local crate. + /// + /// * the `name` is for [`Self::set_resolved_extern_crate_name`] saving `--extern name=` + /// * `extern_crate` is for diagnostics pub(crate) fn update_extern_crate( &mut self, cnum: CrateNum, + name: Symbol, extern_crate: ExternCrate, ) { debug_assert_eq!( extern_crate.dependency_of, LOCAL_CRATE, "this function should not be called on transitive dependencies" ); + self.set_resolved_extern_crate_name(name, cnum); self.update_transitive_extern_crate_diagnostics(cnum, extern_crate); } From c0e5faba815f007fad4bf3036523d4ad70d762ee Mon Sep 17 00:00:00 2001 From: Kornel Date: Sat, 5 Jul 2025 02:00:57 +0100 Subject: [PATCH 4/5] Test renamed crates in rustdoc --- tests/rustdoc/extern/extern-html-alias.rs | 9 +++++++++ tests/rustdoc/extern/extern-html-fallback.rs | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/rustdoc/extern/extern-html-alias.rs create mode 100644 tests/rustdoc/extern/extern-html-fallback.rs diff --git a/tests/rustdoc/extern/extern-html-alias.rs b/tests/rustdoc/extern/extern-html-alias.rs new file mode 100644 index 0000000000000..3ff782d3963cb --- /dev/null +++ b/tests/rustdoc/extern/extern-html-alias.rs @@ -0,0 +1,9 @@ +//@ compile-flags:-Z unstable-options --extern-html-root-url externs_name=https://renamed.example.com --extern-html-root-url empty=https://bad.invalid +//@ aux-crate:externs_name=empty.rs +//@ edition: 2018 + +extern crate externs_name as renamed; + +//@ has extern_html_alias/index.html +//@ has - '//a/@href' 'https://renamed.example.com/empty/index.html' +pub use renamed as yet_different_name; diff --git a/tests/rustdoc/extern/extern-html-fallback.rs b/tests/rustdoc/extern/extern-html-fallback.rs new file mode 100644 index 0000000000000..ddac9bf713c62 --- /dev/null +++ b/tests/rustdoc/extern/extern-html-fallback.rs @@ -0,0 +1,14 @@ +//@ compile-flags:-Z unstable-options --extern-html-root-url yet_another_name=https://bad.invalid --extern-html-root-url renamed_privately=https://bad.invalid --extern-html-root-url renamed_locally=https://bad.invalid --extern-html-root-url empty=https://localhost +//@ aux-crate:externs_name=empty.rs +//@ edition: 2018 + +mod m { + pub extern crate externs_name as renamed_privately; +} + +// renaming within the crate's source code is not supposed to affect CLI flags +extern crate externs_name as renamed_locally; + +//@ has extern_html_fallback/index.html +//@ has - '//a/@href' 'https://localhost/empty/index.html' +pub use crate::renamed_locally as yet_another_name; From 1dd48c554ad5217c57407e463712451ddc202b29 Mon Sep 17 00:00:00 2001 From: Kornel Date: Thu, 3 Jul 2025 15:13:19 +0100 Subject: [PATCH 5/5] Support multiple crate versions in --extern-html-root-url --- compiler/rustc_metadata/src/creader.rs | 8 +++++++ src/doc/rustdoc/src/unstable-features.md | 6 +++++ src/librustdoc/formats/cache.rs | 30 ++++++++++++++++++------ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 75fcc9eeb2148..ddf6da047e33e 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -280,6 +280,14 @@ impl CStore { self.resolved_externs.insert(name, extern_crate); } + /// Crate resolved and loaded via the given extern name + /// (corresponds to names in `sess.opts.externs`) + /// + /// May be `None` if the crate wasn't used + pub fn resolved_extern_crate(&self, externs_name: Symbol) -> Option { + self.resolved_externs.get(&externs_name).copied() + } + pub(crate) fn iter_crate_data(&self) -> impl Iterator { self.metas .iter_enumerated() diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 27910ad0ab796..7bd2970eee70e 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -395,6 +395,12 @@ flags to control that behavior. When the `--extern-html-root-url` flag is given one of your dependencies, rustdoc use that URL for those docs. Keep in mind that if those docs exist in the output directory, those local docs will still override this flag. +The names in this flag are first matched against the names given in the `--extern name=` flags, +which allows selecting between multiple crates with the same name (e.g. multiple versions of +the same crate). For transitive dependencies that haven't been loaded via an `--extern` flag, matching +falls backs to using crate names only, without ability to distinguish between multiple crates with +the same name. + ## `-Z force-unstable-if-unmarked` Using this flag looks like this: diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 5191120ebdb0f..e28cc3a542e5d 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -4,6 +4,7 @@ use rustc_ast::join_path_syms; use rustc_attr_data_structures::StabilityLevel; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet}; +use rustc_metadata::creader::CStore; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::Symbol; use tracing::debug; @@ -158,18 +159,33 @@ impl Cache { assert!(cx.external_traits.is_empty()); cx.cache.traits = mem::take(&mut krate.external_traits); + let render_options = &cx.render_options; + let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence; + let dst = &render_options.output; + + // Make `--extern-html-root-url` support the same names as `--extern` whenever possible + let cstore = CStore::from_tcx(tcx); + for (name, extern_url) in &render_options.extern_html_root_urls { + if let Some(crate_num) = cstore.resolved_extern_crate(Symbol::intern(name)) { + let e = ExternalCrate { crate_num }; + let location = e.location(Some(extern_url), extern_url_takes_precedence, dst, tcx); + cx.cache.extern_locations.insert(e.crate_num, location); + } + } + // Cache where all our extern crates are located - // FIXME: this part is specific to HTML so it'd be nice to remove it from the common code + // This is also used in the JSON output. for &crate_num in tcx.crates(()) { let e = ExternalCrate { crate_num }; let name = e.name(tcx); - let render_options = &cx.render_options; - let extern_url = render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u); - let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence; - let dst = &render_options.output; - let location = e.location(extern_url, extern_url_takes_precedence, dst, tcx); - cx.cache.extern_locations.insert(e.crate_num, location); + cx.cache.extern_locations.entry(e.crate_num).or_insert_with(|| { + // falls back to matching by crates' own names, because + // transitive dependencies and injected crates may be loaded without `--extern` + let extern_url = + render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u); + e.location(extern_url, extern_url_takes_precedence, dst, tcx) + }); cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module)); }