about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs36
-rw-r--r--tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.rs21
-rw-r--r--tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.stderr22
-rw-r--r--tests/rustdoc/intra-doc/link-to-proc-macro.rs13
4 files changed, 84 insertions, 8 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 37628f16600..1daaba3b86c 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -22,6 +22,7 @@ use rustc_resolve::rustdoc::{
     MalformedGenerics, has_primitive_or_keyword_docs, prepare_to_doc_link_resolution,
     source_span_for_markdown_range, strip_generics_from_path,
 };
+use rustc_session::config::CrateType;
 use rustc_session::lint::Lint;
 use rustc_span::BytePos;
 use rustc_span::hygiene::MacroKind;
@@ -1169,7 +1170,6 @@ impl LinkCollector<'_, '_> {
     #[allow(rustc::potential_query_instability)]
     pub(crate) fn resolve_ambiguities(&mut self) {
         let mut ambiguous_links = mem::take(&mut self.ambiguous_links);
-
         for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() {
             for info in info_items {
                 info.resolved.retain(|(res, _)| match res {
@@ -2227,15 +2227,35 @@ fn ambiguity_error(
     emit_error: bool,
 ) -> bool {
     let mut descrs = FxHashSet::default();
-    let kinds = candidates
+    // proc macro can exist in multiple namespaces at once, so we need to compare `DefIds`
+    //  to remove the candidate in the fn namespace.
+    let mut possible_proc_macro_id = None;
+    let is_proc_macro_crate = cx.tcx.crate_types() == &[CrateType::ProcMacro];
+    let mut kinds = candidates
         .iter()
-        .map(
-            |(res, def_id)| {
-                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res }
-            },
-        )
-        .filter(|res| descrs.insert(res.descr()))
+        .map(|(res, def_id)| {
+            let r =
+                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res };
+            if is_proc_macro_crate && let Res::Def(DefKind::Macro(_), id) = r {
+                possible_proc_macro_id = Some(id);
+            }
+            r
+        })
         .collect::<Vec<_>>();
+    // In order to properly dedup proc macros, we have to do it in two passes:
+    // 1. Completing the full traversal to find the possible duplicate in the macro namespace,
+    // 2. Another full traversal to eliminate the candidate in the fn namespace.
+    //
+    // Thus, we have to do an iteration after collection is finished.
+    //
+    // As an optimization, we only deduplicate if we're in a proc-macro crate,
+    // and only if we already found something that looks like a proc macro.
+    if is_proc_macro_crate && let Some(macro_id) = possible_proc_macro_id {
+        kinds.retain(|res| !matches!(res, Res::Def(DefKind::Fn, fn_id) if macro_id == *fn_id));
+    }
+
+    kinds.retain(|res| descrs.insert(res.descr()));
+
     if descrs.len() == 1 {
         // There is no way for users to disambiguate at this point, so better return the first
         // candidate and not show a warning.
diff --git a/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.rs b/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.rs
new file mode 100644
index 00000000000..b449465768e
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.rs
@@ -0,0 +1,21 @@
+//@ compile-flags: --crate-type=proc-macro --document-private-items
+#![deny(rustdoc::broken_intra_doc_links)]
+
+//! Link to [`m`].
+//~^ ERROR `m` is both a module and a macro
+
+// test a further edge case related to https://github.com/rust-lang/rust/issues/91274
+
+// we need to make sure that when there is actually an ambiguity
+// in a proc-macro crate, we print out a sensible error.
+// because proc macro crates can't normally export modules,
+// this can only happen in --document-private-items mode.
+
+extern crate proc_macro;
+
+mod m {}
+
+#[proc_macro]
+pub fn m(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    input
+}
diff --git a/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.stderr b/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.stderr
new file mode 100644
index 00000000000..09a5d26eded
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/bad-link-to-proc-macro.stderr
@@ -0,0 +1,22 @@
+error: `m` is both a module and a macro
+  --> $DIR/bad-link-to-proc-macro.rs:4:15
+   |
+LL | //! Link to [`m`].
+   |               ^ ambiguous link
+   |
+note: the lint level is defined here
+  --> $DIR/bad-link-to-proc-macro.rs:2:9
+   |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: to link to the module, prefix with `mod@`
+   |
+LL | //! Link to [`mod@m`].
+   |               ++++
+help: to link to the macro, add an exclamation mark
+   |
+LL | //! Link to [`m!`].
+   |                +
+
+error: aborting due to 1 previous error
+
diff --git a/tests/rustdoc/intra-doc/link-to-proc-macro.rs b/tests/rustdoc/intra-doc/link-to-proc-macro.rs
new file mode 100644
index 00000000000..6c289078db8
--- /dev/null
+++ b/tests/rustdoc/intra-doc/link-to-proc-macro.rs
@@ -0,0 +1,13 @@
+//@ compile-flags: --crate-type=proc-macro
+//@ has 'foo/index.html' '//a[@href="macro.my_macro.html"]' 'my_macro'
+//! Link to [`my_macro`].
+#![crate_name = "foo"]
+
+// regression test for https://github.com/rust-lang/rust/issues/91274
+
+extern crate proc_macro;
+
+#[proc_macro]
+pub fn my_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    input
+}