Skip to content

Commit b38ece9

Browse files
Rollup merge of #143465 - kornelski:extern-name, r=petrochenkov
Support multiple crate versions in --extern-html-root-url Rustdoc's `--extern-html-root-url` used to use `tcx.crate_name()` to identify crates, but that used crates' internal names from their metadata, instead of names given to them in `--extern`. That was confusing, because both `--extern…` arguments seem related and use similar syntax. Crucially, this didn't work correctly with Cargo's package aliases or multiple versions of crates. `sess.opts.externs` lacks `CrateNum`, and `Resolver.extern_prelude` gets destroyed before `rustdoc` has a chance to see it, so I've had to save this mapping in `CStore`. Just in case, I've kept the previous mapping by crate name as a fallback for crates that weren't matched by their extern name. Fixes #76296
2 parents e5e79f8 + 276c423 commit b38ece9

File tree

7 files changed

+108
-13
lines changed

7 files changed

+108
-13
lines changed

compiler/rustc_metadata/src/creader.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_data_structures::fx::FxHashSet;
1212
use rustc_data_structures::owned_slice::OwnedSlice;
1313
use rustc_data_structures::svh::Svh;
1414
use rustc_data_structures::sync::{self, FreezeReadGuard, FreezeWriteGuard};
15+
use rustc_data_structures::unord::UnordMap;
1516
use rustc_expand::base::SyntaxExtension;
1617
use rustc_fs_util::try_canonicalize;
1718
use rustc_hir as hir;
@@ -69,6 +70,9 @@ pub struct CStore {
6970
/// This crate has a `#[alloc_error_handler]` item.
7071
has_alloc_error_handler: bool,
7172

73+
/// Names that were used to load the crates via `extern crate` or paths.
74+
resolved_externs: UnordMap<Symbol, CrateNum>,
75+
7276
/// Unused externs of the crate
7377
unused_externs: Vec<Symbol>,
7478

@@ -249,6 +253,22 @@ impl CStore {
249253
self.metas[cnum] = Some(Box::new(data));
250254
}
251255

256+
/// Save the name used to resolve the extern crate in the local crate
257+
///
258+
/// The name isn't always the crate's own name, because `sess.opts.externs` can assign it another name.
259+
/// It's also not always the same as the `DefId`'s symbol due to renames `extern crate resolved_name as defid_name`.
260+
pub(crate) fn set_resolved_extern_crate_name(&mut self, name: Symbol, extern_crate: CrateNum) {
261+
self.resolved_externs.insert(name, extern_crate);
262+
}
263+
264+
/// Crate resolved and loaded via the given extern name
265+
/// (corresponds to names in `sess.opts.externs`)
266+
///
267+
/// May be `None` if the crate wasn't used
268+
pub fn resolved_extern_crate(&self, externs_name: Symbol) -> Option<CrateNum> {
269+
self.resolved_externs.get(&externs_name).copied()
270+
}
271+
252272
pub(crate) fn iter_crate_data(&self) -> impl Iterator<Item = (CrateNum, &CrateMetadata)> {
253273
self.metas
254274
.iter_enumerated()
@@ -475,6 +495,7 @@ impl CStore {
475495
alloc_error_handler_kind: None,
476496
has_global_allocator: false,
477497
has_alloc_error_handler: false,
498+
resolved_externs: UnordMap::default(),
478499
unused_externs: Vec::new(),
479500
used_extern_options: Default::default(),
480501
}
@@ -511,7 +532,7 @@ impl CStore {
511532
// We're also sure to compare *paths*, not actual byte slices. The
512533
// `source` stores paths which are normalized which may be different
513534
// from the strings on the command line.
514-
let source = self.get_crate_data(cnum).cdata.source();
535+
let source = data.source();
515536
if let Some(entry) = externs.get(name.as_str()) {
516537
// Only use `--extern crate_name=path` here, not `--extern crate_name`.
517538
if let Some(mut files) = entry.files() {
@@ -1308,6 +1329,7 @@ impl CStore {
13081329
let path_len = definitions.def_path(def_id).data.len();
13091330
self.update_extern_crate(
13101331
cnum,
1332+
name,
13111333
ExternCrate {
13121334
src: ExternCrateSource::Extern(def_id.to_def_id()),
13131335
span: item.span,
@@ -1332,6 +1354,7 @@ impl CStore {
13321354

13331355
self.update_extern_crate(
13341356
cnum,
1357+
name,
13351358
ExternCrate {
13361359
src: ExternCrateSource::Path,
13371360
span,

compiler/rustc_metadata/src/rmeta/decoder.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,9 +1937,13 @@ impl CrateMetadata {
19371937
self.root.decode_target_modifiers(&self.blob).collect()
19381938
}
19391939

1940-
pub(crate) fn update_extern_crate(&mut self, new_extern_crate: ExternCrate) -> bool {
1940+
/// Keep `new_extern_crate` if it looks better in diagnostics
1941+
pub(crate) fn update_extern_crate_diagnostics(
1942+
&mut self,
1943+
new_extern_crate: ExternCrate,
1944+
) -> bool {
19411945
let update =
1942-
Some(new_extern_crate.rank()) > self.extern_crate.as_ref().map(ExternCrate::rank);
1946+
self.extern_crate.as_ref().is_none_or(|old| old.rank() < new_extern_crate.rank());
19431947
if update {
19441948
self.extern_crate = Some(new_extern_crate);
19451949
}

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,14 +627,37 @@ impl CStore {
627627
}
628628
}
629629

630-
pub(crate) fn update_extern_crate(&mut self, cnum: CrateNum, extern_crate: ExternCrate) {
630+
/// Track how an extern crate has been loaded. Called after resolving an import in the local crate.
631+
///
632+
/// * the `name` is for [`Self::set_resolved_extern_crate_name`] saving `--extern name=`
633+
/// * `extern_crate` is for diagnostics
634+
pub(crate) fn update_extern_crate(
635+
&mut self,
636+
cnum: CrateNum,
637+
name: Symbol,
638+
extern_crate: ExternCrate,
639+
) {
640+
debug_assert_eq!(
641+
extern_crate.dependency_of, LOCAL_CRATE,
642+
"this function should not be called on transitive dependencies"
643+
);
644+
self.set_resolved_extern_crate_name(name, cnum);
645+
self.update_transitive_extern_crate_diagnostics(cnum, extern_crate);
646+
}
647+
648+
/// `CrateMetadata` uses `ExternCrate` only for diagnostics
649+
fn update_transitive_extern_crate_diagnostics(
650+
&mut self,
651+
cnum: CrateNum,
652+
extern_crate: ExternCrate,
653+
) {
631654
let cmeta = self.get_crate_data_mut(cnum);
632-
if cmeta.update_extern_crate(extern_crate) {
655+
if cmeta.update_extern_crate_diagnostics(extern_crate) {
633656
// Propagate the extern crate info to dependencies if it was updated.
634657
let extern_crate = ExternCrate { dependency_of: cnum, ..extern_crate };
635658
let dependencies = mem::take(&mut cmeta.dependencies);
636659
for &dep_cnum in &dependencies {
637-
self.update_extern_crate(dep_cnum, extern_crate);
660+
self.update_transitive_extern_crate_diagnostics(dep_cnum, extern_crate);
638661
}
639662
self.get_crate_data_mut(cnum).dependencies = dependencies;
640663
}

src/doc/rustdoc/src/unstable-features.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,12 @@ flags to control that behavior. When the `--extern-html-root-url` flag is given
395395
one of your dependencies, rustdoc use that URL for those docs. Keep in mind that if those docs exist
396396
in the output directory, those local docs will still override this flag.
397397

398+
The names in this flag are first matched against the names given in the `--extern name=` flags,
399+
which allows selecting between multiple crates with the same name (e.g. multiple versions of
400+
the same crate). For transitive dependencies that haven't been loaded via an `--extern` flag, matching
401+
falls backs to using crate names only, without ability to distinguish between multiple crates with
402+
the same name.
403+
398404
## `-Z force-unstable-if-unmarked`
399405

400406
Using this flag looks like this:

src/librustdoc/formats/cache.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_ast::join_path_syms;
44
use rustc_attr_data_structures::StabilityLevel;
55
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
66
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
7+
use rustc_metadata::creader::CStore;
78
use rustc_middle::ty::{self, TyCtxt};
89
use rustc_span::Symbol;
910
use tracing::debug;
@@ -158,18 +159,33 @@ impl Cache {
158159
assert!(cx.external_traits.is_empty());
159160
cx.cache.traits = mem::take(&mut krate.external_traits);
160161

162+
let render_options = &cx.render_options;
163+
let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
164+
let dst = &render_options.output;
165+
166+
// Make `--extern-html-root-url` support the same names as `--extern` whenever possible
167+
let cstore = CStore::from_tcx(tcx);
168+
for (name, extern_url) in &render_options.extern_html_root_urls {
169+
if let Some(crate_num) = cstore.resolved_extern_crate(Symbol::intern(name)) {
170+
let e = ExternalCrate { crate_num };
171+
let ___location = e.___location(Some(extern_url), extern_url_takes_precedence, dst, tcx);
172+
cx.cache.extern_locations.insert(e.crate_num, ___location);
173+
}
174+
}
175+
161176
// Cache where all our extern crates are located
162-
// FIXME: this part is specific to HTML so it'd be nice to remove it from the common code
177+
// This is also used in the JSON output.
163178
for &crate_num in tcx.crates(()) {
164179
let e = ExternalCrate { crate_num };
165180

166181
let name = e.name(tcx);
167-
let render_options = &cx.render_options;
168-
let extern_url = render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
169-
let extern_url_takes_precedence = render_options.extern_html_root_takes_precedence;
170-
let dst = &render_options.output;
171-
let ___location = e.___location(extern_url, extern_url_takes_precedence, dst, tcx);
172-
cx.cache.extern_locations.insert(e.crate_num, ___location);
182+
cx.cache.extern_locations.entry(e.crate_num).or_insert_with(|| {
183+
// falls back to matching by crates' own names, because
184+
// transitive dependencies and injected crates may be loaded without `--extern`
185+
let extern_url =
186+
render_options.extern_html_root_urls.get(name.as_str()).map(|u| &**u);
187+
e.___location(extern_url, extern_url_takes_precedence, dst, tcx)
188+
});
173189
cx.cache.external_paths.insert(e.def_id(), (vec![name], ItemType::Module));
174190
}
175191

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@ compile-flags:-Z unstable-options --extern-html-root-url externs_name=https://renamed.example.com --extern-html-root-url empty=https://bad.invalid
2+
//@ aux-crate:externs_name=empty.rs
3+
//@ edition: 2018
4+
5+
extern crate externs_name as renamed;
6+
7+
//@ has extern_html_alias/index.html
8+
//@ has - '//a/@href' 'https://renamed.example.com/empty/index.html'
9+
pub use renamed as yet_different_name;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ 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
2+
//@ aux-crate:externs_name=empty.rs
3+
//@ edition: 2018
4+
5+
mod m {
6+
pub extern crate externs_name as renamed_privately;
7+
}
8+
9+
// renaming within the crate's source code is not supposed to affect CLI flags
10+
extern crate externs_name as renamed_locally;
11+
12+
//@ has extern_html_fallback/index.html
13+
//@ has - '//a/@href' 'https://localhost/empty/index.html'
14+
pub use crate::renamed_locally as yet_another_name;

0 commit comments

Comments
 (0)