rewrite doc attribute (non-doc-comments)

This commit is contained in:
Jana Dönszelmann 2025-06-20 22:50:22 +02:00 committed by Guillaume Gomez
parent bad50269f8
commit df007cf800
15 changed files with 779 additions and 504 deletions

View file

@ -234,5 +234,34 @@ attr_parsing_unused_multiple =
.suggestion = remove this attribute
.note = attribute also specified here
attr_parsing_doc_alias_duplicated = doc alias is duplicated
.label = first defined here
attr_parsing_whole_archive_needs_static =
linking modifier `whole-archive` is only compatible with `static` linking kind
attr_parsing_unused_no_lints_note =
attribute `{$name}` without any lints has no effect
attr_parsing_doc_alias_empty =
{$attr_str} attribute cannot have empty value
attr_parsing_doc_alias_bad_char =
{$char_} character isn't allowed in {$attr_str}
attr_parsing_doc_alias_start_end =
{$attr_str} cannot start or end with ' '
attr_parsing_doc_keyword_not_keyword =
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
.help = only existing keywords are allowed in core/std
attr_parsing_doc_inline_conflict =
conflicting doc inlining attributes
.help = remove one of the conflicting attributes
attr_parsing_doc_inline_conflict_first =
this attribute...
attr_parsing_doc_inline_conflict_second =
{"."}..conflicts with this attribute

View file

@ -0,0 +1,385 @@
use rustc_attr_data_structures::lints::AttributeLintKind;
use rustc_attr_data_structures::{AttributeKind, DocAttribute, DocInline};
use rustc_errors::MultiSpan;
use rustc_feature::template;
use rustc_span::{Span, Symbol, edition, sym};
use super::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::fluent_generated as fluent;
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasStartEnd, DocKeywordConflict, DocKeywordNotKeyword,
};
#[derive(Default)]
pub(crate) struct DocParser {
attribute: DocAttribute,
}
impl DocParser {
fn parse_single_test_doc_attr_item<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
mip: &'c MetaItemParser<'_>,
) {
let path = mip.path();
let args = mip.args();
match path.word_sym() {
Some(sym::no_crate_inject) => {
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
return;
}
if self.attribute.no_crate_inject.is_some() {
cx.duplicate_key(path.span(), sym::no_crate_inject);
return;
}
self.attribute.no_crate_inject = Some(path.span())
}
Some(sym::attr) => {
let Some(list) = args.list() else {
cx.expected_list(args.span().unwrap_or(path.span()));
return;
};
self.attribute.test_attrs.push(todo!());
}
_ => {
cx.expected_specific_argument(
mip.span(),
[sym::no_crate_inject.as_str(), sym::attr.as_str()].to_vec(),
);
}
}
}
fn add_alias<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
alias: Symbol,
span: Span,
is_list: bool,
) {
let attr_str =
&format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
if alias == sym::empty {
cx.emit_err(DocAliasEmpty { span, attr_str });
return;
}
let alias_str = alias.as_str();
if let Some(c) =
alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
{
cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
return;
}
if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
cx.emit_err(DocAliasStartEnd { span, attr_str });
return;
}
if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
cx.emit_lint(AttributeLintKind::DuplicateDocAlias { first_definition }, span);
}
self.attribute.aliases.insert(alias, span);
}
fn parse_alias<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
) {
match args {
ArgParser::NoArgs => {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
}
ArgParser::List(list) => {
for i in list.mixed() {
let Some(alias) = i.lit().and_then(|i| i.value_str()) else {
cx.expected_string_literal(i.span(), i.lit());
continue;
};
self.add_alias(cx, alias, i.span(), false);
}
}
ArgParser::NameValue(nv) => {
let Some(alias) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
self.add_alias(cx, alias, nv.value_span, false);
}
}
}
fn parse_keyword<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
) {
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(keyword) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
fn is_doc_keyword(s: Symbol) -> bool {
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
}
if !is_doc_keyword(keyword) {
cx.emit_err(DocKeywordNotKeyword { span: nv.value_span, keyword });
}
if self.attribute.keyword.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
self.attribute.keyword = Some((keyword, path.span()));
}
fn parse_inline<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
inline: DocInline,
) {
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
return;
}
let span = path.span();
if let Some((prev_inline, prev_span)) = self.attribute.inline {
if prev_inline == inline {
let mut spans = MultiSpan::from_spans(vec![prev_span, span]);
spans.push_span_label(prev_span, fluent::attr_parsing_doc_inline_conflict_first);
spans.push_span_label(span, fluent::attr_parsing_doc_inline_conflict_second);
cx.emit_err(DocKeywordConflict { spans });
return;
}
}
self.attribute.inline = Some((inline, span));
}
fn parse_single_doc_attr_item<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser<'_>,
) {
let path = mip.path();
let args = mip.args();
macro_rules! no_args {
($ident: ident) => {{
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
return;
}
if self.attribute.$ident.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
self.attribute.$ident = Some(path.span());
}};
}
macro_rules! string_arg {
($ident: ident) => {{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(s) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
if self.attribute.$ident.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
self.attribute.$ident = Some((s, path.span()));
}};
}
match path.word_sym() {
Some(sym::alias) => self.parse_alias(cx, path, args),
Some(sym::hidden) => no_args!(hidden),
Some(sym::html_favicon_url) => string_arg!(html_favicon_url),
Some(sym::html_logo_url) => string_arg!(html_logo_url),
Some(sym::html_no_source) => no_args!(html_no_source),
Some(sym::html_playground_url) => string_arg!(html_playground_url),
Some(sym::html_root_url) => string_arg!(html_root_url),
Some(sym::issue_tracker_base_url) => string_arg!(issue_tracker_base_url),
Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
Some(sym::masked) => no_args!(masked),
Some(sym::cfg) => no_args!(cfg),
Some(sym::cfg_hide) => no_args!(cfg_hide),
Some(sym::notable_trait) => no_args!(notable_trait),
Some(sym::keyword) => self.parse_keyword(cx, path, args),
Some(sym::fake_variadic) => no_args!(fake_variadic),
Some(sym::search_unbox) => no_args!(search_unbox),
Some(sym::rust_logo) => no_args!(rust_logo),
Some(sym::test) => {
let Some(list) = args.list() else {
cx.expected_list(args.span().unwrap_or(path.span()));
return;
};
for i in list.mixed() {
match i {
MetaItemOrLitParser::MetaItemParser(mip) => {
self.parse_single_test_doc_attr_item(cx, mip);
}
MetaItemOrLitParser::Lit(lit) => {
cx.unexpected_literal(lit.span);
}
MetaItemOrLitParser::Err(..) => {
// already had an error here, move on.
}
}
}
// let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
// if i_meta.has_name(sym::spotlight) {
// self.tcx.emit_node_span_lint(
// INVALID_DOC_ATTRIBUTES,
// hir_id,
// i_meta.span,
// errors::DocTestUnknownSpotlight { path, span: i_meta.span },
// );
// } else if i_meta.has_name(sym::include)
// && let Some(value) = i_meta.value_str()
// {
// let applicability = if list.len() == 1 {
// Applicability::MachineApplicable
// } else {
// Applicability::MaybeIncorrect
// };
// // If there are multiple attributes, the suggestion would suggest
// // deleting all of them, which is incorrect.
// self.tcx.emit_node_span_lint(
// INVALID_DOC_ATTRIBUTES,
// hir_id,
// i_meta.span,
// errors::DocTestUnknownInclude {
// path,
// value: value.to_string(),
// inner: match attr.style() {
// AttrStyle::Inner => "!",
// AttrStyle::Outer => "",
// },
// sugg: (attr.span(), applicability),
// },
// );
// } else if i_meta.has_name(sym::passes) || i_meta.has_name(sym::no_default_passes) {
// self.tcx.emit_node_span_lint(
// INVALID_DOC_ATTRIBUTES,
// hir_id,
// i_meta.span,
// errors::DocTestUnknownPasses { path, span: i_meta.span },
// );
// } else if i_meta.has_name(sym::plugins) {
// self.tcx.emit_node_span_lint(
// INVALID_DOC_ATTRIBUTES,
// hir_id,
// i_meta.span,
// errors::DocTestUnknownPlugins { path, span: i_meta.span },
// );
// } else {
// self.tcx.emit_node_span_lint(
// INVALID_DOC_ATTRIBUTES,
// hir_id,
// i_meta.span,
// errors::DocTestUnknownAny { path },
// );
// }
}
_ => {
cx.expected_specific_argument(
mip.span(),
[
sym::alias.as_str(),
sym::hidden.as_str(),
sym::html_favicon_url.as_str(),
sym::html_logo_url.as_str(),
sym::html_no_source.as_str(),
sym::html_playground_url.as_str(),
sym::html_root_url.as_str(),
sym::inline.as_str(),
sym::no_inline.as_str(),
sym::test.as_str(),
]
.to_vec(),
);
}
}
}
fn accept_single_doc_attr<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) {
match args {
ArgParser::NoArgs => {
todo!()
}
ArgParser::List(items) => {
for i in items.mixed() {
match i {
MetaItemOrLitParser::MetaItemParser(mip) => {
self.parse_single_doc_attr_item(cx, mip);
}
MetaItemOrLitParser::Lit(lit) => todo!("error should've used equals"),
MetaItemOrLitParser::Err(..) => {
// already had an error here, move on.
}
}
}
}
ArgParser::NameValue(v) => {
panic!("this should be rare if at all possible");
}
}
}
}
impl<S: Stage> AttributeParser<S> for DocParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[sym::doc],
template!(List: "hidden|inline|...", NameValueStr: "string"),
|this, cx, args| {
this.accept_single_doc_attr(cx, args);
},
)];
fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
todo!()
}
}

View file

@ -40,6 +40,7 @@ pub(crate) mod crate_level;
pub(crate) mod debugger;
pub(crate) mod deprecation;
pub(crate) mod dummy;
pub(crate) mod doc;
pub(crate) mod inline;
pub(crate) mod link_attrs;
pub(crate) mod lint_helpers;

View file

@ -32,35 +32,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
|| attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.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
}
/// Parse a single integer.
///
@ -121,3 +92,7 @@ impl<S: Stage> AcceptContext<'_, '_, S> {
None
}
}
pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> {
first_attr_value_str_by_name(attrs, sym::crate_name)
}

View file

@ -33,6 +33,7 @@ use crate::attributes::crate_level::{
};
use crate::attributes::debugger::DebuggerViualizerParser;
use crate::attributes::deprecation::DeprecationParser;
use crate::attributes::doc::DocParser;
use crate::attributes::dummy::DummyParser;
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
use crate::attributes::link_attrs::{
@ -162,7 +163,7 @@ attribute_parsers!(
BodyStabilityParser,
ConfusablesParser,
ConstStabilityParser,
MacroUseParser,
NakedParser,
StabilityParser,
UsedParser,
@ -427,7 +428,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
&self,
span: Span,
found: String,
options: &'static [&'static str],
options: &[&'static str],
) -> ErrorGuaranteed {
self.emit_err(UnknownMetaItem { span, item: found, expected: options })
}

View file

@ -4,6 +4,7 @@ use rustc_ast::{self as ast};
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
MultiSpan,
};
use rustc_feature::AttributeTemplate;
use rustc_hir::AttrPath;
@ -34,6 +35,40 @@ pub(crate) struct InvalidPredicate {
pub predicate: String,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_empty)]
pub(crate) struct DocAliasEmpty<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_bad_char)]
pub(crate) struct DocAliasBadChar<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub char_: char,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_start_end)]
pub(crate) struct DocAliasStartEnd<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_keyword_not_keyword)]
#[help]
pub(crate) struct DocKeywordNotKeyword {
#[primary_span]
pub span: Span,
pub keyword: Symbol,
}
/// Error code: E0541
pub(crate) struct UnknownMetaItem<'a> {
pub span: Span,
@ -538,6 +573,38 @@ pub(crate) struct NakedFunctionIncompatibleAttribute {
pub attr: String,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_alias_duplicated)]
pub(crate) struct DocAliasDuplicated {
#[label]
pub first_defn: Span,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_inline_conflict)]
#[help]
pub(crate) struct DocKeywordConflict {
#[primary_span]
pub spans: MultiSpan,
}
#[derive(Subdiagnostic)]
pub(crate) enum UnusedNote {
#[note(attr_parsing_unused_empty_lints_note)]
EmptyList { name: Symbol },
#[note(attr_parsing_unused_no_lints_note)]
NoLints { name: Symbol },
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_unused)]
pub(crate) struct Unused {
#[suggestion(code = "", applicability = "machine-applicable")]
pub attr_span: Span,
#[subdiagnostic]
pub note: UnusedNote,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_link_ordinal_out_of_range)]
#[note]

View file

@ -5,6 +5,7 @@ pub use ReprAttr::*;
use rustc_abi::Align;
use rustc_ast::token::CommentKind;
use rustc_ast::{AttrStyle, ast};
use rustc_data_structures::fx::FxIndexMap;
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
use rustc_span::def_id::DefId;
@ -420,6 +421,70 @@ impl WindowsSubsystemKind {
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub enum DocInline {
Inline,
NoInline,
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct DocAttribute {
pub aliases: FxIndexMap<Symbol, Span>,
pub hidden: Option<Span>,
pub inline: Option<(DocInline, Span)>,
// unstable
pub cfg: Option<Span>,
pub cfg_hide: Option<Span>,
// builtin
pub fake_variadic: Option<Span>,
pub keyword: Option<(Symbol, Span)>,
pub masked: Option<Span>,
pub notable_trait: Option<Span>,
pub search_unbox: Option<Span>,
// valid on crate
pub html_favicon_url: Option<(Symbol, Span)>,
pub html_logo_url: Option<(Symbol, Span)>,
pub html_playground_url: Option<(Symbol, Span)>,
pub html_root_url: Option<(Symbol, Span)>,
pub html_no_source: Option<Span>,
pub issue_tracker_base_url: Option<(Symbol, Span)>,
pub rust_logo: Option<Span>,
// #[doc(test(...))]
pub test_attrs: ThinVec<()>,
pub no_crate_inject: Option<Span>,
}
impl Default for DocAttribute {
fn default() -> Self {
Self {
aliases: FxIndexMap::default(),
hidden: None,
inline: None,
cfg: None,
cfg_hide: None,
fake_variadic: None,
keyword: None,
masked: None,
notable_trait: None,
search_unbox: None,
html_favicon_url: None,
html_logo_url: None,
html_playground_url: None,
html_root_url: None,
html_no_source: None,
issue_tracker_base_url: None,
rust_logo: None,
test_attrs: ThinVec::new(),
no_crate_inject: None,
}
}
}
/// Represents parsed *built-in* inert attributes.
///
/// ## Overview
@ -551,7 +616,13 @@ pub enum AttributeKind {
/// Represents `#[rustc_do_not_implement_via_object]`.
DoNotImplementViaObject(Span),
/// Represents [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// Represents [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// Represents all other uses of the [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html)
/// attribute.
Doc(Box<DocAttribute>),
/// Represents specifically [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// i.e. doc comments.
DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol },
/// Represents `#[rustc_dummy]`.

View file

@ -40,6 +40,7 @@ impl AttributeKind {
DenyExplicitImpl(..) => No,
Deprecation { .. } => Yes,
DoNotImplementViaObject(..) => No,
Doc(_) => Yes,
DocComment { .. } => Yes,
Dummy => No,
ExportName { .. } => Yes,

View file

@ -4,6 +4,7 @@ use rustc_abi::Align;
use rustc_ast::token::CommentKind;
use rustc_ast::{AttrStyle, IntTy, UintTy};
use rustc_ast_pretty::pp::Printer;
use rustc_data_structures::fx::FxIndexMap;
use rustc_span::hygiene::Transparency;
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
use rustc_target::spec::SanitizerSet;
@ -64,6 +65,25 @@ impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
p.word("]");
}
}
impl<T: PrintAttribute> PrintAttribute for FxIndexMap<T, Span> {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
}
fn print_attribute(&self, p: &mut Printer) {
let mut last_printed = false;
p.word("[");
for (i, _) in self {
if last_printed {
p.word_space(",");
}
i.print_attribute(p);
last_printed = i.should_render();
}
p.word("]");
}
}
macro_rules! print_skip {
($($t: ty),* $(,)?) => {$(
impl PrintAttribute for $t {

View file

@ -31,3 +31,45 @@ pub struct AttributeLint<Id> {
pub span: Span,
pub kind: AttributeLintKind,
}
#[derive(Debug, HashStable_Generic)]
pub enum AttributeLintKind {
/// Copy of `IllFormedAttributeInput`
/// specifically for the `invalid_macro_export_arguments` lint until that is removed,
/// see <https://github.com/rust-lang/rust/pull/143857#issuecomment-3079175663>
InvalidMacroExportArguments {
suggestions: Vec<String>,
},
UnusedDuplicate {
this: Span,
other: Span,
warning: bool,
},
IllFormedAttributeInput {
suggestions: Vec<String>,
},
EmptyAttribute {
first_span: Span,
attr_path: AttrPath,
valid_without_list: bool,
},
InvalidTarget {
name: AttrPath,
target: Target,
applied: Vec<String>,
only: &'static str,
},
InvalidStyle {
name: AttrPath,
is_used_as_inner: bool,
target: Target,
target_span: Span,
},
UnsafeAttrOutsideUnsafe {
attribute_name_span: Span,
sugg_spans: (Span, Span),
},
DuplicateDocAlias {
first_definition: Span,
},
}

View file

@ -3,7 +3,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_attr_data_structures::{AttributeKind, find_attr};
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sso::SsoHashSet;
use rustc_errors::Applicability;
@ -2535,7 +2535,9 @@ 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) {
if let Some(d) = find_attr!(attrs, AttributeKind::Doc(d) => d)
&& d.aliases.contains_key(&method.name)
{
return true;
}

View file

@ -106,18 +106,9 @@ passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
passes_diagnostic_item_first_defined =
the diagnostic item is first defined here
passes_doc_alias_bad_char =
{$char_} character isn't allowed in {$attr_str}
passes_doc_alias_bad_location =
{$attr_str} isn't allowed on {$location}
passes_doc_alias_duplicated = doc alias is duplicated
.label = first defined here
passes_doc_alias_empty =
{$attr_str} attribute cannot have empty value
passes_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
@ -127,17 +118,6 @@ passes_doc_alias_not_an_alias =
passes_doc_alias_not_string_literal =
`#[doc(alias("a"))]` expects string literals
passes_doc_alias_start_end =
{$attr_str} cannot start or end with ' '
passes_doc_attr_expects_no_value =
`doc({$attr_name})` does not accept a value
.suggestion = use `doc({$attr_name})`
passes_doc_attr_expects_string =
`doc({$attr_name})` expects a string value
.suggestion = use `doc({$attr_name} = "...")`
passes_doc_attr_not_crate_level =
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
@ -163,15 +143,7 @@ passes_doc_expect_str =
passes_doc_fake_variadic_not_valid =
`#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity
passes_doc_inline_conflict =
conflicting doc inlining attributes
.help = remove one of the conflicting attributes
passes_doc_inline_conflict_first =
this attribute...
passes_doc_inline_conflict_second =
{"."}..conflicts with this attribute
passes_doc_inline_only_use =
this attribute can only be applied to a `use` item
@ -188,10 +160,6 @@ passes_doc_keyword_attribute_empty_mod =
passes_doc_keyword_attribute_not_mod =
`#[doc({$attr_name} = "...")]` should be used on modules
passes_doc_keyword_not_keyword =
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
.help = only existing keywords are allowed in core/std
passes_doc_keyword_only_impl =
`#[doc(keyword = "...")]` should be used on impl blocks

View file

@ -141,8 +141,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
target: Target,
item: Option<ItemLike<'_>>,
) {
let mut doc_aliases = FxHashMap::default();
let mut specified_inline = None;
let mut seen = FxHashMap::default();
let attrs = self.tcx.hir_attrs(hir_id);
for attr in attrs {
@ -306,15 +304,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.check_diagnostic_on_const(attr.span(), hir_id, target, item)
}
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
[sym::doc, ..] => self.check_doc_attrs(
attr,
attr.span(),
attr_item.style,
hir_id,
target,
&mut specified_inline,
&mut doc_aliases,
),
[sym::no_link, ..] => self.check_no_link(hir_id, attr, span, target),
[sym::rustc_no_implicit_autorefs, ..] => {
self.check_applied_to_fn_or_method(hir_id, attr.span(), span, target)
@ -786,38 +775,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.dcx().emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
}
fn check_doc_alias_value(
&self,
meta: &MetaItemInner,
doc_alias: Symbol,
hir_id: HirId,
target: Target,
is_list: bool,
aliases: &mut FxHashMap<String, Span>,
) {
fn check_doc_alias_value(&self, span: Span, alias: Symbol, hir_id: HirId, target: Target) {
let tcx = self.tcx;
let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span());
let attr_str =
&format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
if doc_alias == sym::empty {
tcx.dcx().emit_err(errors::DocAliasEmpty { span, attr_str });
return;
}
let doc_alias_str = doc_alias.as_str();
if let Some(c) = doc_alias_str
.chars()
.find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
{
tcx.dcx().emit_err(errors::DocAliasBadChar { span, attr_str, char_: c });
return;
}
if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') {
tcx.dcx().emit_err(errors::DocAliasStartEnd { span, attr_str });
return;
}
let span = meta.span();
if let Some(location) = match target {
Target::AssocTy => {
if let DefKind::Impl { .. } =
@ -874,20 +834,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| Target::MacroCall
| Target::Delegation { .. } => None,
} {
tcx.dcx().emit_err(errors::DocAliasBadLocation { span, attr_str, location });
return;
}
if self.tcx.hir_opt_name(hir_id) == Some(doc_alias) {
tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str });
return;
}
if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) {
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
span,
errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
);
// FIXME: emit proper error
// tcx.dcx().emit_err(errors::DocAliasBadLocation {
// span,
// errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
// );
}
}
@ -927,7 +878,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
fn check_doc_keyword_and_attribute(
&self,
meta: &MetaItemInner,
span: Span,
hir_id: HirId,
attr_kind: DocFakeItemKind,
) {
@ -938,16 +889,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
}
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
fn is_builtin_attr(s: Symbol) -> bool {
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
}
let value = match meta.value_str() {
Some(value) if value != sym::empty => value,
_ => return self.doc_attr_str_error(meta, attr_kind.name()),
};
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
@ -955,21 +896,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
span: meta.span(),
attr_name: attr_kind.name(),
});
self.dcx()
.emit_err(errors::DocKeywordEmptyMod { span, attr_name: attr_kind.name() });
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
span: meta.span(),
attr_name: attr_kind.name(),
});
self.dcx().emit_err(errors::DocKeywordNotMod { span, attr_name: attr_kind.name() });
return;
}
}
match attr_kind {
DocFakeItemKind::Keyword => {
if !is_doc_keyword(value) {
@ -990,7 +927,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
fn check_doc_fake_variadic(&self, meta: &MetaItemInner, hir_id: HirId) {
fn check_doc_fake_variadic(&self, span: Span, hir_id: HirId) {
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
@ -1008,18 +945,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
false
};
if !is_valid {
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span: meta.span() });
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span });
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span: meta.span() });
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span });
}
}
}
fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) {
fn check_doc_search_unbox(&self, span: Span, hir_id: HirId) {
let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
return;
};
match item.kind {
@ -1032,7 +969,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}) => {}
ItemKind::TyAlias(_, generics, _) if generics.params.len() != 0 => {}
_ => {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
}
}
}
@ -1046,60 +983,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
/// already seen an inlining attribute for this item.
/// If so, `specified_inline` holds the value and the span of
/// the first `inline`/`no_inline` attribute.
fn check_doc_inline(
&self,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
specified_inline: &mut Option<(bool, Span)>,
) {
fn check_doc_inline(&self, span: Span, hir_id: HirId, target: Target) {
match target {
Target::Use | Target::ExternCrate => {
let do_inline = meta.has_name(sym::inline);
if let Some((prev_inline, prev_span)) = *specified_inline {
if do_inline != prev_inline {
let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
spans.push_span_label(prev_span, fluent::passes_doc_inline_conflict_first);
spans.push_span_label(
meta.span(),
fluent::passes_doc_inline_conflict_second,
);
self.dcx().emit_err(errors::DocKeywordConflict { spans });
}
} else {
*specified_inline = Some((do_inline, meta.span()));
}
}
Target::Use | Target::ExternCrate => {}
_ => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocInlineOnlyUse {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
}
}
}
fn check_doc_masked(
&self,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
) {
fn check_doc_masked(&self, span: Span, hir_id: HirId, target: Target) {
if target != Target::ExternCrate {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocMaskedOnlyExternCrate {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
return;
@ -1109,125 +1018,38 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocMaskedNotExternCrateSelf {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
}
}
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
fn check_attr_not_crate_level(
&self,
meta: &MetaItemInner,
hir_id: HirId,
attr_name: &str,
) -> bool {
fn check_attr_not_crate_level(&self, span: Span, hir_id: HirId, attr_name: &str) -> bool {
if CRATE_HIR_ID == hir_id {
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name });
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span, attr_name });
return false;
}
true
}
/// Checks that an attribute is used at the crate level. Returns `true` if valid.
fn check_attr_crate_level(
&self,
attr_span: Span,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
) -> bool {
fn check_attr_crate_level(&self, span: Span, hir_id: HirId) -> bool {
if hir_id != CRATE_HIR_ID {
// insert a bang between `#` and `[...`
let bang_span = attr_span.lo() + BytePos(1);
let sugg = (style == AttrStyle::Outer
&& self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID)
.then_some(errors::AttrCrateLevelOnlySugg {
attr: attr_span.with_lo(bang_span).with_hi(bang_span),
});
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::AttrCrateLevelOnly { sugg },
span,
errors::AttrCrateLevelOnly {},
);
return false;
}
true
}
fn check_doc_attr_string_value(&self, meta: &MetaItemInner, hir_id: HirId) {
if meta.value_str().is_none() {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocAttrExpectsString { attr_name: meta.name().unwrap() },
);
}
}
fn check_doc_attr_no_value(&self, meta: &MetaItemInner, hir_id: HirId) {
if !meta.is_word() {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocAttrExpectsNoValue { attr_name: meta.name().unwrap() },
);
}
}
/// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
fn check_test_attr(
&self,
attr_span: Span,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
) {
if let Some(metas) = meta.meta_item_list() {
for i_meta in metas {
match (i_meta.name(), i_meta.meta_item()) {
(Some(sym::attr), _) => {
// Allowed everywhere like `#[doc]`
}
(Some(sym::no_crate_inject), _) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
}
(_, Some(m)) => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span(),
errors::DocTestUnknown {
path: rustc_ast_pretty::pprust::path_to_string(&m.path),
},
);
}
(_, None) => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span(),
errors::DocTestLiteral,
);
}
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocTestTakesList,
);
}
}
/// Check that the `#![doc(auto_cfg)]` attribute has the expected input.
fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) {
match &meta.kind {
@ -1290,173 +1112,118 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
/// of one item. Read the documentation of [`check_doc_inline`] for more information.
///
/// [`check_doc_inline`]: Self::check_doc_inline
fn check_doc_attrs(
&self,
attr: &Attribute,
attr_span: Span,
style: AttrStyle,
hir_id: HirId,
target: Target,
specified_inline: &mut Option<(bool, Span)>,
aliases: &mut FxHashMap<String, Span>,
) {
if let Some(list) = attr.meta_item_list() {
for meta in &list {
if let Some(i_meta) = meta.meta_item() {
match i_meta.name() {
Some(sym::alias) => {
if self.check_attr_not_crate_level(meta, hir_id, "alias") {
self.check_doc_alias(meta, hir_id, target, aliases);
}
}
fn check_doc_attrs(&self, attr: &DocAttribute, hir_id: HirId, target: Target) {
let DocAttribute {
aliases,
// valid pretty much anywhere, not checked here?
// FIXME: should we?
hidden: _,
inline,
// FIXME: currently unchecked
cfg: _,
cfg_hide,
fake_variadic,
keyword,
masked,
// FIXME: currently unchecked
notable_trait: _,
search_unbox,
html_favicon_url,
html_logo_url,
html_playground_url,
html_root_url,
html_no_source,
issue_tracker_base_url,
rust_logo,
// allowed anywhere
test_attrs: _,
no_crate_inject,
} = attr;
Some(sym::keyword) => {
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Keyword,
);
}
}
Some(sym::attribute) => {
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Attribute,
);
}
}
Some(sym::fake_variadic) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_fake_variadic(meta, hir_id);
}
}
Some(sym::search_unbox) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_search_unbox(meta, hir_id);
}
}
Some(sym::test) => {
self.check_test_attr(attr_span, style, meta, hir_id);
}
Some(
sym::html_favicon_url
| sym::html_logo_url
| sym::html_playground_url
| sym::issue_tracker_base_url
| sym::html_root_url,
) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_string_value(meta, hir_id);
}
Some(sym::html_no_source) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_no_value(meta, hir_id);
}
Some(sym::auto_cfg) => {
self.check_doc_auto_cfg(i_meta, hir_id);
}
Some(sym::inline | sym::no_inline) => {
self.check_doc_inline(style, meta, hir_id, target, specified_inline)
}
Some(sym::masked) => self.check_doc_masked(style, meta, hir_id, target),
Some(sym::cfg | sym::hidden | sym::notable_trait) => {}
Some(sym::rust_logo) => {
if self.check_attr_crate_level(attr_span, style, meta, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
meta.span(),
fluent::passes_doc_rust_logo,
)
.emit();
}
}
_ => {
let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
if i_meta.has_name(sym::spotlight) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownSpotlight { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::include)
&& let Some(value) = i_meta.value_str()
{
let applicability = if list.len() == 1 {
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
// If there are multiple attributes, the suggestion would suggest
// deleting all of them, which is incorrect.
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownInclude {
path,
value: value.to_string(),
inner: match style {
AttrStyle::Inner => "!",
AttrStyle::Outer => "",
},
sugg: (attr.span(), applicability),
},
);
} else if i_meta.has_name(sym::passes)
|| i_meta.has_name(sym::no_default_passes)
{
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPasses { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::plugins) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPlugins { path, span: i_meta.span },
);
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownAny { path },
);
}
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocInvalid,
);
}
for (alias, span) in aliases {
if self.check_attr_not_crate_level(*span, hir_id, "alias") {
self.check_doc_alias_value(*span, *alias, hir_id, target);
}
}
if let Some((_, span)) = keyword {
if self.check_attr_not_crate_level(*span, hir_id, "keyword") {
self.check_doc_keyword(*span, hir_id);
}
}
// FIXME: check doc attribute
// self.check_doc_keyword(meta, hir_id);
// self.check_doc_keyword_and_attribute(
// meta,
// hir_id,
// DocFakeItemKind::Keyword,
// );
// }
// }
// Some(sym::attribute) => {
// if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
// self.check_doc_keyword_and_attribute(
// meta,
// hir_id,
// DocFakeItemKind::Attribute,
// );
if let Some(span) = fake_variadic {
if self.check_attr_not_crate_level(*span, hir_id, "fake_variadic") {
self.check_doc_fake_variadic(*span, hir_id);
}
}
if let Some(span) = search_unbox {
if self.check_attr_not_crate_level(*span, hir_id, "search_unbox") {
self.check_doc_search_unbox(*span, hir_id);
}
}
for i in [
html_favicon_url,
html_logo_url,
html_playground_url,
issue_tracker_base_url,
html_root_url,
] {
if let Some((_, span)) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
for i in [html_no_source, no_crate_inject] {
if let Some(span) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
if let Some((_, span)) = inline {
self.check_doc_inline(*span, hir_id, target)
}
if let Some(span) = rust_logo {
if self.check_attr_crate_level(*span, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
*span,
fluent::passes_doc_rust_logo,
)
.emit();
}
}
if let Some(span) = masked {
self.check_doc_masked(*span, hir_id, target);
}
if let Some(span) = cfg_hide {
self.check_attr_crate_level(*span, hir_id);
}
}
fn check_has_incoherent_inherent_impls(&self, attr: &Attribute, span: Span, target: Target) {

View file

@ -30,12 +30,6 @@ pub(crate) struct DocAttrExpectsString {
pub(crate) attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_attr_expects_no_value)]
pub(crate) struct DocAttrExpectsNoValue {
pub(crate) attr_name: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_autodiff_attr)]
pub(crate) struct AutoDiffAttr {
@ -143,31 +137,6 @@ pub(crate) struct DocExpectStr<'a> {
pub attr_name: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_empty)]
pub(crate) struct DocAliasEmpty<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_bad_char)]
pub(crate) struct DocAliasBadChar<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub char_: char,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_start_end)]
pub(crate) struct DocAliasStartEnd<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_bad_location)]
pub(crate) struct DocAliasBadLocation<'a> {
@ -185,13 +154,6 @@ pub(crate) struct DocAliasNotAnAlias<'a> {
pub attr_str: &'a str,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_alias_duplicated)]
pub(crate) struct DocAliasDuplicated {
#[label]
pub first_defn: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_not_string_literal)]
pub(crate) struct DocAliasNotStringLiteral {
@ -261,14 +223,6 @@ pub(crate) struct DocSearchUnboxInvalid {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_inline_conflict)]
#[help]
pub(crate) struct DocKeywordConflict {
#[primary_span]
pub spans: MultiSpan,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_inline_only_use)]
#[note]
@ -276,7 +230,7 @@ pub(crate) struct DocInlineOnlyUse {
#[label]
pub attr_span: Span,
#[label(passes_not_a_use_item_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(LintDiagnostic)]
@ -286,7 +240,7 @@ pub(crate) struct DocMaskedOnlyExternCrate {
#[label]
pub attr_span: Span,
#[label(passes_not_an_extern_crate_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(LintDiagnostic)]
@ -295,7 +249,7 @@ pub(crate) struct DocMaskedNotExternCrateSelf {
#[label]
pub attr_span: Span,
#[label(passes_extern_crate_self_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(Diagnostic)]
@ -1353,17 +1307,7 @@ pub(crate) struct IneffectiveUnstableImpl;
#[derive(LintDiagnostic)]
#[diag(passes_attr_crate_level)]
#[note]
pub(crate) struct AttrCrateLevelOnly {
#[subdiagnostic]
pub sugg: Option<AttrCrateLevelOnlySugg>,
}
#[derive(Subdiagnostic)]
#[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")]
pub(crate) struct AttrCrateLevelOnlySugg {
#[primary_span]
pub attr: Span,
}
pub(crate) struct AttrCrateLevelOnly {}
/// "sanitize attribute not allowed here"
#[derive(Diagnostic)]

View file

@ -10,7 +10,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_attr_data_structures::{AttributeKind, find_attr};
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::{
@ -903,7 +903,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// confused by them.
continue;
}
if is_doc_alias_attrs_contain_symbol(r.tcx.get_attrs(did, sym::doc), item_name) {
if let Some(d) = find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d)
&& d.aliases.contains_key(&item_name)
{
return Some(did);
}
}