about summary refs log tree commit diff
diff options
context:
space:
mode:
authorUrgau <urgau@numericable.fr>2025-05-01 17:24:00 +0200
committerUrgau <urgau@numericable.fr>2025-05-22 19:12:13 +0200
commit80c6a0885079038caa0bc90670afbf5ead806c17 (patch)
treee6f8b221de6b4ac0673c3bd453c91f468beabd8e
parenteec894d3f9bf708d8c4c0679bc57ed899d6c37ab (diff)
downloadrust-80c6a0885079038caa0bc90670afbf5ead806c17.tar.gz
rust-80c6a0885079038caa0bc90670afbf5ead806c17.zip
Allow `#![doc(test(attr(..)))]` at module level too
-rw-r--r--compiler/rustc_passes/messages.ftl5
-rw-r--r--compiler/rustc_passes/src/check_attr.rs51
-rw-r--r--compiler/rustc_passes/src/errors.rs12
-rw-r--r--src/doc/rustdoc/src/write-documentation/the-doc-attribute.md19
-rw-r--r--tests/rustdoc-ui/lints/invalid-doc-attr.rs4
-rw-r--r--tests/rustdoc-ui/lints/invalid-doc-attr.stderr32
-rw-r--r--tests/ui/rustdoc/doc-test-attr-pass.rs4
7 files changed, 107 insertions, 20 deletions
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 6d815e510ea..2f264ed4fc0 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -46,6 +46,11 @@ passes_attr_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_attr_mod_level =
+    this attribute can only be applied at module level
+    .suggestion = to apply to the crate, use an inner attribute at the crate level
+    .note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-module-level> for more information
+
 passes_attr_only_in_functions =
     `{$attr}` attribute can only be used on functions
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 5c0d0cf4796..376b424a1af 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1252,7 +1252,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             let bang_span = attr.span().lo() + BytePos(1);
             let sugg = (attr.style() == AttrStyle::Outer
                 && self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID)
-                .then_some(errors::AttrCrateLevelOnlySugg {
+                .then_some(errors::AttrCrateLevelSugg {
                     attr: attr.span().with_lo(bang_span).with_hi(bang_span),
                 });
             self.tcx.emit_node_span_lint(
@@ -1266,13 +1266,50 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         true
     }
 
-    /// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if
-    /// valid.
-    fn check_test_attr(&self, meta: &MetaItemInner, hir_id: HirId) {
+    /// Checks that an attribute is used at module level. Returns `true` if valid.
+    fn check_attr_mod_level(
+        &self,
+        attr: &Attribute,
+        meta: &MetaItemInner,
+        hir_id: HirId,
+        target: Target,
+    ) -> bool {
+        if target != Target::Mod {
+            // insert a bang between `#` and `[...`
+            let bang_span = attr.span().lo() + BytePos(1);
+            let sugg = (attr.style() == AttrStyle::Outer
+                && self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID)
+                .then_some(errors::AttrCrateLevelSugg {
+                    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::AttrModLevelOnly { sugg },
+            );
+            return false;
+        }
+        true
+    }
+
+    /// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
+    fn check_test_attr(
+        &self,
+        attr: &Attribute,
+        meta: &MetaItemInner,
+        hir_id: HirId,
+        target: Target,
+    ) {
         if let Some(metas) = meta.meta_item_list() {
             for i_meta in metas {
                 match (i_meta.name(), i_meta.meta_item()) {
-                    (Some(sym::attr | sym::no_crate_inject), _) => {}
+                    (Some(sym::attr), _) => {
+                        self.check_attr_mod_level(attr, meta, hir_id, target);
+                    }
+                    (Some(sym::no_crate_inject), _) => {
+                        self.check_attr_crate_level(attr, meta, hir_id);
+                    }
                     (_, Some(m)) => {
                         self.tcx.emit_node_span_lint(
                             INVALID_DOC_ATTRIBUTES,
@@ -1359,9 +1396,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         }
 
                         Some(sym::test) => {
-                            if self.check_attr_crate_level(attr, meta, hir_id) {
-                                self.check_test_attr(meta, hir_id);
-                            }
+                            self.check_test_attr(attr, meta, hir_id, target);
                         }
 
                         Some(
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 00682a9c7a7..38b0db37e12 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1890,12 +1890,20 @@ pub(crate) struct UnusedVarTryIgnoreSugg {
 #[note]
 pub(crate) struct AttrCrateLevelOnly {
     #[subdiagnostic]
-    pub sugg: Option<AttrCrateLevelOnlySugg>,
+    pub sugg: Option<AttrCrateLevelSugg>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_attr_mod_level)]
+#[note]
+pub(crate) struct AttrModLevelOnly {
+    #[subdiagnostic]
+    pub sugg: Option<AttrCrateLevelSugg>,
 }
 
 #[derive(Subdiagnostic)]
 #[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")]
-pub(crate) struct AttrCrateLevelOnlySugg {
+pub(crate) struct AttrCrateLevelSugg {
     #[primary_span]
     pub attr: Span,
 }
diff --git a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md
index 45146993371..afbcf4000c5 100644
--- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md
+++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md
@@ -141,6 +141,11 @@ But if you include this:
 
 it will not.
 
+## At the module level
+
+These forms of the `#[doc]` attribute are used on individual modules, to control how
+they are documented.
+
 ### `test(attr(...))`
 
 This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For
@@ -148,6 +153,20 @@ example, if you want your doctests to fail if they have dead code, you could add
 
 ```rust,no_run
 #![doc(test(attr(deny(dead_code))))]
+
+mod my_mod {
+    #![doc(test(attr(allow(dead_code))))] // but allow `dead_code` for this module
+}
+```
+
+`test(attr(..))` attributes are appended to the parent module's, they do not replace the current
+list of attributes. In the previous example, both attributes would be present:
+
+```rust,no_run
+// For every doctest in `my_mod`
+
+#![deny(dead_code)] // from the crate-root
+#![allow(dead_code)] // from `my_mod`
 ```
 
 ## At the item level
diff --git a/tests/rustdoc-ui/lints/invalid-doc-attr.rs b/tests/rustdoc-ui/lints/invalid-doc-attr.rs
index e1cc08ca242..cd5ae44b126 100644
--- a/tests/rustdoc-ui/lints/invalid-doc-attr.rs
+++ b/tests/rustdoc-ui/lints/invalid-doc-attr.rs
@@ -4,6 +4,10 @@
 #![doc(masked)]
 //~^ ERROR this attribute can only be applied to an `extern crate` item
 
+#[doc(test(attr(allow(warnings))))]
+//~^ ERROR can only be applied at module level
+//~| HELP to apply to the crate, use an inner attribute
+//~| SUGGESTION !
 #[doc(test(no_crate_inject))]
 //~^ ERROR can only be applied at the crate level
 //~| HELP to apply to the crate, use an inner attribute
diff --git a/tests/rustdoc-ui/lints/invalid-doc-attr.stderr b/tests/rustdoc-ui/lints/invalid-doc-attr.stderr
index 7621999a8ca..0fd55ff94d8 100644
--- a/tests/rustdoc-ui/lints/invalid-doc-attr.stderr
+++ b/tests/rustdoc-ui/lints/invalid-doc-attr.stderr
@@ -1,18 +1,30 @@
-error: this attribute can only be applied at the crate level
+error: this attribute can only be applied at module level
   --> $DIR/invalid-doc-attr.rs:7:7
    |
+LL | #[doc(test(attr(allow(warnings))))]
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-module-level> for more information
+   = note: `#[deny(invalid_doc_attributes)]` on by default
+help: to apply to the crate, use an inner attribute at the crate level
+   |
+LL | #![doc(test(attr(allow(warnings))))]
+   |  +
+
+error: this attribute can only be applied at the crate level
+  --> $DIR/invalid-doc-attr.rs:11:7
+   |
 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:15:7
    |
 LL | #[doc(inline)]
    |       ^^^^^^ only applicable on `use` items
@@ -23,7 +35,7 @@ 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:20:12
    |
 LL |     #![doc(test(no_crate_inject))]
    |            ^^^^^^^^^^^^^^^^^^^^^
@@ -31,7 +43,7 @@ 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:30:7
    |
 LL | #[doc(inline)]
    |       ^^^^^^ this attribute...
@@ -41,7 +53,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:36:7
    |
 LL | #[doc(masked)]
    |       ^^^^^^ only applicable on `extern crate` items
@@ -52,7 +64,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:40:7
    |
 LL | #[doc(masked)]
    |       ^^^^^^ not applicable on `extern crate self` items
@@ -69,7 +81,7 @@ LL | #![doc(masked)]
    = 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:23:11
    |
 LL |     #[doc(test(no_crate_inject))]
    |           ^^^^^^^^^^^^^^^^^^^^^
@@ -77,7 +89,7 @@ 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:25:11
    |
 LL |     #[doc(inline)]
    |           ^^^^^^ only applicable on `use` items
@@ -87,5 +99,5 @@ LL |     pub fn baz() {}
    |
    = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
 
-error: aborting due to 9 previous errors
+error: aborting due to 10 previous errors
 
diff --git a/tests/ui/rustdoc/doc-test-attr-pass.rs b/tests/ui/rustdoc/doc-test-attr-pass.rs
index f0120b7c2d0..40ffd5b2572 100644
--- a/tests/ui/rustdoc/doc-test-attr-pass.rs
+++ b/tests/ui/rustdoc/doc-test-attr-pass.rs
@@ -6,4 +6,8 @@
 #![doc(test(attr(deny(warnings))))]
 #![doc(test())]
 
+mod test {
+    #![doc(test(attr(allow(warnings))))]
+}
+
 pub fn foo() {}