Skip to content

collect doc alias as tips during resolution #127721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,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};
pub use rustc_attr_data_structures::*;

Expand Down
37 changes: 7 additions & 30 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
};
Expand All @@ -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
Expand Down
67 changes: 66 additions & 1 deletion compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -852,6 +866,57 @@ 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 {
for (idx, seg) in path.iter().enumerate().rev().skip(1) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use skip(1) because that the last segment must remain unresolved in the current logic, regardless of whether it's the first unresolved segment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave a comment to that effect? It's otherwise very opaque.

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<'_>,
Expand Down
24 changes: 24 additions & 0 deletions tests/ui/attributes/auxiliary/use-doc-alias-name-extern.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
67 changes: 67 additions & 0 deletions tests/ui/attributes/use-doc-alias-name.rs
Original file line number Diff line number Diff line change
@@ -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`
}
}
Loading
Loading