Repository: rust-lang/rust
Pull request: Fix ice in rustdoc of private reexport

Fixes rust-lang/rust#154383



The root cause is rustdoc could still try to resolve links for source docs that resolver did not cache in `ResolveDocLinks::Exported` mode. The test case will not crash with `--document-private-items` option, which will use `ResolveDocLinks::All`.

The fix makes rustdoc skip link resolution based on the source `DefId` of each doc fragment, so its behavior stays aligned with resolver's logic here: https://github.com/chenyukang/rust/blob/dc5cb1719eed6ac9275fe93d914d32141606b2ac/compiler/rustc_resolve/src/late.rs#L685


Changed files:
filename: src/librustdoc/passes/collect_intra_doc_links.rs
patch: @@ -1067,17 +1067,27 @@ impl LinkCollector<'_, '_> {
     #[instrument(level = "debug", skip_all)]
     fn resolve_links(&mut self, item: &Item) {
         let tcx = self.cx.tcx;
-        if !self.cx.document_private()
-            && let Some(def_id) = item.item_id.as_def_id()
-            && let Some(def_id) = def_id.as_local()
-            && !tcx.effective_visibilities(()).is_exported(def_id)
-            && !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs)
+        let document_private = self.cx.document_private();
+        let effective_visibilities = tcx.effective_visibilities(());
+        let should_skip_link_resolution = |item_id: DefId| {
+            !document_private
+                && item_id
+                    .as_local()
+                    .is_some_and(|local_def_id| !effective_visibilities.is_exported(local_def_id))
+                && !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs)
+        };
+
+        if let Some(def_id) = item.item_id.as_def_id()
+            && should_skip_link_resolution(def_id)
         {
             // Skip link resolution for non-exported items.
             return;
         }
 
-        let mut insert_links = |item_id, doc: &str| {
+        let mut try_insert_links = |item_id, doc: &str| {
+            if should_skip_link_resolution(item_id) {
+                return;
+            }
             let module_id = match tcx.def_kind(item_id) {
                 DefKind::Mod if item.inner_docs(tcx) => item_id,
                 _ => find_nearest_parent_module(tcx, item_id).unwrap(),
@@ -1108,7 +1118,7 @@ impl LinkCollector<'_, '_> {
             // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
             // This is a degenerate case and it's not supported by rustdoc.
             let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());
-            insert_links(item_id, &doc)
+            try_insert_links(item_id, &doc)
         }
 
         // Also resolve links in the note text of `#[deprecated]`.
@@ -1137,7 +1147,7 @@ impl LinkCollector<'_, '_> {
             } else {
                 item.item_id.expect_def_id()
             };
-            insert_links(item_id, note)
+            try_insert_links(item_id, note)
         }
     }
 

filename: tests/rustdoc-ui/intra-doc/doc-link-private-reexport-issue-154383.rs
patch: @@ -0,0 +1,10 @@
+//@ check-pass
+
+// Regression test for <https://github.com/rust-lang/rust/issues/154383>.
+// Rustdoc used to ICE on the doc link attached to a `pub use` inside a
+// private module.
+
+mod inner {
+    /// [std::vec::Vec]
+    pub use std::vec::Vec as MyVec;
+}