Skip to content
Draft
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
35 changes: 15 additions & 20 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use super::prelude::{ALL_TARGETS, AllowedTargets};
use super::{AcceptMapping, AttributeParser, template};
use crate::context::{AcceptContext, FinalizeContext};
use crate::diagnostics::{
AttrCrateLevelOnly, DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed,
DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow,
DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd,
DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow,
DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues,
DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,
DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral,
Expand All @@ -26,6 +26,7 @@ use crate::diagnostics::{
use crate::parser::{
ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
};
use crate::target_checking::Policy::Allow;

fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
Expand Down Expand Up @@ -63,15 +64,6 @@ fn check_attr_not_crate_level(
true
}

/// Checks that an attribute is used at the crate level. Returns `true` if valid.
fn check_attr_crate_level(cx: &mut AcceptContext<'_, '_>, span: Span) -> bool {
if cx.shared.target != Target::Crate {
cx.emit_lint(INVALID_DOC_ATTRIBUTES, AttrCrateLevelOnly, span);
return false;
}
true
}

// FIXME: To be removed once merged and replace with `cx.expected_name_value(span, _name)`.
fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {
cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);
Expand Down Expand Up @@ -163,9 +155,10 @@ impl DocParser {
return;
}

if !check_attr_crate_level(cx, path.span()) {
return;
}
cx.check_target(
sym::no_crate_inject.as_str(),
&AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate)]),
);

self.attribute.no_crate_inject = Some(path.span())
}
Expand Down Expand Up @@ -530,9 +523,10 @@ impl DocParser {
return;
}
let span = path.span();
if !check_attr_crate_level(cx, span) {
return;
}
cx.check_target(
concat!("(", stringify!($ident), ")"),
&AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate)]),
);
self.attribute.$ident = Some(span);
}};
}
Expand All @@ -548,9 +542,10 @@ impl DocParser {
return;
};

if !check_attr_crate_level(cx, path.span()) {
return;
}
cx.check_target(
s.as_str(),
&AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate)]),
);

// FIXME: It's errorring when the attribute is passed multiple times on the command
// line.
Expand Down
12 changes: 11 additions & 1 deletion compiler/rustc_attr_parsing/src/target_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_attr_ir::{AttrItem, Attribute, AttributeKind};
use rustc_errors::{DiagArgValue, MultiSpan, StashKey};
use rustc_feature::Features;
use rustc_lint_defs::builtin::{
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES, USELESS_DEPRECATED,
INVALID_DOC_ATTRIBUTES, MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES, USELESS_DEPRECATED,
};
use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Span, Symbol, sym};

Expand Down Expand Up @@ -236,6 +236,16 @@ impl<'sess> AttributeParser<'sess> {
})
.unwrap_or_default();

if name == "doc" {
let diag = crate::diagnostics::AttrCrateLevelOnly;
if warn {
cx.emit_lint(INVALID_DOC_ATTRIBUTES, diag, attr_span);
} else {
cx.emit_err(diag);
}
return;
}

let diag = crate::diagnostics::InvalidAttrStyle {
name,
is_used_as_inner,
Expand Down
22 changes: 22 additions & 0 deletions tests/ui/attributes/doc-crate-level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//@check-pass
#![feature(rustdoc_internals)]

#[doc(rust_logo)]
//~^ WARN this attribute can only be applied at the crate level
#[doc(html_favicon_url = "example.org")]
//~^ WARN this attribute can only be applied at the crate level
#[doc(html_logo_url = "example.org")]
//~^ WARN this attribute can only be applied at the crate level
#[doc(html_playground_url = "example.org")]
//~^ WARN this attribute can only be applied at the crate level
#[doc(issue_tracker_base_url = "example.org")]
//~^ WARN this attribute can only be applied at the crate level
#[doc(html_root_url = "example.org")]
//~^ WARN this attribute can only be applied at the crate level
#[doc(html_no_source)]
//~^ WARN this attribute can only be applied at the crate level
#[doc(test(no_crate_inject))]
//~^ WARN this attribute can only be applied at the crate level
fn function() {}

fn main() {}
67 changes: 67 additions & 0 deletions tests/ui/attributes/doc-crate-level.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:4:1
|
LL | #[doc(rust_logo)]
| ^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
= note: `#[warn(invalid_doc_attributes)]` on by default

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:6:1
|
LL | #[doc(html_favicon_url = "example.org")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:8:1
|
LL | #[doc(html_logo_url = "example.org")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:10:1
|
LL | #[doc(html_playground_url = "example.org")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:12:1
|
LL | #[doc(issue_tracker_base_url = "example.org")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:14:1
|
LL | #[doc(html_root_url = "example.org")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:16:1
|
LL | #[doc(html_no_source)]
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information

warning: this attribute can only be applied at the crate level
--> $DIR/doc-crate-level.rs:18:1
|
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

warning: 8 warnings emitted

2 changes: 0 additions & 2 deletions tests/ui/feature-gates/doc-rust-logo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@
//~^ ERROR this subset of the `doc` attribute is meant for internal use only
//! This is not an official rust crate

#[doc(rust_logo)]
//~^ WARN this attribute can only be applied at the crate level

@JonathanBrouwer JonathanBrouwer Sep 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a reason you removed this from the test?
This is here to show that this doc attribute, incorrectly, is not feature gated

View changes since the review

@evavh evavh Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

With my new test this specific case was duplicated, I figured it fit better with the new one, so I removed it here. I left the specific test for non-feature gated #[doc(rust_logo)] untouched.

fn main() {}
11 changes: 1 addition & 10 deletions tests/ui/feature-gates/doc-rust-logo.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ LL | #![doc(rust_logo)]
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= note: the `#[doc(rust_logo)]` attribute is used for Rust branding

warning: this attribute can only be applied at the crate level
--> $DIR/doc-rust-logo.rs:5:7
|
LL | #[doc(rust_logo)]
| ^^^^^^^^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
= note: `#[warn(invalid_doc_attributes)]` on by default

error: aborting due to 1 previous error; 1 warning emitted
error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0658`.
Loading