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:
commit
5b150d238f
119 changed files with 3649 additions and 2336 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
631
compiler/rustc_attr_parsing/src/attributes/doc.rs
Normal file
631
compiler/rustc_attr_parsing/src/attributes/doc.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ impl AttributeKind {
|
|||
DenyExplicitImpl(..) => No,
|
||||
Deprecation { .. } => Yes,
|
||||
DoNotImplementViaObject(..) => No,
|
||||
Doc(_) => Yes,
|
||||
DocComment { .. } => Yes,
|
||||
Dummy => No,
|
||||
ExportName { .. } => Yes,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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+
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
#![doc(alias = "crate-level-not-working")] //~ ERROR
|
||||
|
||||
#[doc(alias = "shouldn't work!")] //~ ERROR
|
||||
pub fn foo() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
2
tests/rustdoc-ui/doc-alias.rs
Normal file
2
tests/rustdoc-ui/doc-alias.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#[doc(alias = "shouldn't work!")] //~ ERROR
|
||||
pub fn foo() {}
|
||||
8
tests/rustdoc-ui/doc-alias.stderr
Normal file
8
tests/rustdoc-ui/doc-alias.stderr
Normal 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
|
||||
|
||||
18
tests/rustdoc-ui/doc-cfg-2.rs
Normal file
18
tests/rustdoc-ui/doc-cfg-2.rs
Normal 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() {}
|
||||
78
tests/rustdoc-ui/doc-cfg-2.stderr
Normal file
78
tests/rustdoc-ui/doc-cfg-2.stderr
Normal 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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
11
tests/rustdoc-ui/lints/doc-attr-2.rs
Normal file
11
tests/rustdoc-ui/lints/doc-attr-2.rs
Normal 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() {}
|
||||
28
tests/rustdoc-ui/lints/doc-attr-2.stderr
Normal file
28
tests/rustdoc-ui/lints/doc-attr-2.stderr
Normal 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
|
||||
|
||||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
tests/rustdoc/doc-on-keyword.rs
Normal file
13
tests/rustdoc/doc-on-keyword.rs
Normal 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 {}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![doc(test(""))]
|
||||
//~^ ERROR `#![doc(test(...)]` does not take a literal
|
||||
//~^ ERROR malformed `doc` attribute input
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue