Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/rustc_attr_parsing/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ attr_parsing_doc_alias_malformed =
attr_parsing_doc_alias_start_end =
{$attr_str} cannot start or end with ' '

attr_parsing_doc_attr_not_crate_level =
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute

attr_parsing_doc_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std
Expand Down
119 changes: 96 additions & 23 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_feature::template;
use rustc_hir::Target;
use rustc_hir::attrs::{
AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
};
Expand All @@ -12,8 +13,8 @@ use super::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute,
DocKeywordNotKeyword,
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
DocAttributeNotAttribute, DocKeywordNotKeyword,
};

fn check_keyword<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> bool {
Expand Down Expand Up @@ -43,16 +44,39 @@ fn check_attribute<S: Stage>(
false
}

fn parse_keyword_and_attribute<S, F>(
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
fn check_attr_not_crate_level<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
span: Span,
attr_name: Symbol,
) -> bool {
if cx.shared.target.is_some_and(|target| target == Target::Crate) {
cx.emit_err(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<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, span: Span) -> bool {
if cx.shared.target.is_some_and(|target| target != Target::Crate) {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::AttrCrateLevelOnly,
span,
);
return false;
}
true
}

fn parse_keyword_and_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
attr_value: &mut Option<(Symbol, Span)>,
callback: F,
) where
S: Stage,
F: FnOnce(&mut AcceptContext<'_, '_, S>, Symbol, Span) -> bool,
{
attr_name: Symbol,
) {
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
Expand All @@ -63,16 +87,26 @@ fn parse_keyword_and_attribute<S, F>(
return;
};

if !callback(cx, value, nv.value_span) {
let ret = if attr_name == sym::keyword {
check_keyword(cx, value, nv.value_span)
} else {
check_attribute(cx, value, nv.value_span)
};
if !ret {
return;
}

let span = path.span();
if attr_value.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
cx.duplicate_key(span, path.word_sym().unwrap());
return;
}

*attr_value = Some((value, path.span()));
if !check_attr_not_crate_level(cx, span, attr_name) {
return;
}

*attr_value = Some((value, span));
}

#[derive(Default, Debug)]
Expand Down Expand Up @@ -102,6 +136,10 @@ impl DocParser {
return;
}

if !check_attr_crate_level(cx, path.span()) {
return;
}

self.attribute.no_crate_inject = Some(path.span())
}
Some(sym::attr) => {
Expand Down Expand Up @@ -155,6 +193,9 @@ impl DocParser {
cx.emit_err(DocAliasStartEnd { span, attr_str });
return;
}
if !check_attr_not_crate_level(cx, span, sym::alias) {
return;
}

if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
cx.emit_lint(
Expand Down Expand Up @@ -366,7 +407,33 @@ impl DocParser {
self.attribute.$ident = Some(path.span());
}};
}
macro_rules! string_arg {
macro_rules! no_args_and_not_crate_level {
($ident: ident) => {{
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
let span = path.span();
if !check_attr_not_crate_level(cx, span, sym::$ident) {
return;
}
self.attribute.$ident = Some(span);
}};
}
macro_rules! no_args_and_crate_level {
($ident: ident) => {{
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
let span = path.span();
if !check_attr_crate_level(cx, span) {
return;
}
self.attribute.$ident = Some(span);
}};
}
macro_rules! string_arg_and_crate_level {
($ident: ident) => {{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
Expand All @@ -378,6 +445,10 @@ impl DocParser {
return;
};

if !check_attr_crate_level(cx, path.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
Expand All @@ -394,12 +465,14 @@ impl DocParser {
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::html_favicon_url) => string_arg_and_crate_level!(html_favicon_url),
Some(sym::html_logo_url) => string_arg_and_crate_level!(html_logo_url),
Some(sym::html_no_source) => no_args_and_crate_level!(html_no_source),
Some(sym::html_playground_url) => string_arg_and_crate_level!(html_playground_url),
Some(sym::html_root_url) => string_arg_and_crate_level!(html_root_url),
Some(sym::issue_tracker_base_url) => {
string_arg_and_crate_level!(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),
Expand All @@ -410,18 +483,18 @@ impl DocParser {
path,
args,
&mut self.attribute.keyword,
check_keyword,
sym::keyword,
),
Some(sym::attribute) => parse_keyword_and_attribute(
cx,
path,
args,
&mut self.attribute.attribute,
check_attribute,
sym::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::fake_variadic) => no_args_and_not_crate_level!(fake_variadic),
Some(sym::search_unbox) => no_args_and_not_crate_level!(search_unbox),
Some(sym::rust_logo) => no_args_and_crate_level!(rust_logo),
Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
Some(sym::test) => {
let Some(list) = args.list() else {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ pub struct SharedContext<'p, 'sess, S: Stage> {
pub(crate) target_span: Span,
/// The id ([`NodeId`] if `S` is `Early`, [`HirId`] if `S` is `Late`) of the syntactical component this attribute was applied to
pub(crate) target_id: S::Id,
pub(crate) target: Option<rustc_hir::Target>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little weird to me that target can be None, but at the same time I see getting parse_single_args to take a Target is just really annoying...
I think this is acceptable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure how to deal with that. I have some ideas, gonna give it a try later on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it more but I really don't see a nice way of fixing this, but if you do feel free to still take a look

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a hunch, I'll see how far it leads me.


pub(crate) emit_lint: &'p mut dyn FnMut(AttributeLint<S::Id>),
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl<'sess> AttributeParser<'sess, Early> {
cx: &mut parser,
target_span,
target_id: target_node_id,
target: None,
emit_lint: &mut emit_lint,
},
attr_span,
Expand Down Expand Up @@ -378,6 +379,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
cx: self,
target_span,
target_id,
target: Some(target),
emit_lint: &mut emit_lint,
},
attr_span,
Expand Down Expand Up @@ -429,6 +431,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
cx: self,
target_span,
target_id,
target: Some(target),
emit_lint: &mut emit_lint,
},
all_attrs: &attr_paths,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ pub(crate) struct DocAliasStartEnd<'a> {
pub attr_str: &'a str,
}

#[derive(Diagnostic)]
#[diag(attr_parsing_doc_attr_not_crate_level)]
pub(crate) struct DocAttrNotCrateLevel {
#[primary_span]
pub span: Span,
pub attr_name: Symbol,
}

#[derive(Diagnostic)]
#[diag(attr_parsing_doc_keyword_not_keyword)]
#[help]
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ lint_atomic_ordering_load = atomic loads cannot have `Release` or `AcqRel` order
lint_atomic_ordering_store = atomic stores cannot have `Acquire` or `AcqRel` ordering
.help = consider using ordering modes `Release`, `SeqCst` or `Relaxed`

lint_attr_crate_level =
this attribute can only be applied at the crate level
.suggestion = to apply to the crate, use an inner attribute
.note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

lint_bad_attribute_argument = bad attribute argument

lint_bad_opt_access = {$msg}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_lint/src/early/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,5 +417,7 @@ pub fn decorate_attribute_lint(
}

&AttributeLintKind::DocTestLiteral => lints::DocTestLiteral.decorate_lint(diag),

&AttributeLintKind::AttrCrateLevelOnly => lints::AttrCrateLevelOnly.decorate_lint(diag),
}
}
5 changes: 5 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3312,3 +3312,8 @@ pub(crate) struct DocTestUnknown {
#[derive(LintDiagnostic)]
#[diag(lint_doc_test_literal)]
pub(crate) struct DocTestLiteral;

#[derive(LintDiagnostic)]
#[diag(lint_attr_crate_level)]
#[note]
pub(crate) struct AttrCrateLevelOnly;
1 change: 1 addition & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ pub enum AttributeLintKind {
name: Symbol,
},
DocTestLiteral,
AttrCrateLevelOnly,
}

pub type RegisteredTools = FxIndexSet<Ident>;
Expand Down
8 changes: 0 additions & 8 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ passes_attr_application_struct_union =
attribute should be applied to a struct or union
.label = not a struct or union

passes_attr_crate_level =
this attribute can only be applied at the crate level
.suggestion = to apply to the crate, use an inner attribute
.note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

passes_autodiff_attr =
`#[autodiff]` should be applied to a function
.label = not a function
Expand Down Expand Up @@ -112,9 +107,6 @@ passes_doc_alias_bad_location =
passes_doc_alias_not_an_alias =
`#[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_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

Expand Down
Loading
Loading