diff --git a/Cargo.lock b/Cargo.lock index 03a4d71f67c3a..d818d87e08045 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", @@ -4870,6 +4868,7 @@ dependencies = [ "indexmap", "itertools", "minifier", + "proc-macro2", "pulldown-cmark-escape", "regex", "rustdoc-json-types", diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 901b645b8c4ef..a5e630a09afe2 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, } @@ -220,6 +228,24 @@ 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)) + } + + 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 { @@ -300,6 +326,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, @@ -820,7 +865,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. @@ -830,6 +875,12 @@ 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; + + /// 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 @@ -902,7 +953,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/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 02ddf8ac0023d..f2642838b3c8c 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -14,8 +14,29 @@ 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_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_keyword_not_keyword = + nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]` + .help = only existing keywords are allowed in core/std + 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 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/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs index adae3fa635f43..acb234480d5dd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs @@ -2,10 +2,7 @@ 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::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 +34,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/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs new file mode 100644 index 0000000000000..547f00d140415 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -0,0 +1,631 @@ +use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; +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::{ALL_TARGETS, AllowedTargets}; +use super::{AcceptMapping, AttributeParser}; +use crate::context::{AcceptContext, FinalizeContext, Stage}; +use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser}; +use crate::session_diagnostics::{ + DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute, + DocKeywordNotKeyword, +}; + +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(Default, Debug)] +pub(crate) struct DocParser { + attribute: DocAttribute, + nb_doc_attrs: usize, +} + +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 let Err(span) = args.no_args() { + cx.expected_no_args(span); + 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(cx.attr_span); + return; + }; + + // 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) => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocTestUnknown { name }, + path.span(), + ); + } + None => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocTestLiteral, + path.span(), + ); + } + } + } + + fn add_alias<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + alias: Symbol, + span: Span, + ) { + let attr_str = "`#[doc(alias = \"...\")]`"; + 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( + rustc_session::lint::builtin::UNUSED_ATTRIBUTES, + 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.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) }); + } + 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()); + } + } + 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); + } + } + } + + fn parse_inline<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + path: &PathParser<'_>, + args: &ArgParser<'_>, + inline: DocInline, + ) { + if let Err(span) = args.no_args() { + cx.expected_no_args(span); + return; + } + + self.attribute.inline.push((inline, path.span())); + } + + fn parse_cfg<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + args: &ArgParser<'_>, + ) { + // 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); + } + } + + fn parse_auto_cfg<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + path: &PathParser<'_>, + args: &ArgParser<'_>, + ) { + match args { + ArgParser::NoArgs => { + self.attribute.auto_cfg_change.push((true, path.span())); + } + ArgParser::List(list) => { + for meta in list.mixed() { + let MetaItemOrLitParser::MetaItemParser(item) = meta else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + 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( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocAutoCfgExpectsHideOrShow, + item.span(), + ); + continue; + } + }; + let ArgParser::List(list) = item.args() else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + 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( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + 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, 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: 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)), + }) + } + } + _ => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { + attr_name, + }, + sub_item.span(), + ); + continue; + } + } + } + self.attribute.auto_cfg.push((cfg_hide_show, path.span())); + } + } + ArgParser::NameValue(nv) => { + let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit() + else { + 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)); + } + } + } + + 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 let Err(span) = args.no_args() { + cx.expected_no_args(span); + 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()); + }}; + } + 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; + }; + + // 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((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) => self.parse_cfg(cx, args), + Some(sym::notable_trait) => no_args!(notable_trait), + 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.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocTestTakesList, + 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. + } + } + } + } + Some(sym::spotlight) => { + 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 { + AttrStyle::Outer => "", + AttrStyle::Inner => "!", + }; + cx.emit_lint( + 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( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownPasses { name, span: path.span() }, + path.span(), + ); + } + Some(sym::plugins) => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + AttributeLintKind::DocUnknownPlugins { span: path.span() }, + path.span(), + ); + } + Some(name) => { + 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(), + ); + } + } + } + + fn accept_single_doc_attr<'c, S: Stage>( + &mut self, + cx: &'c mut AcceptContext<'_, '_, S>, + args: &'c ArgParser<'_>, + ) { + match args { + ArgParser::NoArgs => { + let suggestions = cx.suggestions(); + let span = cx.attr_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() { + match i { + MetaItemOrLitParser::MetaItemParser(mip) => { + self.nb_doc_attrs += 1; + self.parse_single_doc_attr_item(cx, mip); + } + MetaItemOrLitParser::Lit(lit) => { + cx.expected_name_value(lit.span, None); + } + MetaItemOrLitParser::Err(..) => { + // already had an error here, move on. + } + } + } + } + ArgParser::NameValue(nv) => { + 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" + ); + } + } + } + } +} + +impl AttributeParser for DocParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::doc], + 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); + }, + )]; + // 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 { + 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 65e0957ca9005..64bcb02b0b745 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -39,6 +39,7 @@ pub(crate) mod confusables; pub(crate) mod crate_level; pub(crate) mod debugger; pub(crate) mod deprecation; +pub(crate) mod doc; pub(crate) mod dummy; pub(crate) mod inline; pub(crate) mod link_attrs; diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index 520fd9da7c2ab..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,36 +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. /// /// Used by attributes that take a single integer as argument, such as diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bc74eaad50bc4..f41ea37087888 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,6 +163,7 @@ attribute_parsers!( BodyStabilityParser, ConfusablesParser, ConstStabilityParser, + DocParser, MacroUseParser, NakedParser, StabilityParser, @@ -427,7 +429,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/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index b26a4a29cd2e2..5eefce75ace21 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use rustc_ast as ast; +use rustc_ast::token::DocFragmentKind; use rustc_ast::{AttrStyle, NodeId, Safety}; use rustc_errors::DiagCtxtHandle; use rustc_feature::{AttributeTemplate, Features}; @@ -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; } @@ -293,22 +295,11 @@ 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, })) } - // // 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); @@ -334,6 +325,38 @@ 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: DocFragmentKind::Raw(nv.value_span), + span: attr.span, + comment, + })); + continue; + } + for accept in accepts { let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext { shared: SharedContext { diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 7cef70f88e1ca..cb02bb9d501fc 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -78,6 +78,8 @@ // tidy-alphabetical-start #![feature(decl_macro)] +#![feature(if_let_guard)] +#![feature(iter_intersperse)] #![recursion_limit = "256"] // tidy-alphabetical-end @@ -107,7 +109,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 2b3108a8d3ed9..092bf67249f29 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -34,6 +34,49 @@ 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, +} + +#[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, @@ -944,3 +987,10 @@ pub(crate) struct CfgAttrBadDelim { #[subdiagnostic] pub sugg: MetaBadDelimSugg, } + +#[derive(Diagnostic)] +#[diag(attr_parsing_doc_alias_malformed)] +pub(crate) struct DocAliasMalformed { + #[primary_span] + pub span: Span, +} 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_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index aff79d0558387..b7f8be3ec88f4 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1,10 +1,12 @@ use std::borrow::Cow; +use std::fmt; 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}; use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; use rustc_span::def_id::DefId; @@ -196,14 +198,68 @@ 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 && value1 == value2, + (Self::Version(a, _), Self::Version(b, _)) => a == b, + _ => false, + } + } +} + +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 @@ -420,6 +476,79 @@ impl WindowsSubsystemKind { } } +#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub enum DocInline { + Inline, + 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)>, +} + +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, + pub values: ThinVec, +} + +#[derive(Clone, Debug, Default, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct DocAttribute { + pub aliases: FxIndexMap, + pub hidden: Option, + // 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, + 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, + pub keyword: Option<(Symbol, Span)>, + pub attribute: 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, +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -551,8 +680,14 @@ 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). - DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol }, + /// 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: DocFragmentKind, span: Span, comment: Symbol }, /// Represents `#[rustc_dummy]`. 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..29df586ed296c 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -1,9 +1,10 @@ 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; 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 { @@ -147,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 6444c862d5200..f5470adb87e03 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, @@ -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, } } @@ -1386,14 +1385,11 @@ 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)) } - Attribute::Unparsed(_) if self.has_name(sym::doc) => { - self.value_str().map(|s| (s, CommentKind::Line)) - } _ => None, } } @@ -1418,6 +1414,14 @@ 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 @@ -1503,8 +1507,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_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 a8457134031c9..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_parsing::is_doc_alias_attrs_contain_symbol; 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}; @@ -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_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/builtin.rs b/compiler/rustc_lint/src/builtin.rs index dd0aa848ed2c9..bf66c7f855089 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 } @@ -823,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 || attr.has_name(sym::doc) { + 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_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/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_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/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 86719581a209c..2069c06c3f774 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_empty() { + 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 70e91c081776a..4e8973e4928b1 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -106,60 +106,15 @@ 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"))]` + `#[doc(alias = "...")]` 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_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} = "...")` + `#[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 -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 @@ -179,19 +134,12 @@ 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 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 @@ -212,39 +160,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 1289867987158..030839baad9b9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -10,22 +10,25 @@ 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, MultiSpan, 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, DocInline, 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 +46,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 +109,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>, @@ -141,8 +129,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 { @@ -225,6 +211,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 @@ -306,15 +293,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) @@ -782,42 +760,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, - meta: &MetaItemInner, - doc_alias: Symbol, - hir_id: HirId, - target: Target, - is_list: bool, - aliases: &mut FxHashMap, - ) { - 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(); + 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 { .. } = @@ -874,123 +817,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | Target::MacroCall | Target::Delegation { .. } => None, } { - tcx.dcx().emit_err(errors::DocAliasBadLocation { span, attr_str, location }); + self.tcx.dcx().emit_err(errors::DocAliasBadLocation { span, location }); return; } - if self.tcx.hir_opt_name(hir_id) == Some(doc_alias) { - tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str }); + if self.tcx.hir_opt_name(hir_id) == Some(alias) { + self.tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str: alias }); 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() }, - ); - } - } - - 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, - meta: &MetaItemInner, - 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 - } - - // 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, - }; - 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(), - }); - return; - } - } - _ => { - self.dcx().emit_err(errors::DocKeywordAttributeNotMod { - span: meta.span(), - attr_name: attr_kind.name(), - }); - return; - } - } - match attr_kind { - DocFakeItemKind::Keyword => { - if !is_doc_keyword(value) { - self.dcx().emit_err(errors::DocKeywordNotKeyword { - span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - keyword: value, - }); - } - } - DocFakeItemKind::Attribute => { - if !is_builtin_attr(value) { - self.dcx().emit_err(errors::DocAttributeNotAttribute { - span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - attribute: value, - }); - } - } - } } - 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 +844,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 +868,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 +882,49 @@ 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)>, - ) { - 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 }); + 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; } - } else { - *specified_inline = Some((do_inline, meta.span())); } + *span } + }; + + match target { + 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,353 +934,159 @@ 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), }, ); } } + 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, - 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 { - 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 /// 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, - ); - } - } - - Some(sym::fake_variadic) => { - if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") { - self.check_doc_fake_variadic(meta, hir_id); - } - } - - Some(sym::search_unbox) => { - if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") { - self.check_doc_search_unbox(meta, hir_id); - } - } + 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: _, + // already check in attr_parsing + auto_cfg: _, + // already check in attr_parsing + auto_cfg_change: _, + 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, + attribute, + } = attr; + + for (alias, span) in aliases { + if self.check_attr_not_crate_level(*span, hir_id, "alias") { + self.check_doc_alias_value(*span, hir_id, target, *alias); + } + } - Some(sym::test) => { - self.check_test_attr(attr_span, style, meta, hir_id); - } + 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") + { + self.check_doc_keyword_and_attribute(*span, hir_id, "attribute"); + } - 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); - } + 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); + } - 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); - } + 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 [ + 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::auto_cfg) => { - self.check_doc_auto_cfg(i_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::inline | sym::no_inline) => { - self.check_doc_inline(style, meta, hir_id, target, specified_inline) - } + self.check_doc_inline(hir_id, target, inline); - Some(sym::masked) => self.check_doc_masked(style, meta, hir_id, target), - - Some(sym::cfg | sym::hidden | sym::notable_trait) => {} - - Some(sym::rust_logo) => { - if self.check_attr_crate_level(attr_span, style, meta, hir_id) - && !self.tcx.features().rustdoc_internals() - { - feature_err( - &self.tcx.sess, - sym::rustdoc_internals, - meta.span(), - fluent::passes_doc_rust_logo, - ) - .emit(); - } - } + 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(); + } - _ => { - 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) = masked { + self.check_doc_masked(*span, hir_id, target); } } @@ -2354,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/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 6aa0f5212af70..3a2908d141845 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -24,18 +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(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 { @@ -135,75 +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_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> { #[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(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 { - #[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)] @@ -214,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 { @@ -264,7 +179,7 @@ pub(crate) struct DocSearchUnboxInvalid { #[derive(Diagnostic)] #[diag(passes_doc_inline_conflict)] #[help] -pub(crate) struct DocKeywordConflict { +pub(crate) struct DocInlineConflict { #[primary_span] pub spans: MultiSpan, } @@ -276,7 +191,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 +201,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 +210,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)] @@ -306,90 +221,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 { @@ -1353,17 +1184,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..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_parsing::is_doc_alias_attrs_contain_symbol; 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,10 @@ 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) = + hir::find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d) + && d.aliases.contains_key(&item_name) + { return Some(did); } } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 3c0a89b7c8a7f..7f7c423acb40a 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()); @@ -377,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 @@ -571,7 +553,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 +571,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/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, 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/Cargo.toml b/src/librustdoc/Cargo.toml index 0bc38220614f1..dcfc1ffc251ec 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -15,6 +15,7 @@ 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" } diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 6c77e41965dc5..7ab2a72d75b5c 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -9,12 +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 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::Span; use rustc_span::symbol::{Symbol, sym}; -use {rustc_ast as ast, rustc_hir as hir}; +use rustc_span::{DUMMY_SP, Span}; use crate::display::{Joined as _, MaybeDisplay, Wrapped}; use crate::html::escape::Escape; @@ -22,21 +23,11 @@ use crate::html::escape::Escape; #[cfg(test)] 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), -} +#[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)] pub(crate) struct InvalidCfgError { @@ -44,27 +35,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, + } +} + +/// Returns `false` if is `Any`, otherwise returns `true`. +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(&NameValueCfg::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 +136,23 @@ 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(&NameValueCfg::new(name)) { + Ok(None) + } else { + Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, 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(&NameValueCfg::new_value(name, value)) { + Ok(None) + } else { + Ok(Some(Cfg(CfgEntry::NameValue { + name, + value: Some(value), + span: DUMMY_SP, + }))) + } } _ => Err(InvalidCfgError { // FIXME: if the main #[cfg] syntax decided to support non-string literals, @@ -97,8 +166,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::>(); @@ -132,40 +205,10 @@ 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 { - 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, - } - } - /// 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 +226,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 +248,19 @@ 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::Any(..) + | CfgEntry::All(..) + | CfgEntry::NameValue { .. } + | CfgEntry::Version(..) + | CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true, + CfgEntry::Not(..) | CfgEntry::Bool(..) => 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 @@ -234,22 +269,22 @@ 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 Cfg::All(a) = self { - let mut sub_cfgs: Vec = if let Cfg::All(b) = assume { - a.iter().filter(|a| !b.contains(a)).cloned().collect() + } 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.iter().any(|b| a.is_equivalent_to(b))).cloned().collect() } else { - a.iter().filter(|&a| a != assume).cloned().collect() + a.iter().filter(|&a| !a.is_equivalent_to(&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.iter().any(|b| b.is_equivalent_to(&self.0)) { None } else { @@ -258,81 +293,54 @@ impl Cfg { } fn omit_preposition(&self) -> bool { - matches!(self, Cfg::True | Cfg::False) + matches!(self.0, CfgEntry::Bool(..)) } - 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)) } - } - } + pub(crate) fn inner(&self) -> &CfgEntry { + &self.0 } } 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) { + if !a.iter().any(|a| a.is_equivalent_to(&c)) { a.push(c); } } } - (Cfg::All(a), ref mut b) => { - if !a.contains(b) { - a.push(mem::replace(b, Cfg::True)); + (CfgEntry::All(a, _), ref mut b) => { + if !a.iter().any(|a| a.is_equivalent_to(b)) { + a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } - (s, Cfg::All(mut a)) => { - let b = mem::replace(s, Cfg::True); - if !a.contains(&b) { + (s, CfgEntry::All(mut a, _)) => { + let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); + if !a.iter().any(|a| a.is_equivalent_to(&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]); + 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); } } } @@ -349,32 +357,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) { + if !a.iter().any(|a| a.is_equivalent_to(&c)) { a.push(c); } } } - (Cfg::Any(a), ref mut b) => { - if !a.contains(b) { - a.push(mem::replace(b, Cfg::True)); + (CfgEntry::Any(a, _), ref mut b) => { + if !a.iter().any(|a| a.is_equivalent_to(b)) { + a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP))); } } - (s, Cfg::Any(mut a)) => { - let b = mem::replace(s, Cfg::True); - if !a.contains(&b) { + (s, CfgEntry::Any(mut a, _)) => { + let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP)); + if !a.iter().any(|a| a.is_equivalent_to(&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]); + 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); } } } @@ -417,7 +427,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 +437,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 +468,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,35 +490,41 @@ 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", @@ -572,8 +592,12 @@ 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_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}"); @@ -601,16 +625,52 @@ impl fmt::Display for Display<'_> { .fmt(fmt) } } + + CfgEntry::Version(..) => { + // FIXME: Should we handle it? + Ok(()) + } } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +struct NameValueCfg { + name: Symbol, + value: Option, +} + +impl NameValueCfg { + 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 NameValueCfg { + fn from(cfg: &'a CfgEntry) -> Self { + match cfg { + CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value }, + _ => NameValueCfg { name: sym::empty, value: None }, + } + } +} + +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) } + } +} + /// This type keeps track of (doc) cfg information as we go down the item tree. #[derive(Clone, Debug)] 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 +686,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), + NameValueCfg::new(sym::test), + NameValueCfg::new(sym::doc), + NameValueCfg::new(sym::doctest), ]), - current_cfg: Cfg::True, + current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)), auto_cfg_active: true, parent_is_doc_cfg: false, } @@ -662,33 +722,26 @@ 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, 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? - if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) { - if is_show { - if let Some(span) = new_hide_attrs.get(&(key, value)) { - show_hide_show_conflict_error(tcx, item.span(), *span); - } else { - new_show_attrs.insert((key, value), item.span()); - } - cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value)); - } else { - if let Some(span) = new_show_attrs.get(&(key, value)) { - show_hide_show_conflict_error(tcx, item.span(), *span); - } else { - new_hide_attrs.insert((key, value), item.span()); - } - cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value)); - } + for value in &attr.values { + 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, value.span_for_name_and_value(), *span); + } else { + 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, value.span_for_name_and_value(), *span); + } else { + new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value()); + } + cfg_info.hidden_cfg.insert(simple); } } } @@ -709,7 +762,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, @@ -719,14 +772,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 @@ -737,28 +790,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)) if !d.cfg.is_empty() => 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); - } - } + for new_cfg in attr.cfg.clone() { + cfg_info.current_cfg &= Cfg(new_cfg); } } } else { @@ -769,71 +815,47 @@ 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, _) in &d.auto_cfg { + handle_auto_cfg_hide_show( + tcx, + cfg_info, + value, + &mut new_show_attrs, + &mut new_hide_attrs, + ); } } } else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr { // 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), + span: DUMMY_SP, + }); } continue; } else if !cfg_info.parent_is_doc_cfg @@ -851,7 +873,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 +881,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/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs index f0591295da644..09316ead76aca 100644 --- a/src/librustdoc/clean/cfg/tests.rs +++ b/src/librustdoc/clean/cfg/tests.rs @@ -1,23 +1,57 @@ 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), 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), + + value: Some(Symbol::intern(value)), + 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 +97,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 +146,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 +216,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 +236,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 +308,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 +518,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/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..dd5c50d2ba37c 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,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 = - 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.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)); is_inline = is_inline || import_is_inline; } @@ -2635,63 +2633,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 +2660,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.cfg = cfg.clone(); + } else { + attr.inline = inline.clone(); + attr.hidden = hidden.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 +2889,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 @@ -2968,11 +2918,10 @@ fn clean_extern_crate<'tcx>( 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, - } + matches!( + a, + hir::Attribute::Parsed(AttributeKind::Doc(d)) + if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline)) }) && !cx.is_json_output(); @@ -3035,7 +2984,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.first().is_some_and(|(i, _)| *i == DocInline::Inline) => d + ) + .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; @@ -3053,10 +3006,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 +3024,11 @@ 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.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/clean/types.rs b/src/librustdoc/clean/types.rs index f1b0f4a68beaa..7a4650feac1c5 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,25 +229,27 @@ 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, |d| d.keyword.map(|(v, _)| v)) } 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, |d| d.attribute.map(|(v, _)| v)) } - fn retrieve_keywords_or_documented_attributes( + fn retrieve_keywords_or_documented_attributes Option>( &self, tcx: TyCtxt<'_>, - name: Symbol, + callback: F, ) -> 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()) + tcx.get_all_attrs(did) + .iter() + .find_map(|attr| match attr { + Attribute::Parsed(AttributeKind::Doc(d)) => callback(d), + _ => None, + }) .map(|value| (did, value)) }; self.mapped_root_modules(tcx, as_target) @@ -920,37 +923,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`]. @@ -993,28 +965,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 +1019,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() @@ -2416,7 +2366,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); 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, }] 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..19e8fe3e3ed2b 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}; @@ -16,17 +17,18 @@ 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}; -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_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..ee1419d17d6e1 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -2,16 +2,19 @@ use std::cell::Cell; use std::env; +use std::str::FromStr; use std::sync::Arc; -use rustc_ast_pretty::pprust; +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, 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_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}; @@ -121,28 +124,60 @@ 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; } + 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(); + // 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(); + iter.next(); + // 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(); + 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/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 { diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e4601bfb20d7d..5e582eb61997a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -36,7 +36,6 @@ 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; @@ -77,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}; @@ -902,6 +902,30 @@ 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) => { + 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, + ); + }, + ); + } + } + } + } + } + if render_opts.dep_info().is_some() { rustc_interface::passes::write_dep_info(tcx); } diff --git a/src/librustdoc/passes/check_doc_cfg.rs b/src/librustdoc/passes/check_doc_cfg.rs deleted file mode 100644 index 3284da77a0222..0000000000000 --- a/src/librustdoc/passes/check_doc_cfg.rs +++ /dev/null @@ -1,76 +0,0 @@ -use rustc_hir::HirId; -use rustc_hir::def_id::LocalDefId; -use rustc_middle::ty::TyCtxt; -use rustc_span::sym; - -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) { - 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)); - - 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()), - ); - } - } - } -} - -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/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 2339a6b69cd8a..c2a69baf2989b 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,17 @@ 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_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)))) + } + _ => None, + })); parent = tcx.opt_parent(did); } cx.with_param_env(impl_def_id, |cx| { 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/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index c0b48ab51c7eb..95f5537f394c0 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_empty() + { + 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()); } } } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 51b2821eb1e5f..0f4460bed35a7 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,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 = 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.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; @@ -464,12 +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 = 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.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/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..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 @@ -1,10 +1,10 @@ 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::attrs::AttributeKind; use rustc_hir::{AttrStyle, Attribute}; use rustc_lint::{LateContext, LintContext}; -use rustc_resolve::rustdoc::DocFragmentKind; use std::ops::Range; @@ -43,13 +43,24 @@ 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)) + .filter(|attr| { + matches!( + attr, + Attribute::Parsed(AttributeKind::DocComment { span, .. }) + if 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..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 @@ -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; @@ -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 { - CommentKind::Line => 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]` 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..296a41337f336 100644 --- a/tests/rustdoc-ui/bad-render-options.stderr +++ b/tests/rustdoc-ui/bad-render-options.stderr @@ -1,58 +1,220 @@ -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 = "..."` | - = note: `#[deny(invalid_doc_attributes)]` on by default +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(alias)] + | +LL - #![doc(html_favicon_url)] +LL + #![doc(attribute)] + | +LL - #![doc(html_favicon_url)] +LL + #![doc(auto_cfg)] + | + = and 21 other candidates -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(alias)] + | +LL - #![doc(html_logo_url)] +LL + #![doc(attribute)] + | +LL - #![doc(html_logo_url)] +LL + #![doc(auto_cfg)] + | + = and 21 other candidates -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(alias)] + | +LL - #![doc(html_playground_url)] +LL + #![doc(attribute)] + | +LL - #![doc(html_playground_url)] +LL + #![doc(auto_cfg)] + | + = and 21 other candidates -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(alias)] + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc(attribute)] + | +LL - #![doc(issue_tracker_base_url)] +LL + #![doc(auto_cfg)] + | + = and 21 other candidates -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(alias)] + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc(attribute)] + | +LL - #![doc(html_favicon_url = 1)] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc(attribute)] + | +LL - #![doc(html_logo_url = 2)] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc(attribute)] + | +LL - #![doc(html_playground_url = 3)] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc(attribute)] + | +LL - #![doc(issue_tracker_base_url = 4)] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc(attribute)] + | +LL - #![doc(html_no_source = "asdf")] +LL + #![doc(auto_cfg)] + | + = and 22 other candidates 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/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..6c33f10e87851 100644 --- a/tests/rustdoc-ui/check-doc-alias-attr.stderr +++ b/tests/rustdoc-ui/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/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-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-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-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-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..7a5d1f3e3dbb5 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-2.rs @@ -0,0 +1,18 @@ +#![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")))] +//~^ 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 new file mode 100644 index 0000000000000..1272e569897b5 --- /dev/null +++ b/tests/rustdoc-ui/doc-cfg-2.stderr @@ -0,0 +1,78 @@ +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")] + | ^^^ + +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/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..0efeac66554c8 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -1,90 +1,124 @@ -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(alias)] | -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(attribute)] | -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(auto_cfg)] | -LL | #[doc(auto_cfg = 42)] - | ^^^^^^^^^^^^^ + = and 22 other candidates -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(alias)] | - = 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(attribute)] | -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(auto_cfg)] | -LL | #[doc(cfg(), cfg(foo, bar))] - | ^^^ + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg())] +LL + #[doc(attribute)] + | +LL - #[doc(cfg())] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(attribute)] + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates + +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(alias)] + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc(attribute)] + | +LL - #[doc(auto_cfg(hide(foo::bar)))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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/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/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..3363dbb56fb4f 100644 --- a/tests/rustdoc-ui/invalid-cfg.stderr +++ b/tests/rustdoc-ui/invalid-cfg.stderr @@ -1,50 +1,188 @@ -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(alias)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(attribute)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(attribute)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(attribute)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(attribute)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(attribute)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(attribute)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(attribute)] + | +LL - #[doc(cfg = "x")] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates -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(alias)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(attribute)] + | +LL - #[doc(cfg(x, y))] +LL + #[doc(auto_cfg)] + | + = and 22 other candidates 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/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) 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..1201bd5c71f18 100644 --- a/tests/rustdoc-ui/lints/doc-attr.stderr +++ b/tests/rustdoc-ui/lints/doc-attr.stderr @@ -1,46 +1,75 @@ -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(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: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(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: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(alias)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(attribute)] + | +LL - #[doc("hello", "bar")] +LL + #[doc(auto_cfg)] | -LL | #![doc(as_ptr)] - | ^^^^^^ + = and 22 other candidates -error: aborting due to 7 previous errors +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0539`. 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 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 {} 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/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..24ff917520814 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,8 @@ 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 let x = 12; /// multi-line //~ ERROR unused doc comment @@ -24,7 +26,8 @@ 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 _ => {} } @@ -38,7 +41,8 @@ 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 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..22e63caf607a3 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:41: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 | 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:23: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 | | _ => {} 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:27: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: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:31:5 + --> $DIR/useless-comment.rs:34: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:37: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:38: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: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:42:13 + --> $DIR/useless-comment.rs:46: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:48:5 | LL | /// dox | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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.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 f1af60dec9bbb..48ca499ba78fb 100644 --- a/tests/ui/parser/attribute/attr-unquoted-ident.stderr +++ b/tests/ui/parser/attribute/attr-unquoted-ident.stderr @@ -31,16 +31,20 @@ help: surround the identifier with quotation marks to make it into a string lite LL | #[cfg(key="foo 1 bar 2.0 baz.")] | + + -error: expected unsuffixed literal, found identifier `nickname` +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 diff --git a/tests/ui/rustdoc/check-doc-alias-attr-location.rs b/tests/ui/rustdoc/check-doc-alias-attr-location.rs index 10609e5d8f4d9..45777c5edf4da 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.rs +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.rs @@ -21,11 +21,14 @@ impl Foo for Bar { type X = i32; fn foo(#[doc(alias = "qux")] _x: u32) -> Self::X { //~^ ERROR - #[doc(alias = "stmt")] //~ ERROR + #[doc(alias = "stmt")] + //~^ ERROR let x = 0; - #[doc(alias = "expr")] //~ ERROR + #[doc(alias = "expr")] + //~^ ERROR match x { - #[doc(alias = "arm")] //~ ERROR + #[doc(alias = "arm")] + //~^ ERROR _ => 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..f587c17c1f3e9 100644 --- a/tests/ui/rustdoc/check-doc-alias-attr-location.stderr +++ b/tests/ui/rustdoc/check-doc-alias-attr-location.stderr @@ -5,46 +5,46 @@ 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:24: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:27: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:30:27 | LL | #[doc(alias = "arm")] - | ^^^^^^^^^^^^^ + | ^^^^^ error: aborting due to 8 previous errors 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"))] | ^^^