Auto merge of #149645 - GuillaumeGomez:doc-attr-based, r=jdonszelmann,jonathanbrouwer

Port `doc` attributes to new attribute API

Part of https://github.com/rust-lang/rust/issues/131229.

This PR ports the `doc` attributes to the new attribute API. However, there are things that will need to be fixed in a follow-up:

* Some part of `cfg_old.rs` are likely unused now, so they should be removed.
* Not all error/lints are emitted at the same time anymore, making them kinda less useful considering that you need to run and fix rustc/rustdoc multiple times to get through all of them.
* For coherency with the other attribute errors, I didn't modify the default output too much, meaning that we have some new messages now. I'll likely come back to that to check if the previous ones were better in a case-by-case approach.
* `doc(test(attr(...)))` is handled in a horrifying manner currently. Until we can handle it correctly with the `Attribute` system, it'll remain that thing we're all very ashamed of. 😈
* A type in rustdoc got its size increased, I'll check the impact on performance. But in any case, I plan to improve it in a follow-up so should be "ok".
* Because of error reporting, some fields of `Doc` are suboptimal, like `inline` which instead of being an `Option` is a `ThinVec` because we report the error later on. Part of the things I'm not super happy about but can be postponed to future me.
* In `src/librustdoc/clean/cfg.rs`, the `pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {` function should be removed once `cfg_trace` has been ported to new `cfg` API.
* Size of type `DocFragment` went from 32 to 48. Would be nice to get it back to 32.
* ``malformed `doc` attribute input`` wasn't meant for so many candidates, should be improved.
* See how many of the checks in `check_attr` we can move to attribute parsing
* Port target checking to be in the attribute parser completely
* Fix target checking for `doc(alias)` on fields & patterns

And finally, once this PR is merged, I plan to finally stabilize `doc_cfg` feature. :)

cc `@jdonszelmann`
r? `@JonathanBrouwer`
This commit is contained in:
bors 2025-12-11 21:08:19 +00:00
commit 5b150d238f
119 changed files with 3649 additions and 2336 deletions

View file

@ -4011,7 +4011,6 @@ dependencies = [
"itertools",
"rustc_abi",
"rustc_ast",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
"rustc_fluent_macro",
@ -4433,7 +4432,6 @@ dependencies = [
"rustc_abi",
"rustc_ast",
"rustc_ast_lowering",
"rustc_ast_pretty",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
@ -4870,6 +4868,7 @@ dependencies = [
"indexmap",
"itertools",
"minifier",
"proc-macro2",
"pulldown-cmark-escape",
"regex",
"rustdoc-json-types",

View file

@ -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<Span> {
match &self.args {
AttrArgs::Eq { expr, .. } => Some(expr.span),
AttrArgs::Delimited(_) | AttrArgs::Empty => None,
}
}
pub fn meta(&self, span: Span) -> Option<MetaItem> {
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<AttrStyle>;
/// 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)
}
}

View file

@ -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,

View file

@ -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<Target = pp::Printer> + 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<Target = pp::Printer> + 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 => "<eof>".into(),
}

View file

@ -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

View file

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

View file

@ -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) {

View file

@ -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<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> bool {
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
|| keyword.is_weak()
|| keyword == sym::SelfTy
{
return true;
}
cx.emit_err(DocKeywordNotKeyword { span, keyword });
false
}
fn check_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
attribute: Symbol,
span: Span,
) -> bool {
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&attribute) {
return true;
}
cx.emit_err(DocAttributeNotAttribute { span, attribute });
false
}
fn parse_keyword_and_attribute<'c, S, F>(
cx: &'c mut AcceptContext<'_, '_, S>,
path: &PathParser<'_>,
args: &ArgParser<'_>,
attr_value: &mut Option<(Symbol, Span)>,
callback: F,
) where
S: Stage,
F: FnOnce(&mut AcceptContext<'_, '_, S>, Symbol, Span) -> bool,
{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(value) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
if !callback(cx, value, nv.value_span) {
return;
}
if attr_value.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
*attr_value = Some((value, path.span()));
}
#[derive(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::<String>();
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<S: Stage> AttributeParser<S> for DocParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[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<AttributeKind> {
if self.nb_doc_attrs != 0 {
Some(AttributeKind::Doc(Box::new(self.attribute)))
} else {
None
}
}
}

View file

@ -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;

View file

@ -5,7 +5,7 @@ use rustc_ast::attr::AttributeExt;
use rustc_feature::is_builtin_attr_name;
use rustc_hir::RustcVersion;
use rustc_hir::limit::Limit;
use rustc_span::{Symbol, sym};
use rustc_span::Symbol;
use crate::context::{AcceptContext, Stage};
use crate::parser::{ArgParser, NameValueParser};
@ -32,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<Item = &'tcx T>,
symbol: Symbol,
) -> bool {
let doc_attrs = attrs.filter(|attr| attr.has_name(sym::doc));
for attr in doc_attrs {
let Some(values) = attr.meta_item_list() else {
continue;
};
let alias_values = values.iter().filter(|v| v.has_name(sym::alias));
for v in alias_values {
if let Some(nested) = v.meta_item_list() {
// #[doc(alias("foo", "bar"))]
let mut iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol);
if iter.any(|s| s == symbol) {
return true;
}
} else if let Some(meta) = v.meta_item()
&& let Some(lit) = meta.name_value_literal()
{
// #[doc(alias = "foo")]
if lit.symbol == symbol {
return true;
}
}
}
}
false
}
/// Parse a single integer.
///
/// Used by attributes that take a single integer as argument, such as

View file

@ -33,6 +33,7 @@ use crate::attributes::crate_level::{
};
use crate::attributes::debugger::DebuggerViualizerParser;
use crate::attributes::deprecation::DeprecationParser;
use crate::attributes::doc::DocParser;
use crate::attributes::dummy::DummyParser;
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
use crate::attributes::link_attrs::{
@ -162,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 })
}

View file

@ -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 {

View file

@ -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;

View file

@ -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,
}

View file

@ -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,

View file

@ -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<CfgInfo>,
}
#[derive(Clone, Debug, Default, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct DocAttribute {
pub aliases: FxIndexMap<Symbol, Span>,
pub hidden: Option<Span>,
// Because we need to emit the error if there is more than one `inline` attribute on an item
// at the same time as the other doc attributes, we store a list instead of using `Option`.
pub inline: ThinVec<(DocInline, Span)>,
// unstable
pub cfg: ThinVec<CfgEntry>,
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<Span>,
pub keyword: Option<(Symbol, Span)>,
pub attribute: Option<(Symbol, Span)>,
pub masked: Option<Span>,
pub notable_trait: Option<Span>,
pub search_unbox: Option<Span>,
// valid on crate
pub html_favicon_url: Option<(Symbol, Span)>,
pub html_logo_url: Option<(Symbol, Span)>,
pub html_playground_url: Option<(Symbol, Span)>,
pub html_root_url: Option<(Symbol, Span)>,
pub html_no_source: Option<Span>,
pub issue_tracker_base_url: Option<(Symbol, Span)>,
pub rust_logo: Option<Span>,
// #[doc(test(...))]
pub test_attrs: ThinVec<Span>,
pub no_crate_inject: Option<Span>,
}
/// 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<DocAttribute>),
/// Represents specifically [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// i.e. doc comments.
DocComment { style: AttrStyle, kind: DocFragmentKind, span: Span, comment: Symbol },
/// Represents `#[rustc_dummy]`.
Dummy,

View file

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

View file

@ -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<T: PrintAttribute> PrintAttribute for ThinVec<T> {
p.word("]");
}
}
impl<T: PrintAttribute> PrintAttribute for FxIndexMap<T, Span> {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
}
fn print_attribute(&self, p: &mut Printer) {
let mut last_printed = false;
p.word("[");
for (i, _) in self {
if last_printed {
p.word_space(",");
}
i.print_attribute(p);
last_printed = i.should_render();
}
p.word("]");
}
}
macro_rules! print_skip {
($($t: ty),* $(,)?) => {$(
impl PrintAttribute for $t {
@ -147,6 +167,7 @@ print_debug!(
Align,
AttrStyle,
CommentKind,
DocFragmentKind,
Transparency,
SanitizerSet,
);

View file

@ -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<Symbol> {
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)
}
}

View file

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

View file

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

View file

@ -3,12 +3,12 @@ use std::cell::{Cell, RefCell};
use std::cmp::max;
use std::ops::Deref;
use rustc_attr_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;
}

View file

@ -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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc(plugins)` is now a no-op
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

View file

@ -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
}
@ -822,19 +812,23 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
let mut sugared_span: Option<Span> = 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

View file

@ -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),
}
}

View file

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

View file

@ -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;

View file

@ -785,6 +785,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<Ident>;

View file

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

View file

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

View file

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

View file

@ -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 <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
passes_doc_invalid =
invalid `doc` attribute
passes_doc_keyword_attribute_empty_mod =
`#[doc({$attr_name} = "...")]` should be used on empty modules
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 <https://github.com/rust-lang/rust/issues/44136>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_plugins =
unknown `doc` attribute `{$path}`
.note = `doc` attribute `{$path}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_spotlight =
unknown `doc` attribute `{$path}`
.note = `doc(spotlight)` was renamed to `doc(notable_trait)`
.suggestion = use `notable_trait` instead
.no_op_note = `doc(spotlight)` is now a no-op
passes_duplicate_diagnostic_item_in_crate =
duplicate diagnostic item in crate `{$crate_name}`: `{$name}`
.note = the diagnostic item is first defined in crate `{$orig_crate_name}`

View file

@ -10,22 +10,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<ItemLike<'_>>,
) {
let mut doc_aliases = FxHashMap::default();
let mut specified_inline = None;
let mut seen = FxHashMap::default();
let attrs = self.tcx.hir_attrs(hir_id);
for attr in attrs {
@ -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<String, Span>,
) {
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<String, Span>,
) {
if let Some(values) = meta.meta_item_list() {
for v in values {
match v.lit() {
Some(l) => match l.kind {
LitKind::Str(s, _) => {
self.check_doc_alias_value(v, s, hir_id, target, true, aliases);
}
_ => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
},
None => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
}
}
} else if let Some(doc_alias) = meta.value_str() {
self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases)
} else {
self.dcx().emit_err(errors::DocAliasMalformed { span: meta.span() });
}
}
fn check_doc_keyword_and_attribute(
&self,
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,354 +934,160 @@ 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<String, Span>,
) {
if let Some(list) = attr.meta_item_list() {
for meta in &list {
if let Some(i_meta) = meta.meta_item() {
match i_meta.name() {
Some(sym::alias) => {
if self.check_attr_not_crate_level(meta, hir_id, "alias") {
self.check_doc_alias(meta, hir_id, target, aliases);
}
}
fn check_doc_attrs(&self, attr: &DocAttribute, hir_id: HirId, target: Target) {
let DocAttribute {
aliases,
// valid pretty much anywhere, not checked here?
// FIXME: should we?
hidden: _,
inline,
// FIXME: currently unchecked
cfg: _,
// 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;
Some(sym::keyword) => {
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Keyword,
);
}
}
Some(sym::attribute) => {
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Attribute,
);
}
}
Some(sym::fake_variadic) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_fake_variadic(meta, hir_id);
}
}
Some(sym::search_unbox) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_search_unbox(meta, hir_id);
}
}
Some(sym::test) => {
self.check_test_attr(attr_span, style, meta, hir_id);
}
Some(
sym::html_favicon_url
| sym::html_logo_url
| sym::html_playground_url
| sym::issue_tracker_base_url
| sym::html_root_url,
) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_string_value(meta, hir_id);
}
Some(sym::html_no_source) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_no_value(meta, hir_id);
}
Some(sym::auto_cfg) => {
self.check_doc_auto_cfg(i_meta, hir_id);
}
Some(sym::inline | sym::no_inline) => {
self.check_doc_inline(style, meta, hir_id, target, specified_inline)
}
Some(sym::masked) => self.check_doc_masked(style, meta, hir_id, target),
Some(sym::cfg | sym::hidden | sym::notable_trait) => {}
Some(sym::rust_logo) => {
if self.check_attr_crate_level(attr_span, style, meta, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
meta.span(),
fluent::passes_doc_rust_logo,
)
.emit();
}
}
_ => {
let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
if i_meta.has_name(sym::spotlight) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownSpotlight { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::include)
&& let Some(value) = i_meta.value_str()
{
let applicability = if list.len() == 1 {
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
// If there are multiple attributes, the suggestion would suggest
// deleting all of them, which is incorrect.
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownInclude {
path,
value: value.to_string(),
inner: match style {
AttrStyle::Inner => "!",
AttrStyle::Outer => "",
},
sugg: (attr.span(), applicability),
},
);
} else if i_meta.has_name(sym::passes)
|| i_meta.has_name(sym::no_default_passes)
{
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPasses { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::plugins) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPlugins { path, span: i_meta.span },
);
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownAny { path },
);
}
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocInvalid,
);
}
for (alias, span) in aliases {
if self.check_attr_not_crate_level(*span, hir_id, "alias") {
self.check_doc_alias_value(*span, hir_id, target, *alias);
}
}
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");
}
if let Some(span) = fake_variadic
&& self.check_attr_not_crate_level(*span, hir_id, "fake_variadic")
{
self.check_doc_fake_variadic(*span, hir_id);
}
if let Some(span) = search_unbox
&& self.check_attr_not_crate_level(*span, hir_id, "search_unbox")
{
self.check_doc_search_unbox(*span, hir_id);
}
for i in [
html_favicon_url,
html_logo_url,
html_playground_url,
issue_tracker_base_url,
html_root_url,
] {
if let Some((_, span)) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
for i in [html_no_source, no_crate_inject] {
if let Some(span) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
self.check_doc_inline(hir_id, target, inline);
if let Some(span) = rust_logo
&& self.check_attr_crate_level(*span, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
*span,
fluent::passes_doc_rust_logo,
)
.emit();
}
if let Some(span) = masked {
self.check_doc_masked(*span, hir_id, target);
}
}
fn check_has_incoherent_inherent_impls(&self, attr: &Attribute, span: Span, target: 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::<Vec<_>>();
if !spans.is_empty() {

View file

@ -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<Span>,
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<Span>,
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<Span>,
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<AttrCrateLevelOnlySugg>,
}
#[derive(Subdiagnostic)]
#[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")]
pub(crate) struct AttrCrateLevelOnlySugg {
#[primary_span]
pub attr: Span,
}
pub(crate) struct AttrCrateLevelOnly {}
/// "sanitize attribute not allowed here"
#[derive(Diagnostic)]

View file

@ -10,7 +10,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);
}
}

View file

@ -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::<A>::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

View file

@ -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,

View file

@ -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"

View file

@ -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" }

View file

@ -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<Symbol>),
/// Negates a configuration requirement, i.e., `not(x)`.
Not(Box<Cfg>),
/// Union of a list of configuration requirements, i.e., `any(...)`.
Any(Vec<Cfg>),
/// Intersection of a list of configuration requirements, i.e., `all(...)`.
All(Vec<Cfg>),
}
#[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<NameValueCfg>) -> Option<CfgEntry> {
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::<ThinVec<_>>();
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::<ThinVec<_>>();
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<Cfg>,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, 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<Cfg>,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, 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::<Vec<_>>();
@ -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}<strong>{}</strong>", Display(self, format))
format!("Available{on}<strong>{}</strong>", 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<Self> {
if self == assume {
if self.0.is_equivalent_to(&assume.0) {
None
} else if let Cfg::All(a) = self {
let mut sub_cfgs: Vec<Cfg> = 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<CfgEntry> = 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<Cfg>) -> Option<Self> {
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::<Vec<_>>();
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::<Vec<_>>();
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)),
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::Not(box simple @ CfgEntry::NameValue { .. }, _) => {
write!(fmt, "non-{}", Display(simple, self.1))
}
Cfg::All(sub_cfgs) => self.display_sub_cfgs(fmt, sub_cfgs, " and "),
CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)),
Cfg::True => fmt.write_str("everywhere"),
Cfg::False => fmt.write_str("nowhere"),
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)
}
CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "),
&Cfg::Cfg(name, value) => {
let human_readable = match (name, value) {
CfgEntry::Bool(v, _) => {
if *v {
fmt.write_str("everywhere")
} else {
fmt.write_str("nowhere")
}
}
&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 <code>{feat}</code>");
@ -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<Symbol>,
}
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<Cfg>,
hidden_cfg: FxHashSet<NameValueCfg>,
/// 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<Symbol>), rustc_span::Span>,
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), 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<Item = &'a hir::Attribute>
fn check_changed_auto_active_status(
changed_auto_active_status: &mut Option<rustc_span::Span>,
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<Item = &'a hir::Attribute>
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<Item = &'a hir::Attribute>
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<Item = &'a hir::Attribute>
// 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<Item = &'a hir::Attribute>
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<Item = &'a hir::Attribute>
} 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))),
}
}
}

View file

@ -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<CfgEntry>) -> Cfg {
Cfg(cfg_all_e(v))
}
fn cfg_all_e(v: ThinVec<CfgEntry>) -> CfgEntry {
CfgEntry::All(v, DUMMY_SP)
}
fn cfg_any(v: ThinVec<CfgEntry>) -> Cfg {
Cfg(cfg_any_e(v))
}
fn cfg_any_e(v: ThinVec<CfgEntry>) -> 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")]));
x &= Cfg::All(vec![word_cfg("test6"), word_cfg("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")])
);
x &= cfg_all(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
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")]));
x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("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")])
);
x |= cfg_any(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
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);
});
}

View file

@ -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

View file

@ -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<TokenTree> {
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<DefId>,
) {
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+

View file

@ -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<Item = (DefId, Symbol)> {
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<Item = (DefId, Symbol)> {
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<F: Fn(&DocAttribute) -> Option<Symbol>>(
&self,
tcx: TyCtxt<'_>,
name: Symbol,
callback: F,
) -> impl Iterator<Item = (DefId, Symbol)> {
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<Item = &'a hir::Attribute>>(
attrs: I,
name: Symbol,
) -> impl Iterator<Item = ast::MetaItemInner> + 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,
{
<Self as NestedAttributesExt>::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<ast::MetaItemInner>;
}
impl<I: Iterator<Item = ast::MetaItemInner>> NestedAttributesExt for I {
fn get_word_attr(mut self, word: Symbol) -> Option<ast::MetaItemInner> {
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<Item = ast::MetaItemInner> {
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<F: Fn(&DocAttribute) -> 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::<Vec<_>>().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);

View file

@ -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<DocFragment> {
span: DUMMY_SP,
item_id: None,
doc: Symbol::intern(s),
kind: DocFragmentKind::SugaredDoc,
kind: DocFragmentKind::Sugared(CommentKind::Line),
indent: 0,
from_expansion: false,
}]

View file

@ -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<Item = &'a hir::Attribute>,
flag: Symbol,
pub(crate) fn has_doc_flag<F: Fn(&DocAttribute) -> 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

View file

@ -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

View file

@ -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());
}
}
}
}
}
}
}

View file

@ -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();

View file

@ -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,

View file

@ -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(

View file

@ -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<ItemType> 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<Attribute> {
fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) -> Vec<Attribute> {
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<Attribute>, name: &str, v: &Option<rustc_span::Span>) {
if v.is_some() {
ret.push(Attribute::Other(format!("#[doc({name})]")));
}
}
fn name_value_attr(
ret: &mut Vec<Attribute>,
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 {

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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| {

View file

@ -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),

View file

@ -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<TokenTree> {
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<Attribute>, 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
&& let [ident] = &*normal.path.segments
if let Attribute::Parsed(AttributeKind::Doc(d)) = attr
&& !d.cfg.is_empty()
{
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);
}
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
{
// If it's a `cfg()` attribute, we keep it.
attrs.push(attr.clone());
}
}
}

View file

@ -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,

View file

@ -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<usize>, 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) {

View file

@ -861,7 +861,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, 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))

View file

@ -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 {

View file

@ -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]`

View file

@ -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

View file

@ -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`.

View file

@ -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

View file

@ -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`.

View file

@ -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 <https://github.com/rust-lang/rust/issues/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 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
= note: `doc(plugins)` is now a no-op

View file

@ -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

View file

@ -1,4 +1 @@
#![doc(alias = "crate-level-not-working")] //~ ERROR
#[doc(alias = "shouldn't work!")] //~ ERROR
pub fn foo() {}

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
#[doc(alias = "shouldn't work!")] //~ ERROR
pub fn foo() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> 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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> 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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> 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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
error: aborting due to 6 previous errors; 4 warnings emitted

View file

@ -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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
@ -18,10 +18,10 @@ LL | #[doc(cfg(foo))]
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> 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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration

View file

@ -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() {}

View file

@ -1,90 +1,124 @@
error: only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`
--> $DIR/doc-cfg.rs:11:7
|
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.rs:12:21
|
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(auto_cfg(hide(42)))]
| ^^
error: `#![doc(auto_cfg(hide(...)))]` only accepts identifiers or key/value items
--> $DIR/doc-cfg.rs:14:21
|
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(auto_cfg(hide(foo::bar)))]
| ^^^^^^^^
error: expected boolean for `#[doc(auto_cfg = ...)]`
--> $DIR/doc-cfg.rs:16:7
|
LL | #[doc(auto_cfg = 42)]
| ^^^^^^^^^^^^^
error: expected boolean for `#[doc(auto_cfg = ...)]`
--> $DIR/doc-cfg.rs:17:7
|
LL | #[doc(auto_cfg = "a")]
| ^^^^^^^^^^^^^^
warning: unexpected `cfg` condition name: `foo`
--> $DIR/doc-cfg.rs:6: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 <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> 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(foo), cfg(bar))]
| ^^^
|
= help: to expect this configuration use `--check-cfg=cfg(bar)`
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
error: `cfg` predicate is not specified
--> $DIR/doc-cfg.rs:3:7
error[E0805]: malformed `doc` attribute input
--> $DIR/doc-cfg.rs:3:1
|
LL | #[doc(cfg(), cfg(foo, bar))]
| ^^^^^ 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(), cfg(foo, bar))]
LL + #[doc = "string"]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(alias)]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(attribute)]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(auto_cfg)]
|
= and 22 other candidates
error: multiple `cfg` predicates are specified
--> $DIR/doc-cfg.rs:3:23
error[E0805]: malformed `doc` attribute input
--> $DIR/doc-cfg.rs:3:1
|
LL | #[doc(cfg(), 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(), cfg(foo, bar))]
LL + #[doc = "string"]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(alias)]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(attribute)]
|
LL - #[doc(cfg(), cfg(foo, bar))]
LL + #[doc(auto_cfg)]
|
= 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: aborting due to 11 previous errors; 2 warnings emitted
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 5 previous errors
Some errors have detailed explanations: E0539, E0805.
For more information about an error, try `rustc --explain E0539`.

View file

@ -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

View file

@ -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

View file

@ -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 {}

View file

@ -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`.

View file

@ -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)

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -1,46 +1,75 @@
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: invalid `doc` attribute
--> $DIR/doc-attr.rs:9:7
error[E0539]: malformed `doc` attribute input
--> $DIR/doc-attr.rs:3:1
|
LL | #[doc(123)]
| ^^^
| ^^^^^^---^^
| |
| expected this to be of the form `... = "..."`
|
help: try changing it to one of the following valid forms of the attribute
|
LL - #[doc(123)]
LL + #[doc = "string"]
|
LL - #[doc(123)]
LL + #[doc(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(as_ptr)]
| ^^^^^^
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: aborting due to 7 previous errors
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0539`.

View file

@ -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

View file

@ -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() {}

View file

@ -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 <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> 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 <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> 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 <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> 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 <https://doc.rust-lang.org/unstable-book/language-features/doc-masked.html> 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 <https://doc.rust-lang.org/unstable-book/language-features/doc-masked.html> 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 <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> 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

View file

@ -0,0 +1,13 @@
// While working on <https://github.com/rust-lang/rust/pull/149645>, 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 {}

View file

@ -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

View file

@ -1,3 +1,75 @@
error[E0539]: malformed `doc` attribute input
--> $DIR/doc-attr.rs:9:1
|
LL | #[doc(123)]
| ^^^^^^---^^
| |
| expected this to be of the form `... = "..."`
|
help: try changing it to one of the following valid forms of the attribute
|
LL - #[doc(123)]
LL + #[doc = "string"]
|
LL - #[doc(123)]
LL + #[doc(alias)]
|
LL - #[doc(123)]
LL + #[doc(attribute)]
|
LL - #[doc(123)]
LL + #[doc(auto_cfg)]
|
= and 22 other candidates
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[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
|
@ -6,24 +78,6 @@ LL | #[doc(as_ptr)]
|
= note: `#[deny(invalid_doc_attributes)]` on by default
error: invalid `doc` attribute
--> $DIR/doc-attr.rs:9:7
|
LL | #[doc(123)]
| ^^^
error: invalid `doc` attribute
--> $DIR/doc-attr.rs:11:7
|
LL | #[doc("hello", "bar")]
| ^^^^^^^
error: invalid `doc` attribute
--> $DIR/doc-attr.rs:11:16
|
LL | #[doc("hello", "bar")]
| ^^^^^
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`.

View file

@ -1,4 +1,4 @@
#![doc(test(""))]
//~^ ERROR `#![doc(test(...)]` does not take a literal
//~^ ERROR malformed `doc` attribute input
fn main() {}

View file

@ -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`.

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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 <https://github.com/rust-lang/rust/issues/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`

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more