rewrite doc attribute (non-doc-comments)
This commit is contained in:
parent
bad50269f8
commit
df007cf800
15 changed files with 779 additions and 504 deletions
|
|
@ -234,5 +234,34 @@ attr_parsing_unused_multiple =
|
|||
.suggestion = remove this attribute
|
||||
.note = attribute also specified here
|
||||
|
||||
attr_parsing_doc_alias_duplicated = doc alias is duplicated
|
||||
.label = first defined here
|
||||
|
||||
attr_parsing_whole_archive_needs_static =
|
||||
linking modifier `whole-archive` is only compatible with `static` linking kind
|
||||
|
||||
attr_parsing_unused_no_lints_note =
|
||||
attribute `{$name}` without any lints has no effect
|
||||
|
||||
attr_parsing_doc_alias_empty =
|
||||
{$attr_str} attribute cannot have empty value
|
||||
|
||||
attr_parsing_doc_alias_bad_char =
|
||||
{$char_} character isn't allowed in {$attr_str}
|
||||
|
||||
attr_parsing_doc_alias_start_end =
|
||||
{$attr_str} cannot start or end with ' '
|
||||
|
||||
attr_parsing_doc_keyword_not_keyword =
|
||||
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
|
||||
.help = only existing keywords are allowed in core/std
|
||||
|
||||
attr_parsing_doc_inline_conflict =
|
||||
conflicting doc inlining attributes
|
||||
.help = remove one of the conflicting attributes
|
||||
|
||||
attr_parsing_doc_inline_conflict_first =
|
||||
this attribute...
|
||||
|
||||
attr_parsing_doc_inline_conflict_second =
|
||||
{"."}..conflicts with this attribute
|
||||
|
|
|
|||
385
compiler/rustc_attr_parsing/src/attributes/doc.rs
Normal file
385
compiler/rustc_attr_parsing/src/attributes/doc.rs
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
use rustc_attr_data_structures::lints::AttributeLintKind;
|
||||
use rustc_attr_data_structures::{AttributeKind, DocAttribute, DocInline};
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_feature::template;
|
||||
use rustc_span::{Span, Symbol, edition, sym};
|
||||
|
||||
use super::{AcceptMapping, AttributeParser};
|
||||
use crate::context::{AcceptContext, FinalizeContext, Stage};
|
||||
use crate::fluent_generated as fluent;
|
||||
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, PathParser};
|
||||
use crate::session_diagnostics::{
|
||||
DocAliasBadChar, DocAliasEmpty, DocAliasStartEnd, DocKeywordConflict, DocKeywordNotKeyword,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DocParser {
|
||||
attribute: DocAttribute,
|
||||
}
|
||||
|
||||
impl DocParser {
|
||||
fn parse_single_test_doc_attr_item<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
mip: &'c MetaItemParser<'_>,
|
||||
) {
|
||||
let path = mip.path();
|
||||
let args = mip.args();
|
||||
|
||||
match path.word_sym() {
|
||||
Some(sym::no_crate_inject) => {
|
||||
if !args.no_args() {
|
||||
cx.expected_no_args(args.span().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
if self.attribute.no_crate_inject.is_some() {
|
||||
cx.duplicate_key(path.span(), sym::no_crate_inject);
|
||||
return;
|
||||
}
|
||||
|
||||
self.attribute.no_crate_inject = Some(path.span())
|
||||
}
|
||||
Some(sym::attr) => {
|
||||
let Some(list) = args.list() else {
|
||||
cx.expected_list(args.span().unwrap_or(path.span()));
|
||||
return;
|
||||
};
|
||||
|
||||
self.attribute.test_attrs.push(todo!());
|
||||
}
|
||||
_ => {
|
||||
cx.expected_specific_argument(
|
||||
mip.span(),
|
||||
[sym::no_crate_inject.as_str(), sym::attr.as_str()].to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_alias<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
alias: Symbol,
|
||||
span: Span,
|
||||
is_list: bool,
|
||||
) {
|
||||
let attr_str =
|
||||
&format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
|
||||
if alias == sym::empty {
|
||||
cx.emit_err(DocAliasEmpty { span, attr_str });
|
||||
return;
|
||||
}
|
||||
|
||||
let alias_str = alias.as_str();
|
||||
if let Some(c) =
|
||||
alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
|
||||
{
|
||||
cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
|
||||
return;
|
||||
}
|
||||
if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
|
||||
cx.emit_err(DocAliasStartEnd { span, attr_str });
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
|
||||
cx.emit_lint(AttributeLintKind::DuplicateDocAlias { first_definition }, span);
|
||||
}
|
||||
|
||||
self.attribute.aliases.insert(alias, span);
|
||||
}
|
||||
|
||||
fn parse_alias<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
path: &PathParser<'_>,
|
||||
args: &ArgParser<'_>,
|
||||
) {
|
||||
match args {
|
||||
ArgParser::NoArgs => {
|
||||
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
|
||||
}
|
||||
ArgParser::List(list) => {
|
||||
for i in list.mixed() {
|
||||
let Some(alias) = i.lit().and_then(|i| i.value_str()) else {
|
||||
cx.expected_string_literal(i.span(), i.lit());
|
||||
continue;
|
||||
};
|
||||
|
||||
self.add_alias(cx, alias, i.span(), false);
|
||||
}
|
||||
}
|
||||
ArgParser::NameValue(nv) => {
|
||||
let Some(alias) = nv.value_as_str() else {
|
||||
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
|
||||
return;
|
||||
};
|
||||
self.add_alias(cx, alias, nv.value_span, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_keyword<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
path: &PathParser<'_>,
|
||||
args: &ArgParser<'_>,
|
||||
) {
|
||||
let Some(nv) = args.name_value() else {
|
||||
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(keyword) = nv.value_as_str() else {
|
||||
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
|
||||
return;
|
||||
};
|
||||
|
||||
fn is_doc_keyword(s: Symbol) -> bool {
|
||||
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
|
||||
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
|
||||
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
|
||||
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
|
||||
}
|
||||
|
||||
if !is_doc_keyword(keyword) {
|
||||
cx.emit_err(DocKeywordNotKeyword { span: nv.value_span, keyword });
|
||||
}
|
||||
|
||||
if self.attribute.keyword.is_some() {
|
||||
cx.duplicate_key(path.span(), path.word_sym().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
self.attribute.keyword = Some((keyword, path.span()));
|
||||
}
|
||||
|
||||
fn parse_inline<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
path: &PathParser<'_>,
|
||||
args: &ArgParser<'_>,
|
||||
inline: DocInline,
|
||||
) {
|
||||
if !args.no_args() {
|
||||
cx.expected_no_args(args.span().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
let span = path.span();
|
||||
|
||||
if let Some((prev_inline, prev_span)) = self.attribute.inline {
|
||||
if prev_inline == inline {
|
||||
let mut spans = MultiSpan::from_spans(vec![prev_span, span]);
|
||||
spans.push_span_label(prev_span, fluent::attr_parsing_doc_inline_conflict_first);
|
||||
spans.push_span_label(span, fluent::attr_parsing_doc_inline_conflict_second);
|
||||
cx.emit_err(DocKeywordConflict { spans });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.attribute.inline = Some((inline, span));
|
||||
}
|
||||
|
||||
fn parse_single_doc_attr_item<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
mip: &MetaItemParser<'_>,
|
||||
) {
|
||||
let path = mip.path();
|
||||
let args = mip.args();
|
||||
|
||||
macro_rules! no_args {
|
||||
($ident: ident) => {{
|
||||
if !args.no_args() {
|
||||
cx.expected_no_args(args.span().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
if self.attribute.$ident.is_some() {
|
||||
cx.duplicate_key(path.span(), path.word_sym().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
self.attribute.$ident = Some(path.span());
|
||||
}};
|
||||
}
|
||||
macro_rules! string_arg {
|
||||
($ident: ident) => {{
|
||||
let Some(nv) = args.name_value() else {
|
||||
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(s) = nv.value_as_str() else {
|
||||
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
|
||||
return;
|
||||
};
|
||||
|
||||
if self.attribute.$ident.is_some() {
|
||||
cx.duplicate_key(path.span(), path.word_sym().unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
self.attribute.$ident = Some((s, path.span()));
|
||||
}};
|
||||
}
|
||||
|
||||
match path.word_sym() {
|
||||
Some(sym::alias) => self.parse_alias(cx, path, args),
|
||||
Some(sym::hidden) => no_args!(hidden),
|
||||
Some(sym::html_favicon_url) => string_arg!(html_favicon_url),
|
||||
Some(sym::html_logo_url) => string_arg!(html_logo_url),
|
||||
Some(sym::html_no_source) => no_args!(html_no_source),
|
||||
Some(sym::html_playground_url) => string_arg!(html_playground_url),
|
||||
Some(sym::html_root_url) => string_arg!(html_root_url),
|
||||
Some(sym::issue_tracker_base_url) => string_arg!(issue_tracker_base_url),
|
||||
Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
|
||||
Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
|
||||
Some(sym::masked) => no_args!(masked),
|
||||
Some(sym::cfg) => no_args!(cfg),
|
||||
Some(sym::cfg_hide) => no_args!(cfg_hide),
|
||||
Some(sym::notable_trait) => no_args!(notable_trait),
|
||||
Some(sym::keyword) => self.parse_keyword(cx, path, args),
|
||||
Some(sym::fake_variadic) => no_args!(fake_variadic),
|
||||
Some(sym::search_unbox) => no_args!(search_unbox),
|
||||
Some(sym::rust_logo) => no_args!(rust_logo),
|
||||
Some(sym::test) => {
|
||||
let Some(list) = args.list() else {
|
||||
cx.expected_list(args.span().unwrap_or(path.span()));
|
||||
return;
|
||||
};
|
||||
|
||||
for i in list.mixed() {
|
||||
match i {
|
||||
MetaItemOrLitParser::MetaItemParser(mip) => {
|
||||
self.parse_single_test_doc_attr_item(cx, mip);
|
||||
}
|
||||
MetaItemOrLitParser::Lit(lit) => {
|
||||
cx.unexpected_literal(lit.span);
|
||||
}
|
||||
MetaItemOrLitParser::Err(..) => {
|
||||
// already had an error here, move on.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
|
||||
// if i_meta.has_name(sym::spotlight) {
|
||||
// self.tcx.emit_node_span_lint(
|
||||
// INVALID_DOC_ATTRIBUTES,
|
||||
// hir_id,
|
||||
// i_meta.span,
|
||||
// errors::DocTestUnknownSpotlight { path, span: i_meta.span },
|
||||
// );
|
||||
// } else if i_meta.has_name(sym::include)
|
||||
// && let Some(value) = i_meta.value_str()
|
||||
// {
|
||||
// let applicability = if list.len() == 1 {
|
||||
// Applicability::MachineApplicable
|
||||
// } else {
|
||||
// Applicability::MaybeIncorrect
|
||||
// };
|
||||
// // If there are multiple attributes, the suggestion would suggest
|
||||
// // deleting all of them, which is incorrect.
|
||||
// self.tcx.emit_node_span_lint(
|
||||
// INVALID_DOC_ATTRIBUTES,
|
||||
// hir_id,
|
||||
// i_meta.span,
|
||||
// errors::DocTestUnknownInclude {
|
||||
// path,
|
||||
// value: value.to_string(),
|
||||
// inner: match attr.style() {
|
||||
// AttrStyle::Inner => "!",
|
||||
// AttrStyle::Outer => "",
|
||||
// },
|
||||
// sugg: (attr.span(), applicability),
|
||||
// },
|
||||
// );
|
||||
// } else if i_meta.has_name(sym::passes) || i_meta.has_name(sym::no_default_passes) {
|
||||
// self.tcx.emit_node_span_lint(
|
||||
// INVALID_DOC_ATTRIBUTES,
|
||||
// hir_id,
|
||||
// i_meta.span,
|
||||
// errors::DocTestUnknownPasses { path, span: i_meta.span },
|
||||
// );
|
||||
// } else if i_meta.has_name(sym::plugins) {
|
||||
// self.tcx.emit_node_span_lint(
|
||||
// INVALID_DOC_ATTRIBUTES,
|
||||
// hir_id,
|
||||
// i_meta.span,
|
||||
// errors::DocTestUnknownPlugins { path, span: i_meta.span },
|
||||
// );
|
||||
// } else {
|
||||
// self.tcx.emit_node_span_lint(
|
||||
// INVALID_DOC_ATTRIBUTES,
|
||||
// hir_id,
|
||||
// i_meta.span,
|
||||
// errors::DocTestUnknownAny { path },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
_ => {
|
||||
cx.expected_specific_argument(
|
||||
mip.span(),
|
||||
[
|
||||
sym::alias.as_str(),
|
||||
sym::hidden.as_str(),
|
||||
sym::html_favicon_url.as_str(),
|
||||
sym::html_logo_url.as_str(),
|
||||
sym::html_no_source.as_str(),
|
||||
sym::html_playground_url.as_str(),
|
||||
sym::html_root_url.as_str(),
|
||||
sym::inline.as_str(),
|
||||
sym::no_inline.as_str(),
|
||||
sym::test.as_str(),
|
||||
]
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_single_doc_attr<'c, S: Stage>(
|
||||
&mut self,
|
||||
cx: &'c mut AcceptContext<'_, '_, S>,
|
||||
args: &'c ArgParser<'_>,
|
||||
) {
|
||||
match args {
|
||||
ArgParser::NoArgs => {
|
||||
todo!()
|
||||
}
|
||||
ArgParser::List(items) => {
|
||||
for i in items.mixed() {
|
||||
match i {
|
||||
MetaItemOrLitParser::MetaItemParser(mip) => {
|
||||
self.parse_single_doc_attr_item(cx, mip);
|
||||
}
|
||||
MetaItemOrLitParser::Lit(lit) => todo!("error should've used equals"),
|
||||
MetaItemOrLitParser::Err(..) => {
|
||||
// already had an error here, move on.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ArgParser::NameValue(v) => {
|
||||
panic!("this should be rare if at all possible");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stage> AttributeParser<S> for DocParser {
|
||||
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
|
||||
&[sym::doc],
|
||||
template!(List: "hidden|inline|...", NameValueStr: "string"),
|
||||
|this, cx, args| {
|
||||
this.accept_single_doc_attr(cx, args);
|
||||
},
|
||||
)];
|
||||
|
||||
fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ pub(crate) mod crate_level;
|
|||
pub(crate) mod debugger;
|
||||
pub(crate) mod deprecation;
|
||||
pub(crate) mod dummy;
|
||||
pub(crate) mod doc;
|
||||
pub(crate) mod inline;
|
||||
pub(crate) mod link_attrs;
|
||||
pub(crate) mod lint_helpers;
|
||||
|
|
|
|||
|
|
@ -32,35 +32,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
|
|||
|| attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
|
||||
}
|
||||
|
||||
pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
|
||||
attrs: impl Iterator<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.
|
||||
///
|
||||
|
|
@ -121,3 +92,7 @@ impl<S: Stage> AcceptContext<'_, '_, S> {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> {
|
||||
first_attr_value_str_by_name(attrs, sym::crate_name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use crate::attributes::crate_level::{
|
|||
};
|
||||
use crate::attributes::debugger::DebuggerViualizerParser;
|
||||
use crate::attributes::deprecation::DeprecationParser;
|
||||
use crate::attributes::doc::DocParser;
|
||||
use crate::attributes::dummy::DummyParser;
|
||||
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
|
||||
use crate::attributes::link_attrs::{
|
||||
|
|
@ -162,7 +163,7 @@ attribute_parsers!(
|
|||
BodyStabilityParser,
|
||||
ConfusablesParser,
|
||||
ConstStabilityParser,
|
||||
MacroUseParser,
|
||||
|
||||
NakedParser,
|
||||
StabilityParser,
|
||||
UsedParser,
|
||||
|
|
@ -427,7 +428,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
|
|||
&self,
|
||||
span: Span,
|
||||
found: String,
|
||||
options: &'static [&'static str],
|
||||
options: &[&'static str],
|
||||
) -> ErrorGuaranteed {
|
||||
self.emit_err(UnknownMetaItem { span, item: found, expected: options })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rustc_ast::{self as ast};
|
|||
use rustc_errors::codes::*;
|
||||
use rustc_errors::{
|
||||
Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
|
||||
MultiSpan,
|
||||
};
|
||||
use rustc_feature::AttributeTemplate;
|
||||
use rustc_hir::AttrPath;
|
||||
|
|
@ -34,6 +35,40 @@ pub(crate) struct InvalidPredicate {
|
|||
pub predicate: String,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_doc_alias_empty)]
|
||||
pub(crate) struct DocAliasEmpty<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_doc_alias_bad_char)]
|
||||
pub(crate) struct DocAliasBadChar<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
pub char_: char,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_doc_alias_start_end)]
|
||||
pub(crate) struct DocAliasStartEnd<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_doc_keyword_not_keyword)]
|
||||
#[help]
|
||||
pub(crate) struct DocKeywordNotKeyword {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub keyword: Symbol,
|
||||
}
|
||||
|
||||
/// Error code: E0541
|
||||
pub(crate) struct UnknownMetaItem<'a> {
|
||||
pub span: Span,
|
||||
|
|
@ -538,6 +573,38 @@ pub(crate) struct NakedFunctionIncompatibleAttribute {
|
|||
pub attr: String,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(attr_parsing_doc_alias_duplicated)]
|
||||
pub(crate) struct DocAliasDuplicated {
|
||||
#[label]
|
||||
pub first_defn: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_doc_inline_conflict)]
|
||||
#[help]
|
||||
pub(crate) struct DocKeywordConflict {
|
||||
#[primary_span]
|
||||
pub spans: MultiSpan,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
pub(crate) enum UnusedNote {
|
||||
#[note(attr_parsing_unused_empty_lints_note)]
|
||||
EmptyList { name: Symbol },
|
||||
#[note(attr_parsing_unused_no_lints_note)]
|
||||
NoLints { name: Symbol },
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(attr_parsing_unused)]
|
||||
pub(crate) struct Unused {
|
||||
#[suggestion(code = "", applicability = "machine-applicable")]
|
||||
pub attr_span: Span,
|
||||
#[subdiagnostic]
|
||||
pub note: UnusedNote,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_link_ordinal_out_of_range)]
|
||||
#[note]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub use ReprAttr::*;
|
|||
use rustc_abi::Align;
|
||||
use rustc_ast::token::CommentKind;
|
||||
use rustc_ast::{AttrStyle, ast};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
|
||||
use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
|
||||
use rustc_span::def_id::DefId;
|
||||
|
|
@ -420,6 +421,70 @@ impl WindowsSubsystemKind {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)]
|
||||
pub enum DocInline {
|
||||
Inline,
|
||||
NoInline,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
|
||||
pub struct DocAttribute {
|
||||
pub aliases: FxIndexMap<Symbol, Span>,
|
||||
pub hidden: Option<Span>,
|
||||
pub inline: Option<(DocInline, Span)>,
|
||||
|
||||
// unstable
|
||||
pub cfg: Option<Span>,
|
||||
pub cfg_hide: Option<Span>,
|
||||
|
||||
// builtin
|
||||
pub fake_variadic: Option<Span>,
|
||||
pub keyword: 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<()>,
|
||||
pub no_crate_inject: Option<Span>,
|
||||
}
|
||||
|
||||
impl Default for DocAttribute {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
aliases: FxIndexMap::default(),
|
||||
hidden: None,
|
||||
inline: None,
|
||||
cfg: None,
|
||||
cfg_hide: None,
|
||||
fake_variadic: None,
|
||||
keyword: None,
|
||||
masked: None,
|
||||
notable_trait: None,
|
||||
search_unbox: None,
|
||||
html_favicon_url: None,
|
||||
html_logo_url: None,
|
||||
html_playground_url: None,
|
||||
html_root_url: None,
|
||||
html_no_source: None,
|
||||
issue_tracker_base_url: None,
|
||||
rust_logo: None,
|
||||
test_attrs: ThinVec::new(),
|
||||
no_crate_inject: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents parsed *built-in* inert attributes.
|
||||
///
|
||||
/// ## Overview
|
||||
|
|
@ -551,7 +616,13 @@ pub enum AttributeKind {
|
|||
/// Represents `#[rustc_do_not_implement_via_object]`.
|
||||
DoNotImplementViaObject(Span),
|
||||
|
||||
/// Represents [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
|
||||
/// Represents [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
|
||||
/// Represents all other uses of the [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html)
|
||||
/// attribute.
|
||||
Doc(Box<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: CommentKind, span: Span, comment: Symbol },
|
||||
|
||||
/// Represents `#[rustc_dummy]`.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ impl AttributeKind {
|
|||
DenyExplicitImpl(..) => No,
|
||||
Deprecation { .. } => Yes,
|
||||
DoNotImplementViaObject(..) => No,
|
||||
Doc(_) => Yes,
|
||||
DocComment { .. } => Yes,
|
||||
Dummy => No,
|
||||
ExportName { .. } => Yes,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rustc_abi::Align;
|
|||
use rustc_ast::token::CommentKind;
|
||||
use rustc_ast::{AttrStyle, IntTy, UintTy};
|
||||
use rustc_ast_pretty::pp::Printer;
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_span::hygiene::Transparency;
|
||||
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
|
||||
use rustc_target::spec::SanitizerSet;
|
||||
|
|
@ -64,6 +65,25 @@ impl<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 {
|
||||
|
|
|
|||
|
|
@ -31,3 +31,45 @@ pub struct AttributeLint<Id> {
|
|||
pub span: Span,
|
||||
pub kind: AttributeLintKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, HashStable_Generic)]
|
||||
pub enum AttributeLintKind {
|
||||
/// Copy of `IllFormedAttributeInput`
|
||||
/// specifically for the `invalid_macro_export_arguments` lint until that is removed,
|
||||
/// see <https://github.com/rust-lang/rust/pull/143857#issuecomment-3079175663>
|
||||
InvalidMacroExportArguments {
|
||||
suggestions: Vec<String>,
|
||||
},
|
||||
UnusedDuplicate {
|
||||
this: Span,
|
||||
other: Span,
|
||||
warning: bool,
|
||||
},
|
||||
IllFormedAttributeInput {
|
||||
suggestions: Vec<String>,
|
||||
},
|
||||
EmptyAttribute {
|
||||
first_span: Span,
|
||||
attr_path: AttrPath,
|
||||
valid_without_list: bool,
|
||||
},
|
||||
InvalidTarget {
|
||||
name: AttrPath,
|
||||
target: Target,
|
||||
applied: Vec<String>,
|
||||
only: &'static str,
|
||||
},
|
||||
InvalidStyle {
|
||||
name: AttrPath,
|
||||
is_used_as_inner: bool,
|
||||
target: Target,
|
||||
target_span: Span,
|
||||
},
|
||||
UnsafeAttrOutsideUnsafe {
|
||||
attribute_name_span: Span,
|
||||
sugg_spans: (Span, Span),
|
||||
},
|
||||
DuplicateDocAlias {
|
||||
first_definition: Span,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::cell::{Cell, RefCell};
|
|||
use std::cmp::max;
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::sso::SsoHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -2535,7 +2535,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
|
|||
let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
|
||||
let attrs = self.fcx.tcx.hir_attrs(hir_id);
|
||||
|
||||
if is_doc_alias_attrs_contain_symbol(attrs.into_iter(), method.name) {
|
||||
if let Some(d) = find_attr!(attrs, AttributeKind::Doc(d) => d)
|
||||
&& d.aliases.contains_key(&method.name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,18 +106,9 @@ passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
|
|||
passes_diagnostic_item_first_defined =
|
||||
the diagnostic item is first defined here
|
||||
|
||||
passes_doc_alias_bad_char =
|
||||
{$char_} character isn't allowed in {$attr_str}
|
||||
|
||||
passes_doc_alias_bad_location =
|
||||
{$attr_str} isn't allowed on {$location}
|
||||
|
||||
passes_doc_alias_duplicated = doc alias is duplicated
|
||||
.label = first defined here
|
||||
|
||||
passes_doc_alias_empty =
|
||||
{$attr_str} attribute cannot have empty value
|
||||
|
||||
passes_doc_alias_malformed =
|
||||
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
|
||||
|
||||
|
|
@ -127,17 +118,6 @@ passes_doc_alias_not_an_alias =
|
|||
passes_doc_alias_not_string_literal =
|
||||
`#[doc(alias("a"))]` expects string literals
|
||||
|
||||
passes_doc_alias_start_end =
|
||||
{$attr_str} cannot start or end with ' '
|
||||
|
||||
passes_doc_attr_expects_no_value =
|
||||
`doc({$attr_name})` does not accept a value
|
||||
.suggestion = use `doc({$attr_name})`
|
||||
|
||||
passes_doc_attr_expects_string =
|
||||
`doc({$attr_name})` expects a string value
|
||||
.suggestion = use `doc({$attr_name} = "...")`
|
||||
|
||||
passes_doc_attr_not_crate_level =
|
||||
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
|
||||
|
||||
|
|
@ -163,15 +143,7 @@ passes_doc_expect_str =
|
|||
passes_doc_fake_variadic_not_valid =
|
||||
`#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity
|
||||
|
||||
passes_doc_inline_conflict =
|
||||
conflicting doc inlining attributes
|
||||
.help = remove one of the conflicting attributes
|
||||
|
||||
passes_doc_inline_conflict_first =
|
||||
this attribute...
|
||||
|
||||
passes_doc_inline_conflict_second =
|
||||
{"."}..conflicts with this attribute
|
||||
|
||||
passes_doc_inline_only_use =
|
||||
this attribute can only be applied to a `use` item
|
||||
|
|
@ -188,10 +160,6 @@ passes_doc_keyword_attribute_empty_mod =
|
|||
passes_doc_keyword_attribute_not_mod =
|
||||
`#[doc({$attr_name} = "...")]` should be used on modules
|
||||
|
||||
passes_doc_keyword_not_keyword =
|
||||
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
|
||||
.help = only existing keywords are allowed in core/std
|
||||
|
||||
passes_doc_keyword_only_impl =
|
||||
`#[doc(keyword = "...")]` should be used on impl blocks
|
||||
|
||||
|
|
|
|||
|
|
@ -141,8 +141,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 {
|
||||
|
|
@ -306,15 +304,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
self.check_diagnostic_on_const(attr.span(), hir_id, target, item)
|
||||
}
|
||||
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
|
||||
[sym::doc, ..] => self.check_doc_attrs(
|
||||
attr,
|
||||
attr.span(),
|
||||
attr_item.style,
|
||||
hir_id,
|
||||
target,
|
||||
&mut specified_inline,
|
||||
&mut doc_aliases,
|
||||
),
|
||||
[sym::no_link, ..] => self.check_no_link(hir_id, attr, span, target),
|
||||
[sym::rustc_no_implicit_autorefs, ..] => {
|
||||
self.check_applied_to_fn_or_method(hir_id, attr.span(), span, target)
|
||||
|
|
@ -786,38 +775,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
self.dcx().emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
|
||||
}
|
||||
|
||||
fn check_doc_alias_value(
|
||||
&self,
|
||||
meta: &MetaItemInner,
|
||||
doc_alias: Symbol,
|
||||
hir_id: HirId,
|
||||
target: Target,
|
||||
is_list: bool,
|
||||
aliases: &mut FxHashMap<String, Span>,
|
||||
) {
|
||||
fn check_doc_alias_value(&self, span: Span, alias: Symbol, hir_id: HirId, target: Target) {
|
||||
let tcx = self.tcx;
|
||||
let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span());
|
||||
let attr_str =
|
||||
&format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
|
||||
if doc_alias == sym::empty {
|
||||
tcx.dcx().emit_err(errors::DocAliasEmpty { span, attr_str });
|
||||
return;
|
||||
}
|
||||
|
||||
let doc_alias_str = doc_alias.as_str();
|
||||
if let Some(c) = doc_alias_str
|
||||
.chars()
|
||||
.find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
|
||||
{
|
||||
tcx.dcx().emit_err(errors::DocAliasBadChar { span, attr_str, char_: c });
|
||||
return;
|
||||
}
|
||||
if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') {
|
||||
tcx.dcx().emit_err(errors::DocAliasStartEnd { span, attr_str });
|
||||
return;
|
||||
}
|
||||
|
||||
let span = meta.span();
|
||||
if let Some(location) = match target {
|
||||
Target::AssocTy => {
|
||||
if let DefKind::Impl { .. } =
|
||||
|
|
@ -874,20 +834,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
| Target::MacroCall
|
||||
| Target::Delegation { .. } => None,
|
||||
} {
|
||||
tcx.dcx().emit_err(errors::DocAliasBadLocation { span, attr_str, location });
|
||||
return;
|
||||
}
|
||||
if self.tcx.hir_opt_name(hir_id) == Some(doc_alias) {
|
||||
tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str });
|
||||
return;
|
||||
}
|
||||
if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) {
|
||||
self.tcx.emit_node_span_lint(
|
||||
UNUSED_ATTRIBUTES,
|
||||
hir_id,
|
||||
span,
|
||||
errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
|
||||
);
|
||||
// FIXME: emit proper error
|
||||
// tcx.dcx().emit_err(errors::DocAliasBadLocation {
|
||||
// span,
|
||||
// errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -927,7 +878,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
|
||||
fn check_doc_keyword_and_attribute(
|
||||
&self,
|
||||
meta: &MetaItemInner,
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
attr_kind: DocFakeItemKind,
|
||||
) {
|
||||
|
|
@ -938,16 +889,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
|
||||
}
|
||||
|
||||
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
|
||||
fn is_builtin_attr(s: Symbol) -> bool {
|
||||
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
|
||||
}
|
||||
|
||||
let value = match meta.value_str() {
|
||||
Some(value) if value != sym::empty => value,
|
||||
_ => return self.doc_attr_str_error(meta, attr_kind.name()),
|
||||
};
|
||||
|
||||
let item_kind = match self.tcx.hir_node(hir_id) {
|
||||
hir::Node::Item(item) => Some(&item.kind),
|
||||
_ => None,
|
||||
|
|
@ -955,21 +896,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
match item_kind {
|
||||
Some(ItemKind::Mod(_, module)) => {
|
||||
if !module.item_ids.is_empty() {
|
||||
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
|
||||
span: meta.span(),
|
||||
attr_name: attr_kind.name(),
|
||||
});
|
||||
self.dcx()
|
||||
.emit_err(errors::DocKeywordEmptyMod { span, attr_name: attr_kind.name() });
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
|
||||
span: meta.span(),
|
||||
attr_name: attr_kind.name(),
|
||||
});
|
||||
self.dcx().emit_err(errors::DocKeywordNotMod { span, attr_name: attr_kind.name() });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match attr_kind {
|
||||
DocFakeItemKind::Keyword => {
|
||||
if !is_doc_keyword(value) {
|
||||
|
|
@ -990,7 +927,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_doc_fake_variadic(&self, meta: &MetaItemInner, hir_id: HirId) {
|
||||
fn check_doc_fake_variadic(&self, span: Span, hir_id: HirId) {
|
||||
let item_kind = match self.tcx.hir_node(hir_id) {
|
||||
hir::Node::Item(item) => Some(&item.kind),
|
||||
_ => None,
|
||||
|
|
@ -1008,18 +945,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
false
|
||||
};
|
||||
if !is_valid {
|
||||
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span: meta.span() });
|
||||
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span });
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span: meta.span() });
|
||||
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) {
|
||||
fn check_doc_search_unbox(&self, span: Span, hir_id: HirId) {
|
||||
let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else {
|
||||
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
|
||||
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
|
||||
return;
|
||||
};
|
||||
match item.kind {
|
||||
|
|
@ -1032,7 +969,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
}) => {}
|
||||
ItemKind::TyAlias(_, generics, _) if generics.params.len() != 0 => {}
|
||||
_ => {
|
||||
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
|
||||
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1046,60 +983,32 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
/// already seen an inlining attribute for this item.
|
||||
/// If so, `specified_inline` holds the value and the span of
|
||||
/// the first `inline`/`no_inline` attribute.
|
||||
fn check_doc_inline(
|
||||
&self,
|
||||
style: AttrStyle,
|
||||
meta: &MetaItemInner,
|
||||
hir_id: HirId,
|
||||
target: Target,
|
||||
specified_inline: &mut Option<(bool, Span)>,
|
||||
) {
|
||||
fn check_doc_inline(&self, span: Span, hir_id: HirId, target: Target) {
|
||||
match target {
|
||||
Target::Use | Target::ExternCrate => {
|
||||
let do_inline = meta.has_name(sym::inline);
|
||||
if let Some((prev_inline, prev_span)) = *specified_inline {
|
||||
if do_inline != prev_inline {
|
||||
let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
|
||||
spans.push_span_label(prev_span, fluent::passes_doc_inline_conflict_first);
|
||||
spans.push_span_label(
|
||||
meta.span(),
|
||||
fluent::passes_doc_inline_conflict_second,
|
||||
);
|
||||
self.dcx().emit_err(errors::DocKeywordConflict { spans });
|
||||
}
|
||||
} else {
|
||||
*specified_inline = Some((do_inline, meta.span()));
|
||||
}
|
||||
}
|
||||
Target::Use | Target::ExternCrate => {}
|
||||
_ => {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
span,
|
||||
errors::DocInlineOnlyUse {
|
||||
attr_span: meta.span(),
|
||||
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
|
||||
attr_span: span,
|
||||
item_span: self.tcx.hir_span(hir_id),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_doc_masked(
|
||||
&self,
|
||||
style: AttrStyle,
|
||||
meta: &MetaItemInner,
|
||||
hir_id: HirId,
|
||||
target: Target,
|
||||
) {
|
||||
fn check_doc_masked(&self, span: Span, hir_id: HirId, target: Target) {
|
||||
if target != Target::ExternCrate {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
span,
|
||||
errors::DocMaskedOnlyExternCrate {
|
||||
attr_span: meta.span(),
|
||||
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
|
||||
attr_span: span,
|
||||
item_span: self.tcx.hir_span(hir_id),
|
||||
},
|
||||
);
|
||||
return;
|
||||
|
|
@ -1109,125 +1018,38 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
span,
|
||||
errors::DocMaskedNotExternCrateSelf {
|
||||
attr_span: meta.span(),
|
||||
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
|
||||
attr_span: span,
|
||||
item_span: self.tcx.hir_span(hir_id),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
|
||||
fn check_attr_not_crate_level(
|
||||
&self,
|
||||
meta: &MetaItemInner,
|
||||
hir_id: HirId,
|
||||
attr_name: &str,
|
||||
) -> bool {
|
||||
fn check_attr_not_crate_level(&self, span: Span, hir_id: HirId, attr_name: &str) -> bool {
|
||||
if CRATE_HIR_ID == hir_id {
|
||||
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name });
|
||||
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span, attr_name });
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Checks that an attribute is used at the crate level. Returns `true` if valid.
|
||||
fn check_attr_crate_level(
|
||||
&self,
|
||||
attr_span: Span,
|
||||
style: AttrStyle,
|
||||
meta: &MetaItemInner,
|
||||
hir_id: HirId,
|
||||
) -> bool {
|
||||
fn check_attr_crate_level(&self, span: Span, hir_id: HirId) -> bool {
|
||||
if hir_id != CRATE_HIR_ID {
|
||||
// insert a bang between `#` and `[...`
|
||||
let bang_span = attr_span.lo() + BytePos(1);
|
||||
let sugg = (style == AttrStyle::Outer
|
||||
&& self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID)
|
||||
.then_some(errors::AttrCrateLevelOnlySugg {
|
||||
attr: attr_span.with_lo(bang_span).with_hi(bang_span),
|
||||
});
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
errors::AttrCrateLevelOnly { sugg },
|
||||
span,
|
||||
errors::AttrCrateLevelOnly {},
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn check_doc_attr_string_value(&self, meta: &MetaItemInner, hir_id: HirId) {
|
||||
if meta.value_str().is_none() {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
errors::DocAttrExpectsString { attr_name: meta.name().unwrap() },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_doc_attr_no_value(&self, meta: &MetaItemInner, hir_id: HirId) {
|
||||
if !meta.is_word() {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
errors::DocAttrExpectsNoValue { attr_name: meta.name().unwrap() },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
|
||||
fn check_test_attr(
|
||||
&self,
|
||||
attr_span: Span,
|
||||
style: AttrStyle,
|
||||
meta: &MetaItemInner,
|
||||
hir_id: HirId,
|
||||
) {
|
||||
if let Some(metas) = meta.meta_item_list() {
|
||||
for i_meta in metas {
|
||||
match (i_meta.name(), i_meta.meta_item()) {
|
||||
(Some(sym::attr), _) => {
|
||||
// Allowed everywhere like `#[doc]`
|
||||
}
|
||||
(Some(sym::no_crate_inject), _) => {
|
||||
self.check_attr_crate_level(attr_span, style, meta, hir_id);
|
||||
}
|
||||
(_, Some(m)) => {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
i_meta.span(),
|
||||
errors::DocTestUnknown {
|
||||
path: rustc_ast_pretty::pprust::path_to_string(&m.path),
|
||||
},
|
||||
);
|
||||
}
|
||||
(_, None) => {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
i_meta.span(),
|
||||
errors::DocTestLiteral,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.tcx.emit_node_span_lint(
|
||||
INVALID_DOC_ATTRIBUTES,
|
||||
hir_id,
|
||||
meta.span(),
|
||||
errors::DocTestTakesList,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that the `#![doc(auto_cfg)]` attribute has the expected input.
|
||||
fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) {
|
||||
match &meta.kind {
|
||||
|
|
@ -1290,173 +1112,118 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
/// of one item. Read the documentation of [`check_doc_inline`] for more information.
|
||||
///
|
||||
/// [`check_doc_inline`]: Self::check_doc_inline
|
||||
fn check_doc_attrs(
|
||||
&self,
|
||||
attr: &Attribute,
|
||||
attr_span: Span,
|
||||
style: AttrStyle,
|
||||
hir_id: HirId,
|
||||
target: Target,
|
||||
specified_inline: &mut Option<(bool, Span)>,
|
||||
aliases: &mut FxHashMap<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: _,
|
||||
cfg_hide,
|
||||
fake_variadic,
|
||||
keyword,
|
||||
masked,
|
||||
// FIXME: currently unchecked
|
||||
notable_trait: _,
|
||||
search_unbox,
|
||||
html_favicon_url,
|
||||
html_logo_url,
|
||||
html_playground_url,
|
||||
html_root_url,
|
||||
html_no_source,
|
||||
issue_tracker_base_url,
|
||||
rust_logo,
|
||||
// allowed anywhere
|
||||
test_attrs: _,
|
||||
no_crate_inject,
|
||||
} = attr;
|
||||
|
||||
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, *alias, hir_id, target);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_, span)) = keyword {
|
||||
if self.check_attr_not_crate_level(*span, hir_id, "keyword") {
|
||||
self.check_doc_keyword(*span, hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: check doc attribute
|
||||
// self.check_doc_keyword(meta, hir_id);
|
||||
// self.check_doc_keyword_and_attribute(
|
||||
// meta,
|
||||
// hir_id,
|
||||
// DocFakeItemKind::Keyword,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// Some(sym::attribute) => {
|
||||
// if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
|
||||
// self.check_doc_keyword_and_attribute(
|
||||
// meta,
|
||||
// hir_id,
|
||||
// DocFakeItemKind::Attribute,
|
||||
// );
|
||||
|
||||
if let Some(span) = fake_variadic {
|
||||
if self.check_attr_not_crate_level(*span, hir_id, "fake_variadic") {
|
||||
self.check_doc_fake_variadic(*span, hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(span) = search_unbox {
|
||||
if self.check_attr_not_crate_level(*span, hir_id, "search_unbox") {
|
||||
self.check_doc_search_unbox(*span, hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_, span)) = inline {
|
||||
self.check_doc_inline(*span, hir_id, target)
|
||||
}
|
||||
|
||||
if let Some(span) = rust_logo {
|
||||
if self.check_attr_crate_level(*span, hir_id)
|
||||
&& !self.tcx.features().rustdoc_internals()
|
||||
{
|
||||
feature_err(
|
||||
&self.tcx.sess,
|
||||
sym::rustdoc_internals,
|
||||
*span,
|
||||
fluent::passes_doc_rust_logo,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(span) = masked {
|
||||
self.check_doc_masked(*span, hir_id, target);
|
||||
}
|
||||
|
||||
if let Some(span) = cfg_hide {
|
||||
self.check_attr_crate_level(*span, hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_has_incoherent_inherent_impls(&self, attr: &Attribute, span: Span, target: Target) {
|
||||
|
|
|
|||
|
|
@ -30,12 +30,6 @@ pub(crate) struct DocAttrExpectsString {
|
|||
pub(crate) attr_name: Symbol,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_doc_attr_expects_no_value)]
|
||||
pub(crate) struct DocAttrExpectsNoValue {
|
||||
pub(crate) attr_name: Symbol,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_autodiff_attr)]
|
||||
pub(crate) struct AutoDiffAttr {
|
||||
|
|
@ -143,31 +137,6 @@ pub(crate) struct DocExpectStr<'a> {
|
|||
pub attr_name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_alias_empty)]
|
||||
pub(crate) struct DocAliasEmpty<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_alias_bad_char)]
|
||||
pub(crate) struct DocAliasBadChar<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
pub char_: char,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_alias_start_end)]
|
||||
pub(crate) struct DocAliasStartEnd<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub attr_str: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_alias_bad_location)]
|
||||
pub(crate) struct DocAliasBadLocation<'a> {
|
||||
|
|
@ -185,13 +154,6 @@ pub(crate) struct DocAliasNotAnAlias<'a> {
|
|||
pub attr_str: &'a str,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_doc_alias_duplicated)]
|
||||
pub(crate) struct DocAliasDuplicated {
|
||||
#[label]
|
||||
pub first_defn: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_alias_not_string_literal)]
|
||||
pub(crate) struct DocAliasNotStringLiteral {
|
||||
|
|
@ -261,14 +223,6 @@ pub(crate) struct DocSearchUnboxInvalid {
|
|||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(passes_doc_inline_conflict)]
|
||||
#[help]
|
||||
pub(crate) struct DocKeywordConflict {
|
||||
#[primary_span]
|
||||
pub spans: MultiSpan,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_doc_inline_only_use)]
|
||||
#[note]
|
||||
|
|
@ -276,7 +230,7 @@ pub(crate) struct DocInlineOnlyUse {
|
|||
#[label]
|
||||
pub attr_span: Span,
|
||||
#[label(passes_not_a_use_item_label)]
|
||||
pub item_span: Option<Span>,
|
||||
pub item_span: Span,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
|
|
@ -286,7 +240,7 @@ pub(crate) struct DocMaskedOnlyExternCrate {
|
|||
#[label]
|
||||
pub attr_span: Span,
|
||||
#[label(passes_not_an_extern_crate_label)]
|
||||
pub item_span: Option<Span>,
|
||||
pub item_span: Span,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
|
|
@ -295,7 +249,7 @@ pub(crate) struct DocMaskedNotExternCrateSelf {
|
|||
#[label]
|
||||
pub attr_span: Span,
|
||||
#[label(passes_extern_crate_self_label)]
|
||||
pub item_span: Option<Span>,
|
||||
pub item_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
|
|
@ -1353,17 +1307,7 @@ pub(crate) struct IneffectiveUnstableImpl;
|
|||
#[derive(LintDiagnostic)]
|
||||
#[diag(passes_attr_crate_level)]
|
||||
#[note]
|
||||
pub(crate) struct AttrCrateLevelOnly {
|
||||
#[subdiagnostic]
|
||||
pub sugg: Option<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,7 @@ use rustc_ast::{
|
|||
Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
|
||||
};
|
||||
use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
|
||||
use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
||||
use rustc_errors::codes::*;
|
||||
use rustc_errors::{
|
||||
|
|
@ -903,7 +903,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
|
|||
// confused by them.
|
||||
continue;
|
||||
}
|
||||
if is_doc_alias_attrs_contain_symbol(r.tcx.get_attrs(did, sym::doc), item_name) {
|
||||
if let Some(d) = find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d)
|
||||
&& d.aliases.contains_key(&item_name)
|
||||
{
|
||||
return Some(did);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue