diff --git a/Cargo.lock b/Cargo.lock index 03a4d71f67c3..bb81cada72ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 901b645b8c4e..d54d900128bd 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -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; + + /// Returns `true` if this attribute contains `doc(hidden)`. + fn is_doc_hidden(&self) -> bool; } // FIXME(fn_delegation): use function delegation instead of manually forwarding diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 4482266c6c2e..4d7716560fb2 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -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 + .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 and 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"))]` diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 490e28ed64c5..6ffe25098308 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -171,7 +171,7 @@ fn parse_cfg_entry_target( Ok(CfgEntry::All(result, list.span)) } -fn parse_name_value( +pub(crate) fn parse_name_value( name: Symbol, name_span: Span, value: Option<&NameValueParser>, diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index a5986170c192..65651e617179 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -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(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( + 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, } 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 AttributeParser for DocParser { const ATTRIBUTES: AcceptMapping = &[( &[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 { - todo!() + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + 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 + } } } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 584f62ca51a3..64bcb02b0b74 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -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; diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index ce555be6a47d..105f7164bf3b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -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 AcceptContext<'_, '_, S> { None } } - -pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option { - first_attr_value_str_by_name(attrs, sym::crate_name) -} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index af73bf7f9fd2..5d15588033fd 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -163,6 +163,8 @@ attribute_parsers!( BodyStabilityParser, ConfusablesParser, ConstStabilityParser, + DocParser, + MacroUseParser, NakedParser, StabilityParser, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index b26a4a29cd2e..363e1fcac507 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -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); diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 7cef70f88e1c..37a3189f892e 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -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; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index f122b5a87549..26b615448e3b 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -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, +} diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 538c866567a9..39008914f9ef 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -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, +} + #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct DocAttribute { pub aliases: FxIndexMap, @@ -435,12 +455,15 @@ pub struct DocAttribute { pub inline: Option<(DocInline, Span)>, // unstable - pub cfg: Option, - pub cfg_hide: Option, + pub cfg: Option, + pub auto_cfg: ThinVec, + /// This is for `#[doc(auto_cfg = false|true)]`. + pub auto_cfg_change: Option<(bool, Span)>, // builtin pub fake_variadic: Option, pub keyword: Option<(Symbol, Span)>, + pub attribute: Option<(Symbol, Span)>, pub masked: Option, pub notable_trait: Option, pub search_unbox: Option, @@ -455,7 +478,7 @@ pub struct DocAttribute { pub rust_logo: Option, // #[doc(test(...))] - pub test_attrs: ThinVec<()>, + pub test_attrs: ThinVec, pub no_crate_inject: Option, } @@ -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, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index e8a91dae0749..e6a0f430b63a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -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 diff --git a/compiler/rustc_hir/src/lints.rs b/compiler/rustc_hir/src/lints.rs index abffb9437a51..2d13ceabf8ca 100644 --- a/compiler/rustc_hir/src/lints.rs +++ b/compiler/rustc_hir/src/lints.rs @@ -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, } diff --git a/compiler/rustc_hir_typeck/Cargo.toml b/compiler/rustc_hir_typeck/Cargo.toml index f00125c3e090..246134665174 100644 --- a/compiler/rustc_hir_typeck/Cargo.toml +++ b/compiler/rustc_hir_typeck/Cargo.toml @@ -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" } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 734d8963d6fd..d3ef1d63e8ba 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -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)] diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index e4e892ab10f4..1a25f6a582f2 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -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}; diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 0805ef47079b..ae973a3c49c2 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -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 diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index ac47897b5688..0f6452a2bc99 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -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 { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 86719581a209..9a1cf7f349fd 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -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() { diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 782ea3906ef1..fc03ad52b4bf 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -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). diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml index ba81ef3103bd..10da57f56ecf 100644 --- a/compiler/rustc_passes/Cargo.toml +++ b/compiler/rustc_passes/Cargo.toml @@ -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" } diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 93cd9a9702f7..007ce22c01e6 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -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 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 - .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 and 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}` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 1a48e4360d86..bae15de4bf88 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -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, - ) { - 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) { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 926aba5082de..b693aaf76923 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -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 { diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index a18913c1c1c7..069944b638c7 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -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);