Continue migration to new Attribute API for doc attribute

This commit is contained in:
Guillaume Gomez 2025-10-23 16:59:19 +02:00
parent 3f08e4dcd9
commit acb32df7b1
26 changed files with 597 additions and 608 deletions

View file

@ -4011,7 +4011,6 @@ dependencies = [
"itertools",
"rustc_abi",
"rustc_ast",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
"rustc_fluent_macro",
@ -4433,7 +4432,6 @@ dependencies = [
"rustc_abi",
"rustc_ast",
"rustc_ast_lowering",
"rustc_ast_pretty",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",

View file

@ -220,6 +220,11 @@ impl AttributeExt for Attribute {
fn is_automatically_derived_attr(&self) -> bool {
self.has_name(sym::automatically_derived)
}
fn is_doc_hidden(&self) -> bool {
self.has_name(sym::doc)
&& self.meta_item_list().is_some_and(|l| list_contains_name(&l, sym::hidden))
}
}
impl Attribute {
@ -830,6 +835,9 @@ pub trait AttributeExt: Debug {
/// commented module (for inner doc) vs within its parent module (for outer
/// doc).
fn doc_resolution_scope(&self) -> Option<AttrStyle>;
/// Returns `true` if this attribute contains `doc(hidden)`.
fn is_doc_hidden(&self) -> bool;
}
// FIXME(fn_delegation): use function delegation instead of manually forwarding

View file

@ -256,6 +256,10 @@ 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_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std
attr_parsing_doc_inline_conflict =
conflicting doc inlining attributes
.help = remove one of the conflicting attributes
@ -265,3 +269,54 @@ attr_parsing_doc_inline_conflict_first =
attr_parsing_doc_inline_conflict_second =
{"."}..conflicts with this attribute
attr_parsing_doc_auto_cfg_expects_hide_or_show =
only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`
attr_parsing_doc_auto_cfg_hide_show_unexpected_item =
`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items
attr_parsing_doc_auto_cfg_hide_show_expects_list =
`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items
attr_parsing_doc_invalid =
invalid `doc` attribute
attr_parsing_doc_unknown_include =
unknown `doc` attribute `include`
.suggestion = use `doc = include_str!` instead
attr_parsing_doc_unknown_spotlight =
unknown `doc` attribute `spotlight`
.note = `doc(spotlight)` was renamed to `doc(notable_trait)`
.suggestion = use `notable_trait` instead
.no_op_note = `doc(spotlight)` is now a no-op
attr_parsing_doc_unknown_passes =
unknown `doc` attribute `{$name}`
.note = `doc` attribute `{$name}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136>
.label = no longer functions
.no_op_note = `doc({$name})` is now a no-op
attr_parsing_doc_unknown_plugins =
unknown `doc` attribute `plugins`
.note = `doc` attribute `plugins` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc(plugins)` is now a no-op
attr_parsing_doc_unknown_any =
unknown `doc` attribute `{$name}`
attr_parsing_doc_auto_cfg_wrong_literal =
expected boolean for `#[doc(auto_cfg = ...)]`
attr_parsing_doc_test_takes_list =
`#[doc(test(...)]` takes a list of attributes
attr_parsing_doc_test_unknown =
unknown `doc(test)` attribute `{$name}`
attr_parsing_doc_test_literal = `#![doc(test(...)]` does not take a literal
attr_parsing_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`

View file

@ -171,7 +171,7 @@ fn parse_cfg_entry_target<S: Stage>(
Ok(CfgEntry::All(result, list.span))
}
fn parse_name_value<S: Stage>(
pub(crate) fn parse_name_value<S: Stage>(
name: Symbol,
name_span: Span,
value: Option<&NameValueParser>,

View file

@ -1,20 +1,99 @@
use rustc_attr_data_structures::lints::AttributeLintKind;
use rustc_attr_data_structures::{AttributeKind, DocAttribute, DocInline};
// FIXME: to be removed
#![allow(unused_imports)]
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_ast::token::CommentKind;
use rustc_errors::MultiSpan;
use rustc_feature::template;
use rustc_hir::attrs::{
AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
};
use rustc_hir::lints::AttributeLintKind;
use rustc_span::{Span, Symbol, edition, sym};
use thin_vec::ThinVec;
use super::prelude::{Allow, AllowedTargets, MethodKind, Target};
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,
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute,
DocKeywordConflict, DocKeywordNotKeyword,
};
#[derive(Default)]
fn check_keyword<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> 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`.
if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
|| keyword.is_weak()
|| keyword == sym::SelfTy
{
return true;
}
cx.emit_err(DocKeywordNotKeyword { span, keyword });
false
}
fn check_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
attribute: Symbol,
span: Span,
) -> bool {
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&attribute) {
return true;
}
cx.emit_err(DocAttributeNotAttribute { span, attribute });
false
}
fn parse_keyword_and_attribute<'c, S, F>(
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
attr_value: &mut Option<(Symbol, Span)>,
callback: F,
) where
S: Stage,
F: FnOnce(&mut AcceptContext<'_, '_, S>, Symbol, Span) -> bool,
{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(value) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
if !callback(cx, value, nv.value_span) {
return;
}
if attr_value.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
*attr_value = Some((value, path.span()));
}
#[derive(Debug)]
struct DocComment {
style: AttrStyle,
kind: CommentKind,
span: Span,
comment: Symbol,
}
#[derive(Default, Debug)]
pub(crate) struct DocParser {
attribute: DocAttribute,
nb_doc_attrs: usize,
doc_comment: Option<DocComment>,
}
impl DocParser {
@ -28,8 +107,8 @@ impl DocParser {
match path.word_sym() {
Some(sym::no_crate_inject) => {
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
@ -42,17 +121,20 @@ impl DocParser {
}
Some(sym::attr) => {
let Some(list) = args.list() else {
cx.expected_list(args.span().unwrap_or(path.span()));
cx.expected_list(cx.attr_span);
return;
};
self.attribute.test_attrs.push(todo!());
// FIXME: convert list into a Vec of `AttributeKind`.
for _ in list.mixed() {
// self.attribute.test_attrs.push(AttributeKind::parse());
}
}
_ => {
cx.expected_specific_argument(
mip.span(),
[sym::no_crate_inject.as_str(), sym::attr.as_str()].to_vec(),
);
Some(name) => {
cx.emit_lint(AttributeLintKind::DocTestUnknown { name }, path.span());
}
None => {
cx.emit_lint(AttributeLintKind::DocTestLiteral, path.span());
}
}
}
@ -98,7 +180,7 @@ impl DocParser {
) {
match args {
ArgParser::NoArgs => {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
}
ArgParser::List(list) => {
for i in list.mixed() {
@ -120,41 +202,6 @@ impl DocParser {
}
}
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>,
@ -162,8 +209,8 @@ impl DocParser {
args: &ArgParser<'_>,
inline: DocInline,
) {
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
@ -182,6 +229,103 @@ impl DocParser {
self.attribute.inline = Some((inline, span));
}
fn parse_cfg<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
) {
if let Some(cfg_entry) = super::cfg::parse_cfg(cx, args) {
self.attribute.cfg = Some(cfg_entry);
}
}
fn parse_auto_cfg<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
) {
match args {
ArgParser::NoArgs => {
cx.expected_list(args.span().unwrap_or(path.span()));
}
ArgParser::List(list) => {
for meta in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(item) = meta else {
cx.emit_lint(AttributeLintKind::DocAutoCfgExpectsHideOrShow, meta.span());
continue;
};
let (kind, attr_name) = match item.path().word_sym() {
Some(sym::hide) => (HideOrShow::Hide, sym::hide),
Some(sym::show) => (HideOrShow::Show, sym::show),
_ => {
cx.emit_lint(
AttributeLintKind::DocAutoCfgExpectsHideOrShow,
item.span(),
);
continue;
}
};
let ArgParser::List(list) = item.args() else {
cx.emit_lint(
AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name },
item.span(),
);
continue;
};
let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };
for item in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
cx.emit_lint(
AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name },
item.span(),
);
continue;
};
match sub_item.args() {
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
let Some(name) = sub_item.path().word_sym() else {
cx.expected_identifier(sub_item.path().span());
continue;
};
if let Ok(CfgEntry::NameValue { name, name_span, value, .. }) =
super::cfg::parse_name_value(
name,
sub_item.path().span(),
a.name_value(),
sub_item.span(),
cx,
)
{
cfg_hide_show.values.push(CfgInfo { name, name_span, value })
}
}
_ => {
cx.emit_lint(
AttributeLintKind::DocAutoCfgHideShowUnexpectedItem {
attr_name,
},
sub_item.span(),
);
continue;
}
}
}
}
}
ArgParser::NameValue(nv) => {
let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
else {
cx.emit_lint(AttributeLintKind::DocAutoCfgWrongLiteral, nv.value_span);
return;
};
self.attribute.auto_cfg_change = Some((*bool_value, *span));
}
}
}
fn parse_single_doc_attr_item<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
@ -192,8 +336,8 @@ impl DocParser {
macro_rules! no_args {
($ident: ident) => {{
if !args.no_args() {
cx.expected_no_args(args.span().unwrap());
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
@ -217,10 +361,12 @@ impl DocParser {
return;
};
if self.attribute.$ident.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
// FIXME: It's errorring when the attribute is passed multiple times on the command
// line.
// if self.attribute.$ident.is_some() {
// cx.duplicate_key(path.span(), path.word_sym().unwrap());
// return;
// }
self.attribute.$ident = Some((s, path.span()));
}};
@ -238,16 +384,32 @@ impl DocParser {
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::cfg) => self.parse_cfg(cx, args),
Some(sym::notable_trait) => no_args!(notable_trait),
Some(sym::keyword) => self.parse_keyword(cx, path, args),
Some(sym::keyword) => parse_keyword_and_attribute(
cx,
path,
args,
&mut self.attribute.keyword,
check_keyword,
),
Some(sym::attribute) => parse_keyword_and_attribute(
cx,
path,
args,
&mut self.attribute.attribute,
check_attribute,
),
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::auto_cfg) => self.parse_auto_cfg(cx, path, args),
Some(sym::test) => {
let Some(list) = args.list() else {
cx.expected_list(args.span().unwrap_or(path.span()));
cx.emit_lint(
AttributeLintKind::DocTestTakesList,
args.span().unwrap_or(path.span()),
);
return;
};
@ -264,80 +426,32 @@ impl DocParser {
}
}
}
// 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(),
Some(sym::spotlight) => {
cx.emit_lint(AttributeLintKind::DocUnknownSpotlight, path.span());
}
Some(sym::include) if let Some(nv) = args.name_value() => {
let inner = match cx.attr_style {
AttrStyle::Outer => "",
AttrStyle::Inner => "!",
};
cx.emit_lint(
AttributeLintKind::DocUnknownInclude { inner, value: nv.value_as_lit().symbol },
path.span(),
);
}
Some(name @ (sym::passes | sym::no_default_passes)) => {
cx.emit_lint(AttributeLintKind::DocUnknownPasses { name }, path.span());
}
Some(sym::plugins) => {
cx.emit_lint(AttributeLintKind::DocUnknownPlugins, path.span());
}
Some(name) => {
cx.emit_lint(AttributeLintKind::DocUnknownAny { name }, path.span());
}
None => {
// FIXME: is there anything to do in this case?
}
}
}
@ -354,17 +468,30 @@ impl DocParser {
for i in items.mixed() {
match i {
MetaItemOrLitParser::MetaItemParser(mip) => {
self.nb_doc_attrs += 1;
self.parse_single_doc_attr_item(cx, mip);
}
MetaItemOrLitParser::Lit(lit) => todo!("error should've used equals"),
MetaItemOrLitParser::Lit(lit) => {
cx.expected_name_value(lit.span, None);
}
MetaItemOrLitParser::Err(..) => {
// already had an error here, move on.
}
}
}
}
ArgParser::NameValue(v) => {
panic!("this should be rare if at all possible");
ArgParser::NameValue(nv) => {
let Some(comment) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
self.doc_comment = Some(DocComment {
style: cx.attr_style,
kind: CommentKind::Block,
span: nv.value_span,
comment,
});
}
}
}
@ -373,13 +500,49 @@ impl DocParser {
impl<S: Stage> AttributeParser<S> for DocParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[sym::doc],
template!(List: "hidden|inline|...", NameValueStr: "string"),
template!(List: &["hidden", "inline", "test"], NameValueStr: "string"),
|this, cx, args| {
this.accept_single_doc_attr(cx, args);
},
)];
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
Allow(Target::ExternCrate),
Allow(Target::Use),
Allow(Target::Static),
Allow(Target::Const),
Allow(Target::Fn),
Allow(Target::Mod),
Allow(Target::ForeignMod),
Allow(Target::TyAlias),
Allow(Target::Enum),
Allow(Target::Variant),
Allow(Target::Struct),
Allow(Target::Field),
Allow(Target::Union),
Allow(Target::Trait),
Allow(Target::TraitAlias),
Allow(Target::Impl { of_trait: true }),
Allow(Target::Impl { of_trait: false }),
Allow(Target::AssocConst),
Allow(Target::Method(MethodKind::Inherent)),
Allow(Target::Method(MethodKind::Trait { body: true })),
Allow(Target::Method(MethodKind::Trait { body: false })),
Allow(Target::Method(MethodKind::TraitImpl)),
Allow(Target::AssocTy),
Allow(Target::ForeignFn),
Allow(Target::ForeignStatic),
Allow(Target::ForeignTy),
Allow(Target::MacroDef),
Allow(Target::Crate),
]);
fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
todo!()
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if let Some(DocComment { style, kind, span, comment }) = self.doc_comment {
Some(AttributeKind::DocComment { style, kind, span, comment })
} else if self.nb_doc_attrs != 0 {
Some(AttributeKind::Doc(Box::new(self.attribute)))
} else {
None
}
}
}

View file

@ -39,8 +39,8 @@ pub(crate) mod confusables;
pub(crate) mod crate_level;
pub(crate) mod debugger;
pub(crate) mod deprecation;
pub(crate) mod dummy;
pub(crate) mod doc;
pub(crate) mod dummy;
pub(crate) mod inline;
pub(crate) mod link_attrs;
pub(crate) mod lint_helpers;

View file

@ -5,7 +5,7 @@ use rustc_ast::attr::AttributeExt;
use rustc_feature::is_builtin_attr_name;
use rustc_hir::RustcVersion;
use rustc_hir::limit::Limit;
use rustc_span::{Symbol, sym};
use rustc_span::Symbol;
use crate::context::{AcceptContext, Stage};
use crate::parser::{ArgParser, NameValueParser};
@ -32,7 +32,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
|| attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
}
/// Parse a single integer.
///
/// Used by attributes that take a single integer as argument, such as
@ -92,7 +91,3 @@ 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

@ -163,6 +163,8 @@ attribute_parsers!(
BodyStabilityParser,
ConfusablesParser,
ConstStabilityParser,
DocParser,
MacroUseParser,
NakedParser,
StabilityParser,

View file

@ -298,17 +298,6 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
comment: *symbol,
}))
}
// // FIXME: make doc attributes go through a proper attribute parser
// ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
// let p = GenericMetaItemParser::from_attr(&n, self.dcx());
//
// attributes.push(Attribute::Parsed(AttributeKind::DocComment {
// style: attr.style,
// kind: CommentKind::Line,
// span: attr.span,
// comment: p.args().name_value(),
// }))
// }
ast::AttrKind::Normal(n) => {
attr_paths.push(PathParser(Cow::Borrowed(&n.item.path)));
let attr_path = AttrPath::from_ast(&n.item.path, lower_span);

View file

@ -80,6 +80,7 @@
#![feature(decl_macro)]
#![recursion_limit = "256"]
// tidy-alphabetical-end
#![feature(if_let_guard)]
#[macro_use]
/// All the individual attribute parsers for each of rustc's built-in attributes.
@ -107,7 +108,7 @@ pub use attributes::cfg::{
};
pub use attributes::cfg_old::*;
pub use attributes::cfg_select::*;
pub use attributes::util::{is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version};
pub use attributes::util::{is_builtin_attr, parse_version};
pub use context::{Early, Late, OmitDoc, ShouldEmit};
pub use interface::AttributeParser;
pub use session_diagnostics::ParsedDescription;

View file

@ -69,6 +69,15 @@ pub(crate) struct DocKeywordNotKeyword {
pub keyword: Symbol,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_attribute_not_attribute)]
#[help]
pub(crate) struct DocAttributeNotAttribute {
#[primary_span]
pub span: Span,
pub attribute: Symbol,
}
/// Error code: E0541
pub(crate) struct UnknownMetaItem<'a> {
pub span: Span,
@ -588,23 +597,6 @@ pub(crate) struct DocKeywordConflict {
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]
@ -1011,3 +1003,91 @@ pub(crate) struct CfgAttrBadDelim {
#[subdiagnostic]
pub sugg: MetaBadDelimSugg,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_auto_cfg_expects_hide_or_show)]
pub(crate) struct DocAutoCfgExpectsHideOrShow;
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_auto_cfg_hide_show_unexpected_item)]
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_auto_cfg_hide_show_expects_list)]
pub(crate) struct DocAutoCfgHideShowExpectsList {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_invalid)]
pub(crate) struct DocInvalid;
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_unknown_include)]
pub(crate) struct DocUnknownInclude {
pub inner: &'static str,
pub value: Symbol,
#[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
pub sugg: (Span, Applicability),
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_unknown_spotlight)]
#[note]
#[note(attr_parsing_no_op_note)]
pub(crate) struct DocUnknownSpotlight {
#[suggestion(style = "short", applicability = "machine-applicable", code = "notable_trait")]
pub sugg_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_unknown_passes)]
#[note]
#[note(attr_parsing_no_op_note)]
pub(crate) struct DocUnknownPasses {
pub name: Symbol,
#[label]
pub note_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_unknown_plugins)]
#[note]
#[note(attr_parsing_no_op_note)]
pub(crate) struct DocUnknownPlugins {
#[label]
pub label_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_unknown_any)]
pub(crate) struct DocUnknownAny {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_auto_cfg_wrong_literal)]
pub(crate) struct DocAutoCfgWrongLiteral;
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_test_takes_list)]
pub(crate) struct DocTestTakesList;
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_test_unknown)]
pub(crate) struct DocTestUnknown {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(attr_parsing_doc_test_literal)]
pub(crate) struct DocTestLiteral;
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_malformed)]
pub(crate) struct DocAliasMalformed {
#[primary_span]
pub span: Span,
}

View file

@ -428,6 +428,26 @@ pub enum DocInline {
NoInline,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub enum HideOrShow {
Hide,
Show,
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct CfgInfo {
pub name: Symbol,
pub name_span: Span,
pub value: Option<(Symbol, Span)>,
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct CfgHideShow {
pub kind: HideOrShow,
pub values: ThinVec<CfgInfo>,
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct DocAttribute {
pub aliases: FxIndexMap<Symbol, Span>,
@ -435,12 +455,15 @@ pub struct DocAttribute {
pub inline: Option<(DocInline, Span)>,
// unstable
pub cfg: Option<Span>,
pub cfg_hide: Option<Span>,
pub cfg: Option<CfgEntry>,
pub auto_cfg: ThinVec<CfgHideShow>,
/// This is for `#[doc(auto_cfg = false|true)]`.
pub auto_cfg_change: Option<(bool, Span)>,
// builtin
pub fake_variadic: Option<Span>,
pub keyword: Option<(Symbol, Span)>,
pub attribute: Option<(Symbol, Span)>,
pub masked: Option<Span>,
pub notable_trait: Option<Span>,
pub search_unbox: Option<Span>,
@ -455,7 +478,7 @@ pub struct DocAttribute {
pub rust_logo: Option<Span>,
// #[doc(test(...))]
pub test_attrs: ThinVec<()>,
pub test_attrs: ThinVec<Span>,
pub no_crate_inject: Option<Span>,
}
@ -466,9 +489,11 @@ impl Default for DocAttribute {
hidden: None,
inline: None,
cfg: None,
cfg_hide: None,
auto_cfg: ThinVec::new(),
auto_cfg_change: None,
fake_variadic: None,
keyword: None,
attribute: None,
masked: None,
notable_trait: None,
search_unbox: None,

View file

@ -1414,6 +1414,10 @@ impl AttributeExt for Attribute {
)
)
}
fn is_doc_hidden(&self) -> bool {
matches!(self, Attribute::Parsed(AttributeKind::Doc(d)) if d.hidden.is_some())
}
}
// FIXME(fn_delegation): use function delegation instead of manually forwarding

View file

@ -2,7 +2,7 @@ use rustc_data_structures::fingerprint::Fingerprint;
pub use rustc_lint_defs::AttributeLintKind;
use rustc_lint_defs::LintId;
use rustc_macros::HashStable_Generic;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use crate::HirId;
@ -72,4 +72,30 @@ pub enum AttributeLintKind {
DuplicateDocAlias {
first_definition: Span,
},
DocAutoCfgExpectsHideOrShow,
DocAutoCfgHideShowUnexpectedItem {
attr_name: Symbol,
},
DocAutoCfgHideShowExpectsList {
attr_name: Symbol,
},
DocInvalid,
DocUnknownInclude {
inner: &'static str,
value: Symbol,
},
DocUnknownSpotlight,
DocUnknownPasses {
name: Symbol,
},
DocUnknownPlugins,
DocUnknownAny {
name: Symbol,
},
DocAutoCfgWrongLiteral,
DocTestTakesList,
DocTestUnknown {
name: Symbol,
},
DocTestLiteral,
}

View file

@ -8,7 +8,6 @@ edition = "2024"
itertools = "0.12"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
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" }

View file

@ -1,6 +1,4 @@
// tidy-alphabetical-start
#![allow(rustc::diagnostic_outside_of_impl)]
#![allow(rustc::untranslatable_diagnostic)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(if_let_guard)]

View file

@ -3,12 +3,12 @@ use std::cell::{Cell, RefCell};
use std::cmp::max;
use std::ops::Deref;
use rustc_attr_data_structures::{AttributeKind, find_attr};
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sso::SsoHashSet;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, ExprKind, HirId, Node};
use rustc_hir::{self as hir, ExprKind, HirId, Node, find_attr};
use rustc_hir_analysis::autoderef::{self, Autoderef};
use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TyCtxtInferExt};

View file

@ -825,7 +825,7 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
let span = sugared_span.take().unwrap_or(attr.span);
if is_doc_comment || attr.has_name(sym::doc) {
if is_doc_comment {
let sub = match attr.kind {
AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
BuiltinUnusedDocCommentSub::PlainHelp

View file

@ -657,11 +657,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
// `#[doc(hidden)]` disables missing_docs check.
if attr.has_name(sym::doc)
&& attr
.meta_item_list()
.is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden))
{
if attr.is_doc_hidden() {
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {

View file

@ -870,25 +870,20 @@ fn analyze_attr(attr: &hir::Attribute, state: &mut AnalyzeAttrState<'_>) -> bool
&& !rustc_feature::encode_cross_crate(name)
{
// Attributes not marked encode-cross-crate don't need to be encoded for downstream crates.
} else if attr.doc_str().is_some() {
} else if let hir::Attribute::Parsed(AttributeKind::DocComment { .. }) = attr {
// We keep all doc comments reachable to rustdoc because they might be "imported" into
// downstream crates if they use `#[doc(inline)]` to copy an item's documentation into
// their own.
if state.is_exported {
should_encode = true;
}
} else if attr.has_name(sym::doc) {
} else if let hir::Attribute::Parsed(AttributeKind::Doc(d)) = attr {
// If this is a `doc` attribute that doesn't have anything except maybe `inline` (as in
// `#[doc(inline)]`), then we can remove it. It won't be inlinable in downstream crates.
if let Some(item_list) = attr.meta_item_list() {
for item in item_list {
if !item.has_name(sym::inline) {
should_encode = true;
if item.has_name(sym::hidden) {
state.is_doc_hidden = true;
break;
}
}
if d.inline.is_none() {
should_encode = true;
if d.hidden.is_some() {
state.is_doc_hidden = true;
}
}
} else if let &[sym::diagnostic, seg] = &*attr.path() {

View file

@ -4,12 +4,14 @@ use std::{fmt, iter};
use rustc_abi::{Float, Integer, IntegerType, Size};
use rustc_apfloat::Float as _;
use rustc_ast::attr::AttributeExt;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::ErrorGuaranteed;
use rustc_hashes::Hash128;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
use rustc_hir::limit::Limit;
@ -1664,16 +1666,14 @@ pub fn reveal_opaque_types_in_bounds<'tcx>(
/// Determines whether an item is directly annotated with `doc(hidden)`.
fn is_doc_hidden(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.get_attrs(def_id, sym::doc)
.filter_map(|attr| attr.meta_item_list())
.any(|items| items.iter().any(|item| item.has_name(sym::hidden)))
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
attrs.iter().any(|attr| attr.is_doc_hidden())
}
/// Determines whether an item is annotated with `doc(notable_trait)`.
pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
tcx.get_attrs(def_id, sym::doc)
.filter_map(|attr| attr.meta_item_list())
.any(|items| items.iter().any(|item| item.has_name(sym::notable_trait)))
let attrs = tcx.get_all_attrs(def_id);
attrs.iter().any(|attr| matches!(attr, hir::Attribute::Parsed(AttributeKind::Doc(doc)) if doc.notable_trait.is_some()))
}
/// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute).

View file

@ -8,7 +8,6 @@ edition = "2024"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_ast_lowering = { path = "../rustc_ast_lowering" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }

View file

@ -107,39 +107,14 @@ passes_diagnostic_item_first_defined =
the diagnostic item is first defined here
passes_doc_alias_bad_location =
{$attr_str} isn't allowed on {$location}
passes_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
doc alias attribute isn't allowed on {$location}
passes_doc_alias_not_an_alias =
{$attr_str} is the same as the item's name
passes_doc_alias_not_string_literal =
`#[doc(alias("a"))]` expects string literals
passes_doc_attr_not_crate_level =
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
passes_doc_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std
passes_doc_auto_cfg_expects_hide_or_show =
only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`
passes_doc_auto_cfg_hide_show_expects_list =
`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items
passes_doc_auto_cfg_hide_show_unexpected_item =
`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items
passes_doc_auto_cfg_wrong_literal =
expected boolean for `#[doc(auto_cfg = ...)]`
passes_doc_expect_str =
doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")]
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
@ -151,9 +126,6 @@ passes_doc_inline_only_use =
.not_a_use_item_label = not a `use` item
.note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
passes_doc_invalid =
invalid `doc` attribute
passes_doc_keyword_attribute_empty_mod =
`#[doc({$attr_name} = "...")]` should be used on empty modules
@ -180,39 +152,6 @@ passes_doc_rust_logo =
passes_doc_search_unbox_invalid =
`#[doc(search_unbox)]` should be used on generic structs and enums
passes_doc_test_literal = `#![doc(test(...)]` does not take a literal
passes_doc_test_takes_list =
`#[doc(test(...)]` takes a list of attributes
passes_doc_test_unknown =
unknown `doc(test)` attribute `{$path}`
passes_doc_test_unknown_any =
unknown `doc` attribute `{$path}`
passes_doc_test_unknown_include =
unknown `doc` attribute `{$path}`
.suggestion = use `doc = include_str!` instead
passes_doc_test_unknown_passes =
unknown `doc` attribute `{$path}`
.note = `doc` attribute `{$path}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_plugins =
unknown `doc` attribute `{$path}`
.note = `doc` attribute `{$path}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_spotlight =
unknown `doc` attribute `{$path}`
.note = `doc(spotlight)` was renamed to `doc(notable_trait)`
.suggestion = use `notable_trait` instead
.no_op_note = `doc(spotlight)` is now a no-op
passes_duplicate_diagnostic_item_in_crate =
duplicate diagnostic item in crate `{$crate_name}`: `{$name}`
.note = the diagnostic item is first defined in crate `{$orig_crate_name}`

View file

@ -10,22 +10,24 @@ use std::collections::hash_map::Entry;
use std::slice;
use rustc_abi::{Align, ExternAbi, Size};
use rustc_ast::{AttrStyle, LitKind, MetaItem, MetaItemInner, MetaItemKind, ast};
use rustc_ast::{AttrStyle, LitKind, MetaItemKind, ast};
use rustc_attr_parsing::{AttributeParser, Late};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey};
use rustc_errors::{DiagCtxtHandle, IntoDiagArg, StashKey};
use rustc_feature::{
ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
BuiltinAttribute,
};
use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet};
use rustc_hir::attrs::{
AttributeKind, DocAttribute, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet,
};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalModDefId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{
self as hir, Attribute, CRATE_HIR_ID, CRATE_OWNER_ID, Constness, FnSig, ForeignItem, HirId,
Item, ItemKind, MethodKind, PartialConstStability, Safety, Stability, StabilityLevel, Target,
TraitItem, find_attr,
self as hir, Attribute, CRATE_HIR_ID, Constness, FnSig, ForeignItem, HirId, Item, ItemKind,
MethodKind, PartialConstStability, Safety, Stability, StabilityLevel, Target, TraitItem,
find_attr,
};
use rustc_macros::LintDiagnostic;
use rustc_middle::hir::nested_filter;
@ -43,7 +45,7 @@ use rustc_session::lint::builtin::{
};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, edition, sym};
use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, sym};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
use rustc_trait_selection::traits::ObligationCtxt;
@ -106,21 +108,6 @@ impl IntoDiagArg for ProcMacroKind {
}
}
#[derive(Clone, Copy)]
enum DocFakeItemKind {
Attribute,
Keyword,
}
impl DocFakeItemKind {
fn name(self) -> &'static str {
match self {
Self::Attribute => "attribute",
Self::Keyword => "keyword",
}
}
}
struct CheckAttrVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
@ -223,6 +210,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::MacroExport { span, .. }) => {
self.check_macro_export(hir_id, *span, target)
},
Attribute::Parsed(AttributeKind::Doc(attr)) => self.check_doc_attrs(attr, hir_id, target),
Attribute::Parsed(
AttributeKind::BodyStability { .. }
| AttributeKind::ConstStabilityIndirect
@ -771,13 +759,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
fn doc_attr_str_error(&self, meta: &MetaItemInner, attr_name: &str) {
self.dcx().emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
}
fn check_doc_alias_value(&self, span: Span, alias: Symbol, hir_id: HirId, target: Target) {
let tcx = self.tcx;
fn check_doc_alias_value(&self, span: Span, hir_id: HirId, target: Target, alias: Symbol) {
if let Some(location) = match target {
Target::AssocTy => {
if let DefKind::Impl { .. } =
@ -834,96 +816,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| Target::MacroCall
| Target::Delegation { .. } => None,
} {
// FIXME: emit proper error
// tcx.dcx().emit_err(errors::DocAliasBadLocation {
// span,
// errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
// );
self.tcx.dcx().emit_err(errors::DocAliasBadLocation { span, location });
return;
}
}
fn check_doc_alias(
&self,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
aliases: &mut FxHashMap<String, Span>,
) {
if let Some(values) = meta.meta_item_list() {
for v in values {
match v.lit() {
Some(l) => match l.kind {
LitKind::Str(s, _) => {
self.check_doc_alias_value(v, s, hir_id, target, true, aliases);
}
_ => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
},
None => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
}
}
} else if let Some(doc_alias) = meta.value_str() {
self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases)
} else {
self.dcx().emit_err(errors::DocAliasMalformed { span: meta.span() });
}
}
fn check_doc_keyword_and_attribute(
&self,
span: Span,
hir_id: HirId,
attr_kind: DocFakeItemKind,
) {
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
}
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
};
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx()
.emit_err(errors::DocKeywordEmptyMod { span, attr_name: attr_kind.name() });
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordNotMod { span, attr_name: attr_kind.name() });
return;
}
}
match attr_kind {
DocFakeItemKind::Keyword => {
if !is_doc_keyword(value) {
self.dcx().emit_err(errors::DocKeywordNotKeyword {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
keyword: value,
});
}
}
DocFakeItemKind::Attribute => {
if !is_builtin_attr(value) {
self.dcx().emit_err(errors::DocAttributeNotAttribute {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
attribute: value,
});
}
}
if self.tcx.hir_opt_name(hir_id) == Some(alias) {
self.tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str: alias });
return;
}
}
@ -1027,6 +925,25 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
fn check_doc_keyword_and_attribute(&self, span: Span, hir_id: HirId, attr_name: &'static str) {
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
};
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod { span, attr_name });
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordAttributeNotMod { span, attr_name });
return;
}
}
}
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
fn check_attr_not_crate_level(&self, span: Span, hir_id: HirId, attr_name: &str) -> bool {
if CRATE_HIR_ID == hir_id {
@ -1050,62 +967,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
true
}
/// 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 {
MetaItemKind::Word => {}
MetaItemKind::NameValue(lit) => {
if !matches!(lit.kind, LitKind::Bool(_)) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgWrongLiteral,
);
}
}
MetaItemKind::List(list) => {
for item in list {
let Some(attr_name @ (sym::hide | sym::show)) = item.name() else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgExpectsHideOrShow,
);
continue;
};
if let Some(list) = item.meta_item_list() {
for item in list {
let valid = item.meta_item().is_some_and(|meta| {
meta.path.segments.len() == 1
&& matches!(
&meta.kind,
MetaItemKind::Word | MetaItemKind::NameValue(_)
)
});
if !valid {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
item.span(),
errors::DocAutoCfgHideShowUnexpectedItem { attr_name },
);
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgHideShowExpectsList { attr_name },
);
}
}
}
}
}
/// Runs various checks on `#[doc]` attributes.
///
/// `specified_inline` should be initialized to `None` and kept for the scope
@ -1121,7 +982,10 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
inline,
// FIXME: currently unchecked
cfg: _,
cfg_hide,
// already check in attr_parsing
auto_cfg: _,
// already check in attr_parsing
auto_cfg_change: _,
fake_variadic,
keyword,
masked,
@ -1138,36 +1002,23 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
// allowed anywhere
test_attrs: _,
no_crate_inject,
attribute,
} = attr;
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);
self.check_doc_alias_value(*span, hir_id, target, *alias);
}
}
if let Some((_, span)) = keyword {
if self.check_attr_not_crate_level(*span, hir_id, "keyword") {
self.check_doc_keyword(*span, hir_id);
}
self.check_attr_not_crate_level(*span, hir_id, "keyword");
self.check_doc_keyword_and_attribute(*span, hir_id, "keyword");
}
if let Some((_, span)) = attribute {
self.check_attr_not_crate_level(*span, hir_id, "attribute");
self.check_doc_keyword_and_attribute(*span, hir_id, "attribute");
}
// 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") {
@ -1220,10 +1071,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
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

@ -24,12 +24,6 @@ pub(crate) struct IncorrectDoNotRecommendLocation;
#[diag(passes_incorrect_do_not_recommend_args)]
pub(crate) struct DoNotRecommendDoesNotExpectArgs;
#[derive(LintDiagnostic)]
#[diag(passes_doc_attr_expects_string)]
pub(crate) struct DocAttrExpectsString {
pub(crate) attr_name: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_autodiff_attr)]
pub(crate) struct AutoDiffAttr {
@ -129,43 +123,20 @@ pub(crate) struct AttrShouldBeAppliedToStatic {
pub defn_span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_expect_str)]
pub(crate) struct DocExpectStr<'a> {
#[primary_span]
pub attr_span: Span,
pub attr_name: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_bad_location)]
pub(crate) struct DocAliasBadLocation<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub location: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_not_an_alias)]
pub(crate) struct DocAliasNotAnAlias<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_not_string_literal)]
pub(crate) struct DocAliasNotStringLiteral {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_malformed)]
pub(crate) struct DocAliasMalformed {
pub(crate) struct DocAliasNotAnAlias {
#[primary_span]
pub span: Span,
pub attr_str: Symbol,
}
#[derive(Diagnostic)]
@ -176,24 +147,6 @@ pub(crate) struct DocKeywordAttributeEmptyMod {
pub attr_name: &'static str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_keyword_not_keyword)]
#[help]
pub(crate) struct DocKeywordNotKeyword {
#[primary_span]
pub span: Span,
pub keyword: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_doc_attribute_not_attribute)]
#[help]
pub(crate) struct DocAttributeNotAttribute {
#[primary_span]
pub span: Span,
pub attribute: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_doc_keyword_attribute_not_mod)]
pub(crate) struct DocKeywordAttributeNotMod {
@ -260,90 +213,6 @@ pub(crate) struct DocAttrNotCrateLevel<'a> {
pub attr_name: &'a str,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown)]
pub(crate) struct DocTestUnknown {
pub path: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_literal)]
pub(crate) struct DocTestLiteral;
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_takes_list)]
pub(crate) struct DocTestTakesList;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_wrong_literal)]
pub(crate) struct DocAutoCfgWrongLiteral;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_expects_hide_or_show)]
pub(crate) struct DocAutoCfgExpectsHideOrShow;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_hide_show_expects_list)]
pub(crate) struct DocAutoCfgHideShowExpectsList {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_hide_show_unexpected_item)]
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_any)]
pub(crate) struct DocTestUnknownAny {
pub path: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_spotlight)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownSpotlight {
pub path: String,
#[suggestion(style = "short", applicability = "machine-applicable", code = "notable_trait")]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_passes)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownPasses {
pub path: String,
#[label]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_plugins)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownPlugins {
pub path: String,
#[label]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_include)]
pub(crate) struct DocTestUnknownInclude {
pub path: String,
pub value: String,
pub inner: &'static str,
#[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
pub sugg: (Span, Applicability),
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_invalid)]
pub(crate) struct DocInvalid;
#[derive(Diagnostic)]
#[diag(passes_has_incoherent_inherent_impl)]
pub(crate) struct HasIncoherentInherentImpl {

View file

@ -10,7 +10,6 @@ use rustc_ast::{
Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
};
use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
use rustc_attr_data_structures::{AttributeKind, find_attr};
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::{
@ -18,6 +17,7 @@ use rustc_errors::{
struct_span_code_err,
};
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind, MacroKinds};
use rustc_hir::def_id::{CRATE_DEF_ID, DefId};
@ -903,7 +903,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// confused by them.
continue;
}
if let Some(d) = find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d)
if let Some(d) =
hir::find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d)
&& d.aliases.contains_key(&item_name)
{
return Some(did);