From 097b7a2ac7d918ccd5158550f7f3440d7d4073e2 Mon Sep 17 00:00:00 2001
From: bohan <bohan-zhang@foxmail.com>
Date: Mon, 12 May 2025 21:42:35 +0800
Subject: [PATCH] collect doc alias as tips during resolution

---
 Cargo.lock                                    |   1 +
 .../rustc_attr_parsing/src/attributes/util.rs |  30 ++++
 compiler/rustc_attr_parsing/src/lib.rs        |   4 +-
 compiler/rustc_hir_typeck/Cargo.toml          |   1 +
 compiler/rustc_hir_typeck/src/method/probe.rs |  37 +----
 .../rustc_resolve/src/late/diagnostics.rs     |  75 ++++++++-
 .../auxiliary/use-doc-alias-name-extern.rs    |  24 +++
 tests/ui/attributes/use-doc-alias-name.rs     |  67 ++++++++
 tests/ui/attributes/use-doc-alias-name.stderr | 150 ++++++++++++++++++
 9 files changed, 357 insertions(+), 32 deletions(-)
 create mode 100644 tests/ui/attributes/auxiliary/use-doc-alias-name-extern.rs
 create mode 100644 tests/ui/attributes/use-doc-alias-name.rs
 create mode 100644 tests/ui/attributes/use-doc-alias-name.stderr

diff --git a/Cargo.lock b/Cargo.lock
index e2b7b58c372ba..99cb71cd0ac87 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3809,6 +3809,7 @@ dependencies = [
  "rustc_abi",
  "rustc_ast",
  "rustc_attr_data_structures",
+ "rustc_attr_parsing",
  "rustc_data_structures",
  "rustc_errors",
  "rustc_fluent_macro",
diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs
index 05a9029c59aa5..503d2f1fae168 100644
--- a/compiler/rustc_attr_parsing/src/attributes/util.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/util.rs
@@ -26,3 +26,33 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
 pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> {
     first_attr_value_str_by_name(attrs, sym::crate_name)
 }
+
+pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
+    attrs: impl Iterator<Item = &'tcx T>,
+    symbol: Symbol,
+) -> bool {
+    let doc_attrs = attrs.filter(|attr| attr.has_name(sym::doc));
+    for attr in doc_attrs {
+        let Some(values) = attr.meta_item_list() else {
+            continue;
+        };
+        let alias_values = values.iter().filter(|v| v.has_name(sym::alias));
+        for v in alias_values {
+            if let Some(nested) = v.meta_item_list() {
+                // #[doc(alias("foo", "bar"))]
+                let mut iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol);
+                if iter.any(|s| s == symbol) {
+                    return true;
+                }
+            } else if let Some(meta) = v.meta_item()
+                && let Some(lit) = meta.name_value_literal()
+            {
+                // #[doc(alias = "foo")]
+                if lit.symbol == symbol {
+                    return true;
+                }
+            }
+        }
+    }
+    false
+}
diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs
index da683ad58c134..63bccf5201827 100644
--- a/compiler/rustc_attr_parsing/src/lib.rs
+++ b/compiler/rustc_attr_parsing/src/lib.rs
@@ -90,7 +90,9 @@ pub mod parser;
 mod session_diagnostics;
 
 pub use attributes::cfg::*;
-pub use attributes::util::{find_crate_name, is_builtin_attr, parse_version};
+pub use attributes::util::{
+    find_crate_name, is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version,
+};
 pub use context::{AttributeParser, OmitDoc};
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
diff --git a/compiler/rustc_hir_typeck/Cargo.toml b/compiler/rustc_hir_typeck/Cargo.toml
index 40fb2d6a106fa..c963f0ddefd4c 100644
--- a/compiler/rustc_hir_typeck/Cargo.toml
+++ b/compiler/rustc_hir_typeck/Cargo.toml
@@ -9,6 +9,7 @@ itertools = "0.12"
 rustc_abi = { path = "../rustc_abi" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_attr_data_structures = { path = "../rustc_attr_data_structures" }
+rustc_attr_parsing = { path = "../rustc_attr_parsing" }
 rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_fluent_macro = { path = "../rustc_fluent_macro" }
diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs
index bda051f156084..6090c0f9aee22 100644
--- a/compiler/rustc_hir_typeck/src/method/probe.rs
+++ b/compiler/rustc_hir_typeck/src/method/probe.rs
@@ -2,6 +2,7 @@ use std::cell::{Cell, RefCell};
 use std::cmp::max;
 use std::ops::Deref;
 
+use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::sso::SsoHashSet;
 use rustc_errors::Applicability;
@@ -2333,10 +2334,13 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
         };
         let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
         let attrs = self.fcx.tcx.hir_attrs(hir_id);
+
+        if is_doc_alias_attrs_contain_symbol(attrs.into_iter(), method.name) {
+            return true;
+        }
+
         for attr in attrs {
-            if attr.has_name(sym::doc) {
-                // do nothing
-            } else if attr.has_name(sym::rustc_confusables) {
+            if attr.has_name(sym::rustc_confusables) {
                 let Some(confusables) = attr.meta_item_list() else {
                     continue;
                 };
@@ -2348,33 +2352,6 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
                         return true;
                     }
                 }
-                continue;
-            } else {
-                continue;
-            };
-            let Some(values) = attr.meta_item_list() else {
-                continue;
-            };
-            for v in values {
-                if !v.has_name(sym::alias) {
-                    continue;
-                }
-                if let Some(nested) = v.meta_item_list() {
-                    // #[doc(alias("foo", "bar"))]
-                    for n in nested {
-                        if let Some(lit) = n.lit()
-                            && method.name == lit.symbol
-                        {
-                            return true;
-                        }
-                    }
-                } else if let Some(meta) = v.meta_item()
-                    && let Some(lit) = meta.name_value_literal()
-                    && method.name == lit.symbol
-                {
-                    // #[doc(alias = "foo")]
-                    return true;
-                }
             }
         }
         false
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index b538be34f31f1..6768907addee2 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -11,6 +11,7 @@ use rustc_ast::{
     Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
 };
 use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
+use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
 use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
 use rustc_errors::codes::*;
 use rustc_errors::{
@@ -39,7 +40,7 @@ use crate::late::{
 };
 use crate::ty::fast_reject::SimplifiedType;
 use crate::{
-    Module, ModuleKind, ModuleOrUniformRoot, PathResult, PathSource, Segment, errors,
+    Module, ModuleKind, ModuleOrUniformRoot, PathResult, PathSource, Resolver, Segment, errors,
     path_names_to_string,
 };
 
@@ -477,6 +478,19 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
             return (err, Vec::new());
         }
 
+        if let Some((did, item)) = self.lookup_doc_alias_name(path, source.namespace()) {
+            let item_name = item.name;
+            let suggestion_name = self.r.tcx.item_name(did);
+            err.span_suggestion(
+                item.span,
+                format!("`{suggestion_name}` has a name defined in the doc alias attribute as `{item_name}`"),
+                    suggestion_name,
+                    Applicability::MaybeIncorrect
+                );
+
+            return (err, Vec::new());
+        };
+
         let (found, suggested_candidates, mut candidates) = self.try_lookup_name_relaxed(
             &mut err,
             source,
@@ -852,6 +866,65 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
         (false, suggested_candidates, candidates)
     }
 
+    fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option<(DefId, Ident)> {
+        let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| {
+            for resolution in r.resolutions(m).borrow().values() {
+                let Some(did) =
+                    resolution.borrow().binding.and_then(|binding| binding.res().opt_def_id())
+                else {
+                    continue;
+                };
+                if did.is_local() {
+                    // We don't record the doc alias name in the local crate
+                    // because the people who write doc alias are usually not
+                    // confused by them.
+                    continue;
+                }
+                if is_doc_alias_attrs_contain_symbol(r.tcx.get_attrs(did, sym::doc), item_name) {
+                    return Some(did);
+                }
+            }
+            None
+        };
+
+        if path.len() == 1 {
+            for rib in self.ribs[ns].iter().rev() {
+                let item = path[0].ident;
+                if let RibKind::Module(module) = rib.kind
+                    && let Some(did) = find_doc_alias_name(self.r, module, item.name)
+                {
+                    return Some((did, item));
+                }
+            }
+        } else {
+            // Finds to the last resolved module item in the path
+            // and searches doc aliases within that module.
+            //
+            // Example: For the path `a::b::last_resolved::not_exist::c::d`,
+            // we will try to find any item has doc aliases named `not_exist`
+            // in `last_resolved` module.
+            //
+            // - Use `skip(1)` because the final segment must remain unresolved.
+            for (idx, seg) in path.iter().enumerate().rev().skip(1) {
+                let Some(id) = seg.id else {
+                    continue;
+                };
+                let Some(res) = self.r.partial_res_map.get(&id) else {
+                    continue;
+                };
+                if let Res::Def(DefKind::Mod, module) = res.expect_full_res()
+                    && let Some(module) = self.r.get_module(module)
+                    && let item = path[idx + 1].ident
+                    && let Some(did) = find_doc_alias_name(self.r, module, item.name)
+                {
+                    return Some((did, item));
+                }
+                break;
+            }
+        }
+        None
+    }
+
     fn suggest_trait_and_bounds(
         &mut self,
         err: &mut Diag<'_>,
diff --git a/tests/ui/attributes/auxiliary/use-doc-alias-name-extern.rs b/tests/ui/attributes/auxiliary/use-doc-alias-name-extern.rs
new file mode 100644
index 0000000000000..4c06d0fdfe390
--- /dev/null
+++ b/tests/ui/attributes/auxiliary/use-doc-alias-name-extern.rs
@@ -0,0 +1,24 @@
+#[doc(alias="DocAliasS1")]
+pub struct S1;
+
+#[doc(alias="DocAliasS2")]
+#[doc(alias("DocAliasS3", "DocAliasS4"))]
+pub struct S2;
+
+#[doc(alias("doc_alias_f1", "doc_alias_f2"))]
+pub fn f() {}
+
+pub mod m {
+    #[doc(alias="DocAliasS5")]
+    pub struct S5;
+
+    pub mod n {
+        #[doc(alias("DocAliasX"))]
+        pub mod x {
+            pub mod y {
+                #[doc(alias="DocAliasS6")]
+                pub struct S6;
+            }
+        }
+    }
+}
diff --git a/tests/ui/attributes/use-doc-alias-name.rs b/tests/ui/attributes/use-doc-alias-name.rs
new file mode 100644
index 0000000000000..1fc9199b6e382
--- /dev/null
+++ b/tests/ui/attributes/use-doc-alias-name.rs
@@ -0,0 +1,67 @@
+//@ aux-build: use-doc-alias-name-extern.rs
+
+// issue#124273
+
+extern crate use_doc_alias_name_extern;
+
+use use_doc_alias_name_extern::*;
+
+#[doc(alias="LocalDocAliasS")]
+struct S;
+
+fn main() {
+    LocalDocAliasS; // don't show help in local crate
+    //~^ ERROR: cannot find value `LocalDocAliasS` in this scope
+
+    DocAliasS1;
+    //~^ ERROR: cannot find value `DocAliasS1` in this scope
+    //~| HELP: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+
+    DocAliasS2;
+    //~^ ERROR: cannot find value `DocAliasS2` in this scope
+    //~| HELP: `S2` has a name defined in the doc alias attribute as `DocAliasS2`
+
+    DocAliasS3;
+    //~^ ERROR: cannot find value `DocAliasS3` in this scope
+    //~| HELP: `S2` has a name defined in the doc alias attribute as `DocAliasS3`
+
+    DocAliasS4;
+    //~^ ERROR: cannot find value `DocAliasS4` in this scope
+    //~| HELP: `S2` has a name defined in the doc alias attribute as `DocAliasS4`
+
+    doc_alias_f1();
+    //~^ ERROR: cannot find function `doc_alias_f1` in this scope
+    //~| HELP: `f` has a name defined in the doc alias attribute as `doc_alias_f1`
+
+    doc_alias_f2();
+    //~^ ERROR: cannot find function `doc_alias_f2` in this scope
+    //~| HELP: `f` has a name defined in the doc alias attribute as `doc_alias_f2`
+
+    m::DocAliasS5;
+    //~^ ERROR: cannot find value `DocAliasS5` in module `m`
+    //~| HELP: `S5` has a name defined in the doc alias attribute as `DocAliasS5`
+
+    not_exist_module::DocAliasS1;
+    //~^ ERROR: use of unresolved module or unlinked crate `not_exist_module`
+    //~| HELP: you might be missing a crate named `not_exist_module`
+
+    use_doc_alias_name_extern::DocAliasS1;
+    //~^ ERROR: cannot find value `DocAliasS1` in crate `use_doc_alias_name_extern
+    //~| HELP: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+
+    m::n::DocAliasX::y::S6;
+    //~^ ERROR: could not find `DocAliasX` in `n`
+    //~| HELP: `x` has a name defined in the doc alias attribute as `DocAliasX`
+
+    m::n::x::y::DocAliasS6;
+    //~^ ERROR: cannot find value `DocAliasS6` in module `m::n::x::y`
+    //~| HELP: `S6` has a name defined in the doc alias attribute as `DocAliasS6`
+}
+
+trait T {
+    fn f() {
+        DocAliasS1;
+        //~^ ERROR: cannot find value `DocAliasS1` in this scope
+        //~| HELP: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+    }
+}
diff --git a/tests/ui/attributes/use-doc-alias-name.stderr b/tests/ui/attributes/use-doc-alias-name.stderr
new file mode 100644
index 0000000000000..07f4865e41520
--- /dev/null
+++ b/tests/ui/attributes/use-doc-alias-name.stderr
@@ -0,0 +1,150 @@
+error[E0433]: failed to resolve: could not find `DocAliasX` in `n`
+  --> $DIR/use-doc-alias-name.rs:52:11
+   |
+LL |     m::n::DocAliasX::y::S6;
+   |           ^^^^^^^^^ could not find `DocAliasX` in `n`
+   |
+help: `x` has a name defined in the doc alias attribute as `DocAliasX`
+   |
+LL -     m::n::DocAliasX::y::S6;
+LL +     m::n::x::y::S6;
+   |
+
+error[E0425]: cannot find value `LocalDocAliasS` in this scope
+  --> $DIR/use-doc-alias-name.rs:13:5
+   |
+LL |     LocalDocAliasS; // don't show help in local crate
+   |     ^^^^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `DocAliasS1` in this scope
+  --> $DIR/use-doc-alias-name.rs:16:5
+   |
+LL |     DocAliasS1;
+   |     ^^^^^^^^^^
+   |
+help: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+   |
+LL -     DocAliasS1;
+LL +     S1;
+   |
+
+error[E0425]: cannot find value `DocAliasS2` in this scope
+  --> $DIR/use-doc-alias-name.rs:20:5
+   |
+LL |     DocAliasS2;
+   |     ^^^^^^^^^^
+   |
+help: `S2` has a name defined in the doc alias attribute as `DocAliasS2`
+   |
+LL -     DocAliasS2;
+LL +     S2;
+   |
+
+error[E0425]: cannot find value `DocAliasS3` in this scope
+  --> $DIR/use-doc-alias-name.rs:24:5
+   |
+LL |     DocAliasS3;
+   |     ^^^^^^^^^^
+   |
+help: `S2` has a name defined in the doc alias attribute as `DocAliasS3`
+   |
+LL -     DocAliasS3;
+LL +     S2;
+   |
+
+error[E0425]: cannot find value `DocAliasS4` in this scope
+  --> $DIR/use-doc-alias-name.rs:28:5
+   |
+LL |     DocAliasS4;
+   |     ^^^^^^^^^^
+   |
+help: `S2` has a name defined in the doc alias attribute as `DocAliasS4`
+   |
+LL -     DocAliasS4;
+LL +     S2;
+   |
+
+error[E0425]: cannot find value `DocAliasS5` in module `m`
+  --> $DIR/use-doc-alias-name.rs:40:8
+   |
+LL |     m::DocAliasS5;
+   |        ^^^^^^^^^^
+   |
+help: `S5` has a name defined in the doc alias attribute as `DocAliasS5`
+   |
+LL -     m::DocAliasS5;
+LL +     m::S5;
+   |
+
+error[E0425]: cannot find value `DocAliasS1` in crate `use_doc_alias_name_extern`
+  --> $DIR/use-doc-alias-name.rs:48:32
+   |
+LL |     use_doc_alias_name_extern::DocAliasS1;
+   |                                ^^^^^^^^^^
+   |
+help: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+   |
+LL -     use_doc_alias_name_extern::DocAliasS1;
+LL +     use_doc_alias_name_extern::S1;
+   |
+
+error[E0425]: cannot find value `DocAliasS6` in module `m::n::x::y`
+  --> $DIR/use-doc-alias-name.rs:56:17
+   |
+LL |     m::n::x::y::DocAliasS6;
+   |                 ^^^^^^^^^^
+   |
+help: `S6` has a name defined in the doc alias attribute as `DocAliasS6`
+   |
+LL -     m::n::x::y::DocAliasS6;
+LL +     m::n::x::y::S6;
+   |
+
+error[E0425]: cannot find value `DocAliasS1` in this scope
+  --> $DIR/use-doc-alias-name.rs:63:9
+   |
+LL |         DocAliasS1;
+   |         ^^^^^^^^^^
+   |
+help: `S1` has a name defined in the doc alias attribute as `DocAliasS1`
+   |
+LL -         DocAliasS1;
+LL +         S1;
+   |
+
+error[E0425]: cannot find function `doc_alias_f1` in this scope
+  --> $DIR/use-doc-alias-name.rs:32:5
+   |
+LL |     doc_alias_f1();
+   |     ^^^^^^^^^^^^
+   |
+help: `f` has a name defined in the doc alias attribute as `doc_alias_f1`
+   |
+LL -     doc_alias_f1();
+LL +     f();
+   |
+
+error[E0425]: cannot find function `doc_alias_f2` in this scope
+  --> $DIR/use-doc-alias-name.rs:36:5
+   |
+LL |     doc_alias_f2();
+   |     ^^^^^^^^^^^^
+   |
+help: `f` has a name defined in the doc alias attribute as `doc_alias_f2`
+   |
+LL -     doc_alias_f2();
+LL +     f();
+   |
+
+error[E0433]: failed to resolve: use of unresolved module or unlinked crate `not_exist_module`
+  --> $DIR/use-doc-alias-name.rs:44:5
+   |
+LL |     not_exist_module::DocAliasS1;
+   |     ^^^^^^^^^^^^^^^^ use of unresolved module or unlinked crate `not_exist_module`
+   |
+   = help: you might be missing a crate named `not_exist_module`
+
+error: aborting due to 13 previous errors
+
+Some errors have detailed explanations: E0425, E0433.
+For more information about an error, try `rustc --explain E0425`.