diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 34c24a26f7b13..a38c7cbe69706 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -1239,3 +1239,29 @@ pub fn parse_confusables(attr: &Attribute) -> Option> { return Some(candidates); } + +pub fn collect_doc_alias_symbol_from_attrs<'tcx>( + attrs: impl Iterator, +) -> Vec { + let doc_attrs = attrs.filter(|attr| attr.name_or_empty() == sym::doc); + let mut symbols = vec![]; + for attr in doc_attrs { + let Some(values) = attr.meta_item_list() else { + continue; + }; + let alias_values = values.iter().filter(|v| v.name_or_empty() == sym::alias); + for v in alias_values { + if let Some(nested) = v.meta_item_list() { + // #[doc(alias("foo", "bar"))] + let iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol); + symbols.extend(iter); + } else if let Some(meta) = v.meta_item() + && let Some(lit) = meta.name_value_literal() + { + // #[doc(alias = "foo")] + symbols.push(lit.symbol); + } + } + } + symbols +} diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index d8e6d3525da8a..6a32c34a4da68 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1062,6 +1062,7 @@ pub trait ResolverExpand { fn declare_proc_macro(&mut self, id: NodeId); fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem); + fn append_confusable_attr_symbols(&mut self, node_id: NodeId, symbols: Vec); /// Tools registered with `#![register_tool]` and used by tool attributes and lints. fn registered_tools(&self) -> &RegisteredTools; diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 26fc77c7f33d4..e87ab8906d63a 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -20,6 +20,7 @@ use rustc_ast::{ForeignItemKind, HasAttrs, HasNodeId}; use rustc_ast::{Inline, ItemKind, MacStmtStyle, MetaItemKind, ModKind}; use rustc_ast::{NestedMetaItem, NodeId, PatKind, StmtKind, TyKind}; use rustc_ast_pretty::pprust; +use rustc_attr::collect_doc_alias_symbol_from_attrs; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_data_structures::sync::Lrc; use rustc_errors::PResult; @@ -2021,7 +2022,17 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { } None => { match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| { - assign_id!(this, node.node_id_mut(), || node.noop_flat_map(this)) + assign_id!(this, node.node_id_mut(), || { + let symbols = collect_doc_alias_symbol_from_attrs(node.attrs().iter()); + if !symbols.is_empty() { + this.cx.resolver.append_confusable_attr_symbols( + this.cx.current_expansion.lint_node_id, + symbols, + ); + } + + node.noop_flat_map(this) + }) }) { Ok(output) => output, Err(returned_node) => { @@ -2069,7 +2080,17 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { } None if node.delegation().is_some() => unreachable!(), None => { - assign_id!(self, node.node_id_mut(), || node.noop_visit(self)) + assign_id!(self, node.node_id_mut(), || { + let symbols = collect_doc_alias_symbol_from_attrs(node.attrs().iter()); + if !symbols.is_empty() { + self.cx.resolver.append_confusable_attr_symbols( + self.cx.current_expansion.lint_node_id, + symbols, + ); + } + + node.noop_visit(self) + }) } }; } diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 6a7af5510e074..134bb48b9153e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -4,6 +4,7 @@ use super::MethodError; use super::NoMatchData; use crate::FnCtxt; +use rustc_attr::collect_doc_alias_symbol_from_attrs; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; @@ -1827,9 +1828,16 @@ 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 collect_doc_alias_symbol_from_attrs(attrs.into_iter()) + .iter() + .any(|alias| alias.as_str() == name.as_str()) + { + return true; + } + for attr in attrs { - if sym::doc == attr.name_or_empty() { - } else if sym::rustc_confusables == attr.name_or_empty() { + if sym::rustc_confusables == attr.name_or_empty() { let Some(confusables) = attr.meta_item_list() else { continue; }; @@ -1841,35 +1849,9 @@ 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.name_or_empty() != 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() - && name.as_str() == lit.symbol.as_str() - { - return true; - } - } - } else if let Some(meta) = v.meta_item() - && let Some(lit) = meta.name_value_literal() - && name.as_str() == lit.symbol.as_str() - { - // #[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 941fb6436df92..b58c47d26374c 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -4,9 +4,10 @@ use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind}; use crate::late::{LifetimeBinderKind, LifetimeRes, LifetimeRibKind, LifetimeUseSet}; use crate::ty::fast_reject::SimplifiedType; -use crate::{errors, path_names_to_string}; +use crate::{errors, path_names_to_string, Resolver}; use crate::{Module, ModuleKind, ModuleOrUniformRoot}; use crate::{PathResult, PathSource, Segment}; +use rustc_attr::collect_doc_alias_symbol_from_attrs; use rustc_hir::def::Namespace::{self, *}; use rustc_ast::ptr::P; @@ -452,6 +453,18 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { return (err, Vec::new()); } + if let Some(did) = self.lookup_doc_alias_name(path, source.namespace()) { + err.span_label( + self.r.def_span(did), + format!( + "`{}` has a name defined in the doc alias attribute as `{}`", + self.r.tcx.item_name(did), + path.last().unwrap().ident.as_str() + ), + ); + return (err, Vec::new()); + }; + let (found, mut candidates) = self.try_lookup_name_relaxed( &mut err, source, @@ -776,6 +789,55 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { return (false, candidates); } + fn lookup_doc_alias_name(&mut self, path: &[Segment], ns: Namespace) -> Option { + let item_str = path.last().unwrap().ident; + + let find_doc_alias_name = |r: &mut Resolver<'a, '_>, m: Module<'a>| { + for resolution in r.resolutions(m).borrow().values() { + let Some(did) = + resolution.borrow().binding.and_then(|binding| binding.res().opt_def_id()) + else { + continue; + }; + let symbols = if let Some(local_id) = did.as_local() { + r.def_id_to_node_id.get(local_id).and_then(|node_id| { + r.confusable_attr_symbols.get(node_id).map(Cow::Borrowed) + }) + } else { + let attrs: Vec<_> = r.tcx.get_attrs(did, sym::doc).collect(); + (attrs.is_empty()) + .then(|| Cow::Owned(collect_doc_alias_symbol_from_attrs(attrs.into_iter()))) + }; + let Some(symbols) = symbols else { + continue; + }; + if symbols.contains(&item_str.name) { + return Some(did); + } + } + None + }; + + if path.len() == 1 { + for rib in self.ribs[ns].iter().rev() { + if let RibKind::Module(module) = rib.kind + && let Some(did) = find_doc_alias_name(self.r, module) + { + return Some(did); + } + } + } else { + let mod_path = &path[..path.len() - 1]; + if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(mod_path, Some(TypeNS), None) + && let Some(did) = find_doc_alias_name(self.r, module) + { + return Some(did); + } + } + None + } + fn suggest_trait_and_bounds( &mut self, err: &mut Diag<'_>, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 3dcb83d65b029..c7014bef5d1de 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1160,6 +1160,8 @@ pub struct Resolver<'a, 'tcx> { /// Whether lifetime elision was successful. lifetime_elision_allowed: FxHashSet, + /// Some attributes may cause confusion, for example: `#[doc(alias = "name")` + confusable_attr_symbols: FxHashMap>, /// Names of items that were stripped out via cfg with their corresponding cfg meta item. stripped_cfg_items: Vec>, @@ -1512,6 +1514,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { proc_macros: Default::default(), confused_type_with_std_module: Default::default(), lifetime_elision_allowed: Default::default(), + confusable_attr_symbols: Default::default(), stripped_cfg_items: Default::default(), effective_visibilities: Default::default(), doc_link_resolutions: Default::default(), diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index cb9bebd33d306..237e9f1873518 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -477,6 +477,10 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, name, cfg }); } + fn append_confusable_attr_symbols(&mut self, node_id: NodeId, symbols: Vec) { + self.confusable_attr_symbols.insert(node_id, symbols); + } + fn registered_tools(&self) -> &RegisteredTools { self.registered_tools } 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..9e9570104458b --- /dev/null +++ b/tests/ui/attributes/use-doc-alias-name.rs @@ -0,0 +1,40 @@ +// issue#124273 + +#[doc(alias="DocAliasS1")] +pub struct S1; +//~^ NOTE: `S1` has a name defined in the doc alias attribute as `DocAliasS1` + +#[doc(alias="DocAliasS2")] +#[doc(alias("DocAliasS3", "DocAliasS4"))] +pub struct S2; +//~^ NOTE: `S2` has a name defined in the doc alias attribute as `DocAliasS2` +//~| NOTE: `S2` has a name defined in the doc alias attribute as `DocAliasS3` +//~| NOTE: `S2` has a name defined in the doc alias attribute as `DocAliasS4` + +#[doc(alias("doc_alias_f1", "doc_alias_f2"))] +pub fn f() {} +//~^ NOTE: `f` has a name defined in the doc alias attribute as `doc_alias_f1` +//~| NOTE: `f` has a name defined in the doc alias attribute as `doc_alias_f2` + +mod m { + #[doc(alias="DocAliasS5")] + pub struct S5; + //~^ NOTE: `S5` has a name defined in the doc alias attribute as `DocAliasS5` +} + +fn main() { + DocAliasS1; + //~^ ERROR: cannot find value `DocAliasS1` in this scope + DocAliasS2; + //~^ ERROR: cannot find value `DocAliasS2` in this scope + DocAliasS3; + //~^ ERROR: cannot find value `DocAliasS3` in this scope + DocAliasS4; + //~^ ERROR: cannot find value `DocAliasS4` in this scope + doc_alias_f1(); + //~^ ERROR: cannot find function `doc_alias_f1` in this scope + doc_alias_f2(); + //~^ ERROR: cannot find function `doc_alias_f2` in this scope + m::DocAliasS5; + //~^ ERROR: cannot find value `DocAliasS5` in module `m` +} 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..9ee330e2003ec --- /dev/null +++ b/tests/ui/attributes/use-doc-alias-name.stderr @@ -0,0 +1,66 @@ +error[E0425]: cannot find value `DocAliasS1` in this scope + --> $DIR/use-doc-alias-name.rs:26:3 + | +LL | pub struct S1; + | -------------- `S1` has a name defined in the doc alias attribute as `DocAliasS1` +... +LL | DocAliasS1; + | ^^^^^^^^^^ + +error[E0425]: cannot find value `DocAliasS2` in this scope + --> $DIR/use-doc-alias-name.rs:28:3 + | +LL | pub struct S2; + | -------------- `S2` has a name defined in the doc alias attribute as `DocAliasS2` +... +LL | DocAliasS2; + | ^^^^^^^^^^ + +error[E0425]: cannot find value `DocAliasS3` in this scope + --> $DIR/use-doc-alias-name.rs:30:3 + | +LL | pub struct S2; + | -------------- `S2` has a name defined in the doc alias attribute as `DocAliasS3` +... +LL | DocAliasS3; + | ^^^^^^^^^^ + +error[E0425]: cannot find value `DocAliasS4` in this scope + --> $DIR/use-doc-alias-name.rs:32:3 + | +LL | pub struct S2; + | -------------- `S2` has a name defined in the doc alias attribute as `DocAliasS4` +... +LL | DocAliasS4; + | ^^^^^^^^^^ + +error[E0425]: cannot find value `DocAliasS5` in module `m` + --> $DIR/use-doc-alias-name.rs:38:6 + | +LL | pub struct S5; + | -------------- `S5` has a name defined in the doc alias attribute as `DocAliasS5` +... +LL | m::DocAliasS5; + | ^^^^^^^^^^ + +error[E0425]: cannot find function `doc_alias_f1` in this scope + --> $DIR/use-doc-alias-name.rs:34:3 + | +LL | pub fn f() {} + | ------------- `f` has a name defined in the doc alias attribute as `doc_alias_f1` +... +LL | doc_alias_f1(); + | ^^^^^^^^^^^^ + +error[E0425]: cannot find function `doc_alias_f2` in this scope + --> $DIR/use-doc-alias-name.rs:36:3 + | +LL | pub fn f() {} + | ------------- `f` has a name defined in the doc alias attribute as `doc_alias_f2` +... +LL | doc_alias_f2(); + | ^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0425`.