From df007cf8008732cec446bdde0d9604c0232121ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 20 Jun 2025 22:50:22 +0200 Subject: [PATCH 01/32] rewrite doc attribute (non-doc-comments) --- compiler/rustc_attr_parsing/messages.ftl | 29 + .../rustc_attr_parsing/src/attributes/doc.rs | 385 +++++++++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + .../rustc_attr_parsing/src/attributes/util.rs | 33 +- compiler/rustc_attr_parsing/src/context.rs | 5 +- .../src/session_diagnostics.rs | 67 +++ .../rustc_hir/src/attrs/data_structures.rs | 73 ++- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../rustc_hir/src/attrs/pretty_printing.rs | 20 + compiler/rustc_hir/src/lints.rs | 42 ++ compiler/rustc_hir_typeck/src/method/probe.rs | 6 +- compiler/rustc_passes/messages.ftl | 32 -- compiler/rustc_passes/src/check_attr.rs | 505 +++++------------- compiler/rustc_passes/src/errors.rs | 64 +-- .../rustc_resolve/src/late/diagnostics.rs | 6 +- 15 files changed, 772 insertions(+), 497 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/doc.rs diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 02ddf8ac0023d..4482266c6c2e0 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -234,5 +234,34 @@ attr_parsing_unused_multiple = .suggestion = remove this attribute .note = attribute also specified here +attr_parsing_doc_alias_duplicated = doc alias is duplicated + .label = first defined here + attr_parsing_whole_archive_needs_static = linking modifier `whole-archive` is only compatible with `static` linking kind + +attr_parsing_unused_no_lints_note = + attribute `{$name}` without any lints has no effect + +attr_parsing_doc_alias_empty = + {$attr_str} attribute cannot have empty value + +attr_parsing_doc_alias_bad_char = + {$char_} character isn't allowed in {$attr_str} + +attr_parsing_doc_alias_start_end = + {$attr_str} cannot start or end with ' ' + +attr_parsing_doc_keyword_not_keyword = + nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` + .help = only existing keywords are allowed in core/std + +attr_parsing_doc_inline_conflict = + conflicting doc inlining attributes + .help = remove one of the conflicting attributes + +attr_parsing_doc_inline_conflict_first = + this attribute... + +attr_parsing_doc_inline_conflict_second = + {"."}..conflicts with this attribute diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs new file mode 100644 index 0000000000000..a5986170c192a --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -0,0 +1,385 @@ +use rustc_attr_data_structures::lints::AttributeLintKind; +use rustc_attr_data_structures::{AttributeKind, DocAttribute, DocInline}; +use rustc_errors::MultiSpan; +use rustc_feature::template; +use rustc_span::{Span, Symbol, edition, sym}; + +use super::{AcceptMapping, AttributeParser}; +use crate::context::{AcceptContext, FinalizeContext, Stage}; +use crate::fluent_generated as fluent; +use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser}; +use crate::session_diagnostics::{ + DocAliasBadChar, DocAliasEmpty, DocAliasStartEnd, DocKeywordConflict, DocKeywordNotKeyword, +}; + +#[derive(Default)] +pub(crate) struct DocParser { + attribute: DocAttribute, +} + +impl DocParser { + fn parse_single_test_doc_attr_item<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + mip: &'c MetaItemParser<'_>, + ) { + let path = mip.path(); + let args = mip.args(); + + match path.word_sym() { + Some(sym::no_crate_inject) => { + if !args.no_args() { + cx.expected_no_args(args.span().unwrap()); + return; + } + + if self.attribute.no_crate_inject.is_some() { + cx.duplicate_key(path.span(), sym::no_crate_inject); + return; + } + + self.attribute.no_crate_inject = Some(path.span()) + } + Some(sym::attr) => { + let Some(list) = args.list() else { + cx.expected_list(args.span().unwrap_or(path.span())); + return; + }; + + self.attribute.test_attrs.push(todo!()); + } + _ => { + cx.expected_specific_argument( + mip.span(), + [sym::no_crate_inject.as_str(), sym::attr.as_str()].to_vec(), + ); + } + } + } + + fn add_alias<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + alias: Symbol, + span: Span, + is_list: bool, + ) { + let attr_str = + &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" }); + if alias == sym::empty { + cx.emit_err(DocAliasEmpty { span, attr_str }); + return; + } + + let alias_str = alias.as_str(); + if let Some(c) = + alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) + { + cx.emit_err(DocAliasBadChar { span, attr_str, char_: c }); + return; + } + if alias_str.starts_with(' ') || alias_str.ends_with(' ') { + cx.emit_err(DocAliasStartEnd { span, attr_str }); + return; + } + + if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() { + cx.emit_lint(AttributeLintKind::DuplicateDocAlias { first_definition }, span); + } + + self.attribute.aliases.insert(alias, span); + } + + fn parse_alias<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + path: &PathParser<'_>, + args: &ArgParser<'_>, + ) { + match args { + ArgParser::NoArgs => { + cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym()); + } + ArgParser::List(list) => { + for i in list.mixed() { + let Some(alias) = i.lit().and_then(|i| i.value_str()) else { + cx.expected_string_literal(i.span(), i.lit()); + continue; + }; + + self.add_alias(cx, alias, i.span(), false); + } + } + ArgParser::NameValue(nv) => { + let Some(alias) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return; + }; + self.add_alias(cx, alias, nv.value_span, false); + } + } + } + + fn parse_keyword<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + path: &PathParser<'_>, + args: &ArgParser<'_>, + ) { + let Some(nv) = args.name_value() else { + cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym()); + return; + }; + + let Some(keyword) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return; + }; + + fn is_doc_keyword(s: Symbol) -> bool { + // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we + // can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the + // `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`. + s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy + } + + if !is_doc_keyword(keyword) { + cx.emit_err(DocKeywordNotKeyword { span: nv.value_span, keyword }); + } + + if self.attribute.keyword.is_some() { + cx.duplicate_key(path.span(), path.word_sym().unwrap()); + return; + } + + self.attribute.keyword = Some((keyword, path.span())); + } + + fn parse_inline<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + path: &PathParser<'_>, + args: &ArgParser<'_>, + inline: DocInline, + ) { + if !args.no_args() { + cx.expected_no_args(args.span().unwrap()); + return; + } + + let span = path.span(); + + if let Some((prev_inline, prev_span)) = self.attribute.inline { + if prev_inline == inline { + let mut spans = MultiSpan::from_spans(vec![prev_span, span]); + spans.push_span_label(prev_span, fluent::attr_parsing_doc_inline_conflict_first); + spans.push_span_label(span, fluent::attr_parsing_doc_inline_conflict_second); + cx.emit_err(DocKeywordConflict { spans }); + return; + } + } + + self.attribute.inline = Some((inline, span)); + } + + fn parse_single_doc_attr_item<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + mip: &MetaItemParser<'_>, + ) { + let path = mip.path(); + let args = mip.args(); + + macro_rules! no_args { + ($ident: ident) => {{ + if !args.no_args() { + cx.expected_no_args(args.span().unwrap()); + return; + } + + if self.attribute.$ident.is_some() { + cx.duplicate_key(path.span(), path.word_sym().unwrap()); + return; + } + + self.attribute.$ident = Some(path.span()); + }}; + } + macro_rules! string_arg { + ($ident: ident) => {{ + let Some(nv) = args.name_value() else { + cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym()); + return; + }; + + let Some(s) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return; + }; + + if self.attribute.$ident.is_some() { + cx.duplicate_key(path.span(), path.word_sym().unwrap()); + return; + } + + self.attribute.$ident = Some((s, path.span())); + }}; + } + + match path.word_sym() { + Some(sym::alias) => self.parse_alias(cx, path, args), + Some(sym::hidden) => no_args!(hidden), + Some(sym::html_favicon_url) => string_arg!(html_favicon_url), + Some(sym::html_logo_url) => string_arg!(html_logo_url), + Some(sym::html_no_source) => no_args!(html_no_source), + Some(sym::html_playground_url) => string_arg!(html_playground_url), + Some(sym::html_root_url) => string_arg!(html_root_url), + Some(sym::issue_tracker_base_url) => string_arg!(issue_tracker_base_url), + Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline), + Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline), + Some(sym::masked) => no_args!(masked), + Some(sym::cfg) => no_args!(cfg), + Some(sym::cfg_hide) => no_args!(cfg_hide), + Some(sym::notable_trait) => no_args!(notable_trait), + Some(sym::keyword) => self.parse_keyword(cx, path, args), + Some(sym::fake_variadic) => no_args!(fake_variadic), + Some(sym::search_unbox) => no_args!(search_unbox), + Some(sym::rust_logo) => no_args!(rust_logo), + Some(sym::test) => { + let Some(list) = args.list() else { + cx.expected_list(args.span().unwrap_or(path.span())); + return; + }; + + for i in list.mixed() { + match i { + MetaItemOrLitParser::MetaItemParser(mip) => { + self.parse_single_test_doc_attr_item(cx, mip); + } + MetaItemOrLitParser::Lit(lit) => { + cx.unexpected_literal(lit.span); + } + MetaItemOrLitParser::Err(..) => { + // already had an error here, move on. + } + } + } + + // let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path); + // if i_meta.has_name(sym::spotlight) { + // self.tcx.emit_node_span_lint( + // INVALID_DOC_ATTRIBUTES, + // hir_id, + // i_meta.span, + // errors::DocTestUnknownSpotlight { path, span: i_meta.span }, + // ); + // } else if i_meta.has_name(sym::include) + // && let Some(value) = i_meta.value_str() + // { + // let applicability = if list.len() == 1 { + // Applicability::MachineApplicable + // } else { + // Applicability::MaybeIncorrect + // }; + // // If there are multiple attributes, the suggestion would suggest + // // deleting all of them, which is incorrect. + // self.tcx.emit_node_span_lint( + // INVALID_DOC_ATTRIBUTES, + // hir_id, + // i_meta.span, + // errors::DocTestUnknownInclude { + // path, + // value: value.to_string(), + // inner: match attr.style() { + // AttrStyle::Inner => "!", + // AttrStyle::Outer => "", + // }, + // sugg: (attr.span(), applicability), + // }, + // ); + // } else if i_meta.has_name(sym::passes) || i_meta.has_name(sym::no_default_passes) { + // self.tcx.emit_node_span_lint( + // INVALID_DOC_ATTRIBUTES, + // hir_id, + // i_meta.span, + // errors::DocTestUnknownPasses { path, span: i_meta.span }, + // ); + // } else if i_meta.has_name(sym::plugins) { + // self.tcx.emit_node_span_lint( + // INVALID_DOC_ATTRIBUTES, + // hir_id, + // i_meta.span, + // errors::DocTestUnknownPlugins { path, span: i_meta.span }, + // ); + // } else { + // self.tcx.emit_node_span_lint( + // INVALID_DOC_ATTRIBUTES, + // hir_id, + // i_meta.span, + // errors::DocTestUnknownAny { path }, + // ); + // } + } + _ => { + cx.expected_specific_argument( + mip.span(), + [ + sym::alias.as_str(), + sym::hidden.as_str(), + sym::html_favicon_url.as_str(), + sym::html_logo_url.as_str(), + sym::html_no_source.as_str(), + sym::html_playground_url.as_str(), + sym::html_root_url.as_str(), + sym::inline.as_str(), + sym::no_inline.as_str(), + sym::test.as_str(), + ] + .to_vec(), + ); + } + } + } + + fn accept_single_doc_attr<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + args: &'c ArgParser<'_>, + ) { + match args { + ArgParser::NoArgs => { + todo!() + } + ArgParser::List(items) => { + for i in items.mixed() { + match i { + MetaItemOrLitParser::MetaItemParser(mip) => { + self.parse_single_doc_attr_item(cx, mip); + } + MetaItemOrLitParser::Lit(lit) => todo!("error should've used equals"), + MetaItemOrLitParser::Err(..) => { + // already had an error here, move on. + } + } + } + } + ArgParser::NameValue(v) => { + panic!("this should be rare if at all possible"); + } + } + } +} + +impl AttributeParser for DocParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::doc], + template!(List: "hidden|inline|...", NameValueStr: "string"), + |this, cx, args| { + this.accept_single_doc_attr(cx, args); + }, + )]; + + fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option { + todo!() + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 65e0957ca9005..584f62ca51a37 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -40,6 +40,7 @@ pub(crate) mod crate_level; pub(crate) mod debugger; pub(crate) mod deprecation; pub(crate) mod dummy; +pub(crate) mod doc; pub(crate) mod inline; pub(crate) mod link_attrs; pub(crate) mod lint_helpers; diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index 520fd9da7c2ab..ce555be6a47d4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -32,35 +32,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool { || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name)) } -pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>( - attrs: impl Iterator, - symbol: Symbol, -) -> bool { - let doc_attrs = attrs.filter(|attr| attr.has_name(sym::doc)); - for attr in doc_attrs { - let Some(values) = attr.meta_item_list() else { - continue; - }; - let alias_values = values.iter().filter(|v| v.has_name(sym::alias)); - for v in alias_values { - if let Some(nested) = v.meta_item_list() { - // #[doc(alias("foo", "bar"))] - let mut iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol); - if iter.any(|s| s == symbol) { - return true; - } - } else if let Some(meta) = v.meta_item() - && let Some(lit) = meta.name_value_literal() - { - // #[doc(alias = "foo")] - if lit.symbol == symbol { - return true; - } - } - } - } - false -} /// Parse a single integer. /// @@ -121,3 +92,7 @@ impl 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 bc74eaad50bc4..af73bf7f9fd23 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -33,6 +33,7 @@ use crate::attributes::crate_level::{ }; use crate::attributes::debugger::DebuggerViualizerParser; use crate::attributes::deprecation::DeprecationParser; +use crate::attributes::doc::DocParser; use crate::attributes::dummy::DummyParser; use crate::attributes::inline::{InlineParser, RustcForceInlineParser}; use crate::attributes::link_attrs::{ @@ -162,7 +163,7 @@ attribute_parsers!( BodyStabilityParser, ConfusablesParser, ConstStabilityParser, - MacroUseParser, + NakedParser, StabilityParser, UsedParser, @@ -427,7 +428,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { &self, span: Span, found: String, - options: &'static [&'static str], + options: &[&'static str], ) -> ErrorGuaranteed { self.emit_err(UnknownMetaItem { span, item: found, expected: options }) } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 2b3108a8d3ed9..f122b5a875492 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -4,6 +4,7 @@ use rustc_ast::{self as ast}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, + MultiSpan, }; use rustc_feature::AttributeTemplate; use rustc_hir::AttrPath; @@ -34,6 +35,40 @@ pub(crate) struct InvalidPredicate { pub predicate: String, } +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_alias_empty)] +pub(crate) struct DocAliasEmpty<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_alias_bad_char)] +pub(crate) struct DocAliasBadChar<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, + pub char_: char, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_alias_start_end)] +pub(crate) struct DocAliasStartEnd<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_keyword_not_keyword)] +#[help] +pub(crate) struct DocKeywordNotKeyword { + #[primary_span] + pub span: Span, + pub keyword: Symbol, +} + /// Error code: E0541 pub(crate) struct UnknownMetaItem<'a> { pub span: Span, @@ -538,6 +573,38 @@ pub(crate) struct NakedFunctionIncompatibleAttribute { pub attr: String, } +#[derive(LintDiagnostic)] +#[diag(attr_parsing_doc_alias_duplicated)] +pub(crate) struct DocAliasDuplicated { + #[label] + pub first_defn: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_inline_conflict)] +#[help] +pub(crate) struct DocKeywordConflict { + #[primary_span] + pub spans: MultiSpan, +} + + #[derive(Subdiagnostic)] + pub(crate) enum UnusedNote { + #[note(attr_parsing_unused_empty_lints_note)] + EmptyList { name: Symbol }, + #[note(attr_parsing_unused_no_lints_note)] + NoLints { name: Symbol }, + } + + #[derive(LintDiagnostic)] + #[diag(attr_parsing_unused)] + pub(crate) struct Unused { + #[suggestion(code = "", applicability = "machine-applicable")] + pub attr_span: Span, + #[subdiagnostic] + pub note: UnusedNote, + } + #[derive(Diagnostic)] #[diag(attr_parsing_link_ordinal_out_of_range)] #[note] diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index aff79d0558387..538c866567a9c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -5,6 +5,7 @@ pub use ReprAttr::*; use rustc_abi::Align; use rustc_ast::token::CommentKind; use rustc_ast::{AttrStyle, ast}; +use rustc_data_structures::fx::FxIndexMap; use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; use rustc_span::def_id::DefId; @@ -420,6 +421,70 @@ impl WindowsSubsystemKind { } } +#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub enum DocInline { + Inline, + NoInline, +} + +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct DocAttribute { + pub aliases: FxIndexMap, + pub hidden: Option, + pub inline: Option<(DocInline, Span)>, + + // unstable + pub cfg: Option, + pub cfg_hide: Option, + + // builtin + pub fake_variadic: Option, + pub keyword: Option<(Symbol, Span)>, + pub masked: Option, + pub notable_trait: Option, + pub search_unbox: Option, + + // valid on crate + pub html_favicon_url: Option<(Symbol, Span)>, + pub html_logo_url: Option<(Symbol, Span)>, + pub html_playground_url: Option<(Symbol, Span)>, + pub html_root_url: Option<(Symbol, Span)>, + pub html_no_source: Option, + pub issue_tracker_base_url: Option<(Symbol, Span)>, + pub rust_logo: Option, + + // #[doc(test(...))] + pub test_attrs: ThinVec<()>, + pub no_crate_inject: Option, +} + +impl Default for DocAttribute { + fn default() -> Self { + Self { + aliases: FxIndexMap::default(), + hidden: None, + inline: None, + cfg: None, + cfg_hide: None, + fake_variadic: None, + keyword: None, + masked: None, + notable_trait: None, + search_unbox: None, + html_favicon_url: None, + html_logo_url: None, + html_playground_url: None, + html_root_url: None, + html_no_source: None, + issue_tracker_base_url: None, + rust_logo: None, + test_attrs: ThinVec::new(), + no_crate_inject: None, + } + } +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -551,7 +616,13 @@ pub enum AttributeKind { /// Represents `#[rustc_do_not_implement_via_object]`. DoNotImplementViaObject(Span), - /// Represents [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html). + /// Represents [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html). + /// Represents all other uses of the [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html) + /// attribute. + Doc(Box), + + /// Represents specifically [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html). + /// i.e. doc comments. DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol }, /// Represents `#[rustc_dummy]`. diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index a685eb99b8332..ad120648c1eec 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -40,6 +40,7 @@ impl AttributeKind { DenyExplicitImpl(..) => No, Deprecation { .. } => Yes, DoNotImplementViaObject(..) => No, + Doc(_) => Yes, DocComment { .. } => Yes, Dummy => No, ExportName { .. } => Yes, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index ea86dfbd9c80e..75886fb08a2e0 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -4,6 +4,7 @@ use rustc_abi::Align; use rustc_ast::token::CommentKind; use rustc_ast::{AttrStyle, IntTy, UintTy}; use rustc_ast_pretty::pp::Printer; +use rustc_data_structures::fx::FxIndexMap; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_target::spec::SanitizerSet; @@ -64,6 +65,25 @@ impl PrintAttribute for ThinVec { p.word("]"); } } +impl PrintAttribute for FxIndexMap { + fn should_render(&self) -> bool { + self.is_empty() || self[0].should_render() + } + + fn print_attribute(&self, p: &mut Printer) { + let mut last_printed = false; + p.word("["); + for (i, _) in self { + if last_printed { + p.word_space(","); + } + i.print_attribute(p); + last_printed = i.should_render(); + } + p.word("]"); + } +} + macro_rules! print_skip { ($($t: ty),* $(,)?) => {$( impl PrintAttribute for $t { diff --git a/compiler/rustc_hir/src/lints.rs b/compiler/rustc_hir/src/lints.rs index eba2d182d2c48..abffb9437a519 100644 --- a/compiler/rustc_hir/src/lints.rs +++ b/compiler/rustc_hir/src/lints.rs @@ -31,3 +31,45 @@ pub struct AttributeLint { pub span: Span, pub kind: AttributeLintKind, } + +#[derive(Debug, HashStable_Generic)] +pub enum AttributeLintKind { + /// Copy of `IllFormedAttributeInput` + /// specifically for the `invalid_macro_export_arguments` lint until that is removed, + /// see + InvalidMacroExportArguments { + suggestions: Vec, + }, + UnusedDuplicate { + this: Span, + other: Span, + warning: bool, + }, + IllFormedAttributeInput { + suggestions: Vec, + }, + EmptyAttribute { + first_span: Span, + attr_path: AttrPath, + valid_without_list: bool, + }, + InvalidTarget { + name: AttrPath, + target: Target, + applied: Vec, + only: &'static str, + }, + InvalidStyle { + name: AttrPath, + is_used_as_inner: bool, + target: Target, + target_span: Span, + }, + UnsafeAttrOutsideUnsafe { + attribute_name_span: Span, + sugg_spans: (Span, Span), + }, + DuplicateDocAlias { + first_definition: Span, + }, +} diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index a8457134031c9..e4e892ab10f43 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -3,7 +3,7 @@ use std::cell::{Cell, RefCell}; use std::cmp::max; use std::ops::Deref; -use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::sso::SsoHashSet; use rustc_errors::Applicability; @@ -2535,7 +2535,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id); let attrs = self.fcx.tcx.hir_attrs(hir_id); - if is_doc_alias_attrs_contain_symbol(attrs.into_iter(), method.name) { + if let Some(d) = find_attr!(attrs, AttributeKind::Doc(d) => d) + && d.aliases.contains_key(&method.name) + { return true; } diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 70e91c081776a..93cd9a9702f7c 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -106,18 +106,9 @@ passes_diagnostic_diagnostic_on_unimplemented_only_for_traits = passes_diagnostic_item_first_defined = the diagnostic item is first defined here -passes_doc_alias_bad_char = - {$char_} character isn't allowed in {$attr_str} - passes_doc_alias_bad_location = {$attr_str} isn't allowed on {$location} -passes_doc_alias_duplicated = doc alias is duplicated - .label = first defined here - -passes_doc_alias_empty = - {$attr_str} attribute cannot have empty value - passes_doc_alias_malformed = doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` @@ -127,17 +118,6 @@ passes_doc_alias_not_an_alias = passes_doc_alias_not_string_literal = `#[doc(alias("a"))]` expects string literals -passes_doc_alias_start_end = - {$attr_str} cannot start or end with ' ' - -passes_doc_attr_expects_no_value = - `doc({$attr_name})` does not accept a value - .suggestion = use `doc({$attr_name})` - -passes_doc_attr_expects_string = - `doc({$attr_name})` expects a string value - .suggestion = use `doc({$attr_name} = "...")` - passes_doc_attr_not_crate_level = `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute @@ -163,15 +143,7 @@ passes_doc_expect_str = passes_doc_fake_variadic_not_valid = `#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity -passes_doc_inline_conflict = - conflicting doc inlining attributes - .help = remove one of the conflicting attributes -passes_doc_inline_conflict_first = - this attribute... - -passes_doc_inline_conflict_second = - {"."}..conflicts with this attribute passes_doc_inline_only_use = this attribute can only be applied to a `use` item @@ -188,10 +160,6 @@ passes_doc_keyword_attribute_empty_mod = passes_doc_keyword_attribute_not_mod = `#[doc({$attr_name} = "...")]` should be used on modules -passes_doc_keyword_not_keyword = - nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` - .help = only existing keywords are allowed in core/std - passes_doc_keyword_only_impl = `#[doc(keyword = "...")]` should be used on impl blocks diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 1289867987158..1a48e4360d86d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -141,8 +141,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { target: Target, item: Option>, ) { - let mut doc_aliases = FxHashMap::default(); - let mut specified_inline = None; let mut seen = FxHashMap::default(); let attrs = self.tcx.hir_attrs(hir_id); for attr in attrs { @@ -306,15 +304,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_diagnostic_on_const(attr.span(), hir_id, target, item) } [sym::thread_local, ..] => self.check_thread_local(attr, span, target), - [sym::doc, ..] => self.check_doc_attrs( - attr, - attr.span(), - attr_item.style, - hir_id, - target, - &mut specified_inline, - &mut doc_aliases, - ), [sym::no_link, ..] => self.check_no_link(hir_id, attr, span, target), [sym::rustc_no_implicit_autorefs, ..] => { self.check_applied_to_fn_or_method(hir_id, attr.span(), span, target) @@ -786,38 +775,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.dcx().emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name }); } - fn check_doc_alias_value( - &self, - meta: &MetaItemInner, - doc_alias: Symbol, - hir_id: HirId, - target: Target, - is_list: bool, - aliases: &mut FxHashMap, - ) { + fn check_doc_alias_value(&self, span: Span, alias: Symbol, hir_id: HirId, target: Target) { let tcx = self.tcx; - let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span()); - let attr_str = - &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" }); - if doc_alias == sym::empty { - tcx.dcx().emit_err(errors::DocAliasEmpty { span, attr_str }); - return; - } - let doc_alias_str = doc_alias.as_str(); - if let Some(c) = doc_alias_str - .chars() - .find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) - { - tcx.dcx().emit_err(errors::DocAliasBadChar { span, attr_str, char_: c }); - return; - } - if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') { - tcx.dcx().emit_err(errors::DocAliasStartEnd { span, attr_str }); - return; - } - - let span = meta.span(); if let Some(location) = match target { Target::AssocTy => { if let DefKind::Impl { .. } = @@ -874,20 +834,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | Target::MacroCall | Target::Delegation { .. } => None, } { - tcx.dcx().emit_err(errors::DocAliasBadLocation { span, attr_str, location }); - return; - } - if self.tcx.hir_opt_name(hir_id) == Some(doc_alias) { - tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str }); - return; - } - if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) { - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - span, - errors::DocAliasDuplicated { first_defn: *entry.entry.get() }, - ); + // FIXME: emit proper error + // tcx.dcx().emit_err(errors::DocAliasBadLocation { + // span, + // errors::DocAliasDuplicated { first_defn: *entry.entry.get() }, + // ); } } @@ -927,7 +878,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { fn check_doc_keyword_and_attribute( &self, - meta: &MetaItemInner, + span: Span, hir_id: HirId, attr_kind: DocFakeItemKind, ) { @@ -938,16 +889,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy } - // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`. - fn is_builtin_attr(s: Symbol) -> bool { - rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s) - } - - let value = match meta.value_str() { - Some(value) if value != sym::empty => value, - _ => return self.doc_attr_str_error(meta, attr_kind.name()), - }; - let item_kind = match self.tcx.hir_node(hir_id) { hir::Node::Item(item) => Some(&item.kind), _ => None, @@ -955,21 +896,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { match item_kind { Some(ItemKind::Mod(_, module)) => { if !module.item_ids.is_empty() { - self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod { - span: meta.span(), - attr_name: attr_kind.name(), - }); + self.dcx() + .emit_err(errors::DocKeywordEmptyMod { span, attr_name: attr_kind.name() }); return; } } _ => { - self.dcx().emit_err(errors::DocKeywordAttributeNotMod { - span: meta.span(), - attr_name: attr_kind.name(), - }); + self.dcx().emit_err(errors::DocKeywordNotMod { span, attr_name: attr_kind.name() }); return; } } + match attr_kind { DocFakeItemKind::Keyword => { if !is_doc_keyword(value) { @@ -990,7 +927,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_doc_fake_variadic(&self, meta: &MetaItemInner, hir_id: HirId) { + fn check_doc_fake_variadic(&self, span: Span, hir_id: HirId) { let item_kind = match self.tcx.hir_node(hir_id) { hir::Node::Item(item) => Some(&item.kind), _ => None, @@ -1008,18 +945,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> { false }; if !is_valid { - self.dcx().emit_err(errors::DocFakeVariadicNotValid { span: meta.span() }); + self.dcx().emit_err(errors::DocFakeVariadicNotValid { span }); } } _ => { - self.dcx().emit_err(errors::DocKeywordOnlyImpl { span: meta.span() }); + self.dcx().emit_err(errors::DocKeywordOnlyImpl { span }); } } } - fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) { + fn check_doc_search_unbox(&self, span: Span, hir_id: HirId) { let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else { - self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() }); + self.dcx().emit_err(errors::DocSearchUnboxInvalid { span }); return; }; match item.kind { @@ -1032,7 +969,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }) => {} ItemKind::TyAlias(_, generics, _) if generics.params.len() != 0 => {} _ => { - self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() }); + self.dcx().emit_err(errors::DocSearchUnboxInvalid { span }); } } } @@ -1046,60 +983,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// already seen an inlining attribute for this item. /// If so, `specified_inline` holds the value and the span of /// the first `inline`/`no_inline` attribute. - fn check_doc_inline( - &self, - style: AttrStyle, - meta: &MetaItemInner, - hir_id: HirId, - target: Target, - specified_inline: &mut Option<(bool, Span)>, - ) { + fn check_doc_inline(&self, span: Span, hir_id: HirId, target: Target) { match target { - Target::Use | Target::ExternCrate => { - let do_inline = meta.has_name(sym::inline); - if let Some((prev_inline, prev_span)) = *specified_inline { - if do_inline != prev_inline { - let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]); - spans.push_span_label(prev_span, fluent::passes_doc_inline_conflict_first); - spans.push_span_label( - meta.span(), - fluent::passes_doc_inline_conflict_second, - ); - self.dcx().emit_err(errors::DocKeywordConflict { spans }); - } - } else { - *specified_inline = Some((do_inline, meta.span())); - } - } + Target::Use | Target::ExternCrate => {} _ => { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, - meta.span(), + span, errors::DocInlineOnlyUse { - attr_span: meta.span(), - item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)), + attr_span: span, + item_span: self.tcx.hir_span(hir_id), }, ); } } } - fn check_doc_masked( - &self, - style: AttrStyle, - meta: &MetaItemInner, - hir_id: HirId, - target: Target, - ) { + fn check_doc_masked(&self, span: Span, hir_id: HirId, target: Target) { if target != Target::ExternCrate { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, - meta.span(), + span, errors::DocMaskedOnlyExternCrate { - attr_span: meta.span(), - item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)), + attr_span: span, + item_span: self.tcx.hir_span(hir_id), }, ); return; @@ -1109,125 +1018,38 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, - meta.span(), + span, errors::DocMaskedNotExternCrateSelf { - attr_span: meta.span(), - item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)), + attr_span: span, + item_span: self.tcx.hir_span(hir_id), }, ); } } /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid. - fn check_attr_not_crate_level( - &self, - meta: &MetaItemInner, - hir_id: HirId, - attr_name: &str, - ) -> bool { + fn check_attr_not_crate_level(&self, span: Span, hir_id: HirId, attr_name: &str) -> bool { if CRATE_HIR_ID == hir_id { - self.dcx().emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name }); + self.dcx().emit_err(errors::DocAttrNotCrateLevel { span, attr_name }); return false; } true } /// Checks that an attribute is used at the crate level. Returns `true` if valid. - fn check_attr_crate_level( - &self, - attr_span: Span, - style: AttrStyle, - meta: &MetaItemInner, - hir_id: HirId, - ) -> bool { + fn check_attr_crate_level(&self, span: Span, hir_id: HirId) -> bool { if hir_id != CRATE_HIR_ID { - // insert a bang between `#` and `[...` - let bang_span = attr_span.lo() + BytePos(1); - let sugg = (style == AttrStyle::Outer - && self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID) - .then_some(errors::AttrCrateLevelOnlySugg { - attr: attr_span.with_lo(bang_span).with_hi(bang_span), - }); self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, hir_id, - meta.span(), - errors::AttrCrateLevelOnly { sugg }, + span, + errors::AttrCrateLevelOnly {}, ); return false; } true } - fn check_doc_attr_string_value(&self, meta: &MetaItemInner, hir_id: HirId) { - if meta.value_str().is_none() { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - meta.span(), - errors::DocAttrExpectsString { attr_name: meta.name().unwrap() }, - ); - } - } - - fn check_doc_attr_no_value(&self, meta: &MetaItemInner, hir_id: HirId) { - if !meta.is_word() { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - meta.span(), - errors::DocAttrExpectsNoValue { attr_name: meta.name().unwrap() }, - ); - } - } - - /// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place. - fn check_test_attr( - &self, - attr_span: Span, - style: AttrStyle, - meta: &MetaItemInner, - hir_id: HirId, - ) { - if let Some(metas) = meta.meta_item_list() { - for i_meta in metas { - match (i_meta.name(), i_meta.meta_item()) { - (Some(sym::attr), _) => { - // Allowed everywhere like `#[doc]` - } - (Some(sym::no_crate_inject), _) => { - self.check_attr_crate_level(attr_span, style, meta, hir_id); - } - (_, Some(m)) => { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span(), - errors::DocTestUnknown { - path: rustc_ast_pretty::pprust::path_to_string(&m.path), - }, - ); - } - (_, None) => { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span(), - errors::DocTestLiteral, - ); - } - } - } - } else { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - meta.span(), - errors::DocTestTakesList, - ); - } - } - /// Check that the `#![doc(auto_cfg)]` attribute has the expected input. fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) { match &meta.kind { @@ -1290,172 +1112,117 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// of one item. Read the documentation of [`check_doc_inline`] for more information. /// /// [`check_doc_inline`]: Self::check_doc_inline - fn check_doc_attrs( - &self, - attr: &Attribute, - attr_span: Span, - style: AttrStyle, - hir_id: HirId, - target: Target, - specified_inline: &mut Option<(bool, Span)>, - aliases: &mut FxHashMap, - ) { - if let Some(list) = attr.meta_item_list() { - for meta in &list { - if let Some(i_meta) = meta.meta_item() { - match i_meta.name() { - Some(sym::alias) => { - if self.check_attr_not_crate_level(meta, hir_id, "alias") { - self.check_doc_alias(meta, hir_id, target, aliases); - } - } - - Some(sym::keyword) => { - if self.check_attr_not_crate_level(meta, hir_id, "keyword") { - self.check_doc_keyword_and_attribute( - meta, - hir_id, - DocFakeItemKind::Keyword, - ); - } - } - - Some(sym::attribute) => { - if self.check_attr_not_crate_level(meta, hir_id, "attribute") { - self.check_doc_keyword_and_attribute( - meta, - hir_id, - DocFakeItemKind::Attribute, - ); - } - } + fn check_doc_attrs(&self, attr: &DocAttribute, hir_id: HirId, target: Target) { + let DocAttribute { + aliases, + // valid pretty much anywhere, not checked here? + // FIXME: should we? + hidden: _, + inline, + // FIXME: currently unchecked + cfg: _, + cfg_hide, + fake_variadic, + keyword, + masked, + // FIXME: currently unchecked + notable_trait: _, + search_unbox, + html_favicon_url, + html_logo_url, + html_playground_url, + html_root_url, + html_no_source, + issue_tracker_base_url, + rust_logo, + // allowed anywhere + test_attrs: _, + no_crate_inject, + } = attr; + + 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); + } + } - Some(sym::fake_variadic) => { - if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") { - self.check_doc_fake_variadic(meta, hir_id); - } - } + if let Some((_, span)) = keyword { + if self.check_attr_not_crate_level(*span, hir_id, "keyword") { + self.check_doc_keyword(*span, hir_id); + } + } - Some(sym::search_unbox) => { - if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") { - self.check_doc_search_unbox(meta, hir_id); - } - } + // FIXME: check doc attribute + // self.check_doc_keyword(meta, hir_id); + // self.check_doc_keyword_and_attribute( + // meta, + // hir_id, + // DocFakeItemKind::Keyword, + // ); + // } + // } + // Some(sym::attribute) => { + // if self.check_attr_not_crate_level(meta, hir_id, "attribute") { + // self.check_doc_keyword_and_attribute( + // meta, + // hir_id, + // DocFakeItemKind::Attribute, + // ); + + if let Some(span) = fake_variadic { + if self.check_attr_not_crate_level(*span, hir_id, "fake_variadic") { + self.check_doc_fake_variadic(*span, hir_id); + } + } - Some(sym::test) => { - self.check_test_attr(attr_span, style, meta, hir_id); - } + if let Some(span) = search_unbox { + if self.check_attr_not_crate_level(*span, hir_id, "search_unbox") { + self.check_doc_search_unbox(*span, hir_id); + } + } - Some( - sym::html_favicon_url - | sym::html_logo_url - | sym::html_playground_url - | sym::issue_tracker_base_url - | sym::html_root_url, - ) => { - self.check_attr_crate_level(attr_span, style, meta, hir_id); - self.check_doc_attr_string_value(meta, hir_id); - } + for i in [ + html_favicon_url, + html_logo_url, + html_playground_url, + issue_tracker_base_url, + html_root_url, + ] { + if let Some((_, span)) = i { + self.check_attr_crate_level(*span, hir_id); + } + } - Some(sym::html_no_source) => { - self.check_attr_crate_level(attr_span, style, meta, hir_id); - self.check_doc_attr_no_value(meta, hir_id); - } + for i in [html_no_source, no_crate_inject] { + if let Some(span) = i { + self.check_attr_crate_level(*span, hir_id); + } + } - Some(sym::auto_cfg) => { - self.check_doc_auto_cfg(i_meta, hir_id); - } + if let Some((_, span)) = inline { + self.check_doc_inline(*span, hir_id, target) + } - Some(sym::inline | sym::no_inline) => { - self.check_doc_inline(style, meta, hir_id, target, specified_inline) - } + if let Some(span) = rust_logo { + if self.check_attr_crate_level(*span, hir_id) + && !self.tcx.features().rustdoc_internals() + { + feature_err( + &self.tcx.sess, + sym::rustdoc_internals, + *span, + fluent::passes_doc_rust_logo, + ) + .emit(); + } + } - Some(sym::masked) => self.check_doc_masked(style, meta, hir_id, target), - - Some(sym::cfg | sym::hidden | sym::notable_trait) => {} - - Some(sym::rust_logo) => { - if self.check_attr_crate_level(attr_span, style, meta, hir_id) - && !self.tcx.features().rustdoc_internals() - { - feature_err( - &self.tcx.sess, - sym::rustdoc_internals, - meta.span(), - fluent::passes_doc_rust_logo, - ) - .emit(); - } - } + if let Some(span) = masked { + self.check_doc_masked(*span, hir_id, target); + } - _ => { - let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path); - if i_meta.has_name(sym::spotlight) { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span, - errors::DocTestUnknownSpotlight { path, span: i_meta.span }, - ); - } else if i_meta.has_name(sym::include) - && let Some(value) = i_meta.value_str() - { - let applicability = if list.len() == 1 { - Applicability::MachineApplicable - } else { - Applicability::MaybeIncorrect - }; - // If there are multiple attributes, the suggestion would suggest - // deleting all of them, which is incorrect. - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span, - errors::DocTestUnknownInclude { - path, - value: value.to_string(), - inner: match style { - AttrStyle::Inner => "!", - AttrStyle::Outer => "", - }, - sugg: (attr.span(), applicability), - }, - ); - } else if i_meta.has_name(sym::passes) - || i_meta.has_name(sym::no_default_passes) - { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span, - errors::DocTestUnknownPasses { path, span: i_meta.span }, - ); - } else if i_meta.has_name(sym::plugins) { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span, - errors::DocTestUnknownPlugins { path, span: i_meta.span }, - ); - } else { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - i_meta.span, - errors::DocTestUnknownAny { path }, - ); - } - } - } - } else { - self.tcx.emit_node_span_lint( - INVALID_DOC_ATTRIBUTES, - hir_id, - meta.span(), - errors::DocInvalid, - ); - } - } + if let Some(span) = cfg_hide { + self.check_attr_crate_level(*span, hir_id); } } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 6aa0f5212af70..926aba5082dee 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -30,12 +30,6 @@ pub(crate) struct DocAttrExpectsString { pub(crate) attr_name: Symbol, } -#[derive(LintDiagnostic)] -#[diag(passes_doc_attr_expects_no_value)] -pub(crate) struct DocAttrExpectsNoValue { - pub(crate) attr_name: Symbol, -} - #[derive(Diagnostic)] #[diag(passes_autodiff_attr)] pub(crate) struct AutoDiffAttr { @@ -143,31 +137,6 @@ pub(crate) struct DocExpectStr<'a> { pub attr_name: &'a str, } -#[derive(Diagnostic)] -#[diag(passes_doc_alias_empty)] -pub(crate) struct DocAliasEmpty<'a> { - #[primary_span] - pub span: Span, - pub attr_str: &'a str, -} - -#[derive(Diagnostic)] -#[diag(passes_doc_alias_bad_char)] -pub(crate) struct DocAliasBadChar<'a> { - #[primary_span] - pub span: Span, - pub attr_str: &'a str, - pub char_: char, -} - -#[derive(Diagnostic)] -#[diag(passes_doc_alias_start_end)] -pub(crate) struct DocAliasStartEnd<'a> { - #[primary_span] - pub span: Span, - pub attr_str: &'a str, -} - #[derive(Diagnostic)] #[diag(passes_doc_alias_bad_location)] pub(crate) struct DocAliasBadLocation<'a> { @@ -185,13 +154,6 @@ pub(crate) struct DocAliasNotAnAlias<'a> { pub attr_str: &'a str, } -#[derive(LintDiagnostic)] -#[diag(passes_doc_alias_duplicated)] -pub(crate) struct DocAliasDuplicated { - #[label] - pub first_defn: Span, -} - #[derive(Diagnostic)] #[diag(passes_doc_alias_not_string_literal)] pub(crate) struct DocAliasNotStringLiteral { @@ -261,14 +223,6 @@ pub(crate) struct DocSearchUnboxInvalid { pub span: Span, } -#[derive(Diagnostic)] -#[diag(passes_doc_inline_conflict)] -#[help] -pub(crate) struct DocKeywordConflict { - #[primary_span] - pub spans: MultiSpan, -} - #[derive(LintDiagnostic)] #[diag(passes_doc_inline_only_use)] #[note] @@ -276,7 +230,7 @@ pub(crate) struct DocInlineOnlyUse { #[label] pub attr_span: Span, #[label(passes_not_a_use_item_label)] - pub item_span: Option, + pub item_span: Span, } #[derive(LintDiagnostic)] @@ -286,7 +240,7 @@ pub(crate) struct DocMaskedOnlyExternCrate { #[label] pub attr_span: Span, #[label(passes_not_an_extern_crate_label)] - pub item_span: Option, + pub item_span: Span, } #[derive(LintDiagnostic)] @@ -295,7 +249,7 @@ pub(crate) struct DocMaskedNotExternCrateSelf { #[label] pub attr_span: Span, #[label(passes_extern_crate_self_label)] - pub item_span: Option, + pub item_span: Span, } #[derive(Diagnostic)] @@ -1353,17 +1307,7 @@ pub(crate) struct IneffectiveUnstableImpl; #[derive(LintDiagnostic)] #[diag(passes_attr_crate_level)] #[note] -pub(crate) struct AttrCrateLevelOnly { - #[subdiagnostic] - pub sugg: Option, -} - -#[derive(Subdiagnostic)] -#[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")] -pub(crate) struct AttrCrateLevelOnlySugg { - #[primary_span] - pub attr: Span, -} +pub(crate) struct AttrCrateLevelOnly {} /// "sanitize attribute not allowed here" #[derive(Diagnostic)] diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 71baef458bbfd..a18913c1c1c70 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -10,7 +10,7 @@ use rustc_ast::{ Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind, }; use rustc_ast_pretty::pprust::where_bound_predicate_to_string; -use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ @@ -903,7 +903,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { // confused by them. continue; } - if is_doc_alias_attrs_contain_symbol(r.tcx.get_attrs(did, sym::doc), item_name) { + if let Some(d) = find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d) + && d.aliases.contains_key(&item_name) + { return Some(did); } } From 3f08e4dcd9ab5820c742a0b7f0a3f8fb32d1bd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 22 Jun 2025 17:29:23 +0200 Subject: [PATCH 02/32] start using parsed doc attributes everywhere --- compiler/rustc_hir/src/hir.rs | 4 ---- compiler/rustc_lint/src/builtin.rs | 20 +++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 6444c862d5200..e8a91dae0749f 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1376,7 +1376,6 @@ impl AttributeExt for Attribute { fn doc_str(&self) -> Option { match &self { Attribute::Parsed(AttributeKind::DocComment { comment, .. }) => Some(*comment), - Attribute::Unparsed(_) if self.has_name(sym::doc) => self.value_str(), _ => None, } } @@ -1391,9 +1390,6 @@ impl AttributeExt for Attribute { Attribute::Parsed(AttributeKind::DocComment { kind, comment, .. }) => { Some((*comment, *kind)) } - Attribute::Unparsed(_) if self.has_name(sym::doc) => { - self.value_str().map(|s| (s, CommentKind::Line)) - } _ => None, } } diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index dd0aa848ed2c9..0805ef47079bb 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -25,7 +25,7 @@ use rustc_attr_parsing::AttributeParser; use rustc_errors::{Applicability, LintDiagnostic}; use rustc_feature::GateIssue; use rustc_hir as hir; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, DocAttribute}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId}; use rustc_hir::intravisit::FnKind as HirFnKind; @@ -396,26 +396,16 @@ pub struct MissingDoc; impl_lint_pass!(MissingDoc => [MISSING_DOCS]); fn has_doc(attr: &hir::Attribute) -> bool { - if attr.is_doc_comment().is_some() { + if matches!(attr, hir::Attribute::Parsed(AttributeKind::DocComment { .. })) { return true; } - if !attr.has_name(sym::doc) { - return false; - } - - if attr.value_str().is_some() { + if let hir::Attribute::Parsed(AttributeKind::Doc(d)) = attr + && matches!(d.as_ref(), DocAttribute { hidden: Some(..), .. }) + { return true; } - if let Some(list) = attr.meta_item_list() { - for meta in list { - if meta.has_name(sym::hidden) { - return true; - } - } - } - false } From acb32df7b11d55d11c155aa22d03eeb33a581961 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 23 Oct 2025 16:59:19 +0200 Subject: [PATCH 03/32] Continue migration to new Attribute API for `doc` attribute --- Cargo.lock | 2 - compiler/rustc_ast/src/attr/mod.rs | 8 + compiler/rustc_attr_parsing/messages.ftl | 55 +++ .../rustc_attr_parsing/src/attributes/cfg.rs | 2 +- .../rustc_attr_parsing/src/attributes/doc.rs | 441 ++++++++++++------ .../rustc_attr_parsing/src/attributes/mod.rs | 2 +- .../rustc_attr_parsing/src/attributes/util.rs | 7 +- compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/interface.rs | 11 - compiler/rustc_attr_parsing/src/lib.rs | 3 +- .../src/session_diagnostics.rs | 114 ++++- .../rustc_hir/src/attrs/data_structures.rs | 33 +- compiler/rustc_hir/src/hir.rs | 4 + compiler/rustc_hir/src/lints.rs | 28 +- compiler/rustc_hir_typeck/Cargo.toml | 1 - compiler/rustc_hir_typeck/src/lib.rs | 2 - compiler/rustc_hir_typeck/src/method/probe.rs | 4 +- compiler/rustc_lint/src/builtin.rs | 2 +- compiler/rustc_lint/src/levels.rs | 6 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 17 +- compiler/rustc_middle/src/ty/util.rs | 12 +- compiler/rustc_passes/Cargo.toml | 1 - compiler/rustc_passes/messages.ftl | 63 +-- compiler/rustc_passes/src/check_attr.rs | 247 ++-------- compiler/rustc_passes/src/errors.rs | 135 +----- .../rustc_resolve/src/late/diagnostics.rs | 5 +- 26 files changed, 598 insertions(+), 609 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03a4d71f67c3a..bb81cada72eaf 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 901b645b8c4ef..d54d900128bd3 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 4482266c6c2e0..4d7716560fb23 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 490e28ed64c5d..6ffe25098308a 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 a5986170c192a..65651e617179f 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); }, )]; - - fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option { - todo!() + 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 { + 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 584f62ca51a37..64bcb02b0b745 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 ce555be6a47d4..105f7164bf3b3 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 af73bf7f9fd23..5d15588033fdf 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 b26a4a29cd2e2..363e1fcac5078 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 7cef70f88e1ca..37a3189f892e8 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 f122b5a875492..26b615448e3ba 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 538c866567a9c..39008914f9efe 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 e8a91dae0749f..e6a0f430b63aa 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 abffb9437a519..2d13ceabf8ca1 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 f00125c3e090a..246134665174a 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 734d8963d6fda..d3ef1d63e8ba9 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 e4e892ab10f43..1a25f6a582f22 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 0805ef47079bb..ae973a3c49c25 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 ac47897b56884..0f6452a2bc995 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 86719581a209c..9a1cf7f349fdf 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 782ea3906ef13..fc03ad52b4bf1 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 ba81ef3103bd9..10da57f56ecfa 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 93cd9a9702f7c..007ce22c01e65 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 1a48e4360d86d..bae15de4bf886 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() }, - // ); - } - } - - 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; - } + self.tcx.dcx().emit_err(errors::DocAliasBadLocation { span, location }); + 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 926aba5082dee..b693aaf769236 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 a18913c1c1c70..069944b638c71 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); From 131323f91002ac5277c3ae05046757adb5c48965 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 25 Nov 2025 23:54:10 +0100 Subject: [PATCH 04/32] Migrate `doc(cfg)` to new `Attribute` API --- Cargo.lock | 1 + src/librustdoc/Cargo.toml | 1 + src/librustdoc/clean/cfg.rs | 492 +++++++++++-------- src/librustdoc/clean/inline.rs | 2 +- src/librustdoc/clean/mod.rs | 141 ++---- src/librustdoc/clean/types.rs | 79 ++- src/librustdoc/clean/utils.rs | 27 +- src/librustdoc/doctest.rs | 44 +- src/librustdoc/doctest/rust.rs | 61 ++- src/librustdoc/html/render/context.rs | 50 +- src/librustdoc/html/render/search_index.rs | 4 +- src/librustdoc/html/sources.rs | 6 +- src/librustdoc/lib.rs | 2 - src/librustdoc/passes/check_doc_cfg.rs | 100 ++-- src/librustdoc/passes/collect_trait_impls.rs | 22 +- src/librustdoc/passes/propagate_doc_cfg.rs | 62 +-- 16 files changed, 550 insertions(+), 544 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb81cada72eaf..23708adcc4999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4870,6 +4870,7 @@ dependencies = [ "minifier", "pulldown-cmark-escape", "regex", + "rustc_proc_macro", "rustdoc-json-types", "serde", "serde_json", diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 0bc38220614f1..371da896b9fca 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -18,6 +18,7 @@ minifier = { version = "0.3.5", default-features = false } pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } +rustc_proc_macro = { path = "../../compiler/rustc_proc_macro" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 6c77e41965dc5..38d3a47f8ca06 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -9,11 +9,13 @@ use std::{fmt, mem, ops}; use itertools::Either; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::attrs::AttributeKind; +use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_hir::Attribute; +use rustc_hir::attrs::{AttributeKind, CfgEntry}; use rustc_middle::ty::TyCtxt; use rustc_session::parse::ParseSess; -use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; +use rustc_span::{DUMMY_SP, Span}; use {rustc_ast as ast, rustc_hir as hir}; use crate::display::{Joined as _, MaybeDisplay, Wrapped}; @@ -23,20 +25,7 @@ use crate::html::escape::Escape; mod tests; #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum Cfg { - /// Accepts all configurations. - True, - /// Denies all configurations. - False, - /// A generic configuration option, e.g., `test` or `target_os = "linux"`. - Cfg(Symbol, Option), - /// Negates a configuration requirement, i.e., `not(x)`. - Not(Box), - /// Union of a list of configuration requirements, i.e., `any(...)`. - Any(Vec), - /// Intersection of a list of configuration requirements, i.e., `all(...)`. - All(Vec), -} +pub(crate) struct Cfg(CfgEntry); #[derive(PartialEq, Debug)] pub(crate) struct InvalidCfgError { @@ -44,27 +33,95 @@ pub(crate) struct InvalidCfgError { pub(crate) span: Span, } +/// Whether the configuration consists of just `Cfg` or `Not`. +fn is_simple_cfg(cfg: &CfgEntry) -> bool { + match cfg { + CfgEntry::Bool(..) + | CfgEntry::NameValue { .. } + | CfgEntry::Not(..) + | CfgEntry::Version(..) => true, + CfgEntry::All(..) | CfgEntry::Any(..) => false, + } +} + +/// Whether the configuration consists of just `Cfg`, `Not` or `All`. +fn is_all_cfg(cfg: &CfgEntry) -> bool { + match cfg { + CfgEntry::Bool(..) + | CfgEntry::NameValue { .. } + | CfgEntry::Not(..) + | CfgEntry::Version(..) + | CfgEntry::All(..) => true, + CfgEntry::Any(..) => false, + } +} + +fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet) -> Option { + match cfg { + CfgEntry::Bool(..) => Some(cfg.clone()), + CfgEntry::NameValue { .. } => { + if !hidden.contains(&SimpleCfg::from(cfg)) { + Some(cfg.clone()) + } else { + None + } + } + CfgEntry::Not(cfg, _) => { + if let Some(cfg) = strip_hidden(cfg, hidden) { + Some(CfgEntry::Not(Box::new(cfg), DUMMY_SP)) + } else { + None + } + } + CfgEntry::Any(cfgs, _) => { + let cfgs = + cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::>(); + if cfgs.is_empty() { None } else { Some(CfgEntry::Any(cfgs, DUMMY_SP)) } + } + CfgEntry::All(cfgs, _) => { + let cfgs = + cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::>(); + if cfgs.is_empty() { None } else { Some(CfgEntry::All(cfgs, DUMMY_SP)) } + } + CfgEntry::Version(..) => { + // FIXME: Should be handled. + Some(cfg.clone()) + } + } +} + +fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool { + match cfg { + CfgEntry::Bool(..) | CfgEntry::Not(..) | CfgEntry::Version(..) => true, + CfgEntry::Any(sub_cfgs, _) | CfgEntry::All(sub_cfgs, _) => { + sub_cfgs.first().map(should_capitalize_first_letter).unwrap_or(false) + } + CfgEntry::NameValue { name, .. } => { + *name == sym::debug_assertions || *name == sym::target_endian + } + } +} + impl Cfg { /// Parses a `MetaItemInner` into a `Cfg`. fn parse_nested( nested_cfg: &MetaItemInner, - exclude: &FxHashSet, + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { match nested_cfg { MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude), - MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b { - true => Ok(Some(Cfg::True)), - false => Ok(Some(Cfg::False)), - }, + MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => { + Ok(Some(Cfg(CfgEntry::Bool(*b, DUMMY_SP)))) + } MetaItemInner::Lit(lit) => { Err(InvalidCfgError { msg: "unexpected literal", span: lit.span }) } } } - pub(crate) fn parse_without( + fn parse_without( cfg: &MetaItem, - exclude: &FxHashSet, + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { let name = match cfg.ident() { Some(ident) => ident.name, @@ -77,13 +134,29 @@ impl Cfg { }; match cfg.kind { MetaItemKind::Word => { - let cfg = Cfg::Cfg(name, None); - if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) } + if exclude.contains(&SimpleCfg::new(name)) { + Ok(None) + } else { + Ok(Some(Cfg(CfgEntry::NameValue { + name, + value: None, + name_span: DUMMY_SP, + span: DUMMY_SP, + }))) + } } MetaItemKind::NameValue(ref lit) => match lit.kind { LitKind::Str(value, _) => { - let cfg = Cfg::Cfg(name, Some(value)); - if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) } + if exclude.contains(&SimpleCfg::new_value(name, value)) { + Ok(None) + } else { + Ok(Some(Cfg(CfgEntry::NameValue { + name, + value: Some((value, DUMMY_SP)), + name_span: DUMMY_SP, + span: DUMMY_SP, + }))) + } } _ => Err(InvalidCfgError { // FIXME: if the main #[cfg] syntax decided to support non-string literals, @@ -97,8 +170,12 @@ impl Cfg { let mut sub_cfgs = items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose()); let ret = match name { - sym::all => sub_cfgs.try_fold(Cfg::True, |x, y| Ok(x & y?)), - sym::any => sub_cfgs.try_fold(Cfg::False, |x, y| Ok(x | y?)), + sym::all => { + sub_cfgs.try_fold(Cfg(CfgEntry::Bool(true, DUMMY_SP)), |x, y| Ok(x & y?)) + } + sym::any => { + sub_cfgs.try_fold(Cfg(CfgEntry::Bool(false, DUMMY_SP)), |x, y| Ok(x | y?)) + } sym::not => { if orig_len == 1 { let mut sub_cfgs = sub_cfgs.collect::>(); @@ -136,36 +213,32 @@ impl Cfg { /// /// Equivalent to `attr::cfg_matches`. pub(crate) fn matches(&self, psess: &ParseSess) -> bool { - match *self { - Cfg::False => false, - Cfg::True => true, - Cfg::Not(ref child) => !child.matches(psess), - Cfg::All(ref sub_cfgs) => sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(psess)), - Cfg::Any(ref sub_cfgs) => sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(psess)), - Cfg::Cfg(name, value) => psess.config.contains(&(name, value)), - } - } - - /// Whether the configuration consists of just `Cfg` or `Not`. - fn is_simple(&self) -> bool { - match self { - Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true, - Cfg::All(..) | Cfg::Any(..) => false, - } - } - - /// Whether the configuration consists of just `Cfg`, `Not` or `All`. - fn is_all(&self) -> bool { - match self { - Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true, - Cfg::Any(..) => false, + fn cfg_matches(cfg: &CfgEntry, psess: &ParseSess) -> bool { + match cfg { + CfgEntry::Bool(v, _) => *v, + CfgEntry::Not(child, _) => !cfg_matches(child, psess), + CfgEntry::All(sub_cfgs, _) => { + sub_cfgs.iter().all(|sub_cfg| cfg_matches(sub_cfg, psess)) + } + CfgEntry::Any(sub_cfgs, _) => { + sub_cfgs.iter().any(|sub_cfg| cfg_matches(sub_cfg, psess)) + } + CfgEntry::NameValue { name, value, .. } => { + psess.config.contains(&(*name, value.clone().map(|(s, _)| s))) + } + CfgEntry::Version(..) => { + // FIXME: should be handled. + false + } + } } + cfg_matches(&self.0, psess) } /// Renders the configuration for human display, as a short HTML description. pub(crate) fn render_short_html(&self) -> String { - let mut msg = Display(self, Format::ShortHtml).to_string(); - if self.should_capitalize_first_letter() + let mut msg = Display(&self.0, Format::ShortHtml).to_string(); + if should_capitalize_first_letter(&self.0) && let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) { msg[i..i + 1].make_ascii_uppercase(); @@ -183,9 +256,9 @@ impl Cfg { }; let mut msg = if matches!(format, Format::LongHtml) { - format!("Available{on}{}", Display(self, format)) + format!("Available{on}{}", Display(&self.0, format)) } else { - format!("Available{on}{}", Display(self, format)) + format!("Available{on}{}", Display(&self.0, format)) }; if self.should_append_only_to_description() { msg.push_str(" only"); @@ -205,27 +278,20 @@ impl Cfg { self.render_long_inner(Format::LongPlain) } - fn should_capitalize_first_letter(&self) -> bool { - match *self { - Cfg::False | Cfg::True | Cfg::Not(..) => true, - Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => { - sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false) - } - Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian, - } - } - fn should_append_only_to_description(&self) -> bool { - match self { - Cfg::False | Cfg::True => false, - Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true, - Cfg::Not(box Cfg::Cfg(..)) => true, - Cfg::Not(..) => false, + match self.0 { + CfgEntry::Bool(..) => false, + CfgEntry::Any(..) + | CfgEntry::All(..) + | CfgEntry::NameValue { .. } + | CfgEntry::Version(..) => true, + CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true, + CfgEntry::Not(..) => false, } } fn should_use_with_in_description(&self) -> bool { - matches!(self, Cfg::Cfg(sym::target_feature, _)) + matches!(self.0, CfgEntry::NameValue { name, .. } if name == sym::target_feature) } /// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will @@ -236,20 +302,20 @@ impl Cfg { pub(crate) fn simplify_with(&self, assume: &Self) -> Option { if self == assume { None - } else if let Cfg::All(a) = self { - let mut sub_cfgs: Vec = if let Cfg::All(b) = assume { + } else if let CfgEntry::All(a, _) = &self.0 { + let mut sub_cfgs: ThinVec = if let CfgEntry::All(b, _) = &assume.0 { a.iter().filter(|a| !b.contains(a)).cloned().collect() } else { - a.iter().filter(|&a| a != assume).cloned().collect() + a.iter().filter(|&a| *a != assume.0).cloned().collect() }; let len = sub_cfgs.len(); match len { 0 => None, - 1 => sub_cfgs.pop(), - _ => Some(Cfg::All(sub_cfgs)), + 1 => sub_cfgs.pop().map(Cfg), + _ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))), } - } else if let Cfg::All(b) = assume - && b.contains(self) + } else if let CfgEntry::All(b, _) = &assume.0 + && b.contains(&self.0) { None } else { @@ -258,81 +324,50 @@ impl Cfg { } fn omit_preposition(&self) -> bool { - matches!(self, Cfg::True | Cfg::False) - } - - pub(crate) fn strip_hidden(&self, hidden: &FxHashSet) -> Option { - match self { - Self::True | Self::False => Some(self.clone()), - Self::Cfg(..) => { - if !hidden.contains(self) { - Some(self.clone()) - } else { - None - } - } - Self::Not(cfg) => { - if let Some(cfg) = cfg.strip_hidden(hidden) { - Some(Self::Not(Box::new(cfg))) - } else { - None - } - } - Self::Any(cfgs) => { - let cfgs = - cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::>(); - if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) } - } - Self::All(cfgs) => { - let cfgs = - cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::>(); - if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) } - } - } + matches!(self.0, CfgEntry::Bool(..)) } } impl ops::Not for Cfg { type Output = Cfg; fn not(self) -> Cfg { - match self { - Cfg::False => Cfg::True, - Cfg::True => Cfg::False, - Cfg::Not(cfg) => *cfg, - s => Cfg::Not(Box::new(s)), - } + Cfg(match self.0 { + CfgEntry::Bool(v, s) => CfgEntry::Bool(!v, s), + CfgEntry::Not(cfg, _) => *cfg, + s => CfgEntry::Not(Box::new(s), DUMMY_SP), + }) } } impl ops::BitAndAssign for Cfg { fn bitand_assign(&mut self, other: Cfg) { - match (self, other) { - (Cfg::False, _) | (_, Cfg::True) => {} - (s, Cfg::False) => *s = Cfg::False, - (s @ Cfg::True, b) => *s = b, - (Cfg::All(a), Cfg::All(ref mut b)) => { + match (&mut self.0, other.0) { + (CfgEntry::Bool(false, _), _) | (_, CfgEntry::Bool(true, _)) => {} + (s, CfgEntry::Bool(false, _)) => *s = CfgEntry::Bool(false, DUMMY_SP), + (s @ CfgEntry::Bool(true, _), b) => *s = b, + (CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => { for c in b.drain(..) { if !a.contains(&c) { a.push(c); } } } - (Cfg::All(a), ref mut b) => { + (CfgEntry::All(a, _), ref mut b) => { if !a.contains(b) { - a.push(mem::replace(b, Cfg::True)); + a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } - (s, Cfg::All(mut a)) => { - let b = mem::replace(s, Cfg::True); + (s, CfgEntry::All(mut a, _)) => { + let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); if !a.contains(&b) { a.push(b); } - *s = Cfg::All(a); + *s = CfgEntry::All(a, DUMMY_SP); } (s, b) => { if *s != b { - let a = mem::replace(s, Cfg::True); - *s = Cfg::All(vec![a, b]); + let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); + *s = CfgEntry::All(thin_vec![a, b], DUMMY_SP); } } } @@ -349,32 +384,34 @@ impl ops::BitAnd for Cfg { impl ops::BitOrAssign for Cfg { fn bitor_assign(&mut self, other: Cfg) { - match (self, other) { - (Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {} - (s @ Cfg::False, b) => *s = b, - (Cfg::Any(a), Cfg::Any(ref mut b)) => { + match (&mut self.0, other.0) { + (CfgEntry::Bool(true, _), _) + | (_, CfgEntry::Bool(false, _)) + | (_, CfgEntry::Bool(true, _)) => {} + (s @ CfgEntry::Bool(false, _), b) => *s = b, + (CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => { for c in b.drain(..) { if !a.contains(&c) { a.push(c); } } } - (Cfg::Any(a), ref mut b) => { + (CfgEntry::Any(a, _), ref mut b) => { if !a.contains(b) { - a.push(mem::replace(b, Cfg::True)); + a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } - (s, Cfg::Any(mut a)) => { - let b = mem::replace(s, Cfg::True); + (s, CfgEntry::Any(mut a, _)) => { + let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); if !a.contains(&b) { a.push(b); } - *s = Cfg::Any(a); + *s = CfgEntry::Any(a, DUMMY_SP); } (s, b) => { if *s != b { - let a = mem::replace(s, Cfg::True); - *s = Cfg::Any(vec![a, b]); + let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); + *s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP); } } } @@ -417,7 +454,7 @@ impl Format { } /// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used. -struct Display<'a>(&'a Cfg, Format); +struct Display<'a>(&'a CfgEntry, Format); impl Display<'_> { fn code_wrappers(&self) -> Wrapped<&'static str> { @@ -427,17 +464,21 @@ impl Display<'_> { fn display_sub_cfgs( &self, fmt: &mut fmt::Formatter<'_>, - sub_cfgs: &[Cfg], + sub_cfgs: &[CfgEntry], separator: &str, ) -> fmt::Result { use fmt::Display as _; let short_longhand = self.1.is_long() && { - let all_crate_features = - sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_)))); - let all_target_features = sub_cfgs - .iter() - .all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_)))); + let all_crate_features = sub_cfgs.iter().all(|sub_cfg| { + matches!(sub_cfg, CfgEntry::NameValue { name: sym::feature, value: Some(_), .. }) + }); + let all_target_features = sub_cfgs.iter().all(|sub_cfg| { + matches!( + sub_cfg, + CfgEntry::NameValue { name: sym::target_feature, value: Some(_), .. } + ) + }); if all_crate_features { fmt.write_str("crate features ")?; @@ -454,14 +495,14 @@ impl Display<'_> { sub_cfgs .iter() .map(|sub_cfg| { - if let Cfg::Cfg(_, Some(feat)) = sub_cfg + if let CfgEntry::NameValue { value: Some((feat, _)), .. } = sub_cfg && short_longhand { Either::Left(self.code_wrappers().wrap(feat)) } else { Either::Right( Wrapped::with_parens() - .when(!sub_cfg.is_all()) + .when(!is_all_cfg(sub_cfg)) .wrap(Display(sub_cfg, self.1)), ) } @@ -476,39 +517,45 @@ impl Display<'_> { impl fmt::Display for Display<'_> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - Cfg::Not(box Cfg::Any(sub_cfgs)) => { - let separator = - if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " }; + match &self.0 { + CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => { + let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " }; fmt.write_str("neither ")?; sub_cfgs .iter() .map(|sub_cfg| { Wrapped::with_parens() - .when(!sub_cfg.is_all()) + .when(!is_all_cfg(sub_cfg)) .wrap(Display(sub_cfg, self.1)) }) .joined(separator, fmt) } - Cfg::Not(box simple @ Cfg::Cfg(..)) => write!(fmt, "non-{}", Display(simple, self.1)), - Cfg::Not(box c) => write!(fmt, "not ({})", Display(c, self.1)), + CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => { + write!(fmt, "non-{}", Display(simple, self.1)) + } + CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)), - Cfg::Any(sub_cfgs) => { - let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " }; - self.display_sub_cfgs(fmt, sub_cfgs, separator) + CfgEntry::Any(sub_cfgs, _) => { + let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " }; + self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), separator) } - Cfg::All(sub_cfgs) => self.display_sub_cfgs(fmt, sub_cfgs, " and "), + CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "), - Cfg::True => fmt.write_str("everywhere"), - Cfg::False => fmt.write_str("nowhere"), + CfgEntry::Bool(v, _) => { + if *v { + fmt.write_str("everywhere") + } else { + fmt.write_str("nowhere") + } + } - &Cfg::Cfg(name, value) => { - let human_readable = match (name, value) { + &CfgEntry::NameValue { name, value, .. } => { + let human_readable = match (*name, value) { (sym::unix, None) => "Unix", (sym::windows, None) => "Windows", (sym::debug_assertions, None) => "debug-assertions enabled", - (sym::target_os, Some(os)) => match os.as_str() { + (sym::target_os, Some((os, _))) => match os.as_str() { "android" => "Android", "cygwin" => "Cygwin", "dragonfly" => "DragonFly BSD", @@ -533,7 +580,7 @@ impl fmt::Display for Display<'_> { "visionos" => "visionOS", _ => "", }, - (sym::target_arch, Some(arch)) => match arch.as_str() { + (sym::target_arch, Some((arch, _))) => match arch.as_str() { "aarch64" => "AArch64", "arm" => "ARM", "loongarch32" => "LoongArch LA32", @@ -556,14 +603,14 @@ impl fmt::Display for Display<'_> { "x86_64" => "x86-64", _ => "", }, - (sym::target_vendor, Some(vendor)) => match vendor.as_str() { + (sym::target_vendor, Some((vendor, _))) => match vendor.as_str() { "apple" => "Apple", "pc" => "PC", "sun" => "Sun", "fortanix" => "Fortanix", _ => "", }, - (sym::target_env, Some(env)) => match env.as_str() { + (sym::target_env, Some((env, _))) => match env.as_str() { "gnu" => "GNU", "msvc" => "MSVC", "musl" => "musl", @@ -572,16 +619,20 @@ impl fmt::Display for Display<'_> { "sgx" => "SGX", _ => "", }, - (sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"), - (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"), - (sym::target_feature, Some(feat)) => match self.1 { + (sym::target_endian, Some((endian, _))) => { + return write!(fmt, "{endian}-endian"); + } + (sym::target_pointer_width, Some((bits, _))) => { + return write!(fmt, "{bits}-bit"); + } + (sym::target_feature, Some((feat, _))) => match self.1 { Format::LongHtml => { return write!(fmt, "target feature {feat}"); } Format::LongPlain => return write!(fmt, "target feature `{feat}`"), Format::ShortHtml => return write!(fmt, "{feat}"), }, - (sym::feature, Some(feat)) => match self.1 { + (sym::feature, Some((feat, _))) => match self.1 { Format::LongHtml => { return write!(fmt, "crate feature {feat}"); } @@ -594,13 +645,47 @@ impl fmt::Display for Display<'_> { fmt.write_str(human_readable) } else { let value = value - .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str())))) + .map(|(v, _)| { + fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))) + }) .maybe_display(); self.code_wrappers() .wrap(format_args!("{}{value}", self.1.escape(name.as_str()))) .fmt(fmt) } } + + CfgEntry::Version(..) => { + // FIXME: Should we handle it? + Ok(()) + } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct SimpleCfg { + name: Symbol, + value: Option, +} + +impl SimpleCfg { + fn new(name: Symbol) -> Self { + Self { name, value: None } + } + + fn new_value(name: Symbol, value: Symbol) -> Self { + Self { name, value: Some(value) } + } +} + +impl<'a> From<&'a CfgEntry> for SimpleCfg { + fn from(cfg: &'a CfgEntry) -> Self { + match cfg { + CfgEntry::NameValue { name, value, .. } => { + SimpleCfg { name: *name, value: (*value).map(|(v, _)| v) } + } + _ => SimpleCfg { name: sym::empty, value: None }, } } } @@ -610,7 +695,7 @@ impl fmt::Display for Display<'_> { pub(crate) struct CfgInfo { /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active /// `doc(auto_cfg(show(...)))` cfgs. - hidden_cfg: FxHashSet, + hidden_cfg: FxHashSet, /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while /// taking into account the `hidden_cfg` information. current_cfg: Cfg, @@ -626,11 +711,11 @@ impl Default for CfgInfo { fn default() -> Self { Self { hidden_cfg: FxHashSet::from_iter([ - Cfg::Cfg(sym::test, None), - Cfg::Cfg(sym::doc, None), - Cfg::Cfg(sym::doctest, None), + SimpleCfg::new(sym::test), + SimpleCfg::new(sym::doc), + SimpleCfg::new(sym::doctest), ]), - current_cfg: Cfg::True, + current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, parent_is_doc_cfg: false, } @@ -672,21 +757,24 @@ fn handle_auto_cfg_hide_show( { for item in items { // FIXME: Report in case `Cfg::parse` reports an error? - if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { + let Ok(cfg) = Cfg::parse(item) else { continue }; + if let CfgEntry::NameValue { name, value, .. } = cfg.0 { + let value = value.map(|(v, _)| v); + let simple = SimpleCfg::from(&cfg.0); if is_show { - if let Some(span) = new_hide_attrs.get(&(key, value)) { + if let Some(span) = new_hide_attrs.get(&(name, value)) { show_hide_show_conflict_error(tcx, item.span(), *span); } else { - new_show_attrs.insert((key, value), item.span()); + new_show_attrs.insert((name, value), item.span()); } - cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value)); + cfg_info.hidden_cfg.remove(&simple); } else { - if let Some(span) = new_show_attrs.get(&(key, value)) { + if let Some(span) = new_show_attrs.get(&(name, value)) { show_hide_show_conflict_error(tcx, item.span(), *span); } else { - new_hide_attrs.insert((key, value), item.span()); + new_hide_attrs.insert((name, value), item.span()); } - cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value)); + cfg_info.hidden_cfg.insert(simple); } } } @@ -737,28 +825,21 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator let mut doc_cfg = attrs .clone() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|attr| attr.has_name(sym::cfg)) + .filter_map(|attr| match attr { + Attribute::Parsed(AttributeKind::Doc(d)) => Some(d), + _ => None, + }) .peekable(); // If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes. if doc_cfg.peek().is_some() { - let sess = tcx.sess; // We overwrite existing `cfg`. if !cfg_info.parent_is_doc_cfg { - cfg_info.current_cfg = Cfg::True; + cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP)); cfg_info.parent_is_doc_cfg = true; } for attr in doc_cfg { - if let Some(cfg_mi) = - attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg_old(attr, sess)) - { - match Cfg::parse(cfg_mi) { - Ok(new_cfg) => cfg_info.current_cfg &= new_cfg, - Err(e) => { - sess.dcx().span_err(e.span, e.msg); - } - } + if let Some(new_cfg) = attr.cfg.clone() { + cfg_info.current_cfg &= Cfg(new_cfg); } } } else { @@ -833,7 +914,12 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator // Treat `#[target_feature(enable = "feat")]` attributes as if they were // `#[doc(cfg(target_feature = "feat"))]` attributes as well. for (feature, _) in features { - cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature)); + cfg_info.current_cfg &= Cfg(CfgEntry::NameValue { + name: sym::target_feature, + value: Some((*feature, DUMMY_SP)), + name_span: DUMMY_SP, + span: DUMMY_SP, + }); } continue; } else if !cfg_info.parent_is_doc_cfg @@ -851,7 +937,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg { None } else if cfg_info.parent_is_doc_cfg { - if cfg_info.current_cfg == Cfg::True { + if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) { None } else { Some(Arc::new(cfg_info.current_cfg.clone())) @@ -859,9 +945,9 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator } else { // If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the // hidden ones afterward. - match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) { - None | Some(Cfg::True) => None, - Some(cfg) => Some(Arc::new(cfg)), + match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) { + None | Some(CfgEntry::Bool(true, _)) => None, + Some(cfg) => Some(Arc::new(Cfg(cfg))), } } } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 849df566b4bd0..c86a655c083a5 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -640,7 +640,7 @@ pub(crate) fn build_impl( for_, items: trait_items, polarity, - kind: if utils::has_doc_flag(tcx, did, sym::fake_variadic) { + kind: if utils::has_doc_flag(tcx, did, |d| d.fake_variadic.is_some()) { ImplKind::FakeVariadic } else { ImplKind::Normal diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1f9ce68584ce5..6fd5786283884 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -34,13 +34,11 @@ use std::borrow::Cow; use std::collections::BTreeMap; use std::mem; -use rustc_ast::token::{Token, TokenKind}; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; use rustc_data_structures::thin_vec::ThinVec; use rustc_errors::codes::*; use rustc_errors::{FatalError, struct_span_code_err}; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, DocAttribute, DocInline}; use rustc_hir::def::{CtorKind, DefKind, MacroKinds, Res}; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE, LocalDefId}; use rustc_hir::{LangItem, PredicateOrigin, find_attr}; @@ -198,12 +196,9 @@ fn generate_item_with_correct_attrs( // For glob re-exports the item may or may not exist to be re-exported (potentially the // cfgs on the path up until the glob can be removed, and only cfgs on the globbed item // itself matter), for non-inlined re-exports see #85043. - let import_is_inline = - hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc) - .get_word_attr(sym::inline) - .is_some() - || (is_glob_import(cx.tcx, import_id) - && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); + let import_is_inline = find_attr!(inline::load_attrs(cx, import_id.to_def_id()), AttributeKind::Doc(d) if d.inline.is_some()) + || (is_glob_import(cx.tcx, import_id) + && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); is_inline = is_inline || import_is_inline; } @@ -2635,63 +2630,6 @@ fn get_all_import_attributes<'hir>( attrs } -fn filter_tokens_from_list( - args_tokens: &TokenStream, - should_retain: impl Fn(&TokenTree) -> bool, -) -> Vec { - let mut tokens = Vec::with_capacity(args_tokens.len()); - let mut skip_next_comma = false; - for token in args_tokens.iter() { - match token { - TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) if skip_next_comma => { - skip_next_comma = false; - } - token if should_retain(token) => { - skip_next_comma = false; - tokens.push(token.clone()); - } - _ => { - skip_next_comma = true; - } - } - } - tokens -} - -fn filter_doc_attr_ident(ident: Symbol, is_inline: bool) -> bool { - if is_inline { - ident == sym::hidden || ident == sym::inline || ident == sym::no_inline - } else { - ident == sym::cfg - } -} - -/// Remove attributes from `normal` that should not be inherited by `use` re-export. -/// Before calling this function, make sure `normal` is a `#[doc]` attribute. -fn filter_doc_attr(args: &mut hir::AttrArgs, is_inline: bool) { - match args { - hir::AttrArgs::Delimited(args) => { - let tokens = filter_tokens_from_list(&args.tokens, |token| { - !matches!( - token, - TokenTree::Token( - Token { - kind: TokenKind::Ident( - ident, - _, - ), - .. - }, - _, - ) if filter_doc_attr_ident(*ident, is_inline), - ) - }); - args.tokens = TokenStream::new(tokens); - } - hir::AttrArgs::Empty | hir::AttrArgs::Eq { .. } => {} - } -} - /// When inlining items, we merge their attributes (and all the reexports attributes too) with the /// final reexport. For example: /// @@ -2719,25 +2657,34 @@ fn add_without_unwanted_attributes<'hir>( import_parent: Option, ) { for attr in new_attrs { - if attr.is_doc_comment().is_some() { - attrs.push((Cow::Borrowed(attr), import_parent)); - continue; - } - let mut attr = attr.clone(); match attr { - hir::Attribute::Unparsed(ref mut normal) if let [ident] = &*normal.path.segments => { - let ident = ident.name; - if ident == sym::doc { - filter_doc_attr(&mut normal.args, is_inline); - attrs.push((Cow::Owned(attr), import_parent)); - } else if is_inline || ident != sym::cfg_trace { + hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => { + attrs.push((Cow::Borrowed(attr), import_parent)); + } + hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { + // Remove attributes from `normal` that should not be inherited by `use` re-export. + let DocAttribute { hidden, inline, cfg, .. } = d; + let mut attr = DocAttribute::default(); + if is_inline { + attr.inline = inline.clone(); + attr.hidden = hidden.clone(); + } else { + attr.cfg = cfg.clone(); + } + attrs.push(( + Cow::Owned(hir::Attribute::Parsed(AttributeKind::Doc(Box::new(attr)))), + import_parent, + )); + } + hir::Attribute::Unparsed(normal) if let [ident] = &*normal.path.segments => { + if is_inline || ident.name != sym::cfg_trace { // If it's not a `cfg()` attribute, we keep it. - attrs.push((Cow::Owned(attr), import_parent)); + attrs.push((Cow::Borrowed(attr), import_parent)); } } // FIXME: make sure to exclude `#[cfg_trace]` here when it is ported to the new parsers hir::Attribute::Parsed(..) => { - attrs.push((Cow::Owned(attr), import_parent)); + attrs.push((Cow::Borrowed(attr), import_parent)); } _ => {} } @@ -2939,7 +2886,7 @@ fn clean_impl<'tcx>( } else { ty::ImplPolarity::Positive }, - kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::fake_variadic) { + kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), |d| d.fake_variadic.is_some()) { ImplKind::FakeVariadic } else { ImplKind::Normal @@ -2967,13 +2914,10 @@ fn clean_extern_crate<'tcx>( let attrs = cx.tcx.hir_attrs(krate.hir_id()); let ty_vis = cx.tcx.visibility(krate.owner_id); let please_inline = ty_vis.is_public() - && attrs.iter().any(|a| { - a.has_name(sym::doc) - && match a.meta_item_list() { - Some(l) => ast::attr::list_contains_name(&l, sym::inline), - None => false, - } - }) + && attrs.iter().any(|a| matches!( + a, + hir::Attribute::Parsed(AttributeKind::Doc(d)) if d.inline.is_some_and(|(i, _)| i == DocInline::Inline)) + ) && !cx.is_json_output(); let krate_owner_def_id = krate.owner_id.def_id; @@ -3035,7 +2979,11 @@ fn clean_use_statement_inner<'tcx>( let visibility = cx.tcx.visibility(import.owner_id); let attrs = cx.tcx.hir_attrs(import.hir_id()); - let inline_attr = hir_attr_lists(attrs, sym::doc).get_word_attr(sym::inline); + let inline_attr = find_attr!( + attrs, + AttributeKind::Doc(d) if d.inline.is_some_and(|(i, _)| i == DocInline::Inline) => d + ) + .and_then(|d| d.inline); let pub_underscore = visibility.is_public() && name == Some(kw::Underscore); let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id); let import_def_id = import.owner_id.def_id; @@ -3053,10 +3001,10 @@ fn clean_use_statement_inner<'tcx>( let is_visible_from_parent_mod = visibility.is_accessible_from(parent_mod, cx.tcx) && !current_mod.is_top_level_module(); - if pub_underscore && let Some(ref inline) = inline_attr { + if pub_underscore && let Some((_, inline_span)) = inline_attr { struct_span_code_err!( cx.tcx.dcx(), - inline.span(), + inline_span, E0780, "anonymous imports cannot be inlined" ) @@ -3071,16 +3019,9 @@ fn clean_use_statement_inner<'tcx>( let mut denied = cx.is_json_output() || !(visibility.is_public() || (cx.document_private() && is_visible_from_parent_mod)) || pub_underscore - || attrs.iter().any(|a| { - a.has_name(sym::doc) - && match a.meta_item_list() { - Some(l) => { - ast::attr::list_contains_name(&l, sym::no_inline) - || ast::attr::list_contains_name(&l, sym::hidden) - } - None => false, - } - }); + || attrs.iter().any(|a| matches!( + a, + hir::Attribute::Parsed(AttributeKind::Doc(d)) if d.hidden.is_some() || d.inline.is_some_and(|(i, _)| i == DocInline::NoInline))); // Also check whether imports were asked to be inlined, in case we're trying to re-export a // crate in Rust 2018+ diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index f1b0f4a68beaa..12dcb198bd210 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -9,11 +9,11 @@ use itertools::Either; use rustc_abi::{ExternAbi, VariantIdx}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::thin_vec::ThinVec; -use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; +use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttribute}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_hir::{BodyId, ConstStability, Mutability, Stability, StableSince, find_attr}; +use rustc_hir::{Attribute, BodyId, ConstStability, Mutability, Stability, StableSince, find_attr}; use rustc_index::IndexVec; use rustc_metadata::rendered_const; use rustc_middle::span_bug; @@ -190,12 +190,13 @@ impl ExternalCrate { // Failing that, see if there's an attribute specifying where to find this // external crate let did = self.crate_num.as_def_id(); - tcx.get_attrs(did, sym::doc) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|a| a.has_name(sym::html_root_url)) - .filter_map(|a| a.value_str()) + tcx.get_all_attrs(did) + .iter() + .find_map(|a| match a { + Attribute::Parsed(AttributeKind::Doc(d)) => d.html_root_url.map(|(url, _)| url), + _ => None, + }) .map(to_remote) - .next() .or_else(|| extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false .unwrap_or(Unknown) // Well, at least we tried. } @@ -228,26 +229,34 @@ impl ExternalCrate { } pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator { - self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword) + self.retrieve_keywords_or_documented_attributes(tcx, true) } pub(crate) fn documented_attributes( &self, tcx: TyCtxt<'_>, ) -> impl Iterator { - self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute) + self.retrieve_keywords_or_documented_attributes(tcx, false) } fn retrieve_keywords_or_documented_attributes( &self, tcx: TyCtxt<'_>, - name: Symbol, + look_for_keyword: bool, ) -> impl Iterator { let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> { - tcx.get_attrs(did, sym::doc) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|meta| meta.has_name(name)) - .find_map(|meta| meta.value_str()) - .map(|value| (did, value)) + tcx.get_all_attrs(did) + .iter() + .find_map(|attr| match attr { + Attribute::Parsed(AttributeKind::Doc(d)) => { + if look_for_keyword { + d.keyword + } else { + d.attribute + } + } + _ => None, + }) + .map(|(value, _)| (did, value)) }; self.mapped_root_modules(tcx, as_target) } @@ -993,28 +1002,14 @@ pub(crate) struct Attributes { } impl Attributes { - pub(crate) fn lists(&self, name: Symbol) -> impl Iterator { - hir_attr_lists(&self.other_attrs[..], name) - } - - pub(crate) fn has_doc_flag(&self, flag: Symbol) -> bool { - for attr in &self.other_attrs { - if !attr.has_name(sym::doc) { - continue; - } - - if let Some(items) = attr.meta_item_list() - && items.iter().filter_map(|i| i.meta_item()).any(|it| it.has_name(flag)) - { - return true; - } - } - - false + pub(crate) fn has_doc_flag bool>(&self, callback: F) -> bool { + self.other_attrs + .iter() + .any(|a| matches!(a, Attribute::Parsed(AttributeKind::Doc(d)) if callback(d))) } pub(crate) fn is_doc_hidden(&self) -> bool { - self.has_doc_flag(sym::hidden) + find_attr!(&self.other_attrs, AttributeKind::Doc(d) if d.hidden.is_some()) } pub(crate) fn from_hir(attrs: &[hir::Attribute]) -> Attributes { @@ -1061,19 +1056,11 @@ impl Attributes { pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> { let mut aliases = FxIndexSet::default(); - for attr in - hir_attr_lists(&self.other_attrs[..], sym::doc).filter(|a| a.has_name(sym::alias)) - { - if let Some(values) = attr.meta_item_list() { - for l in values { - if let Some(lit) = l.lit() - && let ast::LitKind::Str(s, _) = lit.kind - { - aliases.insert(s); - } + for attr in &self.other_attrs { + if let Attribute::Parsed(AttributeKind::Doc(d)) = attr { + for (alias, _) in &d.aliases { + aliases.insert(*alias); } - } else if let Some(value) = attr.value_str() { - aliases.insert(value); } } aliases.into_iter().collect::>().into() diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index cb5cd6523afea..4c3f26701baba 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -6,6 +6,8 @@ use std::{ascii, mem}; use rustc_ast::join_path_idents; use rustc_ast::tokenstream::TokenTree; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_hir::Attribute; +use rustc_hir::attrs::{AttributeKind, DocAttribute}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_metadata::rendered_const; @@ -46,7 +48,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { if cx.tcx.is_compiler_builtins(it.item_id.krate()) { cx.cache.masked_crates.insert(it.item_id.krate()); } else if it.is_extern_crate() - && it.attrs.has_doc_flag(sym::masked) + && it.attrs.has_doc_flag(|d| d.masked.is_some()) && let Some(def_id) = it.item_id.as_def_id() && let Some(local_def_id) = def_id.as_local() && let Some(cnum) = cx.tcx.extern_mod_stmt_cnum(local_def_id) @@ -562,24 +564,17 @@ pub(crate) fn find_nearest_parent_module(tcx: TyCtxt<'_>, def_id: DefId) -> Opti } } -/// Checks for the existence of `hidden` in the attribute below if `flag` is `sym::hidden`: -/// -/// ``` -/// #[doc(hidden)] -/// pub fn foo() {} -/// ``` -/// /// This function exists because it runs on `hir::Attributes` whereas the other is a /// `clean::Attributes` method. -pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool { - attrs_have_doc_flag(tcx.get_attrs(did, sym::doc), flag) -} - -pub(crate) fn attrs_have_doc_flag<'a>( - mut attrs: impl Iterator, - flag: Symbol, +pub(crate) fn has_doc_flag bool>( + tcx: TyCtxt<'_>, + did: DefId, + callback: F, ) -> bool { - attrs.any(|attr| attr.meta_item_list().is_some_and(|l| ast::attr::list_contains_name(&l, flag))) + tcx.get_all_attrs(did).iter().any(|attr| match attr { + Attribute::Parsed(AttributeKind::Doc(d)) => callback(d), + _ => false, + }) } /// A link to `doc.rust-lang.org` that includes the channel name. Use this instead of manual links diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 481aa392007c8..7541311be05d7 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -9,6 +9,7 @@ use std::hash::{Hash, Hasher}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; +use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -19,14 +20,15 @@ pub(crate) use markdown::test as test_markdown; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxHasher, FxIndexMap, FxIndexSet}; use rustc_errors::emitter::HumanReadableErrorType; use rustc_errors::{ColorConfig, DiagCtxtHandle}; -use rustc_hir as hir; -use rustc_hir::CRATE_HIR_ID; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::{Attribute, CRATE_HIR_ID}; use rustc_interface::interface; +use rustc_middle::ty::TyCtxt; +use rustc_proc_macro::{TokenStream, TokenTree}; use rustc_session::config::{self, CrateType, ErrorOutputType, Input}; use rustc_session::lint; use rustc_span::edition::Edition; -use rustc_span::symbol::sym; use rustc_span::{FileName, Span}; use rustc_target::spec::{Target, TargetTuple}; use tempfile::{Builder as TempFileBuilder, TempDir}; @@ -212,8 +214,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions let collector = rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); - let crate_attrs = tcx.hir_attrs(CRATE_HIR_ID); - let opts = scrape_test_config(crate_name, crate_attrs, args_path); + let opts = scrape_test_config(tcx, crate_name, args_path); let hir_collector = HirCollector::new( ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()), @@ -417,8 +418,8 @@ pub(crate) fn run_tests( // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade. fn scrape_test_config( + tcx: TyCtxt<'_>, crate_name: String, - attrs: &[hir::Attribute], args_file: PathBuf, ) -> GlobalTestOptions { let mut opts = GlobalTestOptions { @@ -428,19 +429,26 @@ fn scrape_test_config( args_file, }; - let test_attrs: Vec<_> = attrs - .iter() - .filter(|a| a.has_name(sym::doc)) - .flat_map(|a| a.meta_item_list().unwrap_or_default()) - .filter(|a| a.has_name(sym::test)) - .collect(); - let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[])); - - for attr in attrs { - if attr.has_name(sym::no_crate_inject) { - opts.no_crate_inject = true; + let source_map = tcx.sess.source_map(); + 'main: for attr in tcx.hir_attrs(CRATE_HIR_ID) { + let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; + for attr_span in &d.test_attrs { + // FIXME: This is ugly, remove when `test_attrs` has been ported to new attribute API. + if let Ok(snippet) = source_map.span_to_snippet(*attr_span) + && let Ok(stream) = TokenStream::from_str(&snippet) + { + // NOTE: `test(attr(..))` is handled when discovering the individual tests + if stream.into_iter().any(|token| { + matches!( + token, + TokenTree::Ident(i) if i.to_string() == "no_crate_inject", + ) + }) { + opts.no_crate_inject = true; + break 'main; + } + } } - // NOTE: `test(attr(..))` is handled when discovering the individual tests } opts diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 4d3f976c2a6d5..e119344a806a4 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -2,16 +2,18 @@ use std::cell::Cell; use std::env; +use std::str::FromStr; use std::sync::Arc; -use rustc_ast_pretty::pprust; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; -use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit}; +use rustc_hir::{self as hir, Attribute, CRATE_HIR_ID, intravisit}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; +use rustc_proc_macro::{TokenStream, TokenTree}; use rustc_resolve::rustdoc::span_of_fragments; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym}; +use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span}; use super::{DocTestVisitor, ScrapedDocTest}; use crate::clean::{Attributes, CfgInfo, extract_cfg_from_attrs}; @@ -126,23 +128,46 @@ impl HirCollector<'_> { return; } + let source_map = self.tcx.sess.source_map(); // Try collecting `#[doc(test(attr(...)))]` let old_global_crate_attrs_len = self.collector.global_crate_attrs.len(); - for doc_test_attrs in ast_attrs - .iter() - .filter(|a| a.has_name(sym::doc)) - .flat_map(|a| a.meta_item_list().unwrap_or_default()) - .filter(|a| a.has_name(sym::test)) - { - let Some(doc_test_attrs) = doc_test_attrs.meta_item_list() else { continue }; - for attr in doc_test_attrs - .iter() - .filter(|a| a.has_name(sym::attr)) - .flat_map(|a| a.meta_item_list().unwrap_or_default()) - .map(pprust::meta_list_item_to_string) - { - // Add the additional attributes to the global_crate_attrs vector - self.collector.global_crate_attrs.push(attr); + for attr in ast_attrs { + let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; + for attr_span in &d.test_attrs { + // FIXME: This is ugly, remove when `test_attrs` has been ported to new attribute API. + if let Ok(snippet) = source_map.span_to_snippet(*attr_span) + && let Ok(stream) = TokenStream::from_str(&snippet) + { + let mut iter = stream.into_iter().peekable(); + while let Some(token) = iter.next() { + if let TokenTree::Ident(i) = token { + let i = i.to_string(); + let peek = iter.peek(); + match peek { + Some(TokenTree::Group(g)) => { + let g = g.to_string(); + iter.next(); + // Add the additional attributes to the global_crate_attrs vector + self.collector.global_crate_attrs.push(format!("{i}{g}")); + } + Some(TokenTree::Punct(p)) if p.as_char() == '=' => { + let p = p.to_string(); + iter.next(); + if let Some(last) = iter.next() { + // Add the additional attributes to the global_crate_attrs vector + self.collector + .global_crate_attrs + .push(format!("{i}{p}{last}")); + } + } + _ => { + // Add the additional attributes to the global_crate_attrs vector + self.collector.global_crate_attrs.push(i.to_string()); + } + } + } + } + } } } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index e42997d5b4a14..3d4dff4a17d22 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -8,11 +8,13 @@ use std::sync::mpsc::{Receiver, channel}; use askama::Template; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; -use rustc_span::{BytePos, FileName, Symbol, sym}; +use rustc_span::{BytePos, FileName, Symbol}; use tracing::info; use super::print_item::{full_path, print_item, print_item_path}; @@ -260,7 +262,9 @@ impl<'tcx> Context<'tcx> { short_title, description: &desc, resource_suffix: &self.shared.resource_suffix, - rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo), + rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| { + d.rust_logo.is_some() + }), }; layout::render( &self.shared.layout, @@ -522,27 +526,25 @@ impl<'tcx> Context<'tcx> { // Crawl the crate attributes looking for attributes which control how we're // going to emit HTML - for attr in krate.module.attrs.lists(sym::doc) { - match (attr.name(), attr.value_str()) { - (Some(sym::html_favicon_url), Some(s)) => { - layout.favicon = s.to_string(); - } - (Some(sym::html_logo_url), Some(s)) => { - layout.logo = s.to_string(); - } - (Some(sym::html_playground_url), Some(s)) => { - playground = Some(markdown::Playground { - crate_name: Some(krate.name(tcx)), - url: s.to_string(), - }); - } - (Some(sym::issue_tracker_base_url), Some(s)) => { - issue_tracker_base_url = Some(s.to_string()); - } - (Some(sym::html_no_source), None) if attr.is_word() => { - include_sources = false; - } - _ => {} + for attr in &krate.module.attrs.other_attrs { + let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; + if let Some((html_favicon_url, _)) = d.html_favicon_url { + layout.favicon = html_favicon_url.to_string(); + } + if let Some((html_logo_url, _)) = d.html_logo_url { + layout.logo = html_logo_url.to_string(); + } + if let Some((html_playground_url, _)) = d.html_playground_url { + playground = Some(markdown::Playground { + crate_name: Some(krate.name(tcx)), + url: html_playground_url.to_string(), + }); + } + if let Some((s, _)) = d.issue_tracker_base_url { + issue_tracker_base_url = Some(s.to_string()); + } + if d.html_no_source.is_some() { + include_sources = false; } } @@ -645,7 +647,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { static_root_path: shared.static_root_path.as_deref(), description: "List of all items in this crate", resource_suffix: &shared.resource_suffix, - rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo), + rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| d.rust_logo.is_some()), }; let all = shared.all.replace(AllTypes::new()); let mut sidebar = String::new(); diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index a4cbef8c39177..12b207dda5693 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1609,7 +1609,9 @@ pub(crate) fn build_index( let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache; let search_unbox = match id { RenderTypeId::Mut => false, - RenderTypeId::DefId(defid) => utils::has_doc_flag(tcx, defid, sym::search_unbox), + RenderTypeId::DefId(defid) => { + utils::has_doc_flag(tcx, defid, |d| d.search_unbox.is_some()) + } RenderTypeId::Primitive( PrimitiveType::Reference | PrimitiveType::RawPointer | PrimitiveType::Tuple, ) => true, diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index f04f94432b808..55f8ddab25b18 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -8,7 +8,7 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_span::{FileName, FileNameDisplayPreference, RealFileName, sym}; +use rustc_span::{FileName, FileNameDisplayPreference, RealFileName}; use tracing::info; use super::render::Context; @@ -236,7 +236,9 @@ impl SourceCollector<'_, '_> { static_root_path: shared.static_root_path.as_deref(), description: &desc, resource_suffix: &shared.resource_suffix, - rust_logo: has_doc_flag(self.cx.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo), + rust_logo: has_doc_flag(self.cx.tcx(), LOCAL_CRATE.as_def_id(), |d| { + d.rust_logo.is_some() + }), }; let source_context = SourceContext::Standalone { file_path }; let v = layout::render( diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e4601bfb20d7d..a0e359032f9b6 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -32,11 +32,9 @@ extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; -extern crate rustc_attr_parsing; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; -extern crate rustc_expand; extern crate rustc_feature; extern crate rustc_hir; extern crate rustc_hir_analysis; diff --git a/src/librustdoc/passes/check_doc_cfg.rs b/src/librustdoc/passes/check_doc_cfg.rs index 3284da77a0222..63dc375a13c64 100644 --- a/src/librustdoc/passes/check_doc_cfg.rs +++ b/src/librustdoc/passes/check_doc_cfg.rs @@ -1,3 +1,5 @@ +#![allow(dead_code, unused_imports)] + use rustc_hir::HirId; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -14,63 +16,59 @@ pub(crate) const CHECK_DOC_CFG: Pass = Pass { description: "checks `#[doc(cfg(...))]` for stability feature and unexpected cfgs", }; -pub(crate) fn check_doc_cfg(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut checker = DocCfgChecker { cx }; - checker.visit_crate(&krate); +pub(crate) fn check_doc_cfg(krate: Crate, _cx: &mut DocContext<'_>) -> Crate { + // let mut checker = DocCfgChecker { cx }; + // checker.visit_crate(&krate); krate } -struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId); +// struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId); -impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { - fn emit_span_lint( - &self, - sess: &rustc_session::Session, - lint: &'static rustc_lint::Lint, - sp: rustc_span::Span, - builtin_diag: rustc_lint_defs::BuiltinLintDiag, - ) { - self.0.node_span_lint(lint, self.1, sp, |diag| { - rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag) - }); - } -} +// impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { +// fn emit_span_lint( +// &self, +// sess: &rustc_session::Session, +// lint: &'static rustc_lint::Lint, +// sp: rustc_span::Span, +// builtin_diag: rustc_lint_defs::BuiltinLintDiag, +// ) { +// self.0.node_span_lint(lint, self.1, sp, |diag| { +// rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag) +// }); +// } +// } -struct DocCfgChecker<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, -} +// struct DocCfgChecker<'a, 'tcx> { +// cx: &'a mut DocContext<'tcx>, +// } -impl DocCfgChecker<'_, '_> { - fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) { - let doc_cfgs = attrs - .other_attrs - .iter() - .filter(|attr| attr.has_name(sym::doc)) - .flat_map(|attr| attr.meta_item_list().unwrap_or_default()) - .filter(|attr| attr.has_name(sym::cfg)); +// impl DocCfgChecker<'_, '_> { +// fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) { +// for attr in &attrs.other_attrs { +// let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; +// let Some(doc_cfg) = d.cfg else { continue }; - for doc_cfg in doc_cfgs { - if let Some([cfg_mi]) = doc_cfg.meta_item_list() { - let _ = rustc_attr_parsing::cfg_matches( - cfg_mi, - &self.cx.tcx.sess, - RustdocCfgMatchesLintEmitter( - self.cx.tcx, - self.cx.tcx.local_def_id_to_hir_id(did), - ), - Some(self.cx.tcx.features()), - ); - } - } - } -} +// if let Some([cfg_mi]) = doc_cfg.meta_item_list() { +// let _ = rustc_attr_parsing::cfg_matches( +// cfg_mi, +// &self.cx.tcx.sess, +// RustdocCfgMatchesLintEmitter( +// self.cx.tcx, +// self.cx.tcx.local_def_id_to_hir_id(did), +// ), +// Some(self.cx.tcx.features()), +// ); +// } +// } +// } +// } -impl DocVisitor<'_> for DocCfgChecker<'_, '_> { - fn visit_item(&mut self, item: &'_ Item) { - if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) { - self.check_attrs(&item.attrs, local_did); - } +// impl DocVisitor<'_> for DocCfgChecker<'_, '_> { +// fn visit_item(&mut self, item: &'_ Item) { +// if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) { +// self.check_attrs(&item.attrs, local_did); +// } - self.visit_item_recur(item); - } -} +// self.visit_item_recur(item); +// } +// } diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 2339a6b69cd8a..d71c3eaa2dcb9 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -3,9 +3,10 @@ //! struct implements that trait. use rustc_data_structures::fx::FxHashSet; +use rustc_hir::Attribute; +use rustc_hir::attrs::{AttributeKind, DocAttribute}; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE}; use rustc_middle::ty; -use rustc_span::symbol::sym; use tracing::debug; use super::Pass; @@ -65,17 +66,14 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> for &impl_def_id in tcx.trait_impls_in_crate(LOCAL_CRATE) { let mut parent = Some(tcx.parent(impl_def_id)); while let Some(did) = parent { - attr_buf.extend( - tcx.get_attrs(did, sym::doc) - .filter(|attr| { - if let Some([attr]) = attr.meta_item_list().as_deref() { - attr.has_name(sym::cfg) - } else { - false - } - }) - .cloned(), - ); + attr_buf.extend(tcx.get_all_attrs(did).iter().filter_map(|attr| match attr { + Attribute::Parsed(AttributeKind::Doc(d)) if d.cfg.is_some() => { + let mut new_attr = DocAttribute::default(); + new_attr.cfg = d.cfg.clone(); + Some(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))) + } + _ => None, + })); parent = tcx.opt_parent(did); } cx.with_param_env(impl_def_id, |cx| { diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index c0b48ab51c7eb..422393592c765 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -1,8 +1,7 @@ //! Propagates [`#[doc(cfg(...))]`](https://github.com/rust-lang/rust/issues/43781) to child items. -use rustc_ast::token::{Token, TokenKind}; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; -use rustc_hir::{AttrArgs, Attribute}; +use rustc_hir::Attribute; +use rustc_hir::attrs::{AttributeKind, DocAttribute}; use rustc_span::symbol::sym; use crate::clean::inline::{load_attrs, merge_attrs}; @@ -30,59 +29,22 @@ struct CfgPropagator<'a, 'tcx> { cfg_info: CfgInfo, } -/// Returns true if the provided `token` is a `cfg` ident. -fn is_cfg_token(token: &TokenTree) -> bool { - // We only keep `doc(cfg)` items. - matches!(token, TokenTree::Token(Token { kind: TokenKind::Ident(sym::cfg, _,), .. }, _,),) -} - -/// We only want to keep `#[cfg()]` and `#[doc(cfg())]` attributes so we rebuild a vec of -/// `TokenTree` with only the tokens we're interested into. -fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec { - let mut tokens = Vec::with_capacity(args_tokens.len()); - let mut skip_next_delimited = false; - for token in args_tokens.iter() { - match token { - TokenTree::Delimited(..) => { - if !skip_next_delimited { - tokens.push(token.clone()); - } - skip_next_delimited = false; - } - token if is_cfg_token(token) => { - skip_next_delimited = false; - tokens.push(token.clone()); - } - _ => { - skip_next_delimited = true; - } - } - } - tokens -} - /// This function goes through the attributes list (`new_attrs`) and extract the `cfg` tokens from /// it and put them into `attrs`. fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { for attr in new_attrs { - if attr.is_doc_comment().is_some() { - continue; - } - let mut attr = attr.clone(); - if let Attribute::Unparsed(ref mut normal) = attr + if let Attribute::Parsed(AttributeKind::Doc(d)) = attr + && d.cfg.is_some() + { + let mut new_attr = DocAttribute::default(); + new_attr.cfg = d.cfg.clone(); + attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))); + } else if let Attribute::Unparsed(normal) = attr && let [ident] = &*normal.path.segments + && ident.name == sym::cfg_trace { - let ident = ident.name; - if ident == sym::doc - && let AttrArgs::Delimited(args) = &mut normal.args - { - let tokens = filter_non_cfg_tokens_from_list(&args.tokens); - args.tokens = TokenStream::new(tokens); - attrs.push(attr); - } else if ident == sym::cfg_trace { - // If it's a `cfg()` attribute, we keep it. - attrs.push(attr); - } + // If it's a `cfg()` attribute, we keep it. + attrs.push(attr.clone()); } } } From 04bcd83eb5b987afd5b7d43f82c0b5eb2df62767 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Nov 2025 20:51:12 +0100 Subject: [PATCH 05/32] Move `doc = ""` parsing out of `DocParser` to keep doc attributes order --- .../rustc_attr_parsing/src/attributes/doc.rs | 28 +++----------- compiler/rustc_attr_parsing/src/interface.rs | 37 ++++++++++++++++++- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 65651e617179f..fbe1ac63ab9c5 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -2,7 +2,6 @@ #![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::{ @@ -81,19 +80,10 @@ fn parse_keyword_and_attribute<'c, S, F>( *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 { @@ -481,17 +471,11 @@ impl DocParser { } } ArgParser::NameValue(nv) => { - let Some(comment) = nv.value_as_str() else { + if nv.value_as_str().is_none() { 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, - }); + } else { + unreachable!("Should have been handled at the same time as sugar-syntaxed doc comments"); + } } } } @@ -537,9 +521,7 @@ impl AttributeParser for DocParser { ]); 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 { + if self.nb_doc_attrs != 0 { Some(AttributeKind::Doc(Box::new(self.attribute))) } else { None diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 363e1fcac5078..feb7bbcb6234d 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use rustc_ast as ast; use rustc_ast::{AttrStyle, NodeId, Safety}; +use rustc_ast::token::CommentKind; use rustc_errors::DiagCtxtHandle; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::AttributeKind; @@ -281,7 +282,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { // that's expanded right? But no, sometimes, when parsing attributes on macros, // we already use the lowering logic and these are still there. So, when `omit_doc` // is set we *also* want to ignore these. - if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) { + let is_doc_attribute = attr.has_name(sym::doc); + if omit_doc == OmitDoc::Skip && is_doc_attribute { continue; } @@ -323,6 +325,39 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { continue; }; let args = parser.args(); + + // Special-case handling for `#[doc = "..."]`: if we go through with + // `DocParser`, the order of doc comments will be messed up because `///` + // doc comments are added into `attributes` whereas attributes parsed with + // `DocParser` are added into `parsed_attributes` which are then appended + // to `attributes`. So if you have: + // + // /// bla + // #[doc = "a"] + // /// blob + // + // You would get: + // + // bla + // blob + // a + if is_doc_attribute + && let ArgParser::NameValue(nv) = args + // If not a string key/value, it should emit an error, but to make + // things simpler, it's handled in `DocParser` because it's simpler to + // emit an error with `AcceptContext`. + && let Some(comment) = nv.value_as_str() + { + attributes.push(Attribute::Parsed(AttributeKind::DocComment { + style: attr.style, + kind: CommentKind::Block, + span: nv.value_span, + comment, + })); + continue; + } + + let path = parser.path(); for accept in accepts { let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext { shared: SharedContext { From 148e522112d65d23c8615e22da65a64e319674bb Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 28 Nov 2025 16:08:24 +0100 Subject: [PATCH 06/32] Correctly differentiate between sugared and raw doc comments --- compiler/rustc_ast/src/attr/mod.rs | 47 +++++++++++++++---- compiler/rustc_ast/src/token.rs | 26 +++++++++- compiler/rustc_ast_pretty/src/pprust/state.rs | 32 +++++++++---- .../rustc_attr_parsing/src/attributes/doc.rs | 4 +- compiler/rustc_attr_parsing/src/interface.rs | 8 ++-- .../rustc_hir/src/attrs/data_structures.rs | 4 +- .../rustc_hir/src/attrs/pretty_printing.rs | 3 +- compiler/rustc_hir/src/hir.rs | 8 ++-- compiler/rustc_resolve/src/rustdoc.rs | 42 +++++++---------- src/librustdoc/clean/types.rs | 2 +- 10 files changed, 117 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index d54d900128bd3..94e7462d26df5 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -13,7 +13,9 @@ use crate::ast::{ Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path, PathSegment, Safety, }; -use crate::token::{self, CommentKind, Delimiter, InvisibleOrigin, MetaVarKind, Token}; +use crate::token::{ + self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token, +}; use crate::tokenstream::{ DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, }; @@ -179,15 +181,21 @@ impl AttributeExt for Attribute { } /// Returns the documentation and its kind if this is a doc comment or a sugared doc comment. - /// * `///doc` returns `Some(("doc", CommentKind::Line))`. - /// * `/** doc */` returns `Some(("doc", CommentKind::Block))`. - /// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`. + /// * `///doc` returns `Some(("doc", DocFragmentKind::Sugared(CommentKind::Line)))`. + /// * `/** doc */` returns `Some(("doc", DocFragmentKind::Sugared(CommentKind::Block)))`. + /// * `#[doc = "doc"]` returns `Some(("doc", DocFragmentKind::Raw))`. /// * `#[doc(...)]` returns `None`. - fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { + fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> { match &self.kind { - AttrKind::DocComment(kind, data) => Some((*data, *kind)), + AttrKind::DocComment(kind, data) => Some((*data, DocFragmentKind::Sugared(*kind))), AttrKind::Normal(normal) if normal.item.path == sym::doc => { - normal.item.value_str().map(|s| (s, CommentKind::Line)) + if let Some(value) = normal.item.value_str() + && let Some(value_span) = normal.item.value_span() + { + Some((value, DocFragmentKind::Raw(value_span))) + } else { + None + } } _ => None, } @@ -305,6 +313,25 @@ impl AttrItem { } } + /// Returns the span in: + /// + /// ```text + /// #[attribute = "value"] + /// ^^^^^^^ + /// ``` + /// + /// It returns `None` in any other cases like: + /// + /// ```text + /// #[attr("value")] + /// ``` + fn value_span(&self) -> Option { + match &self.args { + AttrArgs::Eq { expr, .. } => Some(expr.span), + AttrArgs::Delimited(_) | AttrArgs::Empty => None, + } + } + pub fn meta(&self, span: Span) -> Option { Some(MetaItem { unsafety: Safety::Default, @@ -825,7 +852,7 @@ pub trait AttributeExt: Debug { /// * `/** doc */` returns `Some(("doc", CommentKind::Block))`. /// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`. /// * `#[doc(...)]` returns `None`. - fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)>; + fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)>; /// Returns outer or inner if this is a doc attribute or a sugared doc /// comment, otherwise None. @@ -910,7 +937,7 @@ impl Attribute { AttributeExt::is_proc_macro_attr(self) } - pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { - AttributeExt::doc_str_and_comment_kind(self) + pub fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> { + AttributeExt::doc_str_and_fragment_kind(self) } } diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index e1231312a2afd..accf4d1816323 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -16,7 +16,31 @@ use rustc_span::{Ident, Symbol}; use crate::ast; use crate::util::case::Case; -#[derive(Clone, Copy, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)] +/// Represents the kind of doc comment it is, ie `///` or `#[doc = ""]`. +#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Debug, HashStable_Generic)] +pub enum DocFragmentKind { + /// A sugared doc comment: `///` or `//!` or `/**` or `/*!`. + Sugared(CommentKind), + /// A "raw" doc comment: `#[doc = ""]`. The `Span` represents the string literal. + Raw(Span), +} + +impl DocFragmentKind { + pub fn is_sugared(self) -> bool { + matches!(self, Self::Sugared(_)) + } + + /// If it is `Sugared`, it will return its associated `CommentKind`, otherwise it will return + /// `CommentKind::Line`. + pub fn comment_kind(self) -> CommentKind { + match self { + Self::Sugared(kind) => kind, + Self::Raw(_) => CommentKind::Line, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Debug, HashStable_Generic)] pub enum CommentKind { Line, Block, diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 3dce0498efbf9..35e47fed9f7ae 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -10,7 +10,7 @@ use std::borrow::Cow; use std::sync::Arc; use rustc_ast::attr::AttrIdGenerator; -use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind}; +use rustc_ast::token::{self, CommentKind, Delimiter, DocFragmentKind, Token, TokenKind}; use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree}; use rustc_ast::util::classify; use rustc_ast::util::comments::{Comment, CommentStyle}; @@ -381,15 +381,24 @@ fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { } pub fn doc_comment_to_string( - comment_kind: CommentKind, + fragment_kind: DocFragmentKind, attr_style: ast::AttrStyle, data: Symbol, ) -> String { - match (comment_kind, attr_style) { - (CommentKind::Line, ast::AttrStyle::Outer) => format!("///{data}"), - (CommentKind::Line, ast::AttrStyle::Inner) => format!("//!{data}"), - (CommentKind::Block, ast::AttrStyle::Outer) => format!("/**{data}*/"), - (CommentKind::Block, ast::AttrStyle::Inner) => format!("/*!{data}*/"), + match fragment_kind { + DocFragmentKind::Sugared(comment_kind) => match (comment_kind, attr_style) { + (CommentKind::Line, ast::AttrStyle::Outer) => format!("///{data}"), + (CommentKind::Line, ast::AttrStyle::Inner) => format!("//!{data}"), + (CommentKind::Block, ast::AttrStyle::Outer) => format!("/**{data}*/"), + (CommentKind::Block, ast::AttrStyle::Inner) => format!("/*!{data}*/"), + }, + DocFragmentKind::Raw(_) => { + format!( + "#{}[doc = {:?}]", + if attr_style == ast::AttrStyle::Inner { "!" } else { "" }, + data.to_string(), + ) + } } } @@ -665,7 +674,11 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere self.word("]"); } ast::AttrKind::DocComment(comment_kind, data) => { - self.word(doc_comment_to_string(*comment_kind, attr.style, *data)); + self.word(doc_comment_to_string( + DocFragmentKind::Sugared(*comment_kind), + attr.style, + *data, + )); self.hardbreak() } } @@ -1029,7 +1042,8 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere /* Other */ token::DocComment(comment_kind, attr_style, data) => { - doc_comment_to_string(comment_kind, attr_style, data).into() + doc_comment_to_string(DocFragmentKind::Sugared(comment_kind), attr_style, data) + .into() } token::Eof => "".into(), } diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index fbe1ac63ab9c5..1a7d8ec93f702 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -474,7 +474,9 @@ impl DocParser { if nv.value_as_str().is_none() { cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); } else { - unreachable!("Should have been handled at the same time as sugar-syntaxed doc comments"); + unreachable!( + "Should have been handled at the same time as sugar-syntaxed doc comments" + ); } } } diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index feb7bbcb6234d..b1538b447da05 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use rustc_ast as ast; use rustc_ast::{AttrStyle, NodeId, Safety}; -use rustc_ast::token::CommentKind; +use rustc_ast::token::DocFragmentKind; use rustc_errors::DiagCtxtHandle; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::AttributeKind; @@ -295,7 +295,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { attributes.push(Attribute::Parsed(AttributeKind::DocComment { style: attr.style, - kind: *comment_kind, + kind: DocFragmentKind::Sugared(*comment_kind), span: lower_span(attr.span), comment: *symbol, })) @@ -350,8 +350,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { { attributes.push(Attribute::Parsed(AttributeKind::DocComment { style: attr.style, - kind: CommentKind::Block, - span: nv.value_span, + kind: DocFragmentKind::Raw(nv.value_span), + span: attr.span, comment, })); continue; diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 39008914f9efe..87a4cf7823f58 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; pub use ReprAttr::*; use rustc_abi::Align; -use rustc_ast::token::CommentKind; +use rustc_ast::token::DocFragmentKind; use rustc_ast::{AttrStyle, ast}; use rustc_data_structures::fx::FxIndexMap; use rustc_error_messages::{DiagArgValue, IntoDiagArg}; @@ -648,7 +648,7 @@ pub enum AttributeKind { /// Represents specifically [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html). /// i.e. doc comments. - DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol }, + DocComment { style: AttrStyle, kind: DocFragmentKind, span: Span, comment: Symbol }, /// Represents `#[rustc_dummy]`. Dummy, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 75886fb08a2e0..29df586ed296c 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -1,7 +1,7 @@ use std::num::NonZero; use rustc_abi::Align; -use rustc_ast::token::CommentKind; +use rustc_ast::token::{CommentKind, DocFragmentKind}; use rustc_ast::{AttrStyle, IntTy, UintTy}; use rustc_ast_pretty::pp::Printer; use rustc_data_structures::fx::FxIndexMap; @@ -167,6 +167,7 @@ print_debug!( Align, AttrStyle, CommentKind, + DocFragmentKind, Transparency, SanitizerSet, ); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index e6a0f430b63aa..afa1a33fe769a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4,7 +4,7 @@ use std::fmt; use rustc_abi::ExternAbi; use rustc_ast::attr::AttributeExt; -use rustc_ast::token::CommentKind; +use rustc_ast::token::DocFragmentKind; use rustc_ast::util::parser::ExprPrecedence; use rustc_ast::{ self as ast, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, LitIntType, @@ -1385,7 +1385,7 @@ impl AttributeExt for Attribute { } #[inline] - fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { + fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> { match &self { Attribute::Parsed(AttributeKind::DocComment { kind, comment, .. }) => { Some((*comment, *kind)) @@ -1503,8 +1503,8 @@ impl Attribute { } #[inline] - pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> { - AttributeExt::doc_str_and_comment_kind(self) + pub fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> { + AttributeExt::doc_str_and_fragment_kind(self) } } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 3c0a89b7c8a7f..0ac8e68ad968e 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -10,6 +10,7 @@ use pulldown_cmark::{ use rustc_ast as ast; use rustc_ast::attr::AttributeExt; use rustc_ast::join_path_syms; +use rustc_ast::token::DocFragmentKind; use rustc_ast::util::comments::beautify_doc_string; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; @@ -23,14 +24,6 @@ use tracing::{debug, trace}; #[cfg(test)] mod tests; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum DocFragmentKind { - /// A doc fragment created from a `///` or `//!` doc comment. - SugaredDoc, - /// A doc fragment created from a "raw" `#[doc=""]` attribute. - RawDoc, -} - /// A portion of documentation, extracted from a `#[doc]` attribute. /// /// Each variant contains the line number within the complete doc-comment where the fragment @@ -125,7 +118,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { // // In this case, you want "hello! another" and not "hello! another". let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) - && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) + && docs.iter().any(|d| d.kind.is_sugared()) { // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to // "decide" how much the minimum indent will be. @@ -155,8 +148,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { // Compare against either space or tab, ignoring whether they are // mixed or not. let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); - whitespace - + (if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }) + whitespace + (if fragment.kind.is_sugared() { 0 } else { add }) }) .min() .unwrap_or(usize::MAX) @@ -171,7 +163,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { continue; } - let indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { + let indent = if !fragment.kind.is_sugared() && min_indent > 0 { min_indent - add } else { min_indent @@ -214,19 +206,17 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( let mut doc_fragments = Vec::with_capacity(size_hint); let mut other_attrs = ThinVec::::with_capacity(if doc_only { 0 } else { size_hint }); for (attr, item_id) in attrs { - if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() { - let doc = beautify_doc_string(doc_str, comment_kind); - let (span, kind, from_expansion) = if let Some(span) = attr.is_doc_comment() { - (span, DocFragmentKind::SugaredDoc, span.from_expansion()) - } else { - let attr_span = attr.span(); - let (span, from_expansion) = match attr.value_span() { - Some(sp) => (sp.with_ctxt(attr_span.ctxt()), sp.from_expansion()), - None => (attr_span, attr_span.from_expansion()), - }; - (span, DocFragmentKind::RawDoc, from_expansion) + if let Some((doc_str, fragment_kind)) = attr.doc_str_and_fragment_kind() { + let doc = beautify_doc_string(doc_str, fragment_kind.comment_kind()); + let attr_span = attr.span(); + let (span, from_expansion) = match fragment_kind { + DocFragmentKind::Sugared(_) => (attr_span, attr_span.from_expansion()), + DocFragmentKind::Raw(value_span) => { + (value_span.with_ctxt(attr_span.ctxt()), value_span.from_expansion()) + } }; - let fragment = DocFragment { span, doc, kind, item_id, indent: 0, from_expansion }; + let fragment = + DocFragment { span, doc, kind: fragment_kind, item_id, indent: 0, from_expansion }; doc_fragments.push(fragment); } else if !doc_only { other_attrs.push(attr.clone()); @@ -571,7 +561,7 @@ pub fn source_span_for_markdown_range_inner( use rustc_span::BytePos; if let &[fragment] = &fragments - && fragment.kind == DocFragmentKind::RawDoc + && !fragment.kind.is_sugared() && let Ok(snippet) = map.span_to_snippet(fragment.span) && snippet.trim_end() == markdown.trim_end() && let Ok(md_range_lo) = u32::try_from(md_range.start) @@ -589,7 +579,7 @@ pub fn source_span_for_markdown_range_inner( )); } - let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc); + let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind.is_sugared()); if !is_all_sugared_doc { // This case ignores the markdown outside of the range so that it can diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 12dcb198bd210..40191d5c98e02 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -2403,7 +2403,7 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(Crate, 16); // frequently moved by-value - static_assert_size!(DocFragment, 32); + static_assert_size!(DocFragment, 48); static_assert_size!(GenericArg, 32); static_assert_size!(GenericArgs, 24); static_assert_size!(GenericParamDef, 40); From 92edc499a69a24e69e6fcc2641dc9bca1098bd5b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 28 Nov 2025 16:50:16 +0100 Subject: [PATCH 07/32] Correctly handle doc items inlining --- src/librustdoc/clean/mod.rs | 21 +++++++++++++-------- src/librustdoc/clean/types.rs | 31 ------------------------------- src/librustdoc/visit_ast.rs | 18 +++++++----------- 3 files changed, 20 insertions(+), 50 deletions(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 6fd5786283884..bea4398ccf862 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -196,9 +196,12 @@ fn generate_item_with_correct_attrs( // For glob re-exports the item may or may not exist to be re-exported (potentially the // cfgs on the path up until the glob can be removed, and only cfgs on the globbed item // itself matter), for non-inlined re-exports see #85043. - let import_is_inline = find_attr!(inline::load_attrs(cx, import_id.to_def_id()), AttributeKind::Doc(d) if d.inline.is_some()) - || (is_glob_import(cx.tcx, import_id) - && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); + let import_is_inline = find_attr!( + inline::load_attrs(cx, import_id.to_def_id()), + AttributeKind::Doc(d) + if d.inline.is_some_and(|(inline, _)| inline == DocInline::Inline) + ) || (is_glob_import(cx.tcx, import_id) + && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); is_inline = is_inline || import_is_inline; } @@ -2666,10 +2669,10 @@ fn add_without_unwanted_attributes<'hir>( let DocAttribute { hidden, inline, cfg, .. } = d; let mut attr = DocAttribute::default(); if is_inline { + attr.cfg = cfg.clone(); + } else { attr.inline = inline.clone(); attr.hidden = hidden.clone(); - } else { - attr.cfg = cfg.clone(); } attrs.push(( Cow::Owned(hir::Attribute::Parsed(AttributeKind::Doc(Box::new(attr)))), @@ -2914,10 +2917,12 @@ fn clean_extern_crate<'tcx>( let attrs = cx.tcx.hir_attrs(krate.hir_id()); let ty_vis = cx.tcx.visibility(krate.owner_id); let please_inline = ty_vis.is_public() - && attrs.iter().any(|a| matches!( + && attrs.iter().any(|a| { + matches!( a, - hir::Attribute::Parsed(AttributeKind::Doc(d)) if d.inline.is_some_and(|(i, _)| i == DocInline::Inline)) - ) + hir::Attribute::Parsed(AttributeKind::Doc(d)) + if d.inline.is_some_and(|(i, _)| i == DocInline::Inline)) + }) && !cx.is_json_output(); let krate_owner_def_id = krate.owner_id.def_id; diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 40191d5c98e02..cefaf1102fb99 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -929,37 +929,6 @@ pub(crate) struct Module { pub(crate) span: Span, } -pub(crate) fn hir_attr_lists<'a, I: IntoIterator>( - attrs: I, - name: Symbol, -) -> impl Iterator + use<'a, I> { - attrs - .into_iter() - .filter(move |attr| attr.has_name(name)) - .filter_map(ast::attr::AttributeExt::meta_item_list) - .flatten() -} - -pub(crate) trait NestedAttributesExt { - /// Returns `true` if the attribute list contains a specific `word` - fn has_word(self, word: Symbol) -> bool - where - Self: Sized, - { - ::get_word_attr(self, word).is_some() - } - - /// Returns `Some(attr)` if the attribute list contains 'attr' - /// corresponding to a specific `word` - fn get_word_attr(self, word: Symbol) -> Option; -} - -impl> NestedAttributesExt for I { - fn get_word_attr(mut self, word: Symbol) -> Option { - self.find(|attr| attr.is_word() && attr.has_name(word)) - } -} - /// A link that has not yet been rendered. /// /// This link will be turned into a rendered link by [`Item::links`]. diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 51b2821eb1e5f..d6da8615d57ea 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -3,9 +3,10 @@ use std::mem; +use rustc_ast::attr::AttributeExt; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir as hir; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, DocInline}; use rustc_hir::def::{DefKind, MacroKinds, Res}; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet}; use rustc_hir::intravisit::{Visitor, walk_body, walk_item}; @@ -14,11 +15,11 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; -use rustc_span::symbol::{Symbol, kw, sym}; +use rustc_span::symbol::{Symbol, kw}; use tracing::debug; +use crate::clean::reexport_chain; use crate::clean::utils::{inherits_doc_hidden, should_ignore_res}; -use crate::clean::{NestedAttributesExt, hir_attr_lists, reexport_chain}; use crate::core; /// This module is used to store stuff from Rust's AST in a more convenient @@ -246,8 +247,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let document_hidden = self.cx.document_hidden(); let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id)); // Don't inline `doc(hidden)` imports so they can be stripped at a later stage. - let is_no_inline = hir_attr_lists(use_attrs, sym::doc).has_word(sym::no_inline) - || (document_hidden && hir_attr_lists(use_attrs, sym::doc).has_word(sym::hidden)); + let is_no_inline = find_attr!(use_attrs, AttributeKind::Doc(d) if d.inline.is_some_and(|(inline, _)| inline == DocInline::NoInline)) + || (document_hidden && use_attrs.iter().any(|attr| attr.is_doc_hidden())); if is_no_inline { return false; @@ -464,12 +465,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. if is_pub && self.inside_public_path { - let please_inline = attrs.iter().any(|item| match item.meta_item_list() { - Some(ref list) if item.has_name(sym::doc) => { - list.iter().any(|i| i.has_name(sym::inline)) - } - _ => false, - }); + let please_inline = find_attr!(attrs, AttributeKind::Doc(d) if d.inline.is_some_and(|(inline, _)| inline == DocInline::Inline)); let ident = match kind { hir::UseKind::Single(ident) => Some(ident.name), hir::UseKind::Glob => None, From 368a103902b00b4b0b93d30050ce2da7d5c2b0d9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 28 Nov 2025 18:49:19 +0100 Subject: [PATCH 08/32] Fix `Cfg` add/or operations --- .../rustc_hir/src/attrs/data_structures.rs | 36 +++++++++++++++---- src/librustdoc/clean/cfg.rs | 24 ++++++------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 87a4cf7823f58..e35af0853651d 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -197,14 +197,38 @@ pub enum CfgEntry { impl CfgEntry { pub fn span(&self) -> Span { - let (CfgEntry::All(_, span) - | CfgEntry::Any(_, span) - | CfgEntry::Not(_, span) - | CfgEntry::Bool(_, span) - | CfgEntry::NameValue { span, .. } - | CfgEntry::Version(_, span)) = self; + let (Self::All(_, span) + | Self::Any(_, span) + | Self::Not(_, span) + | Self::Bool(_, span) + | Self::NameValue { span, .. } + | Self::Version(_, span)) = self; *span } + + /// Same as `PartialEq` but doesn't check spans and ignore order of cfgs. + pub fn is_equivalent_to(&self, other: &Self) -> bool { + match (self, other) { + (Self::All(a, _), Self::All(b, _)) | (Self::Any(a, _), Self::Any(b, _)) => { + a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a.is_equivalent_to(b))) + } + (Self::Not(a, _), Self::Not(b, _)) => a.is_equivalent_to(b), + (Self::Bool(a, _), Self::Bool(b, _)) => a == b, + ( + Self::NameValue { name: name1, value: value1, .. }, + Self::NameValue { name: name2, value: value2, .. }, + ) => { + name1 == name2 + && match (value1, value2) { + (Some((a, _)), Some((b, _))) => a == b, + (None, None) => true, + _ => false, + } + } + (Self::Version(a, _), Self::Version(b, _)) => a == b, + _ => false, + } + } } /// Possible values for the `#[linkage]` attribute, allowing to specify the diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 38d3a47f8ca06..9b59e7ce6befd 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -280,13 +280,12 @@ impl Cfg { fn should_append_only_to_description(&self) -> bool { match self.0 { - CfgEntry::Bool(..) => false, CfgEntry::Any(..) | CfgEntry::All(..) | CfgEntry::NameValue { .. } - | CfgEntry::Version(..) => true, - CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true, - CfgEntry::Not(..) => false, + | CfgEntry::Version(..) + | CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true, + CfgEntry::Not(..) | CfgEntry::Bool(..) => false, } } @@ -347,25 +346,25 @@ impl ops::BitAndAssign for Cfg { (s @ CfgEntry::Bool(true, _), b) => *s = b, (CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => { for c in b.drain(..) { - if !a.contains(&c) { + if !a.iter().any(|a| a.is_equivalent_to(&c)) { a.push(c); } } } (CfgEntry::All(a, _), ref mut b) => { - if !a.contains(b) { + if !a.iter().any(|a| a.is_equivalent_to(b)) { a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } (s, CfgEntry::All(mut a, _)) => { let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); - if !a.contains(&b) { + if !a.iter().any(|a| a.is_equivalent_to(&b)) { a.push(b); } *s = CfgEntry::All(a, DUMMY_SP); } (s, b) => { - if *s != b { + if !s.is_equivalent_to(&b) { let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); *s = CfgEntry::All(thin_vec![a, b], DUMMY_SP); } @@ -391,25 +390,25 @@ impl ops::BitOrAssign for Cfg { (s @ CfgEntry::Bool(false, _), b) => *s = b, (CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => { for c in b.drain(..) { - if !a.contains(&c) { + if !a.iter().any(|a| a.is_equivalent_to(&c)) { a.push(c); } } } (CfgEntry::Any(a, _), ref mut b) => { - if !a.contains(b) { + if !a.iter().any(|a| a.is_equivalent_to(b)) { a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } (s, CfgEntry::Any(mut a, _)) => { let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); - if !a.contains(&b) { + if !a.iter().any(|a| a.is_equivalent_to(&b)) { a.push(b); } *s = CfgEntry::Any(a, DUMMY_SP); } (s, b) => { - if *s != b { + if !s.is_equivalent_to(&b) { let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); *s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP); } @@ -757,6 +756,7 @@ fn handle_auto_cfg_hide_show( { for item in items { // FIXME: Report in case `Cfg::parse` reports an error? + let Ok(cfg) = Cfg::parse(item) else { continue }; if let CfgEntry::NameValue { name, value, .. } = cfg.0 { let value = value.map(|(v, _)| v); From d35ec316c5496b2eda68ba05b9dd28ba94bfa87e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 1 Dec 2025 15:31:10 +0100 Subject: [PATCH 09/32] Keep a list of `CfgEntry` instead of just one --- compiler/rustc_attr_parsing/src/attributes/doc.rs | 5 ++++- compiler/rustc_hir/src/attrs/data_structures.rs | 4 ++-- src/librustdoc/clean/cfg.rs | 12 ++++++------ src/librustdoc/passes/collect_trait_impls.rs | 2 +- src/librustdoc/passes/propagate_doc_cfg.rs | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 1a7d8ec93f702..5e48ade6b7cf0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -225,7 +225,7 @@ impl DocParser { args: &ArgParser<'_>, ) { if let Some(cfg_entry) = super::cfg::parse_cfg(cx, args) { - self.attribute.cfg = Some(cfg_entry); + self.attribute.cfg.push(cfg_entry); } } @@ -524,6 +524,9 @@ impl AttributeParser for DocParser { fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if self.nb_doc_attrs != 0 { + if std::env::var("LOL").is_ok() { + eprintln!("+++++> {:#?}", self.attribute); + } Some(AttributeKind::Doc(Box::new(self.attribute))) } else { None diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index e35af0853651d..323a1290bb2a8 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -479,7 +479,7 @@ pub struct DocAttribute { pub inline: Option<(DocInline, Span)>, // unstable - pub cfg: Option, + pub cfg: ThinVec, pub auto_cfg: ThinVec, /// This is for `#[doc(auto_cfg = false|true)]`. pub auto_cfg_change: Option<(bool, Span)>, @@ -512,7 +512,7 @@ impl Default for DocAttribute { aliases: FxIndexMap::default(), hidden: None, inline: None, - cfg: None, + cfg: ThinVec::new(), auto_cfg: ThinVec::new(), auto_cfg_change: None, fake_variadic: None, diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 9b59e7ce6befd..727ad82eeca5c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -24,7 +24,7 @@ use crate::html::escape::Escape; #[cfg(test)] mod tests; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Hash)] pub(crate) struct Cfg(CfgEntry); #[derive(PartialEq, Debug)] @@ -299,13 +299,13 @@ impl Cfg { /// /// See `tests::test_simplify_with` for examples. pub(crate) fn simplify_with(&self, assume: &Self) -> Option { - if self == assume { + if self.0.is_equivalent_to(&assume.0) { None } else if let CfgEntry::All(a, _) = &self.0 { let mut sub_cfgs: ThinVec = if let CfgEntry::All(b, _) = &assume.0 { - a.iter().filter(|a| !b.contains(a)).cloned().collect() + a.iter().filter(|a| !b.iter().any(|b| a.is_equivalent_to(b))).cloned().collect() } else { - a.iter().filter(|&a| *a != assume.0).cloned().collect() + a.iter().filter(|&a| !a.is_equivalent_to(&assume.0)).cloned().collect() }; let len = sub_cfgs.len(); match len { @@ -314,7 +314,7 @@ impl Cfg { _ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))), } } else if let CfgEntry::All(b, _) = &assume.0 - && b.contains(&self.0) + && b.iter().any(|b| b.is_equivalent_to(&self.0)) { None } else { @@ -838,7 +838,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator cfg_info.parent_is_doc_cfg = true; } for attr in doc_cfg { - if let Some(new_cfg) = attr.cfg.clone() { + for new_cfg in attr.cfg.clone() { cfg_info.current_cfg &= Cfg(new_cfg); } } diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index d71c3eaa2dcb9..357d00ef6521e 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -67,7 +67,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> let mut parent = Some(tcx.parent(impl_def_id)); while let Some(did) = parent { attr_buf.extend(tcx.get_all_attrs(did).iter().filter_map(|attr| match attr { - Attribute::Parsed(AttributeKind::Doc(d)) if d.cfg.is_some() => { + Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => { let mut new_attr = DocAttribute::default(); new_attr.cfg = d.cfg.clone(); Some(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))) diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 422393592c765..95f5537f394c0 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -34,7 +34,7 @@ struct CfgPropagator<'a, 'tcx> { fn add_only_cfg_attributes(attrs: &mut Vec, new_attrs: &[Attribute]) { for attr in new_attrs { if let Attribute::Parsed(AttributeKind::Doc(d)) = attr - && d.cfg.is_some() + && !d.cfg.is_empty() { let mut new_attr = DocAttribute::default(); new_attr.cfg = d.cfg.clone(); From 57870b7242e3b3446abad0bc1672b52a46d92e1f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 1 Dec 2025 17:03:39 +0100 Subject: [PATCH 10/32] Fix `doc(auto_cfg)` attribute parsing --- .../rustc_attr_parsing/src/attributes/doc.rs | 24 ++- .../rustc_hir/src/attrs/data_structures.rs | 8 +- src/librustdoc/clean/cfg.rs | 151 +++++++----------- 3 files changed, 82 insertions(+), 101 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 5e48ade6b7cf0..fb99f43fced86 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -224,7 +224,21 @@ impl DocParser { cx: &'c mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>, ) { - if let Some(cfg_entry) = super::cfg::parse_cfg(cx, args) { + // This function replaces cases like `cfg(all())` with `true`. + fn simplify_cfg(cfg_entry: &mut CfgEntry) { + match cfg_entry { + CfgEntry::All(cfgs, span) if cfgs.is_empty() => { + *cfg_entry = CfgEntry::Bool(true, *span) + } + CfgEntry::Any(cfgs, span) if cfgs.is_empty() => { + *cfg_entry = CfgEntry::Bool(false, *span) + } + CfgEntry::Not(cfg, _) => simplify_cfg(cfg), + _ => {} + } + } + if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) { + simplify_cfg(&mut cfg_entry); self.attribute.cfg.push(cfg_entry); } } @@ -237,7 +251,7 @@ impl DocParser { ) { match args { ArgParser::NoArgs => { - cx.expected_list(args.span().unwrap_or(path.span())); + self.attribute.auto_cfg_change.push((true, path.span())); } ArgParser::List(list) => { for meta in list.mixed() { @@ -303,6 +317,7 @@ impl DocParser { } } } + self.attribute.auto_cfg.push((cfg_hide_show, path.span())); } } ArgParser::NameValue(nv) => { @@ -311,7 +326,7 @@ impl DocParser { cx.emit_lint(AttributeLintKind::DocAutoCfgWrongLiteral, nv.value_span); return; }; - self.attribute.auto_cfg_change = Some((*bool_value, *span)); + self.attribute.auto_cfg_change.push((*bool_value, *span)); } } } @@ -524,9 +539,6 @@ impl AttributeParser for DocParser { fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if self.nb_doc_attrs != 0 { - if std::env::var("LOL").is_ok() { - eprintln!("+++++> {:#?}", self.attribute); - } Some(AttributeKind::Doc(Box::new(self.attribute))) } else { None diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 323a1290bb2a8..4e1de8e5aeae9 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -480,9 +480,9 @@ pub struct DocAttribute { // unstable pub cfg: ThinVec, - pub auto_cfg: ThinVec, - /// This is for `#[doc(auto_cfg = false|true)]`. - pub auto_cfg_change: Option<(bool, Span)>, + pub auto_cfg: ThinVec<(CfgHideShow, Span)>, + /// This is for `#[doc(auto_cfg = false|true)]`/`#[doc(auto_cfg)]`. + pub auto_cfg_change: ThinVec<(bool, Span)>, // builtin pub fake_variadic: Option, @@ -514,7 +514,7 @@ impl Default for DocAttribute { inline: None, cfg: ThinVec::new(), auto_cfg: ThinVec::new(), - auto_cfg_change: None, + auto_cfg_change: ThinVec::new(), fake_variadic: None, keyword: None, attribute: None, diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 727ad82eeca5c..292df1cf95509 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -10,13 +10,13 @@ use itertools::Either; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_hir as hir; use rustc_hir::Attribute; -use rustc_hir::attrs::{AttributeKind, CfgEntry}; +use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow}; use rustc_middle::ty::TyCtxt; use rustc_session::parse::ParseSess; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{DUMMY_SP, Span}; -use {rustc_ast as ast, rustc_hir as hir}; use crate::display::{Joined as _, MaybeDisplay, Wrapped}; use crate::html::escape::Escape; @@ -689,6 +689,12 @@ impl<'a> From<&'a CfgEntry> for SimpleCfg { } } +impl<'a> From<&'a attrs::CfgInfo> for SimpleCfg { + fn from(cfg: &'a attrs::CfgInfo) -> Self { + Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) } + } +} + /// This type keeps track of (doc) cfg information as we go down the item tree. #[derive(Clone, Debug)] pub(crate) struct CfgInfo { @@ -746,37 +752,27 @@ fn show_hide_show_conflict_error( fn handle_auto_cfg_hide_show( tcx: TyCtxt<'_>, cfg_info: &mut CfgInfo, - sub_attr: &MetaItemInner, - is_show: bool, + attr: &CfgHideShow, + attr_span: Span, new_show_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, ) { - if let MetaItemInner::MetaItem(item) = sub_attr - && let MetaItemKind::List(items) = &item.kind - { - for item in items { - // FIXME: Report in case `Cfg::parse` reports an error? - - let Ok(cfg) = Cfg::parse(item) else { continue }; - if let CfgEntry::NameValue { name, value, .. } = cfg.0 { - let value = value.map(|(v, _)| v); - let simple = SimpleCfg::from(&cfg.0); - if is_show { - if let Some(span) = new_hide_attrs.get(&(name, value)) { - show_hide_show_conflict_error(tcx, item.span(), *span); - } else { - new_show_attrs.insert((name, value), item.span()); - } - cfg_info.hidden_cfg.remove(&simple); - } else { - if let Some(span) = new_show_attrs.get(&(name, value)) { - show_hide_show_conflict_error(tcx, item.span(), *span); - } else { - new_hide_attrs.insert((name, value), item.span()); - } - cfg_info.hidden_cfg.insert(simple); - } + for value in &attr.values { + let simple = SimpleCfg::from(value); + if attr.kind == HideOrShow::Show { + if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) { + show_hide_show_conflict_error(tcx, attr_span, *span); + } else { + new_show_attrs.insert((simple.name, simple.value), attr_span); } + cfg_info.hidden_cfg.remove(&simple); + } else { + if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) { + show_hide_show_conflict_error(tcx, attr_span, *span); + } else { + new_hide_attrs.insert((simple.name, simple.value), attr_span); + } + cfg_info.hidden_cfg.insert(simple); } } } @@ -797,7 +793,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator fn check_changed_auto_active_status( changed_auto_active_status: &mut Option, - attr: &ast::MetaItem, + attr_span: Span, cfg_info: &mut CfgInfo, tcx: TyCtxt<'_>, new_value: bool, @@ -807,14 +803,14 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator tcx.sess .dcx() .struct_span_err( - vec![*first_change, attr.span], + vec![*first_change, attr_span], "`auto_cfg` was disabled and enabled more than once on the same item", ) .emit(); return true; } } else { - *changed_auto_active_status = Some(attr.span); + *changed_auto_active_status = Some(attr_span); } cfg_info.auto_cfg_active = new_value; false @@ -826,7 +822,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator let mut doc_cfg = attrs .clone() .filter_map(|attr| match attr { - Attribute::Parsed(AttributeKind::Doc(d)) => Some(d), + Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d), _ => None, }) .peekable(); @@ -850,64 +846,37 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator // We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes. for attr in attrs { - if let Some(ident) = attr.ident() - && ident.name == sym::doc - && let Some(attrs) = attr.meta_item_list() - { - for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) { - let MetaItemInner::MetaItem(attr) = attr else { - continue; - }; - match &attr.kind { - MetaItemKind::Word => { - if check_changed_auto_active_status( - &mut changed_auto_active_status, - attr, - cfg_info, - tcx, - true, - ) { - return None; - } - } - MetaItemKind::NameValue(lit) => { - if let LitKind::Bool(value) = lit.kind { - if check_changed_auto_active_status( - &mut changed_auto_active_status, - attr, - cfg_info, - tcx, - value, - ) { - return None; - } - } - } - MetaItemKind::List(sub_attrs) => { - if check_changed_auto_active_status( - &mut changed_auto_active_status, - attr, - cfg_info, - tcx, - true, - ) { - return None; - } - for sub_attr in sub_attrs.iter() { - if let Some(ident) = sub_attr.ident() - && (ident.name == sym::show || ident.name == sym::hide) - { - handle_auto_cfg_hide_show( - tcx, - cfg_info, - &sub_attr, - ident.name == sym::show, - &mut new_show_attrs, - &mut new_hide_attrs, - ); - } - } - } + if let Attribute::Parsed(AttributeKind::Doc(d)) = attr { + for (new_value, span) in &d.auto_cfg_change { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + *span, + cfg_info, + tcx, + *new_value, + ) { + return None; + } + } + if let Some((_, span)) = d.auto_cfg.first() { + if check_changed_auto_active_status( + &mut changed_auto_active_status, + *span, + cfg_info, + tcx, + true, + ) { + return None; + } + for (value, span) in &d.auto_cfg { + handle_auto_cfg_hide_show( + tcx, + cfg_info, + value, + *span, + &mut new_show_attrs, + &mut new_hide_attrs, + ); } } } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { From 5c47240ad16fc19d093176c606a7529b54401b9e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 2 Dec 2025 16:01:20 +0100 Subject: [PATCH 11/32] Correctly handle `doc(test(attr(...)))` --- Cargo.lock | 2 +- compiler/rustc_attr_parsing/src/attributes/doc.rs | 6 +++--- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/doctest.rs | 2 +- src/librustdoc/doctest/rust.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23708adcc4999..d818d87e08045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4868,9 +4868,9 @@ dependencies = [ "indexmap", "itertools", "minifier", + "proc-macro2", "pulldown-cmark-escape", "regex", - "rustc_proc_macro", "rustdoc-json-types", "serde", "serde_json", diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index fb99f43fced86..26fb53baf2e4d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -115,9 +115,9 @@ impl DocParser { return; }; - // FIXME: convert list into a Vec of `AttributeKind`. - for _ in list.mixed() { - // self.attribute.test_attrs.push(AttributeKind::parse()); + // FIXME: convert list into a Vec of `AttributeKind` because current code is awful. + for attr in list.mixed() { + self.attribute.test_attrs.push(attr.span()); } } Some(name) => { diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 371da896b9fca..dcfc1ffc251ec 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -15,10 +15,10 @@ base64 = "0.21.7" indexmap = { version = "2", features = ["serde"] } itertools = "0.12" minifier = { version = "0.3.5", default-features = false } +proc-macro2 = "1.0.103" pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] } regex = "1" rustdoc-json-types = { path = "../rustdoc-json-types" } -rustc_proc_macro = { path = "../../compiler/rustc_proc_macro" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 7541311be05d7..19e8fe3e3ed2b 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -17,6 +17,7 @@ use std::{panic, str}; pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder}; pub(crate) use markdown::test as test_markdown; +use proc_macro2::{TokenStream, TokenTree}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxHasher, FxIndexMap, FxIndexSet}; use rustc_errors::emitter::HumanReadableErrorType; use rustc_errors::{ColorConfig, DiagCtxtHandle}; @@ -25,7 +26,6 @@ use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{Attribute, CRATE_HIR_ID}; use rustc_interface::interface; use rustc_middle::ty::TyCtxt; -use rustc_proc_macro::{TokenStream, TokenTree}; use rustc_session::config::{self, CrateType, ErrorOutputType, Input}; use rustc_session::lint; use rustc_span::edition::Edition; diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index e119344a806a4..6f294ad962673 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -5,12 +5,12 @@ use std::env; use std::str::FromStr; use std::sync::Arc; +use proc_macro2::{TokenStream, TokenTree}; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{self as hir, Attribute, CRATE_HIR_ID, intravisit}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; -use rustc_proc_macro::{TokenStream, TokenTree}; use rustc_resolve::rustdoc::span_of_fragments; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span}; From 976d45452a90780a3e0b850dfb635232fa2f3058 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 2 Dec 2025 17:25:43 +0100 Subject: [PATCH 12/32] Update rustdoc unit tests --- src/librustdoc/clean/cfg.rs | 3 + src/librustdoc/clean/cfg/tests.rs | 262 +++++++++++++++++----------- src/librustdoc/clean/types/tests.rs | 5 +- 3 files changed, 168 insertions(+), 102 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 292df1cf95509..99409cf838cd6 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -25,6 +25,9 @@ use crate::html::escape::Escape; mod tests; #[derive(Clone, Debug, Hash)] +// Because `CfgEntry` includes `Span`, we must NEVER use `==`/`!=` operators on `Cfg` and instead +// use `is_equivalent_to`. +#[cfg_attr(test, derive(PartialEq))] pub(crate) struct Cfg(CfgEntry); #[derive(PartialEq, Debug)] diff --git a/src/librustdoc/clean/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs index f0591295da644..4eb6c060cbd29 100644 --- a/src/librustdoc/clean/cfg/tests.rs +++ b/src/librustdoc/clean/cfg/tests.rs @@ -1,23 +1,62 @@ use rustc_ast::ast::LitIntType; use rustc_ast::{MetaItemInner, MetaItemLit, Path, Safety, StrStyle}; use rustc_data_structures::thin_vec::thin_vec; +use rustc_hir::attrs::CfgEntry; use rustc_span::symbol::{Ident, kw}; use rustc_span::{DUMMY_SP, create_default_session_globals_then}; use super::*; -fn word_cfg(s: &str) -> Cfg { - Cfg::Cfg(Symbol::intern(s), None) +fn word_cfg(name: &str) -> Cfg { + Cfg(word_cfg_e(name)) +} + +fn word_cfg_e(name: &str) -> CfgEntry { + CfgEntry::NameValue { + name: Symbol::intern(name), + name_span: DUMMY_SP, + value: None, + span: DUMMY_SP, + } } fn name_value_cfg(name: &str, value: &str) -> Cfg { - Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value))) + Cfg(name_value_cfg_e(name, value)) +} + +fn name_value_cfg_e(name: &str, value: &str) -> CfgEntry { + CfgEntry::NameValue { + name: Symbol::intern(name), + name_span: DUMMY_SP, + value: Some((Symbol::intern(value), DUMMY_SP)), + span: DUMMY_SP, + } } fn dummy_lit(symbol: Symbol, kind: LitKind) -> MetaItemInner { MetaItemInner::Lit(MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP }) } +fn cfg_all(v: ThinVec) -> Cfg { + Cfg(cfg_all_e(v)) +} + +fn cfg_all_e(v: ThinVec) -> CfgEntry { + CfgEntry::All(v, DUMMY_SP) +} + +fn cfg_any(v: ThinVec) -> Cfg { + Cfg(cfg_any_e(v)) +} + +fn cfg_any_e(v: ThinVec) -> CfgEntry { + CfgEntry::Any(v, DUMMY_SP) +} + +fn cfg_not(v: CfgEntry) -> Cfg { + Cfg(CfgEntry::Not(Box::new(v), DUMMY_SP)) +} + fn dummy_meta_item_word(name: &str) -> MetaItemInner { MetaItemInner::MetaItem(MetaItem { unsafety: Safety::Default, @@ -63,40 +102,48 @@ macro_rules! dummy_meta_item_list { }; } +fn cfg_true() -> Cfg { + Cfg(CfgEntry::Bool(true, DUMMY_SP)) +} + +fn cfg_false() -> Cfg { + Cfg(CfgEntry::Bool(false, DUMMY_SP)) +} + #[test] fn test_cfg_not() { create_default_session_globals_then(|| { - assert_eq!(!Cfg::False, Cfg::True); - assert_eq!(!Cfg::True, Cfg::False); - assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test")))); + assert_eq!(!cfg_false(), cfg_true()); + assert_eq!(!cfg_true(), cfg_false()); + assert_eq!(!word_cfg("test"), cfg_not(word_cfg_e("test"))); assert_eq!( - !Cfg::All(vec![word_cfg("a"), word_cfg("b")]), - Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")]))) + !cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b")]), + cfg_not(cfg_all_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")])) ); assert_eq!( - !Cfg::Any(vec![word_cfg("a"), word_cfg("b")]), - Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")]))) + !cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b")]), + cfg_not(cfg_any_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")])) ); - assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test")); + assert_eq!(!cfg_not(word_cfg_e("test")), word_cfg("test")); }) } #[test] fn test_cfg_and() { create_default_session_globals_then(|| { - let mut x = Cfg::False; - x &= Cfg::True; - assert_eq!(x, Cfg::False); + let mut x = cfg_false(); + x &= cfg_true(); + assert_eq!(x, cfg_false()); x = word_cfg("test"); - x &= Cfg::False; - assert_eq!(x, Cfg::False); + x &= cfg_false(); + assert_eq!(x, cfg_false()); x = word_cfg("test2"); - x &= Cfg::True; + x &= cfg_true(); assert_eq!(x, word_cfg("test2")); - x = Cfg::True; + x = cfg_true(); x &= word_cfg("test3"); assert_eq!(x, word_cfg("test3")); @@ -104,63 +151,69 @@ fn test_cfg_and() { assert_eq!(x, word_cfg("test3")); x &= word_cfg("test4"); - assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")])); + assert_eq!(x, cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")])); x &= word_cfg("test4"); - assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")])); + assert_eq!(x, cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")])); x &= word_cfg("test5"); - assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")])); + assert_eq!( + x, + cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4"), word_cfg_e("test5")]) + ); - x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]); + x &= cfg_all(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]); assert_eq!( x, - Cfg::All(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), + cfg_all(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), ]) ); - x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]); + x &= cfg_all(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]); assert_eq!( x, - Cfg::All(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), + cfg_all(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), ]) ); - let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]); + let mut y = cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b")]); y &= x; assert_eq!( y, - Cfg::All(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), - Cfg::Any(vec![word_cfg("a"), word_cfg("b")]), + cfg_all(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), + cfg_any_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]), ]) ); let mut z = word_cfg("test8"); - z &= Cfg::All(vec![word_cfg("test9"), word_cfg("test10")]); - assert_eq!(z, Cfg::All(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")])); + z &= cfg_all(thin_vec![word_cfg_e("test9"), word_cfg_e("test10")]); + assert_eq!( + z, + cfg_all(thin_vec![word_cfg_e("test9"), word_cfg_e("test10"), word_cfg_e("test8"),]), + ); let mut z = word_cfg("test11"); - z &= Cfg::All(vec![word_cfg("test11"), word_cfg("test12")]); - assert_eq!(z, Cfg::All(vec![word_cfg("test11"), word_cfg("test12")])); + z &= cfg_all(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]); + assert_eq!(z, cfg_all(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")])); assert_eq!( word_cfg("a") & word_cfg("b") & word_cfg("c"), - Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")]) + cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b"), word_cfg_e("c")]) ); }) } @@ -168,19 +221,19 @@ fn test_cfg_and() { #[test] fn test_cfg_or() { create_default_session_globals_then(|| { - let mut x = Cfg::True; - x |= Cfg::False; - assert_eq!(x, Cfg::True); + let mut x = cfg_true(); + x |= cfg_false(); + assert_eq!(x, cfg_true()); x = word_cfg("test"); - x |= Cfg::True; + x |= cfg_true(); assert_eq!(x, word_cfg("test")); x = word_cfg("test2"); - x |= Cfg::False; + x |= cfg_false(); assert_eq!(x, word_cfg("test2")); - x = Cfg::False; + x = cfg_false(); x |= word_cfg("test3"); assert_eq!(x, word_cfg("test3")); @@ -188,63 +241,69 @@ fn test_cfg_or() { assert_eq!(x, word_cfg("test3")); x |= word_cfg("test4"); - assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")])); + assert_eq!(x, cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")])); x |= word_cfg("test4"); - assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")])); + assert_eq!(x, cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")])); x |= word_cfg("test5"); - assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")])); + assert_eq!( + x, + cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4"), word_cfg_e("test5")]) + ); - x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]); + x |= cfg_any(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]); assert_eq!( x, - Cfg::Any(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), + cfg_any(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), ]) ); - x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]); + x |= cfg_any(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]); assert_eq!( x, - Cfg::Any(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), + cfg_any(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), ]) ); - let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]); + let mut y = cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b")]); y |= x; assert_eq!( y, - Cfg::Any(vec![ - word_cfg("test3"), - word_cfg("test4"), - word_cfg("test5"), - word_cfg("test6"), - word_cfg("test7"), - Cfg::All(vec![word_cfg("a"), word_cfg("b")]), + cfg_any(thin_vec![ + word_cfg_e("test3"), + word_cfg_e("test4"), + word_cfg_e("test5"), + word_cfg_e("test6"), + word_cfg_e("test7"), + cfg_all_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]), ]) ); let mut z = word_cfg("test8"); - z |= Cfg::Any(vec![word_cfg("test9"), word_cfg("test10")]); - assert_eq!(z, Cfg::Any(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")])); + z |= cfg_any(thin_vec![word_cfg_e("test9"), word_cfg_e("test10")]); + assert_eq!( + z, + cfg_any(thin_vec![word_cfg_e("test9"), word_cfg_e("test10"), word_cfg_e("test8")]) + ); let mut z = word_cfg("test11"); - z |= Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")]); - assert_eq!(z, Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")])); + z |= cfg_any(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]); + assert_eq!(z, cfg_any(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")])); assert_eq!( word_cfg("a") | word_cfg("b") | word_cfg("c"), - Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")]) + cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b"), word_cfg_e("c")]) ); }) } @@ -254,11 +313,11 @@ fn test_parse_ok() { create_default_session_globals_then(|| { let r#true = Symbol::intern("true"); let mi = dummy_lit(r#true, LitKind::Bool(true)); - assert_eq!(Cfg::parse(&mi), Ok(Cfg::True)); + assert_eq!(Cfg::parse(&mi), Ok(cfg_true())); let r#false = Symbol::intern("false"); let mi = dummy_lit(r#false, LitKind::Bool(false)); - assert_eq!(Cfg::parse(&mi), Ok(Cfg::False)); + assert_eq!(Cfg::parse(&mi), Ok(cfg_false())); let mi = dummy_meta_item_word("all"); assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all"))); @@ -464,33 +523,36 @@ fn test_simplify_with() { // This is a tiny subset of things that could be simplified, but it likely covers 90% of // real world usecases well. create_default_session_globals_then(|| { - let foo = word_cfg("foo"); - let bar = word_cfg("bar"); - let baz = word_cfg("baz"); - let quux = word_cfg("quux"); + let foo = word_cfg_e("foo"); + let bar = word_cfg_e("bar"); + let baz = word_cfg_e("baz"); + let quux = word_cfg_e("quux"); - let foobar = Cfg::All(vec![foo.clone(), bar.clone()]); - let barbaz = Cfg::All(vec![bar.clone(), baz.clone()]); - let foobarbaz = Cfg::All(vec![foo.clone(), bar.clone(), baz.clone()]); - let bazquux = Cfg::All(vec![baz.clone(), quux.clone()]); + let foobar = cfg_all(thin_vec![foo.clone(), bar.clone()]); + let barbaz = cfg_all(thin_vec![bar.clone(), baz.clone()]); + let foobarbaz = cfg_all(thin_vec![foo.clone(), bar.clone(), baz.clone()]); + let bazquux = cfg_all(thin_vec![baz.clone(), quux.clone()]); // Unrelated cfgs don't affect each other - assert_eq!(foo.simplify_with(&bar).as_ref(), Some(&foo)); + assert_eq!( + Cfg(foo.clone()).simplify_with(&Cfg(bar.clone())).as_ref(), + Some(&Cfg(foo.clone())) + ); assert_eq!(foobar.simplify_with(&bazquux).as_ref(), Some(&foobar)); // Identical cfgs are eliminated - assert_eq!(foo.simplify_with(&foo), None); + assert_eq!(Cfg(foo.clone()).simplify_with(&Cfg(foo.clone())), None); assert_eq!(foobar.simplify_with(&foobar), None); // Multiple cfgs eliminate a single assumed cfg - assert_eq!(foobar.simplify_with(&foo).as_ref(), Some(&bar)); - assert_eq!(foobar.simplify_with(&bar).as_ref(), Some(&foo)); + assert_eq!(foobar.simplify_with(&Cfg(foo.clone())).as_ref(), Some(&Cfg(bar.clone()))); + assert_eq!(foobar.simplify_with(&Cfg(bar)).as_ref(), Some(&Cfg(foo.clone()))); // A single cfg is eliminated by multiple assumed cfg containing it - assert_eq!(foo.simplify_with(&foobar), None); + assert_eq!(Cfg(foo.clone()).simplify_with(&foobar), None); // Multiple cfgs eliminate the matching subset of multiple assumed cfg - assert_eq!(foobar.simplify_with(&barbaz).as_ref(), Some(&foo)); + assert_eq!(foobar.simplify_with(&barbaz).as_ref(), Some(&Cfg(foo))); assert_eq!(foobar.simplify_with(&foobarbaz), None); }); } diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 9499507b2c0f9..915b7d851a3e3 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -1,4 +1,5 @@ -use rustc_resolve::rustdoc::{DocFragmentKind, unindent_doc_fragments}; +use rustc_ast::token::{CommentKind, DocFragmentKind}; +use rustc_resolve::rustdoc::unindent_doc_fragments; use rustc_span::create_default_session_globals_then; use super::*; @@ -8,7 +9,7 @@ fn create_doc_fragment(s: &str) -> Vec { span: DUMMY_SP, item_id: None, doc: Symbol::intern(s), - kind: DocFragmentKind::SugaredDoc, + kind: DocFragmentKind::Sugared(CommentKind::Line), indent: 0, from_expansion: false, }] From 9c8c67bfdd864f6ba6eb1bf23b57cd81499c4e2d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 3 Dec 2025 15:55:57 +0100 Subject: [PATCH 13/32] Fix warning messages --- .../rustc_attr_parsing/src/attributes/doc.rs | 17 +- .../src/session_diagnostics.rs | 9 - .../rustc_hir/src/attrs/data_structures.rs | 6 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_passes/messages.ftl | 12 +- compiler/rustc_passes/src/check_attr.rs | 28 ++- compiler/rustc_passes/src/errors.rs | 8 + src/librustdoc/clean/mod.rs | 14 +- src/librustdoc/lib.rs | 14 ++ src/librustdoc/visit_ast.rs | 14 +- tests/rustdoc-ui/bad-render-options.rs | 36 ++- tests/rustdoc-ui/bad-render-options.stderr | 209 +++++++++++++++--- .../rustdoc-ui/cfg-hide-show-conflict.stderr | 8 +- .../check-doc-alias-attr-location.stderr | 16 +- tests/rustdoc-ui/check-doc-alias-attr.stderr | 61 +++-- tests/rustdoc-ui/doc-alias-assoc-const.stderr | 4 +- tests/rustdoc-ui/doc-alias-same-name.stderr | 6 +- .../rustdoc-ui/doc-include-suggestion.stderr | 2 +- tests/rustdoc-ui/lints/doc_cfg_hide.stderr | 8 +- tests/rustdoc-ui/lints/invalid-doc-attr.rs | 2 - .../rustdoc-ui/lints/invalid-doc-attr.stderr | 36 +-- 21 files changed, 377 insertions(+), 135 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 26fb53baf2e4d..1fd7b702e92fd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -2,7 +2,6 @@ #![allow(unused_imports)] use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; -use rustc_errors::MultiSpan; use rustc_feature::template; use rustc_hir::attrs::{ AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow, @@ -18,7 +17,7 @@ use crate::fluent_generated as fluent; use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser}; use crate::session_diagnostics::{ DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute, - DocKeywordConflict, DocKeywordNotKeyword, + DocKeywordNotKeyword, }; fn check_keyword(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> bool { @@ -204,19 +203,7 @@ impl DocParser { return; } - let span = path.span(); - - if let Some((prev_inline, prev_span)) = self.attribute.inline { - if prev_inline == inline { - let mut spans = MultiSpan::from_spans(vec![prev_span, span]); - spans.push_span_label(prev_span, fluent::attr_parsing_doc_inline_conflict_first); - spans.push_span_label(span, fluent::attr_parsing_doc_inline_conflict_second); - cx.emit_err(DocKeywordConflict { spans }); - return; - } - } - - self.attribute.inline = Some((inline, span)); + self.attribute.inline.push((inline, path.span())); } fn parse_cfg<'c, S: Stage>( diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 26b615448e3ba..38ae597000d2b 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -4,7 +4,6 @@ use rustc_ast::{self as ast}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, - MultiSpan, }; use rustc_feature::AttributeTemplate; use rustc_hir::AttrPath; @@ -589,14 +588,6 @@ pub(crate) struct DocAliasDuplicated { pub first_defn: Span, } -#[derive(Diagnostic)] -#[diag(attr_parsing_doc_inline_conflict)] -#[help] -pub(crate) struct DocKeywordConflict { - #[primary_span] - pub spans: MultiSpan, -} - #[derive(Diagnostic)] #[diag(attr_parsing_link_ordinal_out_of_range)] #[note] diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 4e1de8e5aeae9..0419e219afe40 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -476,7 +476,9 @@ pub struct CfgHideShow { pub struct DocAttribute { pub aliases: FxIndexMap, pub hidden: Option, - pub inline: Option<(DocInline, Span)>, + // Because we need to emit the error if there is more than one `inline` attribute on an item + // at the same time as the other doc attributes, we store a list instead of using `Option`. + pub inline: ThinVec<(DocInline, Span)>, // unstable pub cfg: ThinVec, @@ -511,7 +513,7 @@ impl Default for DocAttribute { Self { aliases: FxIndexMap::default(), hidden: None, - inline: None, + inline: ThinVec::new(), cfg: ThinVec::new(), auto_cfg: ThinVec::new(), auto_cfg_change: ThinVec::new(), diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 9a1cf7f349fdf..2069c06c3f774 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -880,7 +880,7 @@ fn analyze_attr(attr: &hir::Attribute, state: &mut AnalyzeAttrState<'_>) -> bool } 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 d.inline.is_none() { + if d.inline.is_empty() { should_encode = true; if d.hidden.is_some() { state.is_doc_hidden = true; diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 007ce22c01e65..4e8973e4928b1 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -107,10 +107,10 @@ passes_diagnostic_item_first_defined = the diagnostic item is first defined here passes_doc_alias_bad_location = - doc alias attribute isn't allowed on {$location} + `#[doc(alias = "...")]` isn't allowed on {$location} passes_doc_alias_not_an_alias = - {$attr_str} is the same as the item's name + `#[doc(alias = "{$attr_str}"]` is the same as the item's name passes_doc_attr_not_crate_level = `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute @@ -118,7 +118,15 @@ passes_doc_attr_not_crate_level = passes_doc_fake_variadic_not_valid = `#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity +passes_doc_inline_conflict = + conflicting doc inlining attributes + .help = remove one of the conflicting attributes +passes_doc_inline_conflict_first = + this attribute... + +passes_doc_inline_conflict_second = + {"."}..conflicts with this attribute passes_doc_inline_only_use = this attribute can only be applied to a `use` item diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index bae15de4bf886..3f29d943e7d32 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -13,13 +13,14 @@ use rustc_abi::{Align, ExternAbi, Size}; use rustc_ast::{AttrStyle, LitKind, MetaItemKind, ast}; use rustc_attr_parsing::{AttributeParser, Late}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagCtxtHandle, IntoDiagArg, StashKey}; +use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey}; use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, }; use rustc_hir::attrs::{ - AttributeKind, DocAttribute, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet, + AttributeKind, DocAttribute, DocInline, InlineAttr, MirDialect, MirPhase, ReprAttr, + SanitizerSet, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; @@ -881,7 +882,24 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// already seen an inlining attribute for this item. /// If so, `specified_inline` holds the value and the span of /// the first `inline`/`no_inline` attribute. - fn check_doc_inline(&self, span: Span, hir_id: HirId, target: Target) { + fn check_doc_inline(&self, hir_id: HirId, target: Target, inline: &[(DocInline, Span)]) { + let span = match inline { + [] => return, + [(_, span)] => *span, + [(inline, span), rest @ ..] => { + for (inline2, span2) in rest { + if inline2 != inline { + let mut spans = MultiSpan::from_spans(vec![*span, *span2]); + spans.push_span_label(*span, fluent::passes_doc_inline_conflict_first); + spans.push_span_label(*span2, fluent::passes_doc_inline_conflict_second); + self.dcx().emit_err(errors::DocInlineConflict { spans }); + return; + } + } + *span + } + }; + match target { Target::Use | Target::ExternCrate => {} _ => { @@ -1050,9 +1068,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - if let Some((_, span)) = inline { - self.check_doc_inline(*span, hir_id, target) - } + self.check_doc_inline(hir_id, target, inline); if let Some(span) = rust_logo { if self.check_attr_crate_level(*span, hir_id) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index b693aaf769236..3a2908d141845 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -176,6 +176,14 @@ pub(crate) struct DocSearchUnboxInvalid { pub span: Span, } +#[derive(Diagnostic)] +#[diag(passes_doc_inline_conflict)] +#[help] +pub(crate) struct DocInlineConflict { + #[primary_span] + pub spans: MultiSpan, +} + #[derive(LintDiagnostic)] #[diag(passes_doc_inline_only_use)] #[note] diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index bea4398ccf862..dd5c50d2ba37c 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -199,7 +199,7 @@ fn generate_item_with_correct_attrs( let import_is_inline = find_attr!( inline::load_attrs(cx, import_id.to_def_id()), AttributeKind::Doc(d) - if d.inline.is_some_and(|(inline, _)| inline == DocInline::Inline) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) ) || (is_glob_import(cx.tcx, import_id) && (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id))); attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline)); @@ -2921,7 +2921,7 @@ fn clean_extern_crate<'tcx>( matches!( a, hir::Attribute::Parsed(AttributeKind::Doc(d)) - if d.inline.is_some_and(|(i, _)| i == DocInline::Inline)) + if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline)) }) && !cx.is_json_output(); @@ -2986,9 +2986,9 @@ fn clean_use_statement_inner<'tcx>( let attrs = cx.tcx.hir_attrs(import.hir_id()); let inline_attr = find_attr!( attrs, - AttributeKind::Doc(d) if d.inline.is_some_and(|(i, _)| i == DocInline::Inline) => d + AttributeKind::Doc(d) if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline) => d ) - .and_then(|d| d.inline); + .and_then(|d| d.inline.first()); let pub_underscore = visibility.is_public() && name == Some(kw::Underscore); let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id); let import_def_id = import.owner_id.def_id; @@ -3009,7 +3009,7 @@ fn clean_use_statement_inner<'tcx>( if pub_underscore && let Some((_, inline_span)) = inline_attr { struct_span_code_err!( cx.tcx.dcx(), - inline_span, + *inline_span, E0780, "anonymous imports cannot be inlined" ) @@ -3026,7 +3026,9 @@ fn clean_use_statement_inner<'tcx>( || pub_underscore || attrs.iter().any(|a| matches!( a, - hir::Attribute::Parsed(AttributeKind::Doc(d)) if d.hidden.is_some() || d.inline.is_some_and(|(i, _)| i == DocInline::NoInline))); + hir::Attribute::Parsed(AttributeKind::Doc(d)) + if d.hidden.is_some() || d.inline.first().is_some_and(|(i, _)| *i == DocInline::NoInline) + )); // Also check whether imports were asked to be inlined, in case we're trying to re-export a // crate in Rust 2018+ diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index a0e359032f9b6..13be1a04dbc5d 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -32,6 +32,7 @@ extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; +extern crate rustc_attr_parsing; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; @@ -75,6 +76,7 @@ use std::process; use rustc_errors::DiagCtxtHandle; use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::lints::DelayedLint; use rustc_interface::interface; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ErrorOutputType, RustcOptGroup, make_crate_type_option}; @@ -900,6 +902,18 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { return; } + for owner_id in tcx.hir_crate_items(()).delayed_lint_items() { + if let Some(delayed_lints) = tcx.opt_ast_lowering_delayed_lints(owner_id) { + for lint in &delayed_lints.lints { + match lint { + DelayedLint::AttributeParsing(attribute_lint) => { + rustc_attr_parsing::emit_attribute_lint(attribute_lint, tcx) + } + } + } + } + } + if render_opts.dep_info().is_some() { rustc_interface::passes::write_dep_info(tcx); } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index d6da8615d57ea..0f4460bed35a7 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -247,8 +247,12 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let document_hidden = self.cx.document_hidden(); let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id)); // Don't inline `doc(hidden)` imports so they can be stripped at a later stage. - let is_no_inline = find_attr!(use_attrs, AttributeKind::Doc(d) if d.inline.is_some_and(|(inline, _)| inline == DocInline::NoInline)) - || (document_hidden && use_attrs.iter().any(|attr| attr.is_doc_hidden())); + let is_no_inline = find_attr!( + use_attrs, + AttributeKind::Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline) + ) || (document_hidden + && use_attrs.iter().any(|attr| attr.is_doc_hidden())); if is_no_inline { return false; @@ -465,7 +469,11 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. if is_pub && self.inside_public_path { - let please_inline = find_attr!(attrs, AttributeKind::Doc(d) if d.inline.is_some_and(|(inline, _)| inline == DocInline::Inline)); + let please_inline = find_attr!( + attrs, + AttributeKind::Doc(d) + if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline) + ); let ident = match kind { hir::UseKind::Single(ident) => Some(ident.name), hir::UseKind::Glob => None, diff --git a/tests/rustdoc-ui/bad-render-options.rs b/tests/rustdoc-ui/bad-render-options.rs index f2cfd4b76fa8c..0522f68cb6c23 100644 --- a/tests/rustdoc-ui/bad-render-options.rs +++ b/tests/rustdoc-ui/bad-render-options.rs @@ -1,11 +1,29 @@ // regression test for https://github.com/rust-lang/rust/issues/149187 -#![doc(html_favicon_url)] //~ ERROR: `doc(html_favicon_url)` expects a string value [invalid_doc_attributes] -#![doc(html_logo_url)] //~ ERROR: `doc(html_logo_url)` expects a string value [invalid_doc_attributes] -#![doc(html_playground_url)] //~ ERROR: `doc(html_playground_url)` expects a string value [invalid_doc_attributes] -#![doc(issue_tracker_base_url)] //~ ERROR expects a string value -#![doc(html_favicon_url = 1)] //~ ERROR expects a string value -#![doc(html_logo_url = 2)] //~ ERROR expects a string value -#![doc(html_playground_url = 3)] //~ ERROR expects a string value -#![doc(issue_tracker_base_url = 4)] //~ ERROR expects a string value -#![doc(html_no_source = "asdf")] //~ ERROR `doc(html_no_source)` does not accept a value [invalid_doc_attributes] +#![doc(html_favicon_url)] +//~^ ERROR: malformed `doc` attribute +//~| NOTE expected this to be of the form `html_favicon_url = "..."` +#![doc(html_logo_url)] +//~^ ERROR: malformed `doc` attribute +//~| NOTE expected this to be of the form `html_logo_url = "..."` +#![doc(html_playground_url)] +//~^ ERROR: malformed `doc` attribute +//~| NOTE expected this to be of the form `html_playground_url = "..."` +#![doc(issue_tracker_base_url)] +//~^ ERROR: malformed `doc` attribute +//~| NOTE expected this to be of the form `issue_tracker_base_url = "..."` +#![doc(html_favicon_url = 1)] +//~^ ERROR malformed `doc` attribute +//~| NOTE expected a string literal +#![doc(html_logo_url = 2)] +//~^ ERROR malformed `doc` attribute +//~| NOTE expected a string literal +#![doc(html_playground_url = 3)] +//~^ ERROR malformed `doc` attribute +//~| NOTE expected a string literal +#![doc(issue_tracker_base_url = 4)] +//~^ ERROR malformed `doc` attribute +//~| NOTE expected a string literal +#![doc(html_no_source = "asdf")] +//~^ ERROR malformed `doc` attribute +//~| NOTE didn't expect any arguments here diff --git a/tests/rustdoc-ui/bad-render-options.stderr b/tests/rustdoc-ui/bad-render-options.stderr index 9d503363c0bd9..e7f33f4dff1d7 100644 --- a/tests/rustdoc-ui/bad-render-options.stderr +++ b/tests/rustdoc-ui/bad-render-options.stderr @@ -1,58 +1,211 @@ -error: `doc(html_favicon_url)` expects a string value - --> $DIR/bad-render-options.rs:3:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:3:1 | LL | #![doc(html_favicon_url)] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^----------------^^ + | | + | expected this to be of the form `html_favicon_url = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_favicon_url)] +LL + #![doc = "string"] + | +LL - #![doc(html_favicon_url)] +LL + #![doc(hidden)] + | +LL - #![doc(html_favicon_url)] +LL + #![doc(inline)] + | +LL - #![doc(html_favicon_url)] +LL + #![doc(test)] | - = note: `#[deny(invalid_doc_attributes)]` on by default -error: `doc(html_logo_url)` expects a string value - --> $DIR/bad-render-options.rs:4:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:6:1 | LL | #![doc(html_logo_url)] - | ^^^^^^^^^^^^^ + | ^^^^^^^-------------^^ + | | + | expected this to be of the form `html_logo_url = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_logo_url)] +LL + #![doc = "string"] + | +LL - #![doc(html_logo_url)] +LL + #![doc(hidden)] + | +LL - #![doc(html_logo_url)] +LL + #![doc(inline)] + | +LL - #![doc(html_logo_url)] +LL + #![doc(test)] + | -error: `doc(html_playground_url)` expects a string value - --> $DIR/bad-render-options.rs:5:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:9:1 | LL | #![doc(html_playground_url)] - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^-------------------^^ + | | + | expected this to be of the form `html_playground_url = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_playground_url)] +LL + #![doc = "string"] + | +LL - #![doc(html_playground_url)] +LL + #![doc(hidden)] + | +LL - #![doc(html_playground_url)] +LL + #![doc(inline)] + | +LL - #![doc(html_playground_url)] +LL + #![doc(test)] + | -error: `doc(issue_tracker_base_url)` expects a string value - --> $DIR/bad-render-options.rs:6:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:12:1 | LL | #![doc(issue_tracker_base_url)] - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^----------------------^^ + | | + | expected this to be of the form `issue_tracker_base_url = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc = "string"] + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc(hidden)] + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc(inline)] + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc(test)] + | -error: `doc(html_favicon_url)` expects a string value - --> $DIR/bad-render-options.rs:7:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:15:1 | LL | #![doc(html_favicon_url = 1)] - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc = "string"] + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc(hidden)] + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc(inline)] + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc(test)] + | -error: `doc(html_logo_url)` expects a string value - --> $DIR/bad-render-options.rs:8:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:18:1 | LL | #![doc(html_logo_url = 2)] - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc = "string"] + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc(hidden)] + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc(inline)] + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc(test)] + | -error: `doc(html_playground_url)` expects a string value - --> $DIR/bad-render-options.rs:9:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:21:1 | LL | #![doc(html_playground_url = 3)] - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc = "string"] + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc(hidden)] + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc(inline)] + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc(test)] + | -error: `doc(issue_tracker_base_url)` expects a string value - --> $DIR/bad-render-options.rs:10:8 +error[E0539]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:24:1 | LL | #![doc(issue_tracker_base_url = 4)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc = "string"] + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc(hidden)] + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc(inline)] + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc(test)] + | -error: `doc(html_no_source)` does not accept a value - --> $DIR/bad-render-options.rs:11:8 +error[E0565]: malformed `doc` attribute input + --> $DIR/bad-render-options.rs:27:1 | LL | #![doc(html_no_source = "asdf")] - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^--------^^ + | | + | didn't expect any arguments here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc = "string"] + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc(hidden)] + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc(inline)] + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc(test)] + | error: aborting due to 9 previous errors +Some errors have detailed explanations: E0539, E0565. +For more information about an error, try `rustc --explain E0539`. diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr index 22231e82cd7bf..384a9f1a0b1f9 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -1,14 +1,14 @@ error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item - --> $DIR/cfg-hide-show-conflict.rs:3:31 + --> $DIR/cfg-hide-show-conflict.rs:3:8 | LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ | note: first change was here - --> $DIR/cfg-hide-show-conflict.rs:2:22 + --> $DIR/cfg-hide-show-conflict.rs:2:8 | LL | #![doc(auto_cfg(hide(target_os = "linux")))] - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/rustdoc-ui/check-doc-alias-attr-location.stderr b/tests/rustdoc-ui/check-doc-alias-attr-location.stderr index 85c9516236c94..b8e1863ce5608 100644 --- a/tests/rustdoc-ui/check-doc-alias-attr-location.stderr +++ b/tests/rustdoc-ui/check-doc-alias-attr-location.stderr @@ -1,26 +1,26 @@ error: `#[doc(alias = "...")]` isn't allowed on foreign module - --> $DIR/check-doc-alias-attr-location.rs:7:7 + --> $DIR/check-doc-alias-attr-location.rs:7:15 | LL | #[doc(alias = "foo")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: `#[doc(alias = "...")]` isn't allowed on implementation block - --> $DIR/check-doc-alias-attr-location.rs:10:7 + --> $DIR/check-doc-alias-attr-location.rs:10:15 | LL | #[doc(alias = "bar")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: `#[doc(alias = "...")]` isn't allowed on implementation block - --> $DIR/check-doc-alias-attr-location.rs:16:7 + --> $DIR/check-doc-alias-attr-location.rs:16:15 | LL | #[doc(alias = "foobar")] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on type alias in implementation block - --> $DIR/check-doc-alias-attr-location.rs:18:11 + --> $DIR/check-doc-alias-attr-location.rs:18:19 | LL | #[doc(alias = "assoc")] - | ^^^^^^^^^^^^^^^ + | ^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/rustdoc-ui/check-doc-alias-attr.stderr b/tests/rustdoc-ui/check-doc-alias-attr.stderr index 250568be3333f..06d5c65351910 100644 --- a/tests/rustdoc-ui/check-doc-alias-attr.stderr +++ b/tests/rustdoc-ui/check-doc-alias-attr.stderr @@ -4,11 +4,28 @@ error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of s LL | #[doc(alias)] | ^^^^^ -error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` - --> $DIR/check-doc-alias-attr.rs:8:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/check-doc-alias-attr.rs:8:1 | LL | #[doc(alias = 0)] - | ^^^^^^^^^ + | ^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(alias = 0)] +LL + #[doc = "string"] + | +LL - #[doc(alias = 0)] +LL + #[doc(hidden)] + | +LL - #[doc(alias = 0)] +LL + #[doc(inline)] + | +LL - #[doc(alias = 0)] +LL + #[doc(test)] + | error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:9:15 @@ -54,25 +71,42 @@ error: `#[doc(alias = "...")]` attribute cannot have empty value LL | #[doc(alias = "")] | ^^ -error: `#[doc(alias("a"))]` expects string literals - --> $DIR/check-doc-alias-attr.rs:19:13 +error[E0539]: malformed `doc` attribute input + --> $DIR/check-doc-alias-attr.rs:19:1 | LL | #[doc(alias(0))] - | ^ + | ^^^^^^^^^^^^-^^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(alias(0))] +LL + #[doc = "string"] + | +LL - #[doc(alias(0))] +LL + #[doc(hidden)] + | +LL - #[doc(alias(0))] +LL + #[doc(inline)] + | +LL - #[doc(alias(0))] +LL + #[doc(test)] + | -error: '"' character isn't allowed in `#[doc(alias("..."))]` +error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:20:13 | LL | #[doc(alias("\""))] | ^^^^ -error: '\n' character isn't allowed in `#[doc(alias("..."))]` +error: '\n' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:21:13 | LL | #[doc(alias("\n"))] | ^^^^ -error: '\n' character isn't allowed in `#[doc(alias("..."))]` +error: '\n' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:22:13 | LL | #[doc(alias(" @@ -80,25 +114,25 @@ LL | #[doc(alias(" LL | | "))] | |_^ -error: '\t' character isn't allowed in `#[doc(alias("..."))]` +error: '\t' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:24:13 | LL | #[doc(alias("\t"))] | ^^^^ -error: `#[doc(alias("..."))]` cannot start or end with ' ' +error: `#[doc(alias = "...")]` cannot start or end with ' ' --> $DIR/check-doc-alias-attr.rs:25:13 | LL | #[doc(alias(" hello"))] | ^^^^^^^^ -error: `#[doc(alias("..."))]` cannot start or end with ' ' +error: `#[doc(alias = "...")]` cannot start or end with ' ' --> $DIR/check-doc-alias-attr.rs:26:13 | LL | #[doc(alias("hello "))] | ^^^^^^^^ -error: `#[doc(alias("..."))]` attribute cannot have empty value +error: `#[doc(alias = "...")]` attribute cannot have empty value --> $DIR/check-doc-alias-attr.rs:27:13 | LL | #[doc(alias(""))] @@ -106,3 +140,4 @@ LL | #[doc(alias(""))] error: aborting due to 17 previous errors +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/rustdoc-ui/doc-alias-assoc-const.stderr b/tests/rustdoc-ui/doc-alias-assoc-const.stderr index cc628c39400b1..7566ec840da7a 100644 --- a/tests/rustdoc-ui/doc-alias-assoc-const.stderr +++ b/tests/rustdoc-ui/doc-alias-assoc-const.stderr @@ -1,8 +1,8 @@ error: `#[doc(alias = "...")]` isn't allowed on associated constant in trait implementation block - --> $DIR/doc-alias-assoc-const.rs:8:11 + --> $DIR/doc-alias-assoc-const.rs:8:19 | LL | #[doc(alias = "CONST_BAZ")] - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/rustdoc-ui/doc-alias-same-name.stderr b/tests/rustdoc-ui/doc-alias-same-name.stderr index a9da75c0171c0..a76ff5ee0b67f 100644 --- a/tests/rustdoc-ui/doc-alias-same-name.stderr +++ b/tests/rustdoc-ui/doc-alias-same-name.stderr @@ -1,8 +1,8 @@ -error: `#[doc(alias = "...")]` is the same as the item's name - --> $DIR/doc-alias-same-name.rs:3:7 +error: `#[doc(alias = "Foo"]` is the same as the item's name + --> $DIR/doc-alias-same-name.rs:3:15 | LL | #[doc(alias = "Foo")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: aborting due to 1 previous error diff --git a/tests/rustdoc-ui/doc-include-suggestion.stderr b/tests/rustdoc-ui/doc-include-suggestion.stderr index 1b4b78a8f2636..ea5261e5bbd40 100644 --- a/tests/rustdoc-ui/doc-include-suggestion.stderr +++ b/tests/rustdoc-ui/doc-include-suggestion.stderr @@ -2,7 +2,7 @@ error: unknown `doc` attribute `include` --> $DIR/doc-include-suggestion.rs:1:7 | LL | #[doc(include = "external-cross-doc.md")] - | ------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- help: use `doc = include_str!` instead: `#[doc = include_str!("external-cross-doc.md")]` + | ^^^^^^^ help: use `doc = include_str!` instead: `#[doc = include_str!("external-cross-doc.md")]` | = note: `#[deny(invalid_doc_attributes)]` on by default diff --git a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr index c63c8d607fa02..acbe6ef69dd58 100644 --- a/tests/rustdoc-ui/lints/doc_cfg_hide.stderr +++ b/tests/rustdoc-ui/lints/doc_cfg_hide.stderr @@ -1,16 +1,16 @@ error: `#![doc(auto_cfg(hide(...)))]` expects a list of items - --> $DIR/doc_cfg_hide.rs:2:8 + --> $DIR/doc_cfg_hide.rs:2:17 | LL | #![doc(auto_cfg(hide = "test"))] - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ | = note: `#[deny(invalid_doc_attributes)]` on by default error: `#![doc(auto_cfg(hide(...)))]` expects a list of items - --> $DIR/doc_cfg_hide.rs:3:8 + --> $DIR/doc_cfg_hide.rs:3:17 | LL | #![doc(auto_cfg(hide))] - | ^^^^^^^^^^^^^^ + | ^^^^ error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items --> $DIR/doc_cfg_hide.rs:4:22 diff --git a/tests/rustdoc-ui/lints/invalid-doc-attr.rs b/tests/rustdoc-ui/lints/invalid-doc-attr.rs index e1cc08ca24272..a8c42b8fd79c0 100644 --- a/tests/rustdoc-ui/lints/invalid-doc-attr.rs +++ b/tests/rustdoc-ui/lints/invalid-doc-attr.rs @@ -6,8 +6,6 @@ #[doc(test(no_crate_inject))] //~^ ERROR can only be applied at the crate level -//~| HELP to apply to the crate, use an inner attribute -//~| SUGGESTION ! #[doc(inline)] //~^ ERROR can only be applied to a `use` item pub fn foo() {} diff --git a/tests/rustdoc-ui/lints/invalid-doc-attr.stderr b/tests/rustdoc-ui/lints/invalid-doc-attr.stderr index 7621999a8ca52..82e1b62b57a62 100644 --- a/tests/rustdoc-ui/lints/invalid-doc-attr.stderr +++ b/tests/rustdoc-ui/lints/invalid-doc-attr.stderr @@ -1,18 +1,14 @@ error: this attribute can only be applied at the crate level - --> $DIR/invalid-doc-attr.rs:7:7 + --> $DIR/invalid-doc-attr.rs:7:12 | LL | #[doc(test(no_crate_inject))] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = note: read for more information = note: `#[deny(invalid_doc_attributes)]` on by default -help: to apply to the crate, use an inner attribute - | -LL | #![doc(test(no_crate_inject))] - | + error: this attribute can only be applied to a `use` item - --> $DIR/invalid-doc-attr.rs:11:7 + --> $DIR/invalid-doc-attr.rs:9:7 | LL | #[doc(inline)] | ^^^^^^ only applicable on `use` items @@ -23,15 +19,15 @@ LL | pub fn foo() {} = note: read for more information error: this attribute can only be applied at the crate level - --> $DIR/invalid-doc-attr.rs:16:12 + --> $DIR/invalid-doc-attr.rs:14:17 | LL | #![doc(test(no_crate_inject))] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = note: read for more information error: conflicting doc inlining attributes - --> $DIR/invalid-doc-attr.rs:26:7 + --> $DIR/invalid-doc-attr.rs:24:7 | LL | #[doc(inline)] | ^^^^^^ this attribute... @@ -41,7 +37,7 @@ LL | #[doc(no_inline)] = help: remove one of the conflicting attributes error: this attribute can only be applied to an `extern crate` item - --> $DIR/invalid-doc-attr.rs:32:7 + --> $DIR/invalid-doc-attr.rs:30:7 | LL | #[doc(masked)] | ^^^^^^ only applicable on `extern crate` items @@ -52,7 +48,7 @@ LL | pub struct Masked; = note: read for more information error: this attribute cannot be applied to an `extern crate self` item - --> $DIR/invalid-doc-attr.rs:36:7 + --> $DIR/invalid-doc-attr.rs:34:7 | LL | #[doc(masked)] | ^^^^^^ not applicable on `extern crate self` items @@ -63,21 +59,27 @@ LL | pub extern crate self as reexport; error: this attribute can only be applied to an `extern crate` item --> $DIR/invalid-doc-attr.rs:4:8 | -LL | #![doc(masked)] - | ^^^^^^ only applicable on `extern crate` items +LL | / #![crate_type = "lib"] +LL | | #![feature(doc_masked)] +LL | | +LL | | #![doc(masked)] + | | ^^^^^^ only applicable on `extern crate` items +... | +LL | | pub extern crate self as reexport; + | |__________________________________- not an `extern crate` item | = note: read for more information error: this attribute can only be applied at the crate level - --> $DIR/invalid-doc-attr.rs:19:11 + --> $DIR/invalid-doc-attr.rs:17:16 | LL | #[doc(test(no_crate_inject))] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = note: read for more information error: this attribute can only be applied to a `use` item - --> $DIR/invalid-doc-attr.rs:21:11 + --> $DIR/invalid-doc-attr.rs:19:11 | LL | #[doc(inline)] | ^^^^^^ only applicable on `use` items From d1277ccffaff214fb6127930c70f29744e40a877 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Dec 2025 12:58:31 +0100 Subject: [PATCH 14/32] Update `check_doc_cfg` pass in rustdoc, remove old `rustc_attr_parsing::cfg_matches` API --- .../src/attributes/cfg_old.rs | 40 ------- src/librustdoc/passes/check_doc_cfg.rs | 100 +++++++++--------- 2 files changed, 49 insertions(+), 91 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs index adae3fa635f43..29be000d476db 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs @@ -4,8 +4,6 @@ use rustc_feature::{Features, GatedCfg, find_gated_cfg}; use rustc_hir::RustcVersion; use rustc_hir::lints::AttributeLintKind; use rustc_session::Session; -use rustc_session::config::ExpectedValues; -use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::lint::{BuiltinLintDiag, Lint}; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, sym}; @@ -37,44 +35,6 @@ pub struct Condition { pub span: Span, } -/// Tests if a cfg-pattern matches the cfg set -pub fn cfg_matches( - cfg: &MetaItemInner, - sess: &Session, - lint_emitter: impl CfgMatchesLintEmitter, - features: Option<&Features>, -) -> bool { - eval_condition(cfg, sess, features, &mut |cfg| { - try_gate_cfg(cfg.name, cfg.span, sess, features); - match sess.psess.check_config.expecteds.get(&cfg.name) { - Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => { - lint_emitter.emit_span_lint( - sess, - UNEXPECTED_CFGS, - cfg.span, - BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgValue( - (cfg.name, cfg.name_span), - cfg.value.map(|v| (v, cfg.value_span.unwrap())), - )), - ); - } - None if sess.psess.check_config.exhaustive_names => { - lint_emitter.emit_span_lint( - sess, - UNEXPECTED_CFGS, - cfg.span, - BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgName( - (cfg.name, cfg.name_span), - cfg.value.map(|v| (v, cfg.value_span.unwrap())), - )), - ); - } - _ => { /* not unexpected */ } - } - sess.psess.config.contains(&(cfg.name, cfg.value)) - }) -} - pub fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) { let gate = find_gated_cfg(|sym| sym == name); if let (Some(feats), Some(gated_cfg)) = (features, gate) { diff --git a/src/librustdoc/passes/check_doc_cfg.rs b/src/librustdoc/passes/check_doc_cfg.rs index 63dc375a13c64..9e7fae5e14e46 100644 --- a/src/librustdoc/passes/check_doc_cfg.rs +++ b/src/librustdoc/passes/check_doc_cfg.rs @@ -1,9 +1,8 @@ -#![allow(dead_code, unused_imports)] - -use rustc_hir::HirId; +use rustc_attr_parsing::{ShouldEmit, eval_config_entry}; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::LocalDefId; +use rustc_hir::{Attribute, HirId}; use rustc_middle::ty::TyCtxt; -use rustc_span::sym; use super::Pass; use crate::clean::{Attributes, Crate, Item}; @@ -16,59 +15,58 @@ pub(crate) const CHECK_DOC_CFG: Pass = Pass { description: "checks `#[doc(cfg(...))]` for stability feature and unexpected cfgs", }; -pub(crate) fn check_doc_cfg(krate: Crate, _cx: &mut DocContext<'_>) -> Crate { - // let mut checker = DocCfgChecker { cx }; - // checker.visit_crate(&krate); +pub(crate) fn check_doc_cfg(krate: Crate, cx: &mut DocContext<'_>) -> Crate { + let mut checker = DocCfgChecker { cx }; + checker.visit_crate(&krate); krate } -// struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId); +struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId); -// impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { -// fn emit_span_lint( -// &self, -// sess: &rustc_session::Session, -// lint: &'static rustc_lint::Lint, -// sp: rustc_span::Span, -// builtin_diag: rustc_lint_defs::BuiltinLintDiag, -// ) { -// self.0.node_span_lint(lint, self.1, sp, |diag| { -// rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag) -// }); -// } -// } +impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { + fn emit_span_lint( + &self, + sess: &rustc_session::Session, + lint: &'static rustc_lint::Lint, + sp: rustc_span::Span, + builtin_diag: rustc_lint_defs::BuiltinLintDiag, + ) { + self.0.node_span_lint(lint, self.1, sp, |diag| { + rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag) + }); + } +} -// struct DocCfgChecker<'a, 'tcx> { -// cx: &'a mut DocContext<'tcx>, -// } +struct DocCfgChecker<'a, 'tcx> { + cx: &'a mut DocContext<'tcx>, +} -// impl DocCfgChecker<'_, '_> { -// fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) { -// for attr in &attrs.other_attrs { -// let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; -// let Some(doc_cfg) = d.cfg else { continue }; +impl DocCfgChecker<'_, '_> { + fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) { + for attr in &attrs.other_attrs { + let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; -// if let Some([cfg_mi]) = doc_cfg.meta_item_list() { -// let _ = rustc_attr_parsing::cfg_matches( -// cfg_mi, -// &self.cx.tcx.sess, -// RustdocCfgMatchesLintEmitter( -// self.cx.tcx, -// self.cx.tcx.local_def_id_to_hir_id(did), -// ), -// Some(self.cx.tcx.features()), -// ); -// } -// } -// } -// } + for doc_cfg in &d.cfg { + let _ = eval_config_entry( + &self.cx.tcx.sess, + doc_cfg, + &RustdocCfgMatchesLintEmitter( + self.cx.tcx, + self.cx.tcx.local_def_id_to_hir_id(did), + ), + ShouldEmit::ErrorsAndLints, + ); + } + } + } +} -// impl DocVisitor<'_> for DocCfgChecker<'_, '_> { -// fn visit_item(&mut self, item: &'_ Item) { -// if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) { -// self.check_attrs(&item.attrs, local_did); -// } +impl DocVisitor<'_> for DocCfgChecker<'_, '_> { + fn visit_item(&mut self, item: &'_ Item) { + if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) { + self.check_attrs(&item.attrs, local_did); + } -// self.visit_item_recur(item); -// } -// } + self.visit_item_recur(item); + } +} From e4f57dd4b3f3f4dc0bd6c4b2484526375ef3c5cf Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Dec 2025 15:48:56 +0100 Subject: [PATCH 15/32] Finish fixing ui tests --- .../rustc_attr_parsing/src/attributes/doc.rs | 7 +- compiler/rustc_attr_parsing/src/lib.rs | 1 + tests/rustdoc-ui/deprecated-attrs.stderr | 4 +- tests/rustdoc-ui/doc-alias-crate-level.rs | 3 - tests/rustdoc-ui/doc-alias-crate-level.stderr | 12 +- tests/rustdoc-ui/doc-alias.rs | 2 + tests/rustdoc-ui/doc-alias.stderr | 8 + tests/rustdoc-ui/doc-cfg-2.rs | 16 ++ tests/rustdoc-ui/doc-cfg-2.stderr | 60 ++++++ tests/rustdoc-ui/doc-cfg.rs | 17 +- tests/rustdoc-ui/doc-cfg.stderr | 163 +++++++++------- tests/rustdoc-ui/doctest/doc-test-attr.stderr | 4 +- tests/rustdoc-ui/invalid-cfg.rs | 16 +- tests/rustdoc-ui/invalid-cfg.stderr | 178 +++++++++++++++--- tests/rustdoc-ui/lints/doc-attr-2.rs | 11 ++ tests/rustdoc-ui/lints/doc-attr-2.stderr | 28 +++ tests/rustdoc-ui/lints/doc-attr.rs | 15 +- tests/rustdoc-ui/lints/doc-attr.stderr | 90 +++++---- 18 files changed, 460 insertions(+), 175 deletions(-) create mode 100644 tests/rustdoc-ui/doc-alias.rs create mode 100644 tests/rustdoc-ui/doc-alias.stderr create mode 100644 tests/rustdoc-ui/doc-cfg-2.rs create mode 100644 tests/rustdoc-ui/doc-cfg-2.stderr create mode 100644 tests/rustdoc-ui/lints/doc-attr-2.rs create mode 100644 tests/rustdoc-ui/lints/doc-attr-2.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 1fd7b702e92fd..2e8249fb35be9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -442,7 +442,12 @@ impl DocParser { cx.emit_lint(AttributeLintKind::DocUnknownAny { name }, path.span()); } None => { - // FIXME: is there anything to do in this case? + let full_name = + path.segments().map(|s| s.as_str()).intersperse("::").collect::(); + cx.emit_lint( + AttributeLintKind::DocUnknownAny { name: Symbol::intern(&full_name) }, + path.span(), + ); } } } diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 37a3189f892e8..3e9d8087a1927 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -81,6 +81,7 @@ #![recursion_limit = "256"] // tidy-alphabetical-end #![feature(if_let_guard)] +#![feature(iter_intersperse)] #[macro_use] /// All the individual attribute parsers for each of rustc's built-in attributes. diff --git a/tests/rustdoc-ui/deprecated-attrs.stderr b/tests/rustdoc-ui/deprecated-attrs.stderr index 323257f944ce7..6135b1496925f 100644 --- a/tests/rustdoc-ui/deprecated-attrs.stderr +++ b/tests/rustdoc-ui/deprecated-attrs.stderr @@ -17,7 +17,7 @@ error: unknown `doc` attribute `passes` --> $DIR/deprecated-attrs.rs:9:8 | LL | #![doc(passes = "collapse-docs unindent-comments")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no longer functions + | ^^^^^^ no longer functions | = note: `doc` attribute `passes` no longer functions; see issue #44136 = note: `doc(passes)` is now a no-op @@ -26,7 +26,7 @@ error: unknown `doc` attribute `plugins` --> $DIR/deprecated-attrs.rs:14:8 | LL | #![doc(plugins = "xxx")] - | ^^^^^^^^^^^^^^^ no longer functions + | ^^^^^^^ no longer functions | = note: `doc` attribute `plugins` no longer functions; see issue #44136 and CVE-2018-1000622 = note: `doc(plugins)` is now a no-op diff --git a/tests/rustdoc-ui/doc-alias-crate-level.rs b/tests/rustdoc-ui/doc-alias-crate-level.rs index 70618ac01dfbb..9ba7a63d7f102 100644 --- a/tests/rustdoc-ui/doc-alias-crate-level.rs +++ b/tests/rustdoc-ui/doc-alias-crate-level.rs @@ -1,4 +1 @@ #![doc(alias = "crate-level-not-working")] //~ ERROR - -#[doc(alias = "shouldn't work!")] //~ ERROR -pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-alias-crate-level.stderr b/tests/rustdoc-ui/doc-alias-crate-level.stderr index fc8095e03ca93..1742118a67c1b 100644 --- a/tests/rustdoc-ui/doc-alias-crate-level.stderr +++ b/tests/rustdoc-ui/doc-alias-crate-level.stderr @@ -1,14 +1,8 @@ -error: '\'' character isn't allowed in `#[doc(alias = "...")]` - --> $DIR/doc-alias-crate-level.rs:3:15 - | -LL | #[doc(alias = "shouldn't work!")] - | ^^^^^^^^^^^^^^^^^ - error: `#![doc(alias = "...")]` isn't allowed as a crate-level attribute - --> $DIR/doc-alias-crate-level.rs:1:8 + --> $DIR/doc-alias-crate-level.rs:1:16 | LL | #![doc(alias = "crate-level-not-working")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: aborting due to 1 previous error diff --git a/tests/rustdoc-ui/doc-alias.rs b/tests/rustdoc-ui/doc-alias.rs new file mode 100644 index 0000000000000..ae2a5b3871917 --- /dev/null +++ b/tests/rustdoc-ui/doc-alias.rs @@ -0,0 +1,2 @@ +#[doc(alias = "shouldn't work!")] //~ ERROR +pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-alias.stderr b/tests/rustdoc-ui/doc-alias.stderr new file mode 100644 index 0000000000000..1b3cc94269473 --- /dev/null +++ b/tests/rustdoc-ui/doc-alias.stderr @@ -0,0 +1,8 @@ +error: '\'' character isn't allowed in `#[doc(alias = "...")]` + --> $DIR/doc-alias.rs:1:15 + | +LL | #[doc(alias = "shouldn't work!")] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/doc-cfg-2.rs b/tests/rustdoc-ui/doc-cfg-2.rs new file mode 100644 index 0000000000000..bd6a2dc18be93 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-2.rs @@ -0,0 +1,16 @@ +#![feature(doc_cfg)] + +#[doc(cfg(foo), cfg(bar))] +//~^ WARN unexpected `cfg` condition name: `foo` +//~| WARN unexpected `cfg` condition name: `bar` +#[doc(auto_cfg(42))] //~ ERROR +#[doc(auto_cfg(hide(true)))] //~ ERROR +#[doc(auto_cfg(hide(42)))] //~ ERROR +#[doc(auto_cfg(hide("a")))] //~ ERROR +#[doc(auto_cfg = 42)] //~ ERROR +#[doc(auto_cfg = "a")] //~ ERROR +// Shouldn't lint +#[doc(auto_cfg(hide(windows)))] +#[doc(auto_cfg(hide(feature = "windows")))] +#[doc(auto_cfg(hide(foo)))] +pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg-2.stderr b/tests/rustdoc-ui/doc-cfg-2.stderr new file mode 100644 index 0000000000000..f3d67abfb8dd3 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-2.stderr @@ -0,0 +1,60 @@ +warning: unexpected `cfg` condition name: `foo` + --> $DIR/doc-cfg-2.rs:3:11 + | +LL | #[doc(cfg(foo), cfg(bar))] + | ^^^ + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(foo)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: unexpected `cfg` condition name: `bar` + --> $DIR/doc-cfg-2.rs:3:21 + | +LL | #[doc(cfg(foo), cfg(bar))] + | ^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(bar)` + = note: see for more information about checking conditional configuration + +error: only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` + --> $DIR/doc-cfg-2.rs:6:16 + | +LL | #[doc(auto_cfg(42))] + | ^^ + | + = note: `#[deny(invalid_doc_attributes)]` on by default + +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg-2.rs:7:21 + | +LL | #[doc(auto_cfg(hide(true)))] + | ^^^^ + +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg-2.rs:8:21 + | +LL | #[doc(auto_cfg(hide(42)))] + | ^^ + +error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items + --> $DIR/doc-cfg-2.rs:9:21 + | +LL | #[doc(auto_cfg(hide("a")))] + | ^^^ + +error: expected boolean for `#[doc(auto_cfg = ...)]` + --> $DIR/doc-cfg-2.rs:10:18 + | +LL | #[doc(auto_cfg = 42)] + | ^^ + +error: expected boolean for `#[doc(auto_cfg = ...)]` + --> $DIR/doc-cfg-2.rs:11:18 + | +LL | #[doc(auto_cfg = "a")] + | ^^^ + +error: aborting due to 6 previous errors; 2 warnings emitted + diff --git a/tests/rustdoc-ui/doc-cfg.rs b/tests/rustdoc-ui/doc-cfg.rs index d72643e23556b..f30d80aa9cda5 100644 --- a/tests/rustdoc-ui/doc-cfg.rs +++ b/tests/rustdoc-ui/doc-cfg.rs @@ -1,22 +1,9 @@ #![feature(doc_cfg)] #[doc(cfg(), cfg(foo, bar))] -//~^ ERROR -//~^^ ERROR -#[doc(cfg(foo), cfg(bar))] -//~^ WARN unexpected `cfg` condition name: `foo` -//~^^ WARN unexpected `cfg` condition name: `bar` +//~^ ERROR malformed `doc` attribute input +//~| ERROR malformed `doc` attribute input #[doc(cfg())] //~ ERROR #[doc(cfg(foo, bar))] //~ ERROR -#[doc(auto_cfg(42))] //~ ERROR -#[doc(auto_cfg(hide(true)))] //~ ERROR -#[doc(auto_cfg(hide(42)))] //~ ERROR -#[doc(auto_cfg(hide("a")))] //~ ERROR #[doc(auto_cfg(hide(foo::bar)))] //~ ERROR -#[doc(auto_cfg = 42)] //~ ERROR -#[doc(auto_cfg = "a")] //~ ERROR -// Shouldn't lint -#[doc(auto_cfg(hide(windows)))] -#[doc(auto_cfg(hide(feature = "windows")))] -#[doc(auto_cfg(hide(foo)))] pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index 49e8c324facfd..a4c6584d32944 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -1,90 +1,119 @@ -error: only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` - --> $DIR/doc-cfg.rs:11:7 +error[E0805]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:3:1 | -LL | #[doc(auto_cfg(42))] - | ^^^^^^^^^^^^ +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^^^^^^^--^^^^^^^^^^^^^^^^^ + | | + | expected a single argument here | - = note: `#[deny(invalid_doc_attributes)]` on by default - -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items - --> $DIR/doc-cfg.rs:12:21 +help: try changing it to one of the following valid forms of the attribute | -LL | #[doc(auto_cfg(hide(true)))] - | ^^^^ - -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items - --> $DIR/doc-cfg.rs:13:21 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc = "string"] | -LL | #[doc(auto_cfg(hide(42)))] - | ^^ - -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items - --> $DIR/doc-cfg.rs:14:21 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(hidden)] | -LL | #[doc(auto_cfg(hide("a")))] - | ^^^ - -error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items - --> $DIR/doc-cfg.rs:15:21 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(inline)] | -LL | #[doc(auto_cfg(hide(foo::bar)))] - | ^^^^^^^^ - -error: expected boolean for `#[doc(auto_cfg = ...)]` - --> $DIR/doc-cfg.rs:16:7 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(test)] | -LL | #[doc(auto_cfg = 42)] - | ^^^^^^^^^^^^^ -error: expected boolean for `#[doc(auto_cfg = ...)]` - --> $DIR/doc-cfg.rs:17:7 +error[E0805]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:3:1 | -LL | #[doc(auto_cfg = "a")] - | ^^^^^^^^^^^^^^ - -warning: unexpected `cfg` condition name: `foo` - --> $DIR/doc-cfg.rs:6:11 +LL | #[doc(cfg(), cfg(foo, bar))] + | ^^^^^^^^^^^^^^^^----------^^ + | | + | expected a single argument here | -LL | #[doc(cfg(foo), cfg(bar))] - | ^^^ +help: try changing it to one of the following valid forms of the attribute | - = help: expected names are: `FALSE` and `test` and 31 more - = help: to expect this configuration use `--check-cfg=cfg(foo)` - = note: see for more information about checking conditional configuration - = note: `#[warn(unexpected_cfgs)]` on by default - -warning: unexpected `cfg` condition name: `bar` - --> $DIR/doc-cfg.rs:6:21 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc = "string"] | -LL | #[doc(cfg(foo), cfg(bar))] - | ^^^ +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(hidden)] | - = help: to expect this configuration use `--check-cfg=cfg(bar)` - = note: see for more information about checking conditional configuration - -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:3:7 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(inline)] | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` - -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:3:23 +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(test)] | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^ -error: `cfg` predicate is not specified - --> $DIR/doc-cfg.rs:9:7 +error[E0805]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:6:1 | LL | #[doc(cfg())] - | ^^^^^ help: expected syntax is: `cfg(/* predicate */)` + | ^^^^^^^^^--^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg())] +LL + #[doc = "string"] + | +LL - #[doc(cfg())] +LL + #[doc(hidden)] + | +LL - #[doc(cfg())] +LL + #[doc(inline)] + | +LL - #[doc(cfg())] +LL + #[doc(test)] + | -error: multiple `cfg` predicates are specified - --> $DIR/doc-cfg.rs:10:16 +error[E0805]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:7:1 | LL | #[doc(cfg(foo, bar))] - | ^^^ + | ^^^^^^^^^----------^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc = "string"] + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(hidden)] + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(inline)] + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(test)] + | + +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-cfg.rs:8:1 + | +LL | #[doc(auto_cfg(hide(foo::bar)))] + | ^^^^^^^^^^^^^^^^^^^^--------^^^^ + | | + | expected a valid identifier here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc = "string"] + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc(hidden)] + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc(inline)] + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc(test)] + | -error: aborting due to 11 previous errors; 2 warnings emitted +error: aborting due to 5 previous errors +Some errors have detailed explanations: E0539, E0805. +For more information about an error, try `rustc --explain E0539`. diff --git a/tests/rustdoc-ui/doctest/doc-test-attr.stderr b/tests/rustdoc-ui/doctest/doc-test-attr.stderr index 415251cc5e9de..cf7bce66ef404 100644 --- a/tests/rustdoc-ui/doctest/doc-test-attr.stderr +++ b/tests/rustdoc-ui/doctest/doc-test-attr.stderr @@ -7,10 +7,10 @@ LL | #![doc(test)] = note: `#[deny(invalid_doc_attributes)]` on by default error: `#[doc(test(...)]` takes a list of attributes - --> $DIR/doc-test-attr.rs:5:8 + --> $DIR/doc-test-attr.rs:5:13 | LL | #![doc(test = "hello")] - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^ error: unknown `doc(test)` attribute `a` --> $DIR/doc-test-attr.rs:7:13 diff --git a/tests/rustdoc-ui/invalid-cfg.rs b/tests/rustdoc-ui/invalid-cfg.rs index 7e54aeea1defb..4d00edc0c7c24 100644 --- a/tests/rustdoc-ui/invalid-cfg.rs +++ b/tests/rustdoc-ui/invalid-cfg.rs @@ -1,21 +1,21 @@ #![feature(doc_cfg)] -#[doc(cfg = "x")] //~ ERROR not followed by parentheses -#[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates +#[doc(cfg = "x")] //~ ERROR malformed `doc` attribute input +#[doc(cfg(x, y))] //~ ERROR malformed `doc` attribute input pub struct S {} // We check it also fails on private items. -#[doc(cfg = "x")] //~ ERROR not followed by parentheses -#[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates +#[doc(cfg = "x")] //~ ERROR malformed `doc` attribute input +#[doc(cfg(x, y))] //~ ERROR malformed `doc` attribute input struct X {} // We check it also fails on hidden items. -#[doc(cfg = "x")] //~ ERROR not followed by parentheses -#[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates +#[doc(cfg = "x")] //~ ERROR malformed `doc` attribute input +#[doc(cfg(x, y))] //~ ERROR malformed `doc` attribute input #[doc(hidden)] pub struct Y {} // We check it also fails on hidden AND private items. -#[doc(cfg = "x")] //~ ERROR not followed by parentheses -#[doc(cfg(x, y))] //~ ERROR multiple `cfg` predicates +#[doc(cfg = "x")] //~ ERROR malformed `doc` attribute input +#[doc(cfg(x, y))] //~ ERROR malformed `doc` attribute input #[doc(hidden)] struct Z {} diff --git a/tests/rustdoc-ui/invalid-cfg.stderr b/tests/rustdoc-ui/invalid-cfg.stderr index 455626e07bd58..a23784509c8b4 100644 --- a/tests/rustdoc-ui/invalid-cfg.stderr +++ b/tests/rustdoc-ui/invalid-cfg.stderr @@ -1,50 +1,180 @@ -error: `cfg` is not followed by parentheses - --> $DIR/invalid-cfg.rs:2:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:2:1 | LL | #[doc(cfg = "x")] - | ^^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)` + | ^^^^^^^^^^^^^^^^^ expected this to be a list + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg = "x")] +LL + #[doc = "string"] + | +LL - #[doc(cfg = "x")] +LL + #[doc(hidden)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(inline)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(test)] + | -error: multiple `cfg` predicates are specified - --> $DIR/invalid-cfg.rs:3:14 +error[E0805]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:3:1 | LL | #[doc(cfg(x, y))] - | ^ + | ^^^^^^^^^------^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg(x, y))] +LL + #[doc = "string"] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(hidden)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(inline)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(test)] + | -error: `cfg` is not followed by parentheses - --> $DIR/invalid-cfg.rs:7:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:7:1 | LL | #[doc(cfg = "x")] - | ^^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)` + | ^^^^^^^^^^^^^^^^^ expected this to be a list + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg = "x")] +LL + #[doc = "string"] + | +LL - #[doc(cfg = "x")] +LL + #[doc(hidden)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(inline)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(test)] + | -error: multiple `cfg` predicates are specified - --> $DIR/invalid-cfg.rs:8:14 +error[E0805]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:8:1 | LL | #[doc(cfg(x, y))] - | ^ + | ^^^^^^^^^------^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg(x, y))] +LL + #[doc = "string"] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(hidden)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(inline)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(test)] + | -error: `cfg` is not followed by parentheses - --> $DIR/invalid-cfg.rs:12:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:12:1 | LL | #[doc(cfg = "x")] - | ^^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)` + | ^^^^^^^^^^^^^^^^^ expected this to be a list + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg = "x")] +LL + #[doc = "string"] + | +LL - #[doc(cfg = "x")] +LL + #[doc(hidden)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(inline)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(test)] + | -error: multiple `cfg` predicates are specified - --> $DIR/invalid-cfg.rs:13:14 +error[E0805]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:13:1 | LL | #[doc(cfg(x, y))] - | ^ + | ^^^^^^^^^------^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg(x, y))] +LL + #[doc = "string"] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(hidden)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(inline)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(test)] + | -error: `cfg` is not followed by parentheses - --> $DIR/invalid-cfg.rs:18:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:18:1 | LL | #[doc(cfg = "x")] - | ^^^^^^^^^ help: expected syntax is: `cfg(/* predicate */)` + | ^^^^^^^^^^^^^^^^^ expected this to be a list + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg = "x")] +LL + #[doc = "string"] + | +LL - #[doc(cfg = "x")] +LL + #[doc(hidden)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(inline)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(test)] + | -error: multiple `cfg` predicates are specified - --> $DIR/invalid-cfg.rs:19:14 +error[E0805]: malformed `doc` attribute input + --> $DIR/invalid-cfg.rs:19:1 | LL | #[doc(cfg(x, y))] - | ^ + | ^^^^^^^^^------^^ + | | + | expected a single argument here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(cfg(x, y))] +LL + #[doc = "string"] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(hidden)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(inline)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(test)] + | error: aborting due to 8 previous errors +Some errors have detailed explanations: E0539, E0805. +For more information about an error, try `rustc --explain E0539`. diff --git a/tests/rustdoc-ui/lints/doc-attr-2.rs b/tests/rustdoc-ui/lints/doc-attr-2.rs new file mode 100644 index 0000000000000..e5198e3475233 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-attr-2.rs @@ -0,0 +1,11 @@ +#![doc(as_ptr)] +//~^ ERROR unknown `doc` attribute `as_ptr` + +#[doc(as_ptr)] +//~^ ERROR unknown `doc` attribute `as_ptr` +pub fn foo() {} + +#[doc(foo::bar, crate::bar::baz = "bye")] +//~^ ERROR unknown `doc` attribute `foo::bar` +//~| ERROR unknown `doc` attribute `crate::bar::baz` +fn bar() {} diff --git a/tests/rustdoc-ui/lints/doc-attr-2.stderr b/tests/rustdoc-ui/lints/doc-attr-2.stderr new file mode 100644 index 0000000000000..c2bb45c5785e5 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-attr-2.stderr @@ -0,0 +1,28 @@ +error: unknown `doc` attribute `as_ptr` + --> $DIR/doc-attr-2.rs:4:7 + | +LL | #[doc(as_ptr)] + | ^^^^^^ + | + = note: `#[deny(invalid_doc_attributes)]` on by default + +error: unknown `doc` attribute `foo::bar` + --> $DIR/doc-attr-2.rs:8:7 + | +LL | #[doc(foo::bar, crate::bar::baz = "bye")] + | ^^^^^^^^ + +error: unknown `doc` attribute `crate::bar::baz` + --> $DIR/doc-attr-2.rs:8:17 + | +LL | #[doc(foo::bar, crate::bar::baz = "bye")] + | ^^^^^^^^^^^^^^^ + +error: unknown `doc` attribute `as_ptr` + --> $DIR/doc-attr-2.rs:1:8 + | +LL | #![doc(as_ptr)] + | ^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/rustdoc-ui/lints/doc-attr.rs b/tests/rustdoc-ui/lints/doc-attr.rs index 666aeb55cbecd..b27faa81cb92a 100644 --- a/tests/rustdoc-ui/lints/doc-attr.rs +++ b/tests/rustdoc-ui/lints/doc-attr.rs @@ -1,17 +1,8 @@ #![crate_type = "lib"] -#![doc(as_ptr)] -//~^ ERROR unknown `doc` attribute - -#[doc(as_ptr)] -//~^ ERROR unknown `doc` attribute -pub fn foo() {} #[doc(123)] -//~^ ERROR invalid `doc` attribute +//~^ ERROR malformed `doc` attribute #[doc("hello", "bar")] -//~^ ERROR invalid `doc` attribute -//~| ERROR invalid `doc` attribute -#[doc(foo::bar, crate::bar::baz = "bye")] -//~^ ERROR unknown `doc` attribute -//~| ERROR unknown `doc` attribute +//~^ ERROR malformed `doc` attribute +//~| ERROR malformed `doc` attribute fn bar() {} diff --git a/tests/rustdoc-ui/lints/doc-attr.stderr b/tests/rustdoc-ui/lints/doc-attr.stderr index 091ffc20d4655..794b585a9de78 100644 --- a/tests/rustdoc-ui/lints/doc-attr.stderr +++ b/tests/rustdoc-ui/lints/doc-attr.stderr @@ -1,46 +1,72 @@ -error: unknown `doc` attribute `as_ptr` - --> $DIR/doc-attr.rs:5:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:3:1 | -LL | #[doc(as_ptr)] - | ^^^^^^ +LL | #[doc(123)] + | ^^^^^^---^^ + | | + | expected this to be of the form `... = "..."` | - = note: `#[deny(invalid_doc_attributes)]` on by default - -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:9:7 +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(123)] +LL + #[doc = "string"] + | +LL - #[doc(123)] +LL + #[doc(hidden)] + | +LL - #[doc(123)] +LL + #[doc(inline)] + | +LL - #[doc(123)] +LL + #[doc(test)] | -LL | #[doc(123)] - | ^^^ -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:11:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:5:1 | LL | #[doc("hello", "bar")] - | ^^^^^^^ + | ^^^^^^-------^^^^^^^^^ + | | + | expected this to be of the form `... = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc("hello", "bar")] +LL + #[doc = "string"] + | +LL - #[doc("hello", "bar")] +LL + #[doc(hidden)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(inline)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(test)] + | -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:11:16 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:5:1 | LL | #[doc("hello", "bar")] - | ^^^^^ - -error: unknown `doc` attribute `foo::bar` - --> $DIR/doc-attr.rs:14:7 + | ^^^^^^^^^^^^^^^-----^^ + | | + | expected this to be of the form `... = "..."` | -LL | #[doc(foo::bar, crate::bar::baz = "bye")] - | ^^^^^^^^ - -error: unknown `doc` attribute `crate::bar::baz` - --> $DIR/doc-attr.rs:14:17 +help: try changing it to one of the following valid forms of the attribute | -LL | #[doc(foo::bar, crate::bar::baz = "bye")] - | ^^^^^^^^^^^^^^^^^^^^^^^ - -error: unknown `doc` attribute `as_ptr` - --> $DIR/doc-attr.rs:2:8 +LL - #[doc("hello", "bar")] +LL + #[doc = "string"] + | +LL - #[doc("hello", "bar")] +LL + #[doc(hidden)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(inline)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(test)] | -LL | #![doc(as_ptr)] - | ^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0539`. From 3fa499bab0313ad3893b7b765c71c1654924af32 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Dec 2025 16:50:14 +0100 Subject: [PATCH 16/32] Sort fluent messages --- compiler/rustc_attr_parsing/messages.ftl | 164 ++++++++++++----------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 4d7716560fb23..2a98b87e7a7e6 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -14,8 +14,91 @@ attr_parsing_deprecated_item_suggestion = .help = add `#![feature(deprecated_suggestion)]` to the crate root .note = see #94785 for more details +attr_parsing_doc_alias_bad_char = + {$char_} character isn't allowed in {$attr_str} + +attr_parsing_doc_alias_duplicated = doc alias is duplicated + .label = first defined here + +attr_parsing_doc_alias_empty = + {$attr_str} attribute cannot have empty value + +attr_parsing_doc_alias_malformed = + doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` + +attr_parsing_doc_alias_start_end = + {$attr_str} cannot start or end with ' ' + +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_auto_cfg_expects_hide_or_show = + only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` + +attr_parsing_doc_auto_cfg_hide_show_expects_list = + `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items + +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_wrong_literal = + expected boolean for `#[doc(auto_cfg = ...)]` + +attr_parsing_doc_invalid = + invalid `doc` attribute + +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_test_literal = `#![doc(test(...)]` does not take a literal + +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_unknown_any = + unknown `doc` attribute `{$name}` + +attr_parsing_doc_unknown_include = + unknown `doc` attribute `include` + .suggestion = use `doc = include_str!` instead + +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_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_empty_attribute = + unused attribute + .suggestion = {$valid_without_list -> + [true] remove these parentheses + *[other] remove this attribute + } + .note = {$valid_without_list -> + [true] using `{$attr_path}` with an empty list is equivalent to not using a list at all + *[other] using `{$attr_path}` with an empty list has no effect + } + attr_parsing_empty_confusables = expected at least one confusable name + attr_parsing_empty_link_name = link name must not be empty .label = empty link name @@ -239,84 +322,3 @@ attr_parsing_doc_alias_duplicated = doc alias is duplicated attr_parsing_whole_archive_needs_static = linking modifier `whole-archive` is only compatible with `static` linking kind - -attr_parsing_unused_no_lints_note = - attribute `{$name}` without any lints has no effect - -attr_parsing_doc_alias_empty = - {$attr_str} attribute cannot have empty value - -attr_parsing_doc_alias_bad_char = - {$char_} character isn't allowed in {$attr_str} - -attr_parsing_doc_alias_start_end = - {$attr_str} cannot start or end with ' ' - -attr_parsing_doc_keyword_not_keyword = - nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` - .help = only existing keywords are allowed in core/std - -attr_parsing_doc_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 - -attr_parsing_doc_inline_conflict_first = - this attribute... - -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"))]` From a36c462fb86cce3ca7abcdc9c577f68d76f0a4bf Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Dec 2025 17:43:19 +0100 Subject: [PATCH 17/32] Update clippy code to new doc attribute API --- .../src/doc/doc_suspicious_footnotes.rs | 14 +++++++++----- src/tools/clippy/clippy_lints/src/doc/mod.rs | 2 +- .../src/doc/suspicious_doc_comments.rs | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs index 3330cc5defd3f..1944cd7c91d3f 100644 --- a/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs +++ b/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::attr::AttributeExt as _; -use rustc_ast::token::CommentKind; +use rustc_ast::token::{CommentKind, DocFragmentKind}; use rustc_errors::Applicability; use rustc_hir::{AttrStyle, Attribute}; use rustc_lint::{LateContext, LintContext}; -use rustc_resolve::rustdoc::DocFragmentKind; use std::ops::Range; @@ -43,13 +42,18 @@ pub fn check(cx: &LateContext<'_>, doc: &str, range: Range, fragments: &F span, "looks like a footnote ref, but has no matching footnote", |diag| { - if this_fragment.kind == DocFragmentKind::SugaredDoc { - let (doc_attr, (_, doc_attr_comment_kind), attr_style) = attrs + if let DocFragmentKind::Sugared(_) = this_fragment.kind { + let (doc_attr, doc_attr_comment_kind, attr_style) = attrs .iter() .filter(|attr| attr.span().overlaps(this_fragment.span)) .rev() .find_map(|attr| { - Some((attr, attr.doc_str_and_comment_kind()?, attr.doc_resolution_scope()?)) + let (_, fragment) = attr.doc_str_and_fragment_kind()?; + let fragment = match fragment { + DocFragmentKind::Sugared(kind) => kind, + DocFragmentKind::Raw(_) => CommentKind::Line, + }; + Some((attr, fragment, attr.doc_resolution_scope()?)) }) .unwrap(); let (to_add, terminator) = match (doc_attr_comment_kind, attr_style) { diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index 120da92da9442..b11b2f8392c1b 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -861,7 +861,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ let (fragments, _) = attrs_to_doc_fragments( attrs.iter().filter_map(|attr| { - if attr.doc_str_and_comment_kind().is_none() || attr.span().in_external_macro(cx.sess().source_map()) { + if attr.doc_str_and_fragment_kind().is_none() || attr.span().in_external_macro(cx.sess().source_map()) { None } else { Some((attr, None)) diff --git a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs index 47d91b80e7eef..e751600f00a63 100644 --- a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs +++ b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::AttrStyle; -use rustc_ast::token::CommentKind; +use rustc_ast::token::{CommentKind, DocFragmentKind}; use rustc_errors::Applicability; use rustc_hir::Attribute; use rustc_hir::attrs::AttributeKind; @@ -46,8 +46,8 @@ fn collect_doc_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> { && let Some(com) = comment.as_str().strip_prefix('!') { let sugg = match kind { - CommentKind::Line => format!("//!{com}"), - CommentKind::Block => format!("/*!{com}*/"), + DocFragmentKind::Sugared(CommentKind::Block) => format!("/*!{com}*/"), + DocFragmentKind::Sugared(CommentKind::Line) | DocFragmentKind::Raw(_) => format!("//!{com}"), }; Some((attr.span(), sugg)) } else { From aa3bf6fde949990eb75ae4f8d083109cfacba029 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 4 Dec 2025 18:44:59 +0100 Subject: [PATCH 18/32] Update rustc_resolve unit tests --- compiler/rustc_resolve/src/rustdoc/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc/tests.rs b/compiler/rustc_resolve/src/rustdoc/tests.rs index 6a98ae0663048..9fea6f6807e41 100644 --- a/compiler/rustc_resolve/src/rustdoc/tests.rs +++ b/compiler/rustc_resolve/src/rustdoc/tests.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::symbol::sym; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, DUMMY_SP, Span}; use super::{DocFragment, DocFragmentKind, source_span_for_markdown_range_inner}; @@ -17,7 +17,7 @@ fn single_backtick() { &[DocFragment { span: Span::with_root_ctxt(BytePos(8), BytePos(11)), item_id: None, - kind: DocFragmentKind::RawDoc, + kind: DocFragmentKind::Raw(DUMMY_SP), doc: sym::empty, // unused placeholder indent: 0, from_expansion: false, @@ -40,7 +40,7 @@ fn utf8() { &[DocFragment { span: Span::with_root_ctxt(BytePos(8), BytePos(14)), item_id: None, - kind: DocFragmentKind::RawDoc, + kind: DocFragmentKind::Raw(DUMMY_SP), doc: sym::empty, // unused placeholder indent: 0, from_expansion: false, From 4936973d49d653d01a114a00a14d427e37296775 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Dec 2025 01:23:38 +0100 Subject: [PATCH 19/32] Fix ui tests --- .../rustc_attr_parsing/src/attributes/doc.rs | 40 ++++- compiler/rustc_attr_parsing/src/interface.rs | 3 +- compiler/rustc_expand/src/expand.rs | 2 +- compiler/rustc_lint/src/builtin.rs | 10 +- compiler/rustc_passes/src/check_attr.rs | 58 ++++--- tests/rustdoc-ui/bad-render-options.stderr | 63 ++++---- tests/rustdoc-ui/check-doc-alias-attr.stderr | 14 +- tests/rustdoc-ui/doc-cfg.stderr | 35 +++-- tests/rustdoc-ui/invalid-cfg.stderr | 56 ++++--- tests/rustdoc-ui/lints/doc-attr.stderr | 21 +-- tests/ui/attributes/doc-attr.rs | 6 +- tests/ui/attributes/doc-attr.stderr | 89 +++++++++-- tests/ui/attributes/doc-test-literal.rs | 2 +- tests/ui/attributes/doc-test-literal.stderr | 25 ++- .../extented-attribute-macro-error.rs | 1 + .../extented-attribute-macro-error.stderr | 8 +- .../ui/attributes/issue-115264-expr-field.rs | 2 + .../attributes/issue-115264-expr-field.stderr | 12 ++ tests/ui/attributes/issue-115264-pat-field.rs | 2 + .../attributes/issue-115264-pat-field.stderr | 12 ++ .../attributes/key-value-expansion-scope.rs | 16 ++ .../key-value-expansion-scope.stderr | 148 +++++++++++++++--- .../ui/attributes/key-value-expansion.stderr | 12 +- tests/ui/attributes/malformed-attrs.stderr | 60 ++++--- ...es-note-version-and-pr-issue-141619.stderr | 2 +- .../invalid-utf8-binary-file.rs | 4 +- .../invalid-utf8-binary-file.stderr | 8 +- tests/ui/lint/unused/useless-comment.rs | 16 +- tests/ui/lint/unused/useless-comment.stderr | 70 +++++++-- .../ui/malformed/malformed-regressions.stderr | 24 ++- .../attribute/attr-unquoted-ident.stderr | 13 -- .../rustdoc/check-doc-alias-attr-location.rs | 17 +- .../check-doc-alias-attr-location.stderr | 67 ++++++-- tests/ui/rustdoc/check-doc-alias-attr.stderr | 63 ++++++-- tests/ui/rustdoc/doc-alias-crate-level.stderr | 4 +- tests/ui/rustdoc/doc-alias-same-name.stderr | 6 +- tests/ui/rustdoc/doc-primitive.stderr | 2 +- tests/ui/rustdoc/doc-test-attr.stderr | 4 +- tests/ui/rustdoc/doc_keyword.rs | 6 +- tests/ui/rustdoc/doc_keyword.stderr | 30 ++-- tests/ui/rustdoc/duplicate_doc_alias.stderr | 8 +- 41 files changed, 725 insertions(+), 316 deletions(-) create mode 100644 tests/ui/attributes/issue-115264-expr-field.stderr create mode 100644 tests/ui/attributes/issue-115264-pat-field.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 2e8249fb35be9..83a6c4fc44d9a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -10,7 +10,7 @@ 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::prelude::{Allow, AllowedTargets, Error, MethodKind, Target}; use super::{AcceptMapping, AttributeParser}; use crate::context::{AcceptContext, FinalizeContext, Stage}; use crate::fluent_generated as fluent; @@ -459,7 +459,9 @@ impl DocParser { ) { match args { ArgParser::NoArgs => { - todo!() + let suggestions = cx.suggestions(); + let span = cx.attr_span; + cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); } ArgParser::List(items) => { for i in items.mixed() { @@ -493,12 +495,41 @@ impl DocParser { impl AttributeParser for DocParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::doc], - template!(List: &["hidden", "inline", "test"], NameValueStr: "string"), + template!( + List: &[ + "alias", + "attribute", + "hidden", + "html_favicon_url", + "html_logo_url", + "html_no_source", + "html_playground_url", + "html_root_url", + "issue_tracker_base_url", + "inline", + "no_inline", + "masked", + "cfg", + "notable_trait", + "keyword", + "fake_variadic", + "search_unbox", + "rust_logo", + "auto_cfg", + "test", + "spotlight", + "include", + "no_default_passes", + "passes", + "plugins", + ], + NameValueStr: "string" + ), |this, cx, args| { this.accept_single_doc_attr(cx, args); }, )]; - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::ExternCrate), Allow(Target::Use), Allow(Target::Static), @@ -527,6 +558,7 @@ impl AttributeParser for DocParser { Allow(Target::ForeignTy), Allow(Target::MacroDef), Allow(Target::Crate), + Error(Target::WherePredicate), ]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index b1538b447da05..5eefce75ace21 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use rustc_ast as ast; -use rustc_ast::{AttrStyle, NodeId, Safety}; use rustc_ast::token::DocFragmentKind; +use rustc_ast::{AttrStyle, NodeId, Safety}; use rustc_errors::DiagCtxtHandle; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::AttributeKind; @@ -357,7 +357,6 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { continue; } - let path = parser.path(); for accept in accepts { let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext { shared: SharedContext { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 90563b21d2e80..33431556c76ca 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2177,7 +2177,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { continue; } - if attr.is_doc_comment() { + if attr.doc_str_and_fragment_kind().is_some() { self.cx.sess.psess.buffer_lint( UNUSED_DOC_COMMENTS, current_span, diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index ae973a3c49c25..bf66c7f855089 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -813,19 +813,23 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: & let mut sugared_span: Option = None; while let Some(attr) = attrs.next() { - let is_doc_comment = attr.is_doc_comment(); + let (is_doc_comment, is_doc_attribute) = match &attr.kind { + AttrKind::DocComment(..) => (true, false), + AttrKind::Normal(normal) if normal.item.path == sym::doc => (true, true), + _ => (false, false), + }; if is_doc_comment { sugared_span = Some(sugared_span.map_or(attr.span, |span| span.with_hi(attr.span.hi()))); } - if attrs.peek().is_some_and(|next_attr| next_attr.is_doc_comment()) { + if !is_doc_attribute && attrs.peek().is_some_and(|next_attr| next_attr.is_doc_comment()) { continue; } let span = sugared_span.take().unwrap_or(attr.span); - if is_doc_comment { + if is_doc_comment || is_doc_attribute { let sub = match attr.kind { AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => { BuiltinUnusedDocCommentSub::PlainHelp diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3f29d943e7d32..030839baad9b9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1029,25 +1029,27 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - if let Some((_, span)) = keyword { - self.check_attr_not_crate_level(*span, hir_id, "keyword"); + if let Some((_, span)) = keyword + && 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"); + 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"); } - if let Some(span) = fake_variadic { - if self.check_attr_not_crate_level(*span, hir_id, "fake_variadic") { - self.check_doc_fake_variadic(*span, hir_id); - } + if let Some(span) = fake_variadic + && self.check_attr_not_crate_level(*span, hir_id, "fake_variadic") + { + self.check_doc_fake_variadic(*span, hir_id); } - if let Some(span) = search_unbox { - if self.check_attr_not_crate_level(*span, hir_id, "search_unbox") { - self.check_doc_search_unbox(*span, hir_id); - } + if let Some(span) = search_unbox + && self.check_attr_not_crate_level(*span, hir_id, "search_unbox") + { + self.check_doc_search_unbox(*span, hir_id); } for i in [ @@ -1070,18 +1072,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_doc_inline(hir_id, target, inline); - if let Some(span) = rust_logo { - if self.check_attr_crate_level(*span, hir_id) - && !self.tcx.features().rustdoc_internals() - { - feature_err( - &self.tcx.sess, - sym::rustdoc_internals, - *span, - fluent::passes_doc_rust_logo, - ) - .emit(); - } + if let Some(span) = rust_logo + && self.check_attr_crate_level(*span, hir_id) + && !self.tcx.features().rustdoc_internals() + { + feature_err( + &self.tcx.sess, + sym::rustdoc_internals, + *span, + fluent::passes_doc_rust_logo, + ) + .emit(); } if let Some(span) = masked { @@ -1984,7 +1985,14 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { .hir_attrs(where_predicate.hir_id) .iter() .filter(|attr| !ATTRS_ALLOWED.iter().any(|&sym| attr.has_name(sym))) - .filter(|attr| !attr.is_parsed_attr()) + // FIXME: We shouldn't need to special-case `doc`! + .filter(|attr| { + matches!( + attr, + Attribute::Parsed(AttributeKind::DocComment { .. } | AttributeKind::Doc(_)) + | Attribute::Unparsed(_) + ) + }) .map(|attr| attr.span()) .collect::>(); if !spans.is_empty() { diff --git a/tests/rustdoc-ui/bad-render-options.stderr b/tests/rustdoc-ui/bad-render-options.stderr index e7f33f4dff1d7..296a41337f336 100644 --- a/tests/rustdoc-ui/bad-render-options.stderr +++ b/tests/rustdoc-ui/bad-render-options.stderr @@ -12,14 +12,15 @@ LL - #![doc(html_favicon_url)] LL + #![doc = "string"] | LL - #![doc(html_favicon_url)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_favicon_url)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_favicon_url)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 21 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:6:1 @@ -35,14 +36,15 @@ LL - #![doc(html_logo_url)] LL + #![doc = "string"] | LL - #![doc(html_logo_url)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_logo_url)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_logo_url)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 21 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:9:1 @@ -58,14 +60,15 @@ LL - #![doc(html_playground_url)] LL + #![doc = "string"] | LL - #![doc(html_playground_url)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_playground_url)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_playground_url)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 21 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:12:1 @@ -81,14 +84,15 @@ LL - #![doc(issue_tracker_base_url)] LL + #![doc = "string"] | LL - #![doc(issue_tracker_base_url)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(issue_tracker_base_url)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(issue_tracker_base_url)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 21 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:15:1 @@ -104,14 +108,15 @@ LL - #![doc(html_favicon_url = 1)] LL + #![doc = "string"] | LL - #![doc(html_favicon_url = 1)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_favicon_url = 1)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_favicon_url = 1)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:18:1 @@ -127,14 +132,15 @@ LL - #![doc(html_logo_url = 2)] LL + #![doc = "string"] | LL - #![doc(html_logo_url = 2)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_logo_url = 2)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_logo_url = 2)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:21:1 @@ -150,14 +156,15 @@ LL - #![doc(html_playground_url = 3)] LL + #![doc = "string"] | LL - #![doc(html_playground_url = 3)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_playground_url = 3)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_playground_url = 3)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:24:1 @@ -173,14 +180,15 @@ LL - #![doc(issue_tracker_base_url = 4)] LL + #![doc = "string"] | LL - #![doc(issue_tracker_base_url = 4)] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(issue_tracker_base_url = 4)] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(issue_tracker_base_url = 4)] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 22 other candidates error[E0565]: malformed `doc` attribute input --> $DIR/bad-render-options.rs:27:1 @@ -196,14 +204,15 @@ LL - #![doc(html_no_source = "asdf")] LL + #![doc = "string"] | LL - #![doc(html_no_source = "asdf")] -LL + #![doc(hidden)] +LL + #![doc(alias)] | LL - #![doc(html_no_source = "asdf")] -LL + #![doc(inline)] +LL + #![doc(attribute)] | LL - #![doc(html_no_source = "asdf")] -LL + #![doc(test)] +LL + #![doc(auto_cfg)] | + = and 22 other candidates error: aborting due to 9 previous errors diff --git a/tests/rustdoc-ui/check-doc-alias-attr.stderr b/tests/rustdoc-ui/check-doc-alias-attr.stderr index 06d5c65351910..6c33f10e87851 100644 --- a/tests/rustdoc-ui/check-doc-alias-attr.stderr +++ b/tests/rustdoc-ui/check-doc-alias-attr.stderr @@ -18,14 +18,15 @@ LL - #[doc(alias = 0)] LL + #[doc = "string"] | LL - #[doc(alias = 0)] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(alias = 0)] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(alias = 0)] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:9:15 @@ -85,14 +86,15 @@ LL - #[doc(alias(0))] LL + #[doc = "string"] | LL - #[doc(alias(0))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(alias(0))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(alias(0))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:20:13 diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index a4c6584d32944..0efeac66554c8 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -12,14 +12,15 @@ LL - #[doc(cfg(), cfg(foo, bar))] LL + #[doc = "string"] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:3:1 @@ -35,14 +36,15 @@ LL - #[doc(cfg(), cfg(foo, bar))] LL + #[doc = "string"] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(), cfg(foo, bar))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:6:1 @@ -58,14 +60,15 @@ LL - #[doc(cfg())] LL + #[doc = "string"] | LL - #[doc(cfg())] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg())] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg())] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:7:1 @@ -81,14 +84,15 @@ LL - #[doc(cfg(foo, bar))] LL + #[doc = "string"] | LL - #[doc(cfg(foo, bar))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(foo, bar))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(foo, bar))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:8:1 @@ -104,14 +108,15 @@ LL - #[doc(auto_cfg(hide(foo::bar)))] LL + #[doc = "string"] | LL - #[doc(auto_cfg(hide(foo::bar)))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(auto_cfg(hide(foo::bar)))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(auto_cfg(hide(foo::bar)))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error: aborting due to 5 previous errors diff --git a/tests/rustdoc-ui/invalid-cfg.stderr b/tests/rustdoc-ui/invalid-cfg.stderr index a23784509c8b4..3363dbb56fb4f 100644 --- a/tests/rustdoc-ui/invalid-cfg.stderr +++ b/tests/rustdoc-ui/invalid-cfg.stderr @@ -10,14 +10,15 @@ LL - #[doc(cfg = "x")] LL + #[doc = "string"] | LL - #[doc(cfg = "x")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg = "x")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg = "x")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:3:1 @@ -33,14 +34,15 @@ LL - #[doc(cfg(x, y))] LL + #[doc = "string"] | LL - #[doc(cfg(x, y))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(x, y))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(x, y))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:7:1 @@ -54,14 +56,15 @@ LL - #[doc(cfg = "x")] LL + #[doc = "string"] | LL - #[doc(cfg = "x")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg = "x")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg = "x")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:8:1 @@ -77,14 +80,15 @@ LL - #[doc(cfg(x, y))] LL + #[doc = "string"] | LL - #[doc(cfg(x, y))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(x, y))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(x, y))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:12:1 @@ -98,14 +102,15 @@ LL - #[doc(cfg = "x")] LL + #[doc = "string"] | LL - #[doc(cfg = "x")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg = "x")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg = "x")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:13:1 @@ -121,14 +126,15 @@ LL - #[doc(cfg(x, y))] LL + #[doc = "string"] | LL - #[doc(cfg(x, y))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(x, y))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(x, y))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:18:1 @@ -142,14 +148,15 @@ LL - #[doc(cfg = "x")] LL + #[doc = "string"] | LL - #[doc(cfg = "x")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg = "x")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg = "x")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0805]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:19:1 @@ -165,14 +172,15 @@ LL - #[doc(cfg(x, y))] LL + #[doc = "string"] | LL - #[doc(cfg(x, y))] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(cfg(x, y))] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(cfg(x, y))] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error: aborting due to 8 previous errors diff --git a/tests/rustdoc-ui/lints/doc-attr.stderr b/tests/rustdoc-ui/lints/doc-attr.stderr index 794b585a9de78..1201bd5c71f18 100644 --- a/tests/rustdoc-ui/lints/doc-attr.stderr +++ b/tests/rustdoc-ui/lints/doc-attr.stderr @@ -12,14 +12,15 @@ LL - #[doc(123)] LL + #[doc = "string"] | LL - #[doc(123)] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc(123)] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc(123)] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/doc-attr.rs:5:1 @@ -35,14 +36,15 @@ LL - #[doc("hello", "bar")] LL + #[doc = "string"] | LL - #[doc("hello", "bar")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc("hello", "bar")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc("hello", "bar")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error[E0539]: malformed `doc` attribute input --> $DIR/doc-attr.rs:5:1 @@ -58,14 +60,15 @@ LL - #[doc("hello", "bar")] LL + #[doc = "string"] | LL - #[doc("hello", "bar")] -LL + #[doc(hidden)] +LL + #[doc(alias)] | LL - #[doc("hello", "bar")] -LL + #[doc(inline)] +LL + #[doc(attribute)] | LL - #[doc("hello", "bar")] -LL + #[doc(test)] +LL + #[doc(auto_cfg)] | + = and 22 other candidates error: aborting due to 3 previous errors diff --git a/tests/ui/attributes/doc-attr.rs b/tests/ui/attributes/doc-attr.rs index 666aeb55cbecd..8d733be93567e 100644 --- a/tests/ui/attributes/doc-attr.rs +++ b/tests/ui/attributes/doc-attr.rs @@ -7,10 +7,10 @@ pub fn foo() {} #[doc(123)] -//~^ ERROR invalid `doc` attribute +//~^ ERROR malformed `doc` attribute #[doc("hello", "bar")] -//~^ ERROR invalid `doc` attribute -//~| ERROR invalid `doc` attribute +//~^ ERROR malformed `doc` attribute +//~| ERROR malformed `doc` attribute #[doc(foo::bar, crate::bar::baz = "bye")] //~^ ERROR unknown `doc` attribute //~| ERROR unknown `doc` attribute diff --git a/tests/ui/attributes/doc-attr.stderr b/tests/ui/attributes/doc-attr.stderr index 091ffc20d4655..9234c1a0719b6 100644 --- a/tests/ui/attributes/doc-attr.stderr +++ b/tests/ui/attributes/doc-attr.stderr @@ -1,28 +1,82 @@ -error: unknown `doc` attribute `as_ptr` - --> $DIR/doc-attr.rs:5:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:9:1 | -LL | #[doc(as_ptr)] - | ^^^^^^ +LL | #[doc(123)] + | ^^^^^^---^^ + | | + | expected this to be of the form `... = "..."` | - = note: `#[deny(invalid_doc_attributes)]` on by default - -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:9:7 +help: try changing it to one of the following valid forms of the attribute | -LL | #[doc(123)] - | ^^^ +LL - #[doc(123)] +LL + #[doc = "string"] + | +LL - #[doc(123)] +LL + #[doc(alias)] + | +LL - #[doc(123)] +LL + #[doc(attribute)] + | +LL - #[doc(123)] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:11:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:11:1 | LL | #[doc("hello", "bar")] - | ^^^^^^^ + | ^^^^^^-------^^^^^^^^^ + | | + | expected this to be of the form `... = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc("hello", "bar")] +LL + #[doc = "string"] + | +LL - #[doc("hello", "bar")] +LL + #[doc(alias)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(attribute)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -error: invalid `doc` attribute - --> $DIR/doc-attr.rs:11:16 +error[E0539]: malformed `doc` attribute input + --> $DIR/doc-attr.rs:11:1 | LL | #[doc("hello", "bar")] - | ^^^^^ + | ^^^^^^^^^^^^^^^-----^^ + | | + | expected this to be of the form `... = "..."` + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc("hello", "bar")] +LL + #[doc = "string"] + | +LL - #[doc("hello", "bar")] +LL + #[doc(alias)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(attribute)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates + +error: unknown `doc` attribute `as_ptr` + --> $DIR/doc-attr.rs:5:7 + | +LL | #[doc(as_ptr)] + | ^^^^^^ + | + = note: `#[deny(invalid_doc_attributes)]` on by default error: unknown `doc` attribute `foo::bar` --> $DIR/doc-attr.rs:14:7 @@ -34,7 +88,7 @@ error: unknown `doc` attribute `crate::bar::baz` --> $DIR/doc-attr.rs:14:17 | LL | #[doc(foo::bar, crate::bar::baz = "bye")] - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ error: unknown `doc` attribute `as_ptr` --> $DIR/doc-attr.rs:2:8 @@ -44,3 +98,4 @@ LL | #![doc(as_ptr)] error: aborting due to 7 previous errors +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/attributes/doc-test-literal.rs b/tests/ui/attributes/doc-test-literal.rs index 92fe7846f14c6..f9776e9924bd5 100644 --- a/tests/ui/attributes/doc-test-literal.rs +++ b/tests/ui/attributes/doc-test-literal.rs @@ -1,4 +1,4 @@ #![doc(test(""))] -//~^ ERROR `#![doc(test(...)]` does not take a literal +//~^ ERROR malformed `doc` attribute input fn main() {} diff --git a/tests/ui/attributes/doc-test-literal.stderr b/tests/ui/attributes/doc-test-literal.stderr index 39e109a76ce58..3ffbdcbb9fee7 100644 --- a/tests/ui/attributes/doc-test-literal.stderr +++ b/tests/ui/attributes/doc-test-literal.stderr @@ -1,10 +1,27 @@ -error: `#![doc(test(...)]` does not take a literal - --> $DIR/doc-test-literal.rs:1:13 +error[E0565]: malformed `doc` attribute input + --> $DIR/doc-test-literal.rs:1:1 | LL | #![doc(test(""))] - | ^^ + | ^^^^^^^^^^^^--^^^ + | | + | didn't expect a literal here | - = note: `#[deny(invalid_doc_attributes)]` on by default +help: try changing it to one of the following valid forms of the attribute + | +LL - #![doc(test(""))] +LL + #![doc = "string"] + | +LL - #![doc(test(""))] +LL + #![doc(alias)] + | +LL - #![doc(test(""))] +LL + #![doc(attribute)] + | +LL - #![doc(test(""))] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0565`. diff --git a/tests/ui/attributes/extented-attribute-macro-error.rs b/tests/ui/attributes/extented-attribute-macro-error.rs index 83060024dac93..f85a28ad706e5 100644 --- a/tests/ui/attributes/extented-attribute-macro-error.rs +++ b/tests/ui/attributes/extented-attribute-macro-error.rs @@ -3,5 +3,6 @@ #![doc = include_str!("../not_existing_file.md")] struct Documented {} //~^^ ERROR couldn't read +//~| ERROR attribute value must be a literal fn main() {} diff --git a/tests/ui/attributes/extented-attribute-macro-error.stderr b/tests/ui/attributes/extented-attribute-macro-error.stderr index 7b93b98f64cd4..ed46cba3d0407 100644 --- a/tests/ui/attributes/extented-attribute-macro-error.stderr +++ b/tests/ui/attributes/extented-attribute-macro-error.stderr @@ -4,5 +4,11 @@ error: couldn't read the file LL | #![doc = include_str!("../not_existing_file.md")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error: attribute value must be a literal + --> $DIR/extented-attribute-macro-error.rs:3:10 + | +LL | #![doc = include_str!("../not_existing_file.md")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors diff --git a/tests/ui/attributes/issue-115264-expr-field.rs b/tests/ui/attributes/issue-115264-expr-field.rs index 8adb68deb5b4f..d8189626fb0f8 100644 --- a/tests/ui/attributes/issue-115264-expr-field.rs +++ b/tests/ui/attributes/issue-115264-expr-field.rs @@ -12,6 +12,8 @@ struct X { fn main() { let _ = X { #[doc(alias = "StructItem")] + //~^ WARN: attribute cannot be used on struct fields + //~| WARN: this was previously accepted by the compiler but is being phased out foo: 123, }; } diff --git a/tests/ui/attributes/issue-115264-expr-field.stderr b/tests/ui/attributes/issue-115264-expr-field.stderr new file mode 100644 index 0000000000000..6bb9dfc90c934 --- /dev/null +++ b/tests/ui/attributes/issue-115264-expr-field.stderr @@ -0,0 +1,12 @@ +warning: `#[doc]` attribute cannot be used on struct fields + --> $DIR/issue-115264-expr-field.rs:14:9 + | +LL | #[doc(alias = "StructItem")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + = note: requested on the command line with `-W unused-attributes` + +warning: 1 warning emitted + diff --git a/tests/ui/attributes/issue-115264-pat-field.rs b/tests/ui/attributes/issue-115264-pat-field.rs index 53e3b4524d60e..0c8966b5893f6 100644 --- a/tests/ui/attributes/issue-115264-pat-field.rs +++ b/tests/ui/attributes/issue-115264-pat-field.rs @@ -12,6 +12,8 @@ struct X { fn main() { let X { #[doc(alias = "StructItem")] + //~^ WARN: attribute cannot be used on pattern fields + //~| WARN: this was previously accepted by the compiler but is being phased out foo } = X { foo: 123 diff --git a/tests/ui/attributes/issue-115264-pat-field.stderr b/tests/ui/attributes/issue-115264-pat-field.stderr new file mode 100644 index 0000000000000..a5b221110789d --- /dev/null +++ b/tests/ui/attributes/issue-115264-pat-field.stderr @@ -0,0 +1,12 @@ +warning: `#[doc]` attribute cannot be used on pattern fields + --> $DIR/issue-115264-pat-field.rs:14:9 + | +LL | #[doc(alias = "StructItem")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + = note: requested on the command line with `-W unused-attributes` + +warning: 1 warning emitted + diff --git a/tests/ui/attributes/key-value-expansion-scope.rs b/tests/ui/attributes/key-value-expansion-scope.rs index 6688d698f9eae..b30da2eda24a8 100644 --- a/tests/ui/attributes/key-value-expansion-scope.rs +++ b/tests/ui/attributes/key-value-expansion-scope.rs @@ -1,19 +1,29 @@ #![doc = in_root!()] //~ ERROR cannot find macro `in_root` //~| WARN this was previously accepted by the compiler #![doc = in_mod!()] //~ ERROR cannot find macro `in_mod` in this scope + //~| ERROR attribute value must be a literal #![doc = in_mod_escape!()] //~ ERROR cannot find macro `in_mod_escape` //~| WARN this was previously accepted by the compiler #![doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal #[doc = in_root!()] //~ ERROR cannot find macro `in_root` in this scope + //~| ERROR attribute value must be a literal #[doc = in_mod!()] //~ ERROR cannot find macro `in_mod` in this scope + //~| ERROR attribute value must be a literal #[doc = in_mod_escape!()] //~ ERROR cannot find macro `in_mod_escape` in this scope + //~| ERROR attribute value must be a literal #[doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal fn before() { #![doc = in_root!()] //~ ERROR cannot find macro `in_root` in this scope + //~| ERROR attribute value must be a literal #![doc = in_mod!()] //~ ERROR cannot find macro `in_mod` in this scope + //~| ERROR attribute value must be a literal #![doc = in_mod_escape!()] //~ ERROR cannot find macro `in_mod_escape` in this scope + //~| ERROR attribute value must be a literal #![doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal } macro_rules! in_root { () => { "" } } @@ -48,8 +58,10 @@ mod macros_escape { } #[doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal fn block() { #![doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal macro_rules! in_block { () => { "" } } @@ -61,13 +73,17 @@ fn block() { #[doc = in_root!()] // OK #[doc = in_mod!()] //~ ERROR cannot find macro `in_mod` in this scope + //~| ERROR attribute value must be a literal #[doc = in_mod_escape!()] // OK #[doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal fn after() { #![doc = in_root!()] // OK #![doc = in_mod!()] //~ ERROR cannot find macro `in_mod` in this scope + //~| ERROR attribute value must be a literal #![doc = in_mod_escape!()] // OK #![doc = in_block!()] //~ ERROR cannot find macro `in_block` in this scope + //~| ERROR attribute value must be a literal } fn main() {} diff --git a/tests/ui/attributes/key-value-expansion-scope.stderr b/tests/ui/attributes/key-value-expansion-scope.stderr index 71a83d80617a4..1ebed6b8b6fce 100644 --- a/tests/ui/attributes/key-value-expansion-scope.stderr +++ b/tests/ui/attributes/key-value-expansion-scope.stderr @@ -7,7 +7,7 @@ LL | #![doc = in_mod!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:6:10 + --> $DIR/key-value-expansion-scope.rs:7:10 | LL | #![doc = in_block!()] | ^^^^^^^^ @@ -15,7 +15,7 @@ LL | #![doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_root` in this scope - --> $DIR/key-value-expansion-scope.rs:8:9 + --> $DIR/key-value-expansion-scope.rs:10:9 | LL | #[doc = in_root!()] | ^^^^^^^ @@ -23,7 +23,7 @@ LL | #[doc = in_root!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod` in this scope - --> $DIR/key-value-expansion-scope.rs:9:9 + --> $DIR/key-value-expansion-scope.rs:12:9 | LL | #[doc = in_mod!()] | ^^^^^^ @@ -31,7 +31,7 @@ LL | #[doc = in_mod!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod_escape` in this scope - --> $DIR/key-value-expansion-scope.rs:10:9 + --> $DIR/key-value-expansion-scope.rs:14:9 | LL | #[doc = in_mod_escape!()] | ^^^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | #[doc = in_mod_escape!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:11:9 + --> $DIR/key-value-expansion-scope.rs:16:9 | LL | #[doc = in_block!()] | ^^^^^^^^ @@ -47,7 +47,7 @@ LL | #[doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_root` in this scope - --> $DIR/key-value-expansion-scope.rs:13:14 + --> $DIR/key-value-expansion-scope.rs:19:14 | LL | #![doc = in_root!()] | ^^^^^^^ @@ -55,7 +55,7 @@ LL | #![doc = in_root!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod` in this scope - --> $DIR/key-value-expansion-scope.rs:14:14 + --> $DIR/key-value-expansion-scope.rs:21:14 | LL | #![doc = in_mod!()] | ^^^^^^ @@ -63,7 +63,7 @@ LL | #![doc = in_mod!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod_escape` in this scope - --> $DIR/key-value-expansion-scope.rs:15:14 + --> $DIR/key-value-expansion-scope.rs:23:14 | LL | #![doc = in_mod_escape!()] | ^^^^^^^^^^^^^ @@ -71,7 +71,7 @@ LL | #![doc = in_mod_escape!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:16:14 + --> $DIR/key-value-expansion-scope.rs:25:14 | LL | #![doc = in_block!()] | ^^^^^^^^ @@ -79,7 +79,7 @@ LL | #![doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:50:9 + --> $DIR/key-value-expansion-scope.rs:60:9 | LL | #[doc = in_block!()] | ^^^^^^^^ @@ -87,7 +87,7 @@ LL | #[doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:52:14 + --> $DIR/key-value-expansion-scope.rs:63:14 | LL | #![doc = in_block!()] | ^^^^^^^^ @@ -95,7 +95,7 @@ LL | #![doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod` in this scope - --> $DIR/key-value-expansion-scope.rs:63:9 + --> $DIR/key-value-expansion-scope.rs:75:9 | LL | #[doc = in_mod!()] | ^^^^^^ @@ -103,7 +103,7 @@ LL | #[doc = in_mod!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:65:9 + --> $DIR/key-value-expansion-scope.rs:78:9 | LL | #[doc = in_block!()] | ^^^^^^^^ @@ -111,7 +111,7 @@ LL | #[doc = in_block!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_mod` in this scope - --> $DIR/key-value-expansion-scope.rs:68:14 + --> $DIR/key-value-expansion-scope.rs:82:14 | LL | #![doc = in_mod!()] | ^^^^^^ @@ -119,7 +119,7 @@ LL | #![doc = in_mod!()] = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `in_block` in this scope - --> $DIR/key-value-expansion-scope.rs:70:14 + --> $DIR/key-value-expansion-scope.rs:85:14 | LL | #![doc = in_block!()] | ^^^^^^^^ @@ -138,7 +138,7 @@ LL | #![doc = in_root!()] = note: `#[deny(out_of_scope_macro_calls)]` (part of `#[deny(future_incompatible)]`) on by default error: cannot find macro `in_mod_escape` in the current scope when looking from the crate root - --> $DIR/key-value-expansion-scope.rs:4:10 + --> $DIR/key-value-expansion-scope.rs:5:10 | LL | #![doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from the crate root @@ -148,7 +148,7 @@ LL | #![doc = in_mod_escape!()] = help: import `macro_rules` with `use` to make it callable above its definition error: cannot find macro `in_mod` in the current scope when looking from module `macros_stay` - --> $DIR/key-value-expansion-scope.rs:21:9 + --> $DIR/key-value-expansion-scope.rs:31:9 | LL | #[doc = in_mod!()] | ^^^^^^ not found from module `macros_stay` @@ -158,7 +158,7 @@ LL | #[doc = in_mod!()] = help: import `macro_rules` with `use` to make it callable above its definition error: cannot find macro `in_mod` in the current scope when looking from module `macros_stay` - --> $DIR/key-value-expansion-scope.rs:24:14 + --> $DIR/key-value-expansion-scope.rs:34:14 | LL | #![doc = in_mod!()] | ^^^^^^ not found from module `macros_stay` @@ -168,7 +168,7 @@ LL | #![doc = in_mod!()] = help: import `macro_rules` with `use` to make it callable above its definition error: cannot find macro `in_mod_escape` in the current scope when looking from module `macros_escape` - --> $DIR/key-value-expansion-scope.rs:36:9 + --> $DIR/key-value-expansion-scope.rs:46:9 | LL | #[doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from module `macros_escape` @@ -178,7 +178,7 @@ LL | #[doc = in_mod_escape!()] = help: import `macro_rules` with `use` to make it callable above its definition error: cannot find macro `in_mod_escape` in the current scope when looking from module `macros_escape` - --> $DIR/key-value-expansion-scope.rs:39:14 + --> $DIR/key-value-expansion-scope.rs:49:14 | LL | #![doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from module `macros_escape` @@ -187,7 +187,103 @@ LL | #![doc = in_mod_escape!()] = note: for more information, see issue #124535 = help: import `macro_rules` with `use` to make it callable above its definition -error: aborting due to 22 previous errors +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:3:10 + | +LL | #![doc = in_mod!()] + | ^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:7:10 + | +LL | #![doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:10:9 + | +LL | #[doc = in_root!()] + | ^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:12:9 + | +LL | #[doc = in_mod!()] + | ^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:14:9 + | +LL | #[doc = in_mod_escape!()] + | ^^^^^^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:16:9 + | +LL | #[doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:19:14 + | +LL | #![doc = in_root!()] + | ^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:21:14 + | +LL | #![doc = in_mod!()] + | ^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:23:14 + | +LL | #![doc = in_mod_escape!()] + | ^^^^^^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:25:14 + | +LL | #![doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:60:9 + | +LL | #[doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:63:14 + | +LL | #![doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:75:9 + | +LL | #[doc = in_mod!()] + | ^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:78:9 + | +LL | #[doc = in_block!()] + | ^^^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:82:14 + | +LL | #![doc = in_mod!()] + | ^^^^^^^^^ + +error: attribute value must be a literal + --> $DIR/key-value-expansion-scope.rs:85:14 + | +LL | #![doc = in_block!()] + | ^^^^^^^^^^^ + +error: aborting due to 38 previous errors Future incompatibility report: Future breakage diagnostic: error: cannot find macro `in_root` in the current scope when looking from the crate root @@ -203,7 +299,7 @@ LL | #![doc = in_root!()] Future breakage diagnostic: error: cannot find macro `in_mod_escape` in the current scope when looking from the crate root - --> $DIR/key-value-expansion-scope.rs:4:10 + --> $DIR/key-value-expansion-scope.rs:5:10 | LL | #![doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from the crate root @@ -215,7 +311,7 @@ LL | #![doc = in_mod_escape!()] Future breakage diagnostic: error: cannot find macro `in_mod` in the current scope when looking from module `macros_stay` - --> $DIR/key-value-expansion-scope.rs:21:9 + --> $DIR/key-value-expansion-scope.rs:31:9 | LL | #[doc = in_mod!()] | ^^^^^^ not found from module `macros_stay` @@ -227,7 +323,7 @@ LL | #[doc = in_mod!()] Future breakage diagnostic: error: cannot find macro `in_mod` in the current scope when looking from module `macros_stay` - --> $DIR/key-value-expansion-scope.rs:24:14 + --> $DIR/key-value-expansion-scope.rs:34:14 | LL | #![doc = in_mod!()] | ^^^^^^ not found from module `macros_stay` @@ -239,7 +335,7 @@ LL | #![doc = in_mod!()] Future breakage diagnostic: error: cannot find macro `in_mod_escape` in the current scope when looking from module `macros_escape` - --> $DIR/key-value-expansion-scope.rs:36:9 + --> $DIR/key-value-expansion-scope.rs:46:9 | LL | #[doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from module `macros_escape` @@ -251,7 +347,7 @@ LL | #[doc = in_mod_escape!()] Future breakage diagnostic: error: cannot find macro `in_mod_escape` in the current scope when looking from module `macros_escape` - --> $DIR/key-value-expansion-scope.rs:39:14 + --> $DIR/key-value-expansion-scope.rs:49:14 | LL | #![doc = in_mod_escape!()] | ^^^^^^^^^^^^^ not found from module `macros_escape` diff --git a/tests/ui/attributes/key-value-expansion.stderr b/tests/ui/attributes/key-value-expansion.stderr index d785bf978196b..54d79c5bebb7f 100644 --- a/tests/ui/attributes/key-value-expansion.stderr +++ b/tests/ui/attributes/key-value-expansion.stderr @@ -1,3 +1,9 @@ +error: attribute value must be a literal + --> $DIR/key-value-expansion.rs:21:6 + | +LL | bug!((column!())); + | ^^^^^^^^^^^ + error: attribute value must be a literal --> $DIR/key-value-expansion.rs:27:14 | @@ -20,11 +26,5 @@ LL | some_macro!(u8); | = note: this error originates in the macro `some_macro` (in Nightly builds, run with -Z macro-backtrace for more info) -error: attribute value must be a literal - --> $DIR/key-value-expansion.rs:21:6 - | -LL | bug!((column!())); - | ^^^^^^^^^^^ - error: aborting due to 3 previous errors diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr index 0ef62d70a15ca..a6bd62fa1214e 100644 --- a/tests/ui/attributes/malformed-attrs.stderr +++ b/tests/ui/attributes/malformed-attrs.stderr @@ -182,27 +182,6 @@ LL | #[allow_internal_unsafe = 1] = help: add `#![feature(allow_internal_unsafe)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` - --> $DIR/malformed-attrs.rs:41:1 - | -LL | #[doc] - | ^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #57571 - = note: for more information, visit - = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default - -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` - --> $DIR/malformed-attrs.rs:77:1 - | -LL | #[doc] - | ^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #57571 - = note: for more information, visit - error[E0539]: malformed `windows_subsystem` attribute input --> $DIR/malformed-attrs.rs:26:1 | @@ -790,6 +769,16 @@ LL | #[diagnostic::on_unimplemented = 1] | = help: only `message`, `note` and `label` are allowed as options +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` + --> $DIR/malformed-attrs.rs:41:1 + | +LL | #[doc] + | ^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default + error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` --> $DIR/malformed-attrs.rs:52:1 | @@ -814,6 +803,15 @@ LL | | #[coroutine = 63] || {} LL | | } | |_^ +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` + --> $DIR/malformed-attrs.rs:77:1 + | +LL | #[doc] + | ^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + warning: `#[link_name]` attribute cannot be used on functions --> $DIR/malformed-attrs.rs:88:1 | @@ -875,7 +873,7 @@ error: aborting due to 76 previous errors; 8 warnings emitted Some errors have detailed explanations: E0308, E0463, E0539, E0565, E0658, E0805. For more information about an error, try `rustc --explain E0308`. Future incompatibility report: Future breakage diagnostic: -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` --> $DIR/malformed-attrs.rs:41:1 | LL | #[doc] @@ -883,27 +881,25 @@ LL | #[doc] | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #57571 - = note: for more information, visit = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default Future breakage diagnostic: -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` - --> $DIR/malformed-attrs.rs:77:1 +error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` + --> $DIR/malformed-attrs.rs:52:1 | -LL | #[doc] - | ^^^^^^ +LL | #[inline = 5] + | ^^^^^^^^^^^^^ | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #57571 - = note: for more information, visit = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default Future breakage diagnostic: -error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` - --> $DIR/malformed-attrs.rs:52:1 +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` + --> $DIR/malformed-attrs.rs:77:1 | -LL | #[inline = 5] - | ^^^^^^^^^^^^^ +LL | #[doc] + | ^^^^^^ | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #57571 diff --git a/tests/ui/feature-gates/removed-features-note-version-and-pr-issue-141619.stderr b/tests/ui/feature-gates/removed-features-note-version-and-pr-issue-141619.stderr index bd8c56c61c3c9..d9556560b01c2 100644 --- a/tests/ui/feature-gates/removed-features-note-version-and-pr-issue-141619.stderr +++ b/tests/ui/feature-gates/removed-features-note-version-and-pr-issue-141619.stderr @@ -11,7 +11,7 @@ error: unknown `doc` attribute `include` --> $DIR/removed-features-note-version-and-pr-issue-141619.rs:2:8 | LL | #![doc(include("README.md"))] - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ | = note: `#[deny(invalid_doc_attributes)]` on by default diff --git a/tests/ui/include-macros/invalid-utf8-binary-file.rs b/tests/ui/include-macros/invalid-utf8-binary-file.rs index 0bbdbac611fd0..c733f07c26f00 100644 --- a/tests/ui/include-macros/invalid-utf8-binary-file.rs +++ b/tests/ui/include-macros/invalid-utf8-binary-file.rs @@ -5,6 +5,8 @@ //! Ensure that ICE does not occur when reading an invalid UTF8 file with an absolute path. //! regression test for issue -#![doc = include_str!(concat!(env!("INVALID_UTF8_BIN")))] //~ ERROR: wasn't a utf-8 file +#![doc = include_str!(concat!(env!("INVALID_UTF8_BIN")))] +//~^ ERROR: wasn't a utf-8 file +//~| ERROR: attribute value must be a literal fn main() {} diff --git a/tests/ui/include-macros/invalid-utf8-binary-file.stderr b/tests/ui/include-macros/invalid-utf8-binary-file.stderr index 4ac4def1b00a1..232cb5042b72f 100644 --- a/tests/ui/include-macros/invalid-utf8-binary-file.stderr +++ b/tests/ui/include-macros/invalid-utf8-binary-file.stderr @@ -6,5 +6,11 @@ LL | #![doc = include_str!(concat!(env!("INVALID_UTF8_BIN")))] | = note: invalid utf-8 at byte `$BYTE` -error: aborting due to 1 previous error +error: attribute value must be a literal + --> $DIR/invalid-utf8-binary-file.rs:8:10 + | +LL | #![doc = include_str!(concat!(env!("INVALID_UTF8_BIN")))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors diff --git a/tests/ui/lint/unused/useless-comment.rs b/tests/ui/lint/unused/useless-comment.rs index 898665278e39c..fee6cff640f6d 100644 --- a/tests/ui/lint/unused/useless-comment.rs +++ b/tests/ui/lint/unused/useless-comment.rs @@ -1,6 +1,7 @@ #![feature(stmt_expr_attributes)] #![deny(unused_doc_comments)] +#![deny(unused_attributes)] macro_rules! mac { () => {} @@ -15,7 +16,10 @@ unsafe extern "C" { } fn foo() { /// a //~ ERROR unused doc comment - #[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment + #[doc(test(attr(allow(dead_code))))] + //~^ ERROR unused doc comment + //~| ERROR `#[doc]` attribute cannot be used on statements + //~| WARN this was previously accepted by the compiler let x = 12; /// multi-line //~ ERROR unused doc comment @@ -24,7 +28,10 @@ fn foo() { match x { /// c //~ ERROR unused doc comment 1 => {}, - #[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment + #[doc(test(attr(allow(dead_code))))] + //~^ ERROR unused doc comment + //~| ERROR `#[doc]` attribute cannot be used on match arms [unused_attributes] + //~| WARN this was previously accepted by the compiler _ => {} } @@ -38,7 +45,10 @@ fn foo() { /// bar //~ ERROR unused doc comment mac!(); - #[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment + #[doc(test(attr(allow(dead_code))))] + //~^ ERROR unused doc comment + //~| ERROR `#[doc]` attribute cannot be used on statements + //~| WARN this was previously accepted by the compiler let x = /** comment */ 47; //~ ERROR unused doc comment /// dox //~ ERROR unused doc comment diff --git a/tests/ui/lint/unused/useless-comment.stderr b/tests/ui/lint/unused/useless-comment.stderr index 39873b82b7575..3d3937e7a402f 100644 --- a/tests/ui/lint/unused/useless-comment.stderr +++ b/tests/ui/lint/unused/useless-comment.stderr @@ -1,5 +1,5 @@ error: unused doc comment - --> $DIR/useless-comment.rs:9:1 + --> $DIR/useless-comment.rs:10:1 | LL | /// foo | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macro invocations @@ -12,7 +12,7 @@ LL | #![deny(unused_doc_comments)] | ^^^^^^^^^^^^^^^^^^^ error: unused doc comment - --> $DIR/useless-comment.rs:12:1 + --> $DIR/useless-comment.rs:13:1 | LL | /// a | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | unsafe extern "C" { } = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:13:1 + --> $DIR/useless-comment.rs:14:1 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | unsafe extern "C" { } = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:38:5 + --> $DIR/useless-comment.rs:45:5 | LL | /// bar | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macro invocations @@ -41,28 +41,29 @@ LL | /// bar = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion error: unused doc comment - --> $DIR/useless-comment.rs:17:5 + --> $DIR/useless-comment.rs:18:5 | LL | /// a | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | #[doc(test(attr(allow(dead_code))))] +... LL | let x = 12; | ----------- rustdoc does not generate documentation for statements | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:18:5 + --> $DIR/useless-comment.rs:19:5 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... LL | let x = 12; | ----------- rustdoc does not generate documentation for statements | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:21:5 + --> $DIR/useless-comment.rs:25:5 | LL | / /// multi-line LL | | /// doc comment @@ -72,6 +73,7 @@ LL | / match x { LL | | /// c LL | | 1 => {}, LL | | #[doc(test(attr(allow(dead_code))))] +... | LL | | _ => {} LL | | } | |_____- rustdoc does not generate documentation for expressions @@ -79,7 +81,7 @@ LL | | } = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:25:9 + --> $DIR/useless-comment.rs:29:9 | LL | /// c | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -89,17 +91,18 @@ LL | 1 => {}, = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:27:9 + --> $DIR/useless-comment.rs:31:9 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... LL | _ => {} | ------- rustdoc does not generate documentation for match arms | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:31:5 + --> $DIR/useless-comment.rs:38:5 | LL | /// foo | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -109,7 +112,7 @@ LL | unsafe {} = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:34:5 + --> $DIR/useless-comment.rs:41:5 | LL | #[doc = "foo"] | ^^^^^^^^^^^^^^ @@ -120,7 +123,7 @@ LL | 3; = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:35:5 + --> $DIR/useless-comment.rs:42:5 | LL | #[doc = "bar"] | ^^^^^^^^^^^^^^ @@ -130,17 +133,18 @@ LL | 3; = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:41:5 + --> $DIR/useless-comment.rs:48:5 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... LL | let x = /** comment */ 47; | -------------------------- rustdoc does not generate documentation for statements | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:42:13 + --> $DIR/useless-comment.rs:52:13 | LL | let x = /** comment */ 47; | ^^^^^^^^^^^^^^ -- rustdoc does not generate documentation for expressions @@ -148,7 +152,7 @@ LL | let x = /** comment */ 47; = help: use `/* */` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:44:5 + --> $DIR/useless-comment.rs:54:5 | LL | /// dox | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -159,5 +163,37 @@ LL | | } | = help: use `//` for a plain comment -error: aborting due to 15 previous errors +error: `#[doc]` attribute cannot be used on statements + --> $DIR/useless-comment.rs:19:5 + | +LL | #[doc(test(attr(allow(dead_code))))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements +note: the lint level is defined here + --> $DIR/useless-comment.rs:4:9 + | +LL | #![deny(unused_attributes)] + | ^^^^^^^^^^^^^^^^^ + +error: `#[doc]` attribute cannot be used on match arms + --> $DIR/useless-comment.rs:31:9 + | +LL | #[doc(test(attr(allow(dead_code))))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + +error: `#[doc]` attribute cannot be used on statements + --> $DIR/useless-comment.rs:48:5 + | +LL | #[doc(test(attr(allow(dead_code))))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + +error: aborting due to 18 previous errors diff --git a/tests/ui/malformed/malformed-regressions.stderr b/tests/ui/malformed/malformed-regressions.stderr index 1812079848774..29734fd84e6ba 100644 --- a/tests/ui/malformed/malformed-regressions.stderr +++ b/tests/ui/malformed/malformed-regressions.stderr @@ -1,14 +1,3 @@ -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` - --> $DIR/malformed-regressions.rs:1:1 - | -LL | #[doc] - | ^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: for more information, see issue #57571 - = note: for more information, visit - = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default - error[E0539]: malformed `link` attribute input --> $DIR/malformed-regressions.rs:7:1 | @@ -63,6 +52,16 @@ LL | fn main() {} = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: requested on the command line with `-W unused-attributes` +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` + --> $DIR/malformed-regressions.rs:1:1 + | +LL | #[doc] + | ^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #57571 + = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default + error: valid forms for the attribute are `#[ignore = "reason"]` and `#[ignore]` --> $DIR/malformed-regressions.rs:3:1 | @@ -85,7 +84,7 @@ error: aborting due to 5 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0539`. Future incompatibility report: Future breakage diagnostic: -error: valid forms for the attribute are `#[doc(hidden)]`, `#[doc(inline)]`, and `#[doc = "string"]` +error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` --> $DIR/malformed-regressions.rs:1:1 | LL | #[doc] @@ -93,7 +92,6 @@ LL | #[doc] | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #57571 - = note: for more information, visit = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default Future breakage diagnostic: diff --git a/tests/ui/parser/attribute/attr-unquoted-ident.stderr b/tests/ui/parser/attribute/attr-unquoted-ident.stderr index f1af60dec9bbb..00fb539f8a865 100644 --- a/tests/ui/parser/attribute/attr-unquoted-ident.stderr +++ b/tests/ui/parser/attribute/attr-unquoted-ident.stderr @@ -20,19 +20,6 @@ help: surround the identifier with quotation marks to make it into a string lite LL | #[cfg(key="foo bar baz")] | + + -error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression - --> $DIR/attr-unquoted-ident.rs:18:15 - | -LL | #[cfg(key=foo 1 bar 2.0 baz.)] - | ^^^ expressions are not allowed here - | -help: surround the identifier with quotation marks to make it into a string literal - | -LL | #[cfg(key="foo 1 bar 2.0 baz.")] - | + + - -error: expected unsuffixed literal, found identifier `nickname` - --> $DIR/attr-unquoted-ident.rs:28:38 | LL | ($name:ident) => { #[doc(alias = $name)] pub struct S; } | ^^^^^ diff --git a/tests/ui/rustdoc/check-doc-alias-attr-location.rs b/tests/ui/rustdoc/check-doc-alias-attr-location.rs index 10609e5d8f4d9..8ba6cfde2d6d0 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.rs +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.rs @@ -21,11 +21,22 @@ impl Foo for Bar { type X = i32; fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { //~^ ERROR - #[doc(alias = "stmt")] //~ ERROR + //~| WARN `#[doc]` attribute cannot be used on function params + //~| WARN: this was previously accepted by the compiler + #[doc(alias = "stmt")] + //~^ ERROR + //~| WARN `#[doc]` attribute cannot be used on statements + //~| WARN: this was previously accepted by the compiler let x = 0; - #[doc(alias = "expr")] //~ ERROR + #[doc(alias = "expr")] + //~^ ERROR + //~| WARN `#[doc]` attribute cannot be used on expressions + //~| WARN: this was previously accepted by the compiler match x { - #[doc(alias = "arm")] //~ ERROR + #[doc(alias = "arm")] + //~^ ERROR + //~| WARN `#[doc]` attribute cannot be used on match arms + //~| WARN: this was previously accepted by the compiler _ => 0 } } diff --git a/tests/ui/rustdoc/check-doc-alias-attr-location.stderr b/tests/ui/rustdoc/check-doc-alias-attr-location.stderr index 23c93a4ed8bdb..24be42036f6fd 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.stderr +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.stderr @@ -5,46 +5,83 @@ LL | fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { | ^^^^^^^^^^^^^^^^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on foreign module - --> $DIR/check-doc-alias-attr-location.rs:9:7 + --> $DIR/check-doc-alias-attr-location.rs:9:15 | LL | #[doc(alias = "foo")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: `#[doc(alias = "...")]` isn't allowed on implementation block - --> $DIR/check-doc-alias-attr-location.rs:12:7 + --> $DIR/check-doc-alias-attr-location.rs:12:15 | LL | #[doc(alias = "bar")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: `#[doc(alias = "...")]` isn't allowed on implementation block - --> $DIR/check-doc-alias-attr-location.rs:18:7 + --> $DIR/check-doc-alias-attr-location.rs:18:15 | LL | #[doc(alias = "foobar")] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on type alias in implementation block - --> $DIR/check-doc-alias-attr-location.rs:20:11 + --> $DIR/check-doc-alias-attr-location.rs:20:19 | LL | #[doc(alias = "assoc")] - | ^^^^^^^^^^^^^^^ + | ^^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on statement - --> $DIR/check-doc-alias-attr-location.rs:24:15 + --> $DIR/check-doc-alias-attr-location.rs:26:23 | LL | #[doc(alias = "stmt")] - | ^^^^^^^^^^^^^^ + | ^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on expression - --> $DIR/check-doc-alias-attr-location.rs:26:15 + --> $DIR/check-doc-alias-attr-location.rs:31:23 | LL | #[doc(alias = "expr")] - | ^^^^^^^^^^^^^^ + | ^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on match arm - --> $DIR/check-doc-alias-attr-location.rs:28:19 + --> $DIR/check-doc-alias-attr-location.rs:36:27 | LL | #[doc(alias = "arm")] - | ^^^^^^^^^^^^^ + | ^^^^^ -error: aborting due to 8 previous errors +warning: `#[doc]` attribute cannot be used on function params + --> $DIR/check-doc-alias-attr-location.rs:22:12 + | +LL | fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { + | ^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + = note: requested on the command line with `-W unused-attributes` + +warning: `#[doc]` attribute cannot be used on statements + --> $DIR/check-doc-alias-attr-location.rs:26:9 + | +LL | #[doc(alias = "stmt")] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + +warning: `#[doc]` attribute cannot be used on expressions + --> $DIR/check-doc-alias-attr-location.rs:31:9 + | +LL | #[doc(alias = "expr")] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + +warning: `#[doc]` attribute cannot be used on match arms + --> $DIR/check-doc-alias-attr-location.rs:36:13 + | +LL | #[doc(alias = "arm")] + | ^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements + +error: aborting due to 8 previous errors; 4 warnings emitted diff --git a/tests/ui/rustdoc/check-doc-alias-attr.stderr b/tests/ui/rustdoc/check-doc-alias-attr.stderr index 250568be3333f..6c33f10e87851 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr.stderr +++ b/tests/ui/rustdoc/check-doc-alias-attr.stderr @@ -4,11 +4,29 @@ error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of s LL | #[doc(alias)] | ^^^^^ -error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` - --> $DIR/check-doc-alias-attr.rs:8:7 +error[E0539]: malformed `doc` attribute input + --> $DIR/check-doc-alias-attr.rs:8:1 | LL | #[doc(alias = 0)] - | ^^^^^^^^^ + | ^^^^^^^^^^^^^^-^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(alias = 0)] +LL + #[doc = "string"] + | +LL - #[doc(alias = 0)] +LL + #[doc(alias)] + | +LL - #[doc(alias = 0)] +LL + #[doc(attribute)] + | +LL - #[doc(alias = 0)] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:9:15 @@ -54,25 +72,43 @@ error: `#[doc(alias = "...")]` attribute cannot have empty value LL | #[doc(alias = "")] | ^^ -error: `#[doc(alias("a"))]` expects string literals - --> $DIR/check-doc-alias-attr.rs:19:13 +error[E0539]: malformed `doc` attribute input + --> $DIR/check-doc-alias-attr.rs:19:1 | LL | #[doc(alias(0))] - | ^ + | ^^^^^^^^^^^^-^^^ + | | + | expected a string literal here + | +help: try changing it to one of the following valid forms of the attribute + | +LL - #[doc(alias(0))] +LL + #[doc = "string"] + | +LL - #[doc(alias(0))] +LL + #[doc(alias)] + | +LL - #[doc(alias(0))] +LL + #[doc(attribute)] + | +LL - #[doc(alias(0))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -error: '"' character isn't allowed in `#[doc(alias("..."))]` +error: '"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:20:13 | LL | #[doc(alias("\""))] | ^^^^ -error: '\n' character isn't allowed in `#[doc(alias("..."))]` +error: '\n' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:21:13 | LL | #[doc(alias("\n"))] | ^^^^ -error: '\n' character isn't allowed in `#[doc(alias("..."))]` +error: '\n' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:22:13 | LL | #[doc(alias(" @@ -80,25 +116,25 @@ LL | #[doc(alias(" LL | | "))] | |_^ -error: '\t' character isn't allowed in `#[doc(alias("..."))]` +error: '\t' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:24:13 | LL | #[doc(alias("\t"))] | ^^^^ -error: `#[doc(alias("..."))]` cannot start or end with ' ' +error: `#[doc(alias = "...")]` cannot start or end with ' ' --> $DIR/check-doc-alias-attr.rs:25:13 | LL | #[doc(alias(" hello"))] | ^^^^^^^^ -error: `#[doc(alias("..."))]` cannot start or end with ' ' +error: `#[doc(alias = "...")]` cannot start or end with ' ' --> $DIR/check-doc-alias-attr.rs:26:13 | LL | #[doc(alias("hello "))] | ^^^^^^^^ -error: `#[doc(alias("..."))]` attribute cannot have empty value +error: `#[doc(alias = "...")]` attribute cannot have empty value --> $DIR/check-doc-alias-attr.rs:27:13 | LL | #[doc(alias(""))] @@ -106,3 +142,4 @@ LL | #[doc(alias(""))] error: aborting due to 17 previous errors +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/rustdoc/doc-alias-crate-level.stderr b/tests/ui/rustdoc/doc-alias-crate-level.stderr index bd32609ade296..a40e31714f145 100644 --- a/tests/ui/rustdoc/doc-alias-crate-level.stderr +++ b/tests/ui/rustdoc/doc-alias-crate-level.stderr @@ -5,10 +5,10 @@ LL | #[doc(alias = "shouldn't work!")] | ^^^^^^^^^^^^^^^^^ error: `#![doc(alias = "...")]` isn't allowed as a crate-level attribute - --> $DIR/doc-alias-crate-level.rs:5:8 + --> $DIR/doc-alias-crate-level.rs:5:16 | LL | #![doc(alias = "not working!")] - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ error: aborting due to 2 previous errors diff --git a/tests/ui/rustdoc/doc-alias-same-name.stderr b/tests/ui/rustdoc/doc-alias-same-name.stderr index a9da75c0171c0..a76ff5ee0b67f 100644 --- a/tests/ui/rustdoc/doc-alias-same-name.stderr +++ b/tests/ui/rustdoc/doc-alias-same-name.stderr @@ -1,8 +1,8 @@ -error: `#[doc(alias = "...")]` is the same as the item's name - --> $DIR/doc-alias-same-name.rs:3:7 +error: `#[doc(alias = "Foo"]` is the same as the item's name + --> $DIR/doc-alias-same-name.rs:3:15 | LL | #[doc(alias = "Foo")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/rustdoc/doc-primitive.stderr b/tests/ui/rustdoc/doc-primitive.stderr index 8f6f330b3e5d8..4ba310ca24101 100644 --- a/tests/ui/rustdoc/doc-primitive.stderr +++ b/tests/ui/rustdoc/doc-primitive.stderr @@ -2,7 +2,7 @@ error: unknown `doc` attribute `primitive` --> $DIR/doc-primitive.rs:3:7 | LL | #[doc(primitive = "foo")] - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^ | note: the lint level is defined here --> $DIR/doc-primitive.rs:1:9 diff --git a/tests/ui/rustdoc/doc-test-attr.stderr b/tests/ui/rustdoc/doc-test-attr.stderr index 51672314a4311..1a4b5e45142a1 100644 --- a/tests/ui/rustdoc/doc-test-attr.stderr +++ b/tests/ui/rustdoc/doc-test-attr.stderr @@ -11,10 +11,10 @@ LL | #![deny(invalid_doc_attributes)] | ^^^^^^^^^^^^^^^^^^^^^^ error: `#[doc(test(...)]` takes a list of attributes - --> $DIR/doc-test-attr.rs:6:8 + --> $DIR/doc-test-attr.rs:6:13 | LL | #![doc(test = "hello")] - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^ error: unknown `doc(test)` attribute `a` --> $DIR/doc-test-attr.rs:8:13 diff --git a/tests/ui/rustdoc/doc_keyword.rs b/tests/ui/rustdoc/doc_keyword.rs index e0995f336da3b..abf06d7a78663 100644 --- a/tests/ui/rustdoc/doc_keyword.rs +++ b/tests/ui/rustdoc/doc_keyword.rs @@ -1,14 +1,14 @@ #![crate_type = "lib"] #![feature(rustdoc_internals)] -#![doc(keyword = "hello")] +#![doc(keyword = "match")] //~^ ERROR `#![doc(keyword = "...")]` isn't allowed as a crate-level attribute -#[doc(keyword = "hell")] //~ ERROR `#[doc(keyword = "...")]` should be used on empty modules +#[doc(keyword = "match")] //~ ERROR `#[doc(keyword = "...")]` should be used on empty modules mod foo { fn hell() {} } -#[doc(keyword = "hall")] //~ ERROR `#[doc(keyword = "...")]` should be used on modules +#[doc(keyword = "match")] //~ ERROR `#[doc(keyword = "...")]` should be used on modules fn foo() {} diff --git a/tests/ui/rustdoc/doc_keyword.stderr b/tests/ui/rustdoc/doc_keyword.stderr index 584daae2f1aa5..56da4b00724a5 100644 --- a/tests/ui/rustdoc/doc_keyword.stderr +++ b/tests/ui/rustdoc/doc_keyword.stderr @@ -1,15 +1,3 @@ -error: `#[doc(keyword = "...")]` should be used on empty modules - --> $DIR/doc_keyword.rs:6:7 - | -LL | #[doc(keyword = "hell")] - | ^^^^^^^^^^^^^^^^ - -error: `#[doc(keyword = "...")]` should be used on modules - --> $DIR/doc_keyword.rs:11:7 - | -LL | #[doc(keyword = "hall")] - | ^^^^^^^^^^^^^^^^ - error: nonexistent keyword `tadam` used in `#[doc(keyword = "...")]` --> $DIR/doc_keyword.rs:22:17 | @@ -18,17 +6,29 @@ LL | #[doc(keyword = "tadam")] | = help: only existing keywords are allowed in core/std +error: `#[doc(keyword = "...")]` should be used on empty modules + --> $DIR/doc_keyword.rs:6:7 + | +LL | #[doc(keyword = "match")] + | ^^^^^^^ + +error: `#[doc(keyword = "...")]` should be used on modules + --> $DIR/doc_keyword.rs:11:7 + | +LL | #[doc(keyword = "match")] + | ^^^^^^^ + error: `#[doc(keyword = "...")]` should be used on modules --> $DIR/doc_keyword.rs:17:11 | LL | #[doc(keyword = "match")] - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^ error: `#![doc(keyword = "...")]` isn't allowed as a crate-level attribute --> $DIR/doc_keyword.rs:4:8 | -LL | #![doc(keyword = "hello")] - | ^^^^^^^^^^^^^^^^^ +LL | #![doc(keyword = "match")] + | ^^^^^^^ error: aborting due to 5 previous errors diff --git a/tests/ui/rustdoc/duplicate_doc_alias.stderr b/tests/ui/rustdoc/duplicate_doc_alias.stderr index 4b2dd1f8eb68e..eba48ca599b04 100644 --- a/tests/ui/rustdoc/duplicate_doc_alias.stderr +++ b/tests/ui/rustdoc/duplicate_doc_alias.stderr @@ -1,10 +1,10 @@ error: doc alias is duplicated - --> $DIR/duplicate_doc_alias.rs:4:7 + --> $DIR/duplicate_doc_alias.rs:4:15 | LL | #[doc(alias = "A")] - | ----------- first defined here + | --- first defined here LL | #[doc(alias = "A")] - | ^^^^^^^^^^^ + | ^^^ | note: the lint level is defined here --> $DIR/duplicate_doc_alias.rs:1:9 @@ -16,7 +16,7 @@ error: doc alias is duplicated --> $DIR/duplicate_doc_alias.rs:6:13 | LL | #[doc(alias = "B")] - | ----------- first defined here + | --- first defined here LL | #[doc(alias("B"))] | ^^^ From 348d9d98e07753c2adb0a303bc3855e0843efbcb Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Dec 2025 22:54:56 +0100 Subject: [PATCH 20/32] Correctly iterate doc comments in intra-doc resolution in `rustc_resolve` --- compiler/rustc_ast/src/attr/mod.rs | 16 ++++++++++++++++ compiler/rustc_hir/src/hir.rs | 4 ++++ compiler/rustc_resolve/src/rustdoc.rs | 10 +--------- tests/rustdoc/doc-on-keyword.rs | 13 +++++++++++++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 tests/rustdoc/doc-on-keyword.rs diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 94e7462d26df5..a5e630a09afe2 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -233,6 +233,19 @@ impl AttributeExt for Attribute { self.has_name(sym::doc) && self.meta_item_list().is_some_and(|l| list_contains_name(&l, sym::hidden)) } + + fn is_doc_keyword_or_attribute(&self) -> bool { + if self.has_name(sym::doc) + && let Some(items) = self.meta_item_list() + { + for item in items { + if item.has_name(sym::keyword) || item.has_name(sym::attribute) { + return true; + } + } + } + false + } } impl Attribute { @@ -865,6 +878,9 @@ pub trait AttributeExt: Debug { /// Returns `true` if this attribute contains `doc(hidden)`. fn is_doc_hidden(&self) -> bool; + + /// Returns `true` is this attribute contains `doc(keyword)` or `doc(attribute)`. + fn is_doc_keyword_or_attribute(&self) -> bool; } // FIXME(fn_delegation): use function delegation instead of manually forwarding diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index afa1a33fe769a..f5470adb87e03 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1418,6 +1418,10 @@ impl AttributeExt for Attribute { fn is_doc_hidden(&self) -> bool { matches!(self, Attribute::Parsed(AttributeKind::Doc(d)) if d.hidden.is_some()) } + + fn is_doc_keyword_or_attribute(&self) -> bool { + matches!(self, Attribute::Parsed(AttributeKind::Doc(d)) if d.attribute.is_some() || d.keyword.is_some()) + } } // FIXME(fn_delegation): use function delegation instead of manually forwarding diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 0ac8e68ad968e..7f7c423acb40a 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -367,16 +367,8 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool { /// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`. pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool { for attr in attrs { - if attr.has_name(sym::rustc_doc_primitive) { + if attr.has_name(sym::rustc_doc_primitive) || attr.is_doc_keyword_or_attribute() { return true; - } else if attr.has_name(sym::doc) - && let Some(items) = attr.meta_item_list() - { - for item in items { - if item.has_name(sym::keyword) || item.has_name(sym::attribute) { - return true; - } - } } } false diff --git a/tests/rustdoc/doc-on-keyword.rs b/tests/rustdoc/doc-on-keyword.rs new file mode 100644 index 0000000000000..0c62eda60a6b3 --- /dev/null +++ b/tests/rustdoc/doc-on-keyword.rs @@ -0,0 +1,13 @@ +// While working on , the +// intra doc links on keyword/attribute items were not processed. + +#![feature(rustdoc_internals)] +#![crate_name = "foo"] + +//@ has 'foo/keyword.trait.html' +//@ has - '//a[@href="{{channel}}/core/marker/trait.Send.html"]' 'Send' +//@ has - '//a[@href="{{channel}}/core/marker/trait.Sync.html"]' 'Sync' +#[doc(keyword = "trait")] +// +/// [`Send`] and [Sync] +mod bar {} From 8d4f59bed7af3c8be61f1b96c5bbb25dc4d5b3a8 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 5 Dec 2025 23:13:09 +0100 Subject: [PATCH 21/32] Clean up code --- compiler/rustc_attr_parsing/src/attributes/doc.rs | 6 ++---- compiler/rustc_attr_parsing/src/context.rs | 1 - compiler/rustc_attr_parsing/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 83a6c4fc44d9a..4c0a7a19e5b84 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -1,6 +1,3 @@ -// FIXME: to be removed -#![allow(unused_imports)] - use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; use rustc_feature::template; use rustc_hir::attrs::{ @@ -13,7 +10,6 @@ use thin_vec::ThinVec; use super::prelude::{Allow, AllowedTargets, Error, 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, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute, @@ -355,6 +351,8 @@ impl DocParser { // FIXME: It's errorring when the attribute is passed multiple times on the command // line. + // The right fix for this would be to only check this rule if the attribute is + // not set on the command line but directly in the code. // if self.attribute.$ident.is_some() { // cx.duplicate_key(path.span(), path.word_sym().unwrap()); // return; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 5d15588033fdf..f41ea37087888 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -165,7 +165,6 @@ attribute_parsers!( ConstStabilityParser, DocParser, MacroUseParser, - NakedParser, StabilityParser, UsedParser, diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 3e9d8087a1927..cb02bb9d501fc 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -78,10 +78,10 @@ // tidy-alphabetical-start #![feature(decl_macro)] -#![recursion_limit = "256"] -// tidy-alphabetical-end #![feature(if_let_guard)] #![feature(iter_intersperse)] +#![recursion_limit = "256"] +// tidy-alphabetical-end #[macro_use] /// All the individual attribute parsers for each of rustc's built-in attributes. From 2340f8054c7a97c44bcee69eb314c56667e337a0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Dec 2025 21:29:35 +0100 Subject: [PATCH 22/32] Update to new lint API --- compiler/rustc_attr_parsing/messages.ftl | 65 -------------- .../rustc_attr_parsing/src/attributes/doc.rs | 73 ++++++++++++--- .../src/session_diagnostics.rs | 88 ------------------- compiler/rustc_hir/src/lints.rs | 70 +-------------- compiler/rustc_lint/messages.ftl | 52 +++++++++++ compiler/rustc_lint/src/early/diagnostics.rs | 50 +++++++++++ compiler/rustc_lint/src/lints.rs | 88 +++++++++++++++++++ compiler/rustc_lint_defs/src/lib.rs | 35 ++++++++ src/librustdoc/lib.rs | 14 ++- 9 files changed, 301 insertions(+), 234 deletions(-) diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 2a98b87e7a7e6..f2642838b3c8c 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -17,9 +17,6 @@ attr_parsing_deprecated_item_suggestion = attr_parsing_doc_alias_bad_char = {$char_} character isn't allowed in {$attr_str} -attr_parsing_doc_alias_duplicated = doc alias is duplicated - .label = first defined here - attr_parsing_doc_alias_empty = {$attr_str} attribute cannot have empty value @@ -33,69 +30,10 @@ 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_auto_cfg_expects_hide_or_show = - only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` - -attr_parsing_doc_auto_cfg_hide_show_expects_list = - `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items - -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_wrong_literal = - expected boolean for `#[doc(auto_cfg = ...)]` - -attr_parsing_doc_invalid = - invalid `doc` attribute - 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_test_literal = `#![doc(test(...)]` does not take a literal - -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_unknown_any = - unknown `doc` attribute `{$name}` - -attr_parsing_doc_unknown_include = - unknown `doc` attribute `include` - .suggestion = use `doc = include_str!` instead - -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_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_empty_attribute = - unused attribute - .suggestion = {$valid_without_list -> - [true] remove these parentheses - *[other] remove this attribute - } - .note = {$valid_without_list -> - [true] using `{$attr_path}` with an empty list is equivalent to not using a list at all - *[other] using `{$attr_path}` with an empty list has no effect - } - attr_parsing_empty_confusables = expected at least one confusable name @@ -317,8 +255,5 @@ attr_parsing_unused_multiple = .suggestion = remove this attribute .note = attribute also specified here -attr_parsing_doc_alias_duplicated = doc alias is duplicated - .label = first defined here - attr_parsing_whole_archive_needs_static = linking modifier `whole-archive` is only compatible with `static` linking kind diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 4c0a7a19e5b84..5714d33e861f4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -116,10 +116,18 @@ impl DocParser { } } Some(name) => { - cx.emit_lint(AttributeLintKind::DocTestUnknown { name }, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocTestUnknown { name }, + path.span(), + ); } None => { - cx.emit_lint(AttributeLintKind::DocTestLiteral, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocTestLiteral, + path.span(), + ); } } } @@ -151,7 +159,11 @@ impl DocParser { } if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() { - cx.emit_lint(AttributeLintKind::DuplicateDocAlias { first_definition }, span); + cx.emit_lint( + rustc_session::lint::builtin::UNUSED_ATTRIBUTES, + AttributeLintKind::DuplicateDocAlias { first_definition }, + span, + ); } self.attribute.aliases.insert(alias, span); @@ -239,7 +251,11 @@ impl DocParser { ArgParser::List(list) => { for meta in list.mixed() { let MetaItemOrLitParser::MetaItemParser(item) = meta else { - cx.emit_lint(AttributeLintKind::DocAutoCfgExpectsHideOrShow, meta.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocAutoCfgExpectsHideOrShow, + meta.span(), + ); continue; }; let (kind, attr_name) = match item.path().word_sym() { @@ -247,6 +263,7 @@ impl DocParser { Some(sym::show) => (HideOrShow::Show, sym::show), _ => { cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocAutoCfgExpectsHideOrShow, item.span(), ); @@ -255,6 +272,7 @@ impl DocParser { }; let ArgParser::List(list) = item.args() else { cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name }, item.span(), ); @@ -266,6 +284,7 @@ impl DocParser { for item in list.mixed() { let MetaItemOrLitParser::MetaItemParser(sub_item) = item else { cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name }, item.span(), ); @@ -291,6 +310,7 @@ impl DocParser { } _ => { cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name, }, @@ -306,7 +326,11 @@ impl DocParser { ArgParser::NameValue(nv) => { let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit() else { - cx.emit_lint(AttributeLintKind::DocAutoCfgWrongLiteral, nv.value_span); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocAutoCfgWrongLiteral, + nv.value_span, + ); return; }; self.attribute.auto_cfg_change.push((*bool_value, *span)); @@ -397,6 +421,7 @@ impl DocParser { Some(sym::test) => { let Some(list) = args.list() else { cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocTestTakesList, args.span().unwrap_or(path.span()), ); @@ -418,7 +443,11 @@ impl DocParser { } } Some(sym::spotlight) => { - cx.emit_lint(AttributeLintKind::DocUnknownSpotlight, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownSpotlight { span: path.span() }, + path.span(), + ); } Some(sym::include) if let Some(nv) = args.name_value() => { let inner = match cx.attr_style { @@ -426,23 +455,41 @@ impl DocParser { AttrStyle::Inner => "!", }; cx.emit_lint( - AttributeLintKind::DocUnknownInclude { inner, value: nv.value_as_lit().symbol }, + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownInclude { + inner, + value: nv.value_as_lit().symbol, + span: path.span(), + }, path.span(), ); } Some(name @ (sym::passes | sym::no_default_passes)) => { - cx.emit_lint(AttributeLintKind::DocUnknownPasses { name }, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownPasses { name, span: path.span() }, + path.span(), + ); } Some(sym::plugins) => { - cx.emit_lint(AttributeLintKind::DocUnknownPlugins, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownPlugins { span: path.span() }, + path.span(), + ); } Some(name) => { - cx.emit_lint(AttributeLintKind::DocUnknownAny { name }, path.span()); + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownAny { name }, + path.span(), + ); } None => { let full_name = path.segments().map(|s| s.as_str()).intersperse("::").collect::(); cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, AttributeLintKind::DocUnknownAny { name: Symbol::intern(&full_name) }, path.span(), ); @@ -459,7 +506,11 @@ impl DocParser { ArgParser::NoArgs => { let suggestions = cx.suggestions(); let span = cx.attr_span; - cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); + cx.emit_lint( + rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT, + AttributeLintKind::IllFormedAttributeInput { suggestions, docs: None }, + span, + ); } ArgParser::List(items) => { for i in items.mixed() { diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 38ae597000d2b..092bf67249f29 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -581,13 +581,6 @@ pub(crate) struct NakedFunctionIncompatibleAttribute { pub attr: String, } -#[derive(LintDiagnostic)] -#[diag(attr_parsing_doc_alias_duplicated)] -pub(crate) struct DocAliasDuplicated { - #[label] - pub first_defn: Span, -} - #[derive(Diagnostic)] #[diag(attr_parsing_link_ordinal_out_of_range)] #[note] @@ -995,87 +988,6 @@ pub(crate) struct CfgAttrBadDelim { 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 { diff --git a/compiler/rustc_hir/src/lints.rs b/compiler/rustc_hir/src/lints.rs index 2d13ceabf8ca1..eba2d182d2c48 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, Symbol}; +use rustc_span::Span; use crate::HirId; @@ -31,71 +31,3 @@ pub struct AttributeLint { pub span: Span, pub kind: AttributeLintKind, } - -#[derive(Debug, HashStable_Generic)] -pub enum AttributeLintKind { - /// Copy of `IllFormedAttributeInput` - /// specifically for the `invalid_macro_export_arguments` lint until that is removed, - /// see - InvalidMacroExportArguments { - suggestions: Vec, - }, - UnusedDuplicate { - this: Span, - other: Span, - warning: bool, - }, - IllFormedAttributeInput { - suggestions: Vec, - }, - EmptyAttribute { - first_span: Span, - attr_path: AttrPath, - valid_without_list: bool, - }, - InvalidTarget { - name: AttrPath, - target: Target, - applied: Vec, - only: &'static str, - }, - InvalidStyle { - name: AttrPath, - is_used_as_inner: bool, - target: Target, - target_span: Span, - }, - UnsafeAttrOutsideUnsafe { - attribute_name_span: Span, - sugg_spans: (Span, Span), - }, - DuplicateDocAlias { - first_definition: Span, - }, - 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_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 0553bc95caa29..94f638b676e8f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -233,6 +233,58 @@ lint_deprecated_where_clause_location = where clause not allowed here lint_diag_out_of_impl = diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls +lint_doc_alias_duplicated = doc alias is duplicated + .label = first defined here + +lint_doc_auto_cfg_expects_hide_or_show = + only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]` + +lint_doc_auto_cfg_hide_show_expects_list = + `#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items + +lint_doc_auto_cfg_hide_show_unexpected_item = + `#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items + +lint_doc_auto_cfg_wrong_literal = + expected boolean for `#[doc(auto_cfg = ...)]` + +lint_doc_invalid = + invalid `doc` attribute + +lint_doc_test_literal = `#![doc(test(...)]` does not take a literal + +lint_doc_test_takes_list = + `#[doc(test(...)]` takes a list of attributes + +lint_doc_test_unknown = + unknown `doc(test)` attribute `{$name}` + +lint_doc_unknown_any = + unknown `doc` attribute `{$name}` + +lint_doc_unknown_include = + unknown `doc` attribute `include` + .suggestion = use `doc = include_str!` instead + +lint_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 + +lint_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 + +lint_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 + + lint_drop_glue = types that do not implement `Drop` can still have drop glue, consider instead using `{$needs_drop}` to detect whether a type is trivially dropped diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 589594a3ec5e6..4eb50bda53a15 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -367,5 +367,55 @@ pub fn decorate_attribute_lint( &AttributeLintKind::UnexpectedCfgValue(name, value) => { check_cfg::unexpected_cfg_value(sess, tcx, name, value).decorate_lint(diag) } + &AttributeLintKind::DuplicateDocAlias { first_definition } => { + lints::DocAliasDuplicated { first_defn: first_definition }.decorate_lint(diag) + } + + &AttributeLintKind::DocAutoCfgExpectsHideOrShow => { + lints::DocAutoCfgExpectsHideOrShow.decorate_lint(diag) + } + + &AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name } => { + lints::DocAutoCfgHideShowUnexpectedItem { attr_name }.decorate_lint(diag) + } + + &AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name } => { + lints::DocAutoCfgHideShowExpectsList { attr_name }.decorate_lint(diag) + } + + &AttributeLintKind::DocInvalid => { lints::DocInvalid }.decorate_lint(diag), + + &AttributeLintKind::DocUnknownInclude { span, inner, value } => { + lints::DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) } + } + .decorate_lint(diag), + + &AttributeLintKind::DocUnknownSpotlight { span } => { + lints::DocUnknownSpotlight { sugg_span: span }.decorate_lint(diag) + } + + &AttributeLintKind::DocUnknownPasses { name, span } => { + lints::DocUnknownPasses { name, note_span: span }.decorate_lint(diag) + } + + &AttributeLintKind::DocUnknownPlugins { span } => { + lints::DocUnknownPlugins { label_span: span }.decorate_lint(diag) + } + + &AttributeLintKind::DocUnknownAny { name } => { + lints::DocUnknownAny { name }.decorate_lint(diag) + } + + &AttributeLintKind::DocAutoCfgWrongLiteral => { + lints::DocAutoCfgWrongLiteral.decorate_lint(diag) + } + + &AttributeLintKind::DocTestTakesList => lints::DocTestTakesList.decorate_lint(diag), + + &AttributeLintKind::DocTestUnknown { name } => { + lints::DocTestUnknown { name }.decorate_lint(diag) + } + + &AttributeLintKind::DocTestLiteral => lints::DocTestLiteral.decorate_lint(diag), } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 1bec316ce45a7..4aeeddcac71d2 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3199,3 +3199,91 @@ pub(crate) struct UnusedVisibility { #[suggestion(style = "short", code = "", applicability = "machine-applicable")] pub span: Span, } + +#[derive(LintDiagnostic)] +#[diag(lint_doc_alias_duplicated)] +pub(crate) struct DocAliasDuplicated { + #[label] + pub first_defn: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_auto_cfg_expects_hide_or_show)] +pub(crate) struct DocAutoCfgExpectsHideOrShow; + +#[derive(LintDiagnostic)] +#[diag(lint_doc_auto_cfg_hide_show_unexpected_item)] +pub(crate) struct DocAutoCfgHideShowUnexpectedItem { + pub attr_name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_auto_cfg_hide_show_expects_list)] +pub(crate) struct DocAutoCfgHideShowExpectsList { + pub attr_name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_invalid)] +pub(crate) struct DocInvalid; + +#[derive(LintDiagnostic)] +#[diag(lint_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(lint_doc_unknown_spotlight)] +#[note] +#[note(lint_no_op_note)] +pub(crate) struct DocUnknownSpotlight { + #[suggestion(style = "short", applicability = "machine-applicable", code = "notable_trait")] + pub sugg_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_unknown_passes)] +#[note] +#[note(lint_no_op_note)] +pub(crate) struct DocUnknownPasses { + pub name: Symbol, + #[label] + pub note_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_unknown_plugins)] +#[note] +#[note(lint_no_op_note)] +pub(crate) struct DocUnknownPlugins { + #[label] + pub label_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_unknown_any)] +pub(crate) struct DocUnknownAny { + pub name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_auto_cfg_wrong_literal)] +pub(crate) struct DocAutoCfgWrongLiteral; + +#[derive(LintDiagnostic)] +#[diag(lint_doc_test_takes_list)] +pub(crate) struct DocTestTakesList; + +#[derive(LintDiagnostic)] +#[diag(lint_doc_test_unknown)] +pub(crate) struct DocTestUnknown { + pub name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_doc_test_literal)] +pub(crate) struct DocTestLiteral; diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 376310838cc74..28657c7474b47 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -733,6 +733,41 @@ pub enum AttributeLintKind { }, UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>), UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>), + DuplicateDocAlias { + first_definition: Span, + }, + DocAutoCfgExpectsHideOrShow, + DocAutoCfgHideShowUnexpectedItem { + attr_name: Symbol, + }, + DocAutoCfgHideShowExpectsList { + attr_name: Symbol, + }, + DocInvalid, + DocUnknownInclude { + span: Span, + inner: &'static str, + value: Symbol, + }, + DocUnknownSpotlight { + span: Span, + }, + DocUnknownPasses { + name: Symbol, + span: Span, + }, + DocUnknownPlugins { + span: Span, + }, + DocUnknownAny { + name: Symbol, + }, + DocAutoCfgWrongLiteral, + DocTestTakesList, + DocTestUnknown { + name: Symbol, + }, + DocTestLiteral, } pub type RegisteredTools = FxIndexSet; diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 13be1a04dbc5d..5e582eb61997a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -907,7 +907,19 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { for lint in &delayed_lints.lints { match lint { DelayedLint::AttributeParsing(attribute_lint) => { - rustc_attr_parsing::emit_attribute_lint(attribute_lint, tcx) + tcx.node_span_lint( + attribute_lint.lint_id.lint, + attribute_lint.id, + attribute_lint.span, + |diag| { + rustc_lint::decorate_attribute_lint( + tcx.sess, + Some(tcx), + &attribute_lint.kind, + diag, + ); + }, + ); } } } From 06238bd93e6a871f46b09519cd14676c2642a253 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Dec 2025 22:33:24 +0100 Subject: [PATCH 23/32] Update rustdoc JSON output to new attribute API --- .../rustc_hir/src/attrs/data_structures.rs | 38 ++++++ src/librustdoc/json/conversions.rs | 129 ++++++++++++++++-- 2 files changed, 155 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 0419e219afe40..696d85631e16e 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::fmt; use std::path::PathBuf; pub use ReprAttr::*; @@ -231,6 +232,43 @@ impl CfgEntry { } } +impl fmt::Display for CfgEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn write_entries( + name: &str, + entries: &[CfgEntry], + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + write!(f, "{name}(")?; + for (nb, entry) in entries.iter().enumerate() { + if nb != 0 { + f.write_str(", ")?; + } + entry.fmt(f)?; + } + f.write_str(")") + } + match self { + Self::All(entries, _) => write_entries("all", entries, f), + Self::Any(entries, _) => write_entries("any", entries, f), + Self::Not(entry, _) => write!(f, "not({entry})"), + Self::Bool(value, _) => write!(f, "{value}"), + Self::NameValue { name, value, .. } => { + match value { + // We use `as_str` and debug display to have characters escaped and `"` + // characters surrounding the string. + Some((value, _)) => write!(f, "{name} = {:?}", value.as_str()), + None => write!(f, "{name}"), + } + } + Self::Version(version, _) => match version { + Some(version) => write!(f, "{version}"), + None => Ok(()), + }, + } + } +} + /// Possible values for the `#[linkage]` attribute, allowing to specify the /// linkage type for a `MonoItem`. /// diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index edafc9e7a089f..892cc483dbd6f 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -7,14 +7,14 @@ use rustc_ast::ast; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; -use rustc_hir::attrs::{self, DeprecatedSince}; +use rustc_hir::attrs::{self, DeprecatedSince, DocAttribute, DocInline, HideOrShow}; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; use rustc_hir::{HeaderSafety, Safety}; use rustc_metadata::rendered_const; use rustc_middle::ty::TyCtxt; use rustc_middle::{bug, ty}; -use rustc_span::{Pos, kw, sym}; +use rustc_span::{Pos, Symbol, kw, sym}; use rustdoc_json_types::*; use crate::clean::{self, ItemId}; @@ -46,7 +46,7 @@ impl JsonRenderer<'_> { .attrs .other_attrs .iter() - .filter_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx)) + .flat_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx)) .collect(); let span = item.span(self.tcx); let visibility = item.visibility(self.tcx); @@ -902,11 +902,7 @@ impl FromClean for ItemKind { /// Maybe convert a attribute from hir to json. /// /// Returns `None` if the attribute shouldn't be in the output. -fn maybe_from_hir_attr( - attr: &hir::Attribute, - item_id: ItemId, - tcx: TyCtxt<'_>, -) -> Option { +fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) -> Vec { use attrs::AttributeKind as AK; let kind = match attr { @@ -914,12 +910,12 @@ fn maybe_from_hir_attr( hir::Attribute::Unparsed(_) => { // FIXME: We should handle `#[doc(hidden)]`. - return Some(other_attr(tcx, attr)); + return vec![other_attr(tcx, attr)]; } }; - Some(match kind { - AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation. + vec![match kind { + AK::Deprecation { .. } => return Vec::new(), // Handled separately into Item::deprecation. AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"), AK::MacroExport { .. } => Attribute::MacroExport, @@ -939,9 +935,118 @@ fn maybe_from_hir_attr( AK::NoMangle(_) => Attribute::NoMangle, AK::NonExhaustive(_) => Attribute::NonExhaustive, AK::AutomaticallyDerived(_) => Attribute::AutomaticallyDerived, + AK::Doc(d) => { + fn toggle_attr(ret: &mut Vec, name: &str, v: &Option) { + if v.is_some() { + ret.push(Attribute::Other(format!("#[doc({name})]"))); + } + } + + fn name_value_attr( + ret: &mut Vec, + name: &str, + v: &Option<(Symbol, rustc_span::Span)>, + ) { + if let Some((v, _)) = v { + // We use `as_str` and debug display to have characters escaped and `"` + // characters surrounding the string. + ret.push(Attribute::Other(format!("#[doc({name} = {:?})]", v.as_str()))); + } + } + + let DocAttribute { + aliases, + hidden, + inline, + cfg, + auto_cfg, + auto_cfg_change, + fake_variadic, + keyword, + attribute, + masked, + notable_trait, + search_unbox, + html_favicon_url, + html_logo_url, + html_playground_url, + html_root_url, + html_no_source, + issue_tracker_base_url, + rust_logo, + test_attrs, + no_crate_inject, + } = &**d; + + let mut ret = Vec::new(); + + for (alias, _) in aliases { + // We use `as_str` and debug display to have characters escaped and `"` characters + // surrounding the string. + ret.push(Attribute::Other(format!("#[doc(alias = {:?})]", alias.as_str()))); + } + toggle_attr(&mut ret, "hidden", hidden); + if let Some(inline) = inline.first() { + ret.push(Attribute::Other(format!( + "#[doc({})]", + match inline.0 { + DocInline::Inline => "inline", + DocInline::NoInline => "no_inline", + } + ))); + } + for sub_cfg in cfg { + ret.push(Attribute::Other(format!("#[doc(cfg({sub_cfg}))]"))); + } + for (auto_cfg, _) in auto_cfg { + let kind = match auto_cfg.kind { + HideOrShow::Hide => "hide", + HideOrShow::Show => "show", + }; + let mut out = format!("#[doc(auto_cfg({kind}("); + for (pos, value) in auto_cfg.values.iter().enumerate() { + if pos > 0 { + out.push_str(", "); + } + out.push_str(value.name.as_str()); + if let Some((value, _)) = value.value { + // We use `as_str` and debug display to have characters escaped and `"` + // characters surrounding the string. + out.push_str(&format!(" = {:?}", value.as_str())); + } + } + out.push_str(")))]"); + ret.push(Attribute::Other(out)); + } + for (change, _) in auto_cfg_change { + ret.push(Attribute::Other(format!("#[doc(auto_cfg = {change})]"))); + } + toggle_attr(&mut ret, "fake_variadic", fake_variadic); + name_value_attr(&mut ret, "keyword", keyword); + name_value_attr(&mut ret, "attribute", attribute); + toggle_attr(&mut ret, "masked", masked); + toggle_attr(&mut ret, "notable_trait", notable_trait); + toggle_attr(&mut ret, "search_unbox", search_unbox); + name_value_attr(&mut ret, "html_favicon_url", html_favicon_url); + name_value_attr(&mut ret, "html_logo_url", html_logo_url); + name_value_attr(&mut ret, "html_playground_url", html_playground_url); + name_value_attr(&mut ret, "html_root_url", html_root_url); + toggle_attr(&mut ret, "html_no_source", html_no_source); + name_value_attr(&mut ret, "issue_tracker_base_url", issue_tracker_base_url); + toggle_attr(&mut ret, "rust_logo", rust_logo); + let source_map = tcx.sess.source_map(); + for attr_span in test_attrs { + // FIXME: This is ugly, remove when `test_attrs` has been ported to new attribute API. + if let Ok(snippet) = source_map.span_to_snippet(*attr_span) { + ret.push(Attribute::Other(format!("#[doc(test(attr({snippet})))"))); + } + } + toggle_attr(&mut ret, "no_crate_inject", no_crate_inject); + return ret; + } _ => other_attr(tcx, attr), - }) + }] } fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute { From 1da7684c7d0f1ec2bd9aa99a9844cff3296502ea Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Dec 2025 12:18:53 +0100 Subject: [PATCH 24/32] Improve code and add more comments --- .../rustc_hir/src/attrs/data_structures.rs | 30 +--------------- src/librustdoc/clean/cfg.rs | 34 +++++++++---------- src/librustdoc/clean/types.rs | 18 ++++------ src/librustdoc/doctest/rust.rs | 9 +++++ src/librustdoc/passes/collect_trait_impls.rs | 3 ++ 5 files changed, 36 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 696d85631e16e..09ef6dc18814c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -510,7 +510,7 @@ pub struct CfgHideShow { pub values: ThinVec, } -#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +#[derive(Clone, Debug, Default, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct DocAttribute { pub aliases: FxIndexMap, pub hidden: Option, @@ -546,34 +546,6 @@ pub struct DocAttribute { pub no_crate_inject: Option, } -impl Default for DocAttribute { - fn default() -> Self { - Self { - aliases: FxIndexMap::default(), - hidden: None, - inline: ThinVec::new(), - cfg: ThinVec::new(), - auto_cfg: ThinVec::new(), - auto_cfg_change: ThinVec::new(), - fake_variadic: None, - keyword: None, - attribute: None, - masked: None, - notable_trait: None, - search_unbox: None, - html_favicon_url: None, - html_logo_url: None, - html_playground_url: None, - html_root_url: None, - html_no_source: None, - issue_tracker_base_url: None, - rust_logo: None, - test_attrs: ThinVec::new(), - no_crate_inject: None, - } - } -} - /// Represents parsed *built-in* inert attributes. /// /// ## Overview diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 99409cf838cd6..6cad4301b3135 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -59,11 +59,11 @@ fn is_all_cfg(cfg: &CfgEntry) -> bool { } } -fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet) -> Option { +fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet) -> Option { match cfg { CfgEntry::Bool(..) => Some(cfg.clone()), CfgEntry::NameValue { .. } => { - if !hidden.contains(&SimpleCfg::from(cfg)) { + if !hidden.contains(&NameValueCfg::from(cfg)) { Some(cfg.clone()) } else { None @@ -109,7 +109,7 @@ impl Cfg { /// Parses a `MetaItemInner` into a `Cfg`. fn parse_nested( nested_cfg: &MetaItemInner, - exclude: &FxHashSet, + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { match nested_cfg { MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude), @@ -124,7 +124,7 @@ impl Cfg { fn parse_without( cfg: &MetaItem, - exclude: &FxHashSet, + exclude: &FxHashSet, ) -> Result, InvalidCfgError> { let name = match cfg.ident() { Some(ident) => ident.name, @@ -137,7 +137,7 @@ impl Cfg { }; match cfg.kind { MetaItemKind::Word => { - if exclude.contains(&SimpleCfg::new(name)) { + if exclude.contains(&NameValueCfg::new(name)) { Ok(None) } else { Ok(Some(Cfg(CfgEntry::NameValue { @@ -150,7 +150,7 @@ impl Cfg { } MetaItemKind::NameValue(ref lit) => match lit.kind { LitKind::Str(value, _) => { - if exclude.contains(&SimpleCfg::new_value(name, value)) { + if exclude.contains(&NameValueCfg::new_value(name, value)) { Ok(None) } else { Ok(Some(Cfg(CfgEntry::NameValue { @@ -666,12 +666,12 @@ impl fmt::Display for Display<'_> { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct SimpleCfg { +struct NameValueCfg { name: Symbol, value: Option, } -impl SimpleCfg { +impl NameValueCfg { fn new(name: Symbol) -> Self { Self { name, value: None } } @@ -681,18 +681,18 @@ impl SimpleCfg { } } -impl<'a> From<&'a CfgEntry> for SimpleCfg { +impl<'a> From<&'a CfgEntry> for NameValueCfg { fn from(cfg: &'a CfgEntry) -> Self { match cfg { CfgEntry::NameValue { name, value, .. } => { - SimpleCfg { name: *name, value: (*value).map(|(v, _)| v) } + NameValueCfg { name: *name, value: (*value).map(|(v, _)| v) } } - _ => SimpleCfg { name: sym::empty, value: None }, + _ => NameValueCfg { name: sym::empty, value: None }, } } } -impl<'a> From<&'a attrs::CfgInfo> for SimpleCfg { +impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg { fn from(cfg: &'a attrs::CfgInfo) -> Self { Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) } } @@ -703,7 +703,7 @@ impl<'a> From<&'a attrs::CfgInfo> for SimpleCfg { pub(crate) struct CfgInfo { /// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active /// `doc(auto_cfg(show(...)))` cfgs. - hidden_cfg: FxHashSet, + hidden_cfg: FxHashSet, /// Current computed `cfg`. Each time we enter a new item, this field is updated as well while /// taking into account the `hidden_cfg` information. current_cfg: Cfg, @@ -719,9 +719,9 @@ impl Default for CfgInfo { fn default() -> Self { Self { hidden_cfg: FxHashSet::from_iter([ - SimpleCfg::new(sym::test), - SimpleCfg::new(sym::doc), - SimpleCfg::new(sym::doctest), + NameValueCfg::new(sym::test), + NameValueCfg::new(sym::doc), + NameValueCfg::new(sym::doctest), ]), current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, @@ -761,7 +761,7 @@ fn handle_auto_cfg_hide_show( new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, ) { for value in &attr.values { - let simple = SimpleCfg::from(value); + let simple = NameValueCfg::from(value); if attr.kind == HideOrShow::Show { if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) { show_hide_show_conflict_error(tcx, attr_span, *span); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index cefaf1102fb99..7a4650feac1c5 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -229,34 +229,28 @@ impl ExternalCrate { } pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator { - self.retrieve_keywords_or_documented_attributes(tcx, true) + self.retrieve_keywords_or_documented_attributes(tcx, |d| d.keyword.map(|(v, _)| v)) } pub(crate) fn documented_attributes( &self, tcx: TyCtxt<'_>, ) -> impl Iterator { - self.retrieve_keywords_or_documented_attributes(tcx, false) + self.retrieve_keywords_or_documented_attributes(tcx, |d| d.attribute.map(|(v, _)| v)) } - fn retrieve_keywords_or_documented_attributes( + fn retrieve_keywords_or_documented_attributes Option>( &self, tcx: TyCtxt<'_>, - look_for_keyword: bool, + callback: F, ) -> impl Iterator { let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> { tcx.get_all_attrs(did) .iter() .find_map(|attr| match attr { - Attribute::Parsed(AttributeKind::Doc(d)) => { - if look_for_keyword { - d.keyword - } else { - d.attribute - } - } + Attribute::Parsed(AttributeKind::Doc(d)) => callback(d), _ => None, }) - .map(|(value, _)| (did, value)) + .map(|value| (did, value)) }; self.mapped_root_modules(tcx, as_target) } diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 6f294ad962673..987e1c2ddbf9e 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -143,6 +143,13 @@ impl HirCollector<'_> { if let TokenTree::Ident(i) = token { let i = i.to_string(); let peek = iter.peek(); + // From this ident, we can have things like: + // + // * Group: `allow(...)` + // * Name/value: `crate_name = "..."` + // * Tokens: `html_no_url` + // + // So we peek next element to know what case we are in. match peek { Some(TokenTree::Group(g)) => { let g = g.to_string(); @@ -150,6 +157,8 @@ impl HirCollector<'_> { // Add the additional attributes to the global_crate_attrs vector self.collector.global_crate_attrs.push(format!("{i}{g}")); } + // If next item is `=`, it means it's a name value so we will need + // to get the value as well. Some(TokenTree::Punct(p)) if p.as_char() == '=' => { let p = p.to_string(); iter.next(); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 357d00ef6521e..c2a69baf2989b 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -68,6 +68,9 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> while let Some(did) = parent { attr_buf.extend(tcx.get_all_attrs(did).iter().filter_map(|attr| match attr { Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => { + // The only doc attributes we're interested into for trait impls are the + // `cfg`s for the `doc_cfg` feature. So we create a new empty `DocAttribute` + // and then only clone the actual `DocAttribute::cfg` field. let mut new_attr = DocAttribute::default(); new_attr.cfg = d.cfg.clone(); Some(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr)))) From 4191e94715647b2490f0bb8040f589239ab65813 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 8 Dec 2025 18:06:36 +0100 Subject: [PATCH 25/32] Improve spans for `auto_cfg(hide/show)` errors --- compiler/rustc_hir/src/attrs/data_structures.rs | 10 ++++++++++ src/librustdoc/clean/cfg.rs | 14 ++++++-------- tests/rustdoc-ui/cfg-hide-show-conflict.stderr | 8 ++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 09ef6dc18814c..e4a55bf393336 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -504,6 +504,16 @@ pub struct CfgInfo { pub value: Option<(Symbol, Span)>, } +impl CfgInfo { + pub fn span_for_name_and_value(&self) -> Span { + if let Some((_, value_span)) = self.value { + self.name_span.with_hi(value_span.hi()) + } else { + self.name_span + } + } +} + #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct CfgHideShow { pub kind: HideOrShow, diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 6cad4301b3135..1413f7f56c969 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -47,7 +47,7 @@ fn is_simple_cfg(cfg: &CfgEntry) -> bool { } } -/// Whether the configuration consists of just `Cfg`, `Not` or `All`. +/// Returns `false` if is `Any`, otherwise returns `true`. fn is_all_cfg(cfg: &CfgEntry) -> bool { match cfg { CfgEntry::Bool(..) @@ -756,7 +756,6 @@ fn handle_auto_cfg_hide_show( tcx: TyCtxt<'_>, cfg_info: &mut CfgInfo, attr: &CfgHideShow, - attr_span: Span, new_show_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, new_hide_attrs: &mut FxHashMap<(Symbol, Option), rustc_span::Span>, ) { @@ -764,16 +763,16 @@ fn handle_auto_cfg_hide_show( let simple = NameValueCfg::from(value); if attr.kind == HideOrShow::Show { if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) { - show_hide_show_conflict_error(tcx, attr_span, *span); + show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span); } else { - new_show_attrs.insert((simple.name, simple.value), attr_span); + new_show_attrs.insert((simple.name, simple.value), value.span_for_name_and_value()); } cfg_info.hidden_cfg.remove(&simple); } else { if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) { - show_hide_show_conflict_error(tcx, attr_span, *span); + show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span); } else { - new_hide_attrs.insert((simple.name, simple.value), attr_span); + new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value()); } cfg_info.hidden_cfg.insert(simple); } @@ -871,12 +870,11 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator ) { return None; } - for (value, span) in &d.auto_cfg { + for (value, _) in &d.auto_cfg { handle_auto_cfg_hide_show( tcx, cfg_info, value, - *span, &mut new_show_attrs, &mut new_hide_attrs, ); diff --git a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr index 384a9f1a0b1f9..22231e82cd7bf 100644 --- a/tests/rustdoc-ui/cfg-hide-show-conflict.stderr +++ b/tests/rustdoc-ui/cfg-hide-show-conflict.stderr @@ -1,14 +1,14 @@ error: same `cfg` was in `auto_cfg(hide(...))` and `auto_cfg(show(...))` on the same item - --> $DIR/cfg-hide-show-conflict.rs:3:8 + --> $DIR/cfg-hide-show-conflict.rs:3:31 | LL | #![doc(auto_cfg(show(windows, target_os = "linux")))] - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ | note: first change was here - --> $DIR/cfg-hide-show-conflict.rs:2:8 + --> $DIR/cfg-hide-show-conflict.rs:2:22 | LL | #![doc(auto_cfg(hide(target_os = "linux")))] - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error From 6230b56eaa04df03ee46100823ec4aedd78e522a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 8 Dec 2025 22:44:41 +0100 Subject: [PATCH 26/32] Update clippy code --- .../clippy_lints/src/doc/doc_suspicious_footnotes.rs | 9 ++++++++- .../clippy_lints/src/doc/suspicious_doc_comments.rs | 8 ++++---- src/tools/clippy/clippy_utils/src/attrs.rs | 7 +------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs index 1944cd7c91d3f..deca29a1885f0 100644 --- a/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs +++ b/src/tools/clippy/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::attr::AttributeExt as _; use rustc_ast::token::{CommentKind, DocFragmentKind}; use rustc_errors::Applicability; +use rustc_hir::attrs::AttributeKind; use rustc_hir::{AttrStyle, Attribute}; use rustc_lint::{LateContext, LintContext}; @@ -45,7 +46,13 @@ pub fn check(cx: &LateContext<'_>, doc: &str, range: Range, fragments: &F if let DocFragmentKind::Sugared(_) = this_fragment.kind { let (doc_attr, doc_attr_comment_kind, attr_style) = attrs .iter() - .filter(|attr| attr.span().overlaps(this_fragment.span)) + .filter(|attr| { + matches!( + attr, + Attribute::Parsed(AttributeKind::DocComment { span, .. }) + if span.overlaps(this_fragment.span), + ) + }) .rev() .find_map(|attr| { let (_, fragment) = attr.doc_str_and_fragment_kind()?; diff --git a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs index e751600f00a63..178d688264b78 100644 --- a/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs +++ b/src/tools/clippy/clippy_lints/src/doc/suspicious_doc_comments.rs @@ -39,15 +39,15 @@ fn collect_doc_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> { .filter_map(|attr| { if let Attribute::Parsed(AttributeKind::DocComment { style: AttrStyle::Outer, - kind, + kind: DocFragmentKind::Sugared(comment_kind), comment, .. }) = attr && let Some(com) = comment.as_str().strip_prefix('!') { - let sugg = match kind { - DocFragmentKind::Sugared(CommentKind::Block) => format!("/*!{com}*/"), - DocFragmentKind::Sugared(CommentKind::Line) | DocFragmentKind::Raw(_) => format!("//!{com}"), + let sugg = match comment_kind { + CommentKind::Block => format!("/*!{com}*/"), + CommentKind::Line => format!("//!{com}"), }; Some((attr.span(), sugg)) } else { diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs index 671b266ba0086..2fd773b06781d 100644 --- a/src/tools/clippy/clippy_utils/src/attrs.rs +++ b/src/tools/clippy/clippy_utils/src/attrs.rs @@ -2,7 +2,6 @@ use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; -use rustc_ast::attr; use rustc_ast::attr::AttributeExt; use rustc_errors::Applicability; use rustc_hir::attrs::AttributeKind; @@ -87,11 +86,7 @@ pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { /// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { - attrs - .iter() - .filter(|attr| attr.has_name(sym::doc)) - .filter_map(AttributeExt::meta_item_list) - .any(|l| attr::list_contains_name(&l, sym::hidden)) + attrs.iter().any(|attr| attr.is_doc_hidden()) } /// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` From 40907f522d6e57abb83f80db7c62719bed7d8d46 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 8 Dec 2025 23:17:16 +0100 Subject: [PATCH 27/32] Fix doc alias suggestion --- compiler/rustc_attr_parsing/src/attributes/doc.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 5714d33e861f4..a26f22c4455de 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -137,10 +137,8 @@ impl DocParser { cx: &'c mut AcceptContext<'_, '_, S>, alias: Symbol, span: Span, - is_list: bool, ) { - let attr_str = - &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" }); + let attr_str = "`#[doc(alias = \"...\")]`"; if alias == sym::empty { cx.emit_err(DocAliasEmpty { span, attr_str }); return; @@ -186,7 +184,7 @@ impl DocParser { continue; }; - self.add_alias(cx, alias, i.span(), false); + self.add_alias(cx, alias, i.span()); } } ArgParser::NameValue(nv) => { @@ -194,7 +192,7 @@ impl DocParser { cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); return; }; - self.add_alias(cx, alias, nv.value_span, false); + self.add_alias(cx, alias, nv.value_span); } } } From 64aaeacd7168ae1995fed60974142db14e246840 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 9 Dec 2025 22:27:28 +0100 Subject: [PATCH 28/32] Update to new API, allowing to remove `check_doc_cfg.rs` file from librustdoc --- .../src/attributes/cfg_old.rs | 1 - .../rustc_attr_parsing/src/attributes/doc.rs | 11 ++- .../rustc_hir/src/attrs/data_structures.rs | 11 +-- library/std/build.rs | 3 + src/librustdoc/clean/cfg.rs | 43 ++++------- src/librustdoc/clean/cfg/tests.rs | 11 +-- src/librustdoc/lib.rs | 1 - src/librustdoc/passes/check_doc_cfg.rs | 72 ------------------- src/librustdoc/passes/mod.rs | 5 -- tests/rustdoc-ui/doc-cfg-2.rs | 2 + tests/rustdoc-ui/doc-cfg-2.stderr | 20 +++++- .../doc-cfg-check-cfg.cfg_empty.stderr | 12 ++-- tests/rustdoc-ui/issues/issue-91713.stdout | 2 - 13 files changed, 59 insertions(+), 135 deletions(-) delete mode 100644 src/librustdoc/passes/check_doc_cfg.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs index 29be000d476db..acb234480d5dd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs @@ -2,7 +2,6 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, Nod use rustc_ast_pretty::pprust; use rustc_feature::{Features, GatedCfg, find_gated_cfg}; use rustc_hir::RustcVersion; -use rustc_hir::lints::AttributeLintKind; use rustc_session::Session; use rustc_session::lint::{BuiltinLintDiag, Lint}; use rustc_session::parse::feature_err; diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index a26f22c4455de..ccb6a873fb120 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -294,7 +294,7 @@ impl DocParser { cx.expected_identifier(sub_item.path().span()); continue; }; - if let Ok(CfgEntry::NameValue { name, name_span, value, .. }) = + if let Ok(CfgEntry::NameValue { name, value, .. }) = super::cfg::parse_name_value( name, sub_item.path().span(), @@ -303,7 +303,14 @@ impl DocParser { cx, ) { - cfg_hide_show.values.push(CfgInfo { name, name_span, value }) + cfg_hide_show.values.push(CfgInfo { + name, + name_span: sub_item.path().span(), + // If `value` is `Some`, `a.name_value()` will always return + // `Some` as well. + value: value + .map(|v| (v, a.name_value().unwrap().value_span)), + }) } } _ => { diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index e4a55bf393336..b7f8be3ec88f4 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -218,14 +218,7 @@ impl CfgEntry { ( Self::NameValue { name: name1, value: value1, .. }, Self::NameValue { name: name2, value: value2, .. }, - ) => { - name1 == name2 - && match (value1, value2) { - (Some((a, _)), Some((b, _))) => a == b, - (None, None) => true, - _ => false, - } - } + ) => name1 == name2 && value1 == value2, (Self::Version(a, _), Self::Version(b, _)) => a == b, _ => false, } @@ -257,7 +250,7 @@ impl fmt::Display for CfgEntry { match value { // We use `as_str` and debug display to have characters escaped and `"` // characters surrounding the string. - Some((value, _)) => write!(f, "{name} = {:?}", value.as_str()), + Some(value) => write!(f, "{name} = {:?}", value.as_str()), None => write!(f, "{name}"), } } diff --git a/library/std/build.rs b/library/std/build.rs index bee28e88491d0..c0a6e30b38082 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -13,6 +13,9 @@ fn main() { println!("cargo:rustc-cfg=netbsd10"); } + // Needed for `#![doc(auto_cfg(hide(no_global_oom_handling)))]` attribute. + println!("cargo::rustc-check-cfg=cfg(no_global_oom_handling)"); + println!("cargo:rustc-check-cfg=cfg(restricted_std)"); if target_os == "linux" || target_os == "android" diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 1413f7f56c969..97a60c5a50983 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -140,12 +140,7 @@ impl Cfg { if exclude.contains(&NameValueCfg::new(name)) { Ok(None) } else { - Ok(Some(Cfg(CfgEntry::NameValue { - name, - value: None, - name_span: DUMMY_SP, - span: DUMMY_SP, - }))) + Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, span: DUMMY_SP }))) } } MetaItemKind::NameValue(ref lit) => match lit.kind { @@ -155,8 +150,7 @@ impl Cfg { } else { Ok(Some(Cfg(CfgEntry::NameValue { name, - value: Some((value, DUMMY_SP)), - name_span: DUMMY_SP, + value: Some(value), span: DUMMY_SP, }))) } @@ -226,9 +220,7 @@ impl Cfg { CfgEntry::Any(sub_cfgs, _) => { sub_cfgs.iter().any(|sub_cfg| cfg_matches(sub_cfg, psess)) } - CfgEntry::NameValue { name, value, .. } => { - psess.config.contains(&(*name, value.clone().map(|(s, _)| s))) - } + CfgEntry::NameValue { name, value, .. } => psess.config.contains(&(*name, *value)), CfgEntry::Version(..) => { // FIXME: should be handled. false @@ -497,7 +489,7 @@ impl Display<'_> { sub_cfgs .iter() .map(|sub_cfg| { - if let CfgEntry::NameValue { value: Some((feat, _)), .. } = sub_cfg + if let CfgEntry::NameValue { value: Some(feat), .. } = sub_cfg && short_longhand { Either::Left(self.code_wrappers().wrap(feat)) @@ -557,7 +549,7 @@ impl fmt::Display for Display<'_> { (sym::unix, None) => "Unix", (sym::windows, None) => "Windows", (sym::debug_assertions, None) => "debug-assertions enabled", - (sym::target_os, Some((os, _))) => match os.as_str() { + (sym::target_os, Some(os)) => match os.as_str() { "android" => "Android", "cygwin" => "Cygwin", "dragonfly" => "DragonFly BSD", @@ -582,7 +574,7 @@ impl fmt::Display for Display<'_> { "visionos" => "visionOS", _ => "", }, - (sym::target_arch, Some((arch, _))) => match arch.as_str() { + (sym::target_arch, Some(arch)) => match arch.as_str() { "aarch64" => "AArch64", "arm" => "ARM", "loongarch32" => "LoongArch LA32", @@ -605,14 +597,14 @@ impl fmt::Display for Display<'_> { "x86_64" => "x86-64", _ => "", }, - (sym::target_vendor, Some((vendor, _))) => match vendor.as_str() { + (sym::target_vendor, Some(vendor)) => match vendor.as_str() { "apple" => "Apple", "pc" => "PC", "sun" => "Sun", "fortanix" => "Fortanix", _ => "", }, - (sym::target_env, Some((env, _))) => match env.as_str() { + (sym::target_env, Some(env)) => match env.as_str() { "gnu" => "GNU", "msvc" => "MSVC", "musl" => "musl", @@ -621,20 +613,20 @@ impl fmt::Display for Display<'_> { "sgx" => "SGX", _ => "", }, - (sym::target_endian, Some((endian, _))) => { + (sym::target_endian, Some(endian)) => { return write!(fmt, "{endian}-endian"); } - (sym::target_pointer_width, Some((bits, _))) => { + (sym::target_pointer_width, Some(bits)) => { return write!(fmt, "{bits}-bit"); } - (sym::target_feature, Some((feat, _))) => match self.1 { + (sym::target_feature, Some(feat)) => match self.1 { Format::LongHtml => { return write!(fmt, "target feature {feat}"); } Format::LongPlain => return write!(fmt, "target feature `{feat}`"), Format::ShortHtml => return write!(fmt, "{feat}"), }, - (sym::feature, Some((feat, _))) => match self.1 { + (sym::feature, Some(feat)) => match self.1 { Format::LongHtml => { return write!(fmt, "crate feature {feat}"); } @@ -647,9 +639,7 @@ impl fmt::Display for Display<'_> { fmt.write_str(human_readable) } else { let value = value - .map(|(v, _)| { - fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str()))) - }) + .map(|v| fmt::from_fn(move |f| write!(f, "={}", self.1.escape(v.as_str())))) .maybe_display(); self.code_wrappers() .wrap(format_args!("{}{value}", self.1.escape(name.as_str()))) @@ -684,9 +674,7 @@ impl NameValueCfg { impl<'a> From<&'a CfgEntry> for NameValueCfg { fn from(cfg: &'a CfgEntry) -> Self { match cfg { - CfgEntry::NameValue { name, value, .. } => { - NameValueCfg { name: *name, value: (*value).map(|(v, _)| v) } - } + CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value }, _ => NameValueCfg { name: sym::empty, value: None }, } } @@ -886,8 +874,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator for (feature, _) in features { cfg_info.current_cfg &= Cfg(CfgEntry::NameValue { name: sym::target_feature, - value: Some((*feature, DUMMY_SP)), - name_span: DUMMY_SP, + value: Some(*feature), span: DUMMY_SP, }); } diff --git a/src/librustdoc/clean/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs index 4eb6c060cbd29..09316ead76aca 100644 --- a/src/librustdoc/clean/cfg/tests.rs +++ b/src/librustdoc/clean/cfg/tests.rs @@ -12,12 +12,7 @@ fn word_cfg(name: &str) -> Cfg { } fn word_cfg_e(name: &str) -> CfgEntry { - CfgEntry::NameValue { - name: Symbol::intern(name), - name_span: DUMMY_SP, - value: None, - span: DUMMY_SP, - } + CfgEntry::NameValue { name: Symbol::intern(name), value: None, span: DUMMY_SP } } fn name_value_cfg(name: &str, value: &str) -> Cfg { @@ -27,8 +22,8 @@ fn name_value_cfg(name: &str, value: &str) -> Cfg { fn name_value_cfg_e(name: &str, value: &str) -> CfgEntry { CfgEntry::NameValue { name: Symbol::intern(name), - name_span: DUMMY_SP, - value: Some((Symbol::intern(value), DUMMY_SP)), + + value: Some(Symbol::intern(value)), span: DUMMY_SP, } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 5e582eb61997a..cc8a308688d23 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -32,7 +32,6 @@ extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; -extern crate rustc_attr_parsing; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; diff --git a/src/librustdoc/passes/check_doc_cfg.rs b/src/librustdoc/passes/check_doc_cfg.rs deleted file mode 100644 index 9e7fae5e14e46..0000000000000 --- a/src/librustdoc/passes/check_doc_cfg.rs +++ /dev/null @@ -1,72 +0,0 @@ -use rustc_attr_parsing::{ShouldEmit, eval_config_entry}; -use rustc_hir::attrs::AttributeKind; -use rustc_hir::def_id::LocalDefId; -use rustc_hir::{Attribute, HirId}; -use rustc_middle::ty::TyCtxt; - -use super::Pass; -use crate::clean::{Attributes, Crate, Item}; -use crate::core::DocContext; -use crate::visit::DocVisitor; - -pub(crate) const CHECK_DOC_CFG: Pass = Pass { - name: "check-doc-cfg", - run: Some(check_doc_cfg), - description: "checks `#[doc(cfg(...))]` for stability feature and unexpected cfgs", -}; - -pub(crate) fn check_doc_cfg(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut checker = DocCfgChecker { cx }; - checker.visit_crate(&krate); - krate -} - -struct RustdocCfgMatchesLintEmitter<'a>(TyCtxt<'a>, HirId); - -impl<'a> rustc_attr_parsing::CfgMatchesLintEmitter for RustdocCfgMatchesLintEmitter<'a> { - fn emit_span_lint( - &self, - sess: &rustc_session::Session, - lint: &'static rustc_lint::Lint, - sp: rustc_span::Span, - builtin_diag: rustc_lint_defs::BuiltinLintDiag, - ) { - self.0.node_span_lint(lint, self.1, sp, |diag| { - rustc_lint::decorate_builtin_lint(sess, Some(self.0), builtin_diag, diag) - }); - } -} - -struct DocCfgChecker<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, -} - -impl DocCfgChecker<'_, '_> { - fn check_attrs(&mut self, attrs: &Attributes, did: LocalDefId) { - for attr in &attrs.other_attrs { - let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue }; - - for doc_cfg in &d.cfg { - let _ = eval_config_entry( - &self.cx.tcx.sess, - doc_cfg, - &RustdocCfgMatchesLintEmitter( - self.cx.tcx, - self.cx.tcx.local_def_id_to_hir_id(did), - ), - ShouldEmit::ErrorsAndLints, - ); - } - } - } -} - -impl DocVisitor<'_> for DocCfgChecker<'_, '_> { - fn visit_item(&mut self, item: &'_ Item) { - if let Some(Some(local_did)) = item.def_id().map(|did| did.as_local()) { - self.check_attrs(&item.attrs, local_did); - } - - self.visit_item_recur(item); - } -} diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index a1e8e75306235..18e1afaf8a242 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -32,9 +32,6 @@ pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; mod check_doc_test_visibility; pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY; -mod check_doc_cfg; -pub(crate) use self::check_doc_cfg::CHECK_DOC_CFG; - mod collect_trait_impls; pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS; @@ -75,7 +72,6 @@ pub(crate) enum Condition { /// The full list of passes. pub(crate) const PASSES: &[Pass] = &[ - CHECK_DOC_CFG, CHECK_DOC_TEST_VISIBILITY, PROPAGATE_DOC_CFG, STRIP_ALIASED_NON_LOCAL, @@ -93,7 +89,6 @@ pub(crate) const PASSES: &[Pass] = &[ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::always(COLLECT_TRAIT_IMPLS), ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), - ConditionalPass::always(CHECK_DOC_CFG), ConditionalPass::always(STRIP_ALIASED_NON_LOCAL), ConditionalPass::always(PROPAGATE_DOC_CFG), ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), diff --git a/tests/rustdoc-ui/doc-cfg-2.rs b/tests/rustdoc-ui/doc-cfg-2.rs index bd6a2dc18be93..7a5d1f3e3dbb5 100644 --- a/tests/rustdoc-ui/doc-cfg-2.rs +++ b/tests/rustdoc-ui/doc-cfg-2.rs @@ -12,5 +12,7 @@ // Shouldn't lint #[doc(auto_cfg(hide(windows)))] #[doc(auto_cfg(hide(feature = "windows")))] +//~^ WARN unexpected `cfg` condition name: `feature` #[doc(auto_cfg(hide(foo)))] +//~^ WARN unexpected `cfg` condition name: `foo` pub fn foo() {} diff --git a/tests/rustdoc-ui/doc-cfg-2.stderr b/tests/rustdoc-ui/doc-cfg-2.stderr index f3d67abfb8dd3..1272e569897b5 100644 --- a/tests/rustdoc-ui/doc-cfg-2.stderr +++ b/tests/rustdoc-ui/doc-cfg-2.stderr @@ -56,5 +56,23 @@ error: expected boolean for `#[doc(auto_cfg = ...)]` LL | #[doc(auto_cfg = "a")] | ^^^ -error: aborting due to 6 previous errors; 2 warnings emitted +warning: unexpected `cfg` condition name: `feature` + --> $DIR/doc-cfg-2.rs:14:21 + | +LL | #[doc(auto_cfg(hide(feature = "windows")))] + | ^^^^^^^^^^^^^^^^^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(feature, values("windows"))` + = note: see for more information about checking conditional configuration + +warning: unexpected `cfg` condition name: `foo` + --> $DIR/doc-cfg-2.rs:16:21 + | +LL | #[doc(auto_cfg(hide(foo)))] + | ^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(foo)` + = note: see for more information about checking conditional configuration + +error: aborting due to 6 previous errors; 4 warnings emitted diff --git a/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr b/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr index 0878f7edbf480..3f67b85900aab 100644 --- a/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr +++ b/tests/rustdoc-ui/doc-cfg-check-cfg.cfg_empty.stderr @@ -1,8 +1,8 @@ warning: unexpected `cfg` condition name: `foo` - --> $DIR/doc-cfg-check-cfg.rs:12:12 + --> $DIR/doc-cfg-check-cfg.rs:15:11 | -LL | #![doc(cfg(foo))] - | ^^^ +LL | #[doc(cfg(foo))] + | ^^^ | = help: to expect this configuration use `--check-cfg=cfg(foo)` = note: see for more information about checking conditional configuration @@ -18,10 +18,10 @@ LL | #[doc(cfg(foo))] = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition name: `foo` - --> $DIR/doc-cfg-check-cfg.rs:15:11 + --> $DIR/doc-cfg-check-cfg.rs:12:12 | -LL | #[doc(cfg(foo))] - | ^^^ +LL | #![doc(cfg(foo))] + | ^^^ | = help: to expect this configuration use `--check-cfg=cfg(foo)` = note: see for more information about checking conditional configuration diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout index c0cd454e8f3ad..e7b8a1dccf802 100644 --- a/tests/rustdoc-ui/issues/issue-91713.stdout +++ b/tests/rustdoc-ui/issues/issue-91713.stdout @@ -1,5 +1,4 @@ Available passes for running rustdoc: - check-doc-cfg - checks `#[doc(cfg(...))]` for stability feature and unexpected cfgs check_doc_test_visibility - run various visibility-related lints on doctests propagate-doc-cfg - propagates `#[doc(cfg(...))]` to child items strip-aliased-non-local - strips all non-local private aliased items from the output @@ -15,7 +14,6 @@ calculate-doc-coverage - counts the number of items with and without documentati Default passes for rustdoc: collect-trait-impls check_doc_test_visibility - check-doc-cfg strip-aliased-non-local propagate-doc-cfg strip-hidden (when not --document-hidden-items) From 3ea946216700c1c6ede64a0d5db658419afc3017 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 10 Dec 2025 12:27:26 +0100 Subject: [PATCH 29/32] Remove `Cfg::matches` and use `eval_config_entry` instead --- src/librustdoc/clean/cfg.rs | 29 ++++------------------------- src/librustdoc/doctest/rust.rs | 3 ++- src/librustdoc/lib.rs | 1 + 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 97a60c5a50983..7ab2a72d75b5c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -14,7 +14,6 @@ use rustc_hir as hir; use rustc_hir::Attribute; use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow}; use rustc_middle::ty::TyCtxt; -use rustc_session::parse::ParseSess; use rustc_span::symbol::{Symbol, sym}; use rustc_span::{DUMMY_SP, Span}; @@ -206,30 +205,6 @@ impl Cfg { Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap()) } - /// Checks whether the given configuration can be matched in the current session. - /// - /// Equivalent to `attr::cfg_matches`. - pub(crate) fn matches(&self, psess: &ParseSess) -> bool { - fn cfg_matches(cfg: &CfgEntry, psess: &ParseSess) -> bool { - match cfg { - CfgEntry::Bool(v, _) => *v, - CfgEntry::Not(child, _) => !cfg_matches(child, psess), - CfgEntry::All(sub_cfgs, _) => { - sub_cfgs.iter().all(|sub_cfg| cfg_matches(sub_cfg, psess)) - } - CfgEntry::Any(sub_cfgs, _) => { - sub_cfgs.iter().any(|sub_cfg| cfg_matches(sub_cfg, psess)) - } - CfgEntry::NameValue { name, value, .. } => psess.config.contains(&(*name, *value)), - CfgEntry::Version(..) => { - // FIXME: should be handled. - false - } - } - } - cfg_matches(&self.0, psess) - } - /// Renders the configuration for human display, as a short HTML description. pub(crate) fn render_short_html(&self) -> String { let mut msg = Display(&self.0, Format::ShortHtml).to_string(); @@ -320,6 +295,10 @@ impl Cfg { fn omit_preposition(&self) -> bool { matches!(self.0, CfgEntry::Bool(..)) } + + pub(crate) fn inner(&self) -> &CfgEntry { + &self.0 + } } impl ops::Not for Cfg { diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 987e1c2ddbf9e..ee1419d17d6e1 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use std::sync::Arc; use proc_macro2::{TokenStream, TokenTree}; +use rustc_attr_parsing::eval_config_entry; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{self as hir, Attribute, CRATE_HIR_ID, intravisit}; @@ -123,7 +124,7 @@ impl HirCollector<'_> { let ast_attrs = self.tcx.hir_attrs(self.tcx.local_def_id_to_hir_id(def_id)); if let Some(ref cfg) = extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &mut CfgInfo::default()) - && !cfg.matches(&self.tcx.sess.psess) + && !eval_config_entry(&self.tcx.sess, cfg.inner()).as_bool() { return; } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index cc8a308688d23..5e582eb61997a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -32,6 +32,7 @@ extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_ast_pretty; +extern crate rustc_attr_parsing; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; From 9fdec8194e6045133629d899bc4b7048d00311d4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 10 Dec 2025 12:35:17 +0100 Subject: [PATCH 30/32] Fix new merge conflict --- .../parser/attribute/attr-unquoted-ident.rs | 5 ++++- .../attribute/attr-unquoted-ident.stderr | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/ui/parser/attribute/attr-unquoted-ident.rs b/tests/ui/parser/attribute/attr-unquoted-ident.rs index 152448bf8a0f9..6207662a6e19d 100644 --- a/tests/ui/parser/attribute/attr-unquoted-ident.rs +++ b/tests/ui/parser/attribute/attr-unquoted-ident.rs @@ -26,7 +26,10 @@ fn main() { macro_rules! make { ($name:ident) => { #[doc(alias = $name)] pub struct S; } - //~^ ERROR: expected unsuffixed literal, found identifier `nickname` + //~^ ERROR: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression + //~| NOTE: expressions are not allowed here + //~| HELP: surround the identifier with quotation marks to make it into a string literal } make!(nickname); //~ NOTE: in this expansion +//~^ NOTE in this expansion of make diff --git a/tests/ui/parser/attribute/attr-unquoted-ident.stderr b/tests/ui/parser/attribute/attr-unquoted-ident.stderr index 00fb539f8a865..48ca499ba78fb 100644 --- a/tests/ui/parser/attribute/attr-unquoted-ident.stderr +++ b/tests/ui/parser/attribute/attr-unquoted-ident.stderr @@ -20,14 +20,31 @@ help: surround the identifier with quotation marks to make it into a string lite LL | #[cfg(key="foo bar baz")] | + + +error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression + --> $DIR/attr-unquoted-ident.rs:18:15 + | +LL | #[cfg(key=foo 1 bar 2.0 baz.)] + | ^^^ expressions are not allowed here + | +help: surround the identifier with quotation marks to make it into a string literal + | +LL | #[cfg(key="foo 1 bar 2.0 baz.")] + | + + + +error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression + --> $DIR/attr-unquoted-ident.rs:28:38 | LL | ($name:ident) => { #[doc(alias = $name)] pub struct S; } - | ^^^^^ + | ^^^^^ expressions are not allowed here ... LL | make!(nickname); | --------------- in this macro invocation | = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info) +help: surround the identifier with quotation marks to make it into a string literal + | +LL | ($name:ident) => { #[doc(alias = "$name")] pub struct S; } + | + + error: aborting due to 4 previous errors From 2bc2a0db6956f960a5218cea19f07ff8dd4c04cd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 10 Dec 2025 20:18:42 +0100 Subject: [PATCH 31/32] For now, ignore target checking for doc attributes in attr_parsing --- .../rustc_attr_parsing/src/attributes/doc.rs | 66 ++++++++++--------- .../ui/attributes/issue-115264-expr-field.rs | 2 - .../attributes/issue-115264-expr-field.stderr | 12 ---- tests/ui/attributes/issue-115264-pat-field.rs | 2 - .../attributes/issue-115264-pat-field.stderr | 12 ---- tests/ui/lint/unused/useless-comment.rs | 6 -- tests/ui/lint/unused/useless-comment.stderr | 62 +++++------------ .../rustdoc/check-doc-alias-attr-location.rs | 8 --- .../check-doc-alias-attr-location.stderr | 45 ++----------- 9 files changed, 53 insertions(+), 162 deletions(-) delete mode 100644 tests/ui/attributes/issue-115264-expr-field.stderr delete mode 100644 tests/ui/attributes/issue-115264-pat-field.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index ccb6a873fb120..2fe1b4ad174c9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -7,7 +7,7 @@ use rustc_hir::lints::AttributeLintKind; use rustc_span::{Span, Symbol, edition, sym}; use thin_vec::ThinVec; -use super::prelude::{Allow, AllowedTargets, Error, MethodKind, Target}; +use super::prelude::{ALL_TARGETS, AllowedTargets}; use super::{AcceptMapping, AttributeParser}; use crate::context::{AcceptContext, FinalizeContext, Stage}; use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser}; @@ -583,37 +583,39 @@ impl AttributeParser for DocParser { this.accept_single_doc_attr(cx, args); }, )]; - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ - 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), - Error(Target::WherePredicate), - ]); + // FIXME: Currently emitted from 2 different places, generating duplicated warnings. + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + // const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + // 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), + // Error(Target::WherePredicate), + // ]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if self.nb_doc_attrs != 0 { diff --git a/tests/ui/attributes/issue-115264-expr-field.rs b/tests/ui/attributes/issue-115264-expr-field.rs index d8189626fb0f8..8adb68deb5b4f 100644 --- a/tests/ui/attributes/issue-115264-expr-field.rs +++ b/tests/ui/attributes/issue-115264-expr-field.rs @@ -12,8 +12,6 @@ struct X { fn main() { let _ = X { #[doc(alias = "StructItem")] - //~^ WARN: attribute cannot be used on struct fields - //~| WARN: this was previously accepted by the compiler but is being phased out foo: 123, }; } diff --git a/tests/ui/attributes/issue-115264-expr-field.stderr b/tests/ui/attributes/issue-115264-expr-field.stderr deleted file mode 100644 index 6bb9dfc90c934..0000000000000 --- a/tests/ui/attributes/issue-115264-expr-field.stderr +++ /dev/null @@ -1,12 +0,0 @@ -warning: `#[doc]` attribute cannot be used on struct fields - --> $DIR/issue-115264-expr-field.rs:14:9 - | -LL | #[doc(alias = "StructItem")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - = note: requested on the command line with `-W unused-attributes` - -warning: 1 warning emitted - diff --git a/tests/ui/attributes/issue-115264-pat-field.rs b/tests/ui/attributes/issue-115264-pat-field.rs index 0c8966b5893f6..53e3b4524d60e 100644 --- a/tests/ui/attributes/issue-115264-pat-field.rs +++ b/tests/ui/attributes/issue-115264-pat-field.rs @@ -12,8 +12,6 @@ struct X { fn main() { let X { #[doc(alias = "StructItem")] - //~^ WARN: attribute cannot be used on pattern fields - //~| WARN: this was previously accepted by the compiler but is being phased out foo } = X { foo: 123 diff --git a/tests/ui/attributes/issue-115264-pat-field.stderr b/tests/ui/attributes/issue-115264-pat-field.stderr deleted file mode 100644 index a5b221110789d..0000000000000 --- a/tests/ui/attributes/issue-115264-pat-field.stderr +++ /dev/null @@ -1,12 +0,0 @@ -warning: `#[doc]` attribute cannot be used on pattern fields - --> $DIR/issue-115264-pat-field.rs:14:9 - | -LL | #[doc(alias = "StructItem")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - = note: requested on the command line with `-W unused-attributes` - -warning: 1 warning emitted - diff --git a/tests/ui/lint/unused/useless-comment.rs b/tests/ui/lint/unused/useless-comment.rs index fee6cff640f6d..24ff917520814 100644 --- a/tests/ui/lint/unused/useless-comment.rs +++ b/tests/ui/lint/unused/useless-comment.rs @@ -18,8 +18,6 @@ fn foo() { /// a //~ ERROR unused doc comment #[doc(test(attr(allow(dead_code))))] //~^ ERROR unused doc comment - //~| ERROR `#[doc]` attribute cannot be used on statements - //~| WARN this was previously accepted by the compiler let x = 12; /// multi-line //~ ERROR unused doc comment @@ -30,8 +28,6 @@ fn foo() { 1 => {}, #[doc(test(attr(allow(dead_code))))] //~^ ERROR unused doc comment - //~| ERROR `#[doc]` attribute cannot be used on match arms [unused_attributes] - //~| WARN this was previously accepted by the compiler _ => {} } @@ -47,8 +43,6 @@ fn foo() { #[doc(test(attr(allow(dead_code))))] //~^ ERROR unused doc comment - //~| ERROR `#[doc]` attribute cannot be used on statements - //~| WARN this was previously accepted by the compiler let x = /** comment */ 47; //~ ERROR unused doc comment /// dox //~ ERROR unused doc comment diff --git a/tests/ui/lint/unused/useless-comment.stderr b/tests/ui/lint/unused/useless-comment.stderr index 3d3937e7a402f..22e63caf607a3 100644 --- a/tests/ui/lint/unused/useless-comment.stderr +++ b/tests/ui/lint/unused/useless-comment.stderr @@ -33,7 +33,7 @@ LL | unsafe extern "C" { } = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:45:5 + --> $DIR/useless-comment.rs:41:5 | LL | /// bar | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macro invocations @@ -56,14 +56,14 @@ error: unused doc comment | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... +LL | LL | let x = 12; | ----------- rustdoc does not generate documentation for statements | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:25:5 + --> $DIR/useless-comment.rs:23:5 | LL | / /// multi-line LL | | /// doc comment @@ -73,7 +73,7 @@ LL | / match x { LL | | /// c LL | | 1 => {}, LL | | #[doc(test(attr(allow(dead_code))))] -... | +LL | | LL | | _ => {} LL | | } | |_____- rustdoc does not generate documentation for expressions @@ -81,7 +81,7 @@ LL | | } = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:29:9 + --> $DIR/useless-comment.rs:27:9 | LL | /// c | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -91,18 +91,18 @@ LL | 1 => {}, = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:31:9 + --> $DIR/useless-comment.rs:29:9 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... +LL | LL | _ => {} | ------- rustdoc does not generate documentation for match arms | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:38:5 + --> $DIR/useless-comment.rs:34:5 | LL | /// foo | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,7 +112,7 @@ LL | unsafe {} = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:41:5 + --> $DIR/useless-comment.rs:37:5 | LL | #[doc = "foo"] | ^^^^^^^^^^^^^^ @@ -123,7 +123,7 @@ LL | 3; = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:42:5 + --> $DIR/useless-comment.rs:38:5 | LL | #[doc = "bar"] | ^^^^^^^^^^^^^^ @@ -133,18 +133,18 @@ LL | 3; = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:48:5 + --> $DIR/useless-comment.rs:44:5 | LL | #[doc(test(attr(allow(dead_code))))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... +LL | LL | let x = /** comment */ 47; | -------------------------- rustdoc does not generate documentation for statements | = help: use `//` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:52:13 + --> $DIR/useless-comment.rs:46:13 | LL | let x = /** comment */ 47; | ^^^^^^^^^^^^^^ -- rustdoc does not generate documentation for expressions @@ -152,7 +152,7 @@ LL | let x = /** comment */ 47; = help: use `/* */` for a plain comment error: unused doc comment - --> $DIR/useless-comment.rs:54:5 + --> $DIR/useless-comment.rs:48:5 | LL | /// dox | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -163,37 +163,5 @@ LL | | } | = help: use `//` for a plain comment -error: `#[doc]` attribute cannot be used on statements - --> $DIR/useless-comment.rs:19:5 - | -LL | #[doc(test(attr(allow(dead_code))))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements -note: the lint level is defined here - --> $DIR/useless-comment.rs:4:9 - | -LL | #![deny(unused_attributes)] - | ^^^^^^^^^^^^^^^^^ - -error: `#[doc]` attribute cannot be used on match arms - --> $DIR/useless-comment.rs:31:9 - | -LL | #[doc(test(attr(allow(dead_code))))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - -error: `#[doc]` attribute cannot be used on statements - --> $DIR/useless-comment.rs:48:5 - | -LL | #[doc(test(attr(allow(dead_code))))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - -error: aborting due to 18 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/rustdoc/check-doc-alias-attr-location.rs b/tests/ui/rustdoc/check-doc-alias-attr-location.rs index 8ba6cfde2d6d0..45777c5edf4da 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.rs +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.rs @@ -21,22 +21,14 @@ impl Foo for Bar { type X = i32; fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { //~^ ERROR - //~| WARN `#[doc]` attribute cannot be used on function params - //~| WARN: this was previously accepted by the compiler #[doc(alias = "stmt")] //~^ ERROR - //~| WARN `#[doc]` attribute cannot be used on statements - //~| WARN: this was previously accepted by the compiler let x = 0; #[doc(alias = "expr")] //~^ ERROR - //~| WARN `#[doc]` attribute cannot be used on expressions - //~| WARN: this was previously accepted by the compiler match x { #[doc(alias = "arm")] //~^ ERROR - //~| WARN `#[doc]` attribute cannot be used on match arms - //~| WARN: this was previously accepted by the compiler _ => 0 } } diff --git a/tests/ui/rustdoc/check-doc-alias-attr-location.stderr b/tests/ui/rustdoc/check-doc-alias-attr-location.stderr index 24be42036f6fd..f587c17c1f3e9 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.stderr +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.stderr @@ -29,59 +29,22 @@ LL | #[doc(alias = "assoc")] | ^^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on statement - --> $DIR/check-doc-alias-attr-location.rs:26:23 + --> $DIR/check-doc-alias-attr-location.rs:24:23 | LL | #[doc(alias = "stmt")] | ^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on expression - --> $DIR/check-doc-alias-attr-location.rs:31:23 + --> $DIR/check-doc-alias-attr-location.rs:27:23 | LL | #[doc(alias = "expr")] | ^^^^^^ error: `#[doc(alias = "...")]` isn't allowed on match arm - --> $DIR/check-doc-alias-attr-location.rs:36:27 + --> $DIR/check-doc-alias-attr-location.rs:30:27 | LL | #[doc(alias = "arm")] | ^^^^^ -warning: `#[doc]` attribute cannot be used on function params - --> $DIR/check-doc-alias-attr-location.rs:22:12 - | -LL | fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { - | ^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - = note: requested on the command line with `-W unused-attributes` - -warning: `#[doc]` attribute cannot be used on statements - --> $DIR/check-doc-alias-attr-location.rs:26:9 - | -LL | #[doc(alias = "stmt")] - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - -warning: `#[doc]` attribute cannot be used on expressions - --> $DIR/check-doc-alias-attr-location.rs:31:9 - | -LL | #[doc(alias = "expr")] - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - -warning: `#[doc]` attribute cannot be used on match arms - --> $DIR/check-doc-alias-attr-location.rs:36:13 - | -LL | #[doc(alias = "arm")] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = help: `#[doc]` can be applied to associated consts, associated types, constants, crates, data types, enum variants, extern crates, foreign modules, foreign statics, functions, impl blocks, macro defs, modules, statics, struct fields, trait aliases, traits, type aliases, unions, and use statements - -error: aborting due to 8 previous errors; 4 warnings emitted +error: aborting due to 8 previous errors From 51806323f9288e9cdab756a6b528823bcadcaa7a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 11 Dec 2025 01:35:28 +0100 Subject: [PATCH 32/32] Do not error if there are duplicated doc attributes --- compiler/rustc_attr_parsing/src/attributes/doc.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 2fe1b4ad174c9..547f00d140415 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -358,10 +358,14 @@ 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. + // The right fix for this would be to only check this rule if the attribute is + // not set on the command line but directly in the code. + // if self.attribute.$ident.is_some() { + // cx.duplicate_key(path.span(), path.word_sym().unwrap()); + // return; + // } self.attribute.$ident = Some(path.span()); }};