From 22e172d54e3796787475f8a610170bf7d5722230 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 1 May 2025 21:56:30 +0200 Subject: [PATCH 1/6] Fix future-incompatible lint group test --- tests/ui/future-incompatible-lint-group.rs | 10 ++--- .../ui/future-incompatible-lint-group.stderr | 40 ++++++++++++++----- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/tests/ui/future-incompatible-lint-group.rs b/tests/ui/future-incompatible-lint-group.rs index c84538318f70f..ed2c47bb60907 100644 --- a/tests/ui/future-incompatible-lint-group.rs +++ b/tests/ui/future-incompatible-lint-group.rs @@ -2,16 +2,14 @@ // lints for changes that are not tied to an edition #![deny(future_incompatible)] +// Error since this is a `future_incompatible` lint +macro_rules! m { ($i) => {} } //~ ERROR missing fragment specifier + //~| WARN this was previously accepted + trait Tr { // Warn only since this is not a `future_incompatible` lint fn f(u8) {} //~ WARN anonymous parameters are deprecated //~| WARN this is accepted in the current edition } -pub mod submodule { - // Error since this is a `future_incompatible` lint - #![doc(test(some_test))] - //~^ ERROR this attribute can only be applied at the crate level -} - fn main() {} diff --git a/tests/ui/future-incompatible-lint-group.stderr b/tests/ui/future-incompatible-lint-group.stderr index 4e6c434fa29b2..4c867e0aab3cb 100644 --- a/tests/ui/future-incompatible-lint-group.stderr +++ b/tests/ui/future-incompatible-lint-group.stderr @@ -1,5 +1,20 @@ +error: missing fragment specifier + --> $DIR/future-incompatible-lint-group.rs:6:19 + | +LL | macro_rules! m { ($i) => {} } + | ^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #40107 +note: the lint level is defined here + --> $DIR/future-incompatible-lint-group.rs:3:9 + | +LL | #![deny(future_incompatible)] + | ^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(missing_fragment_specifier)]` implied by `#[deny(future_incompatible)]` + warning: anonymous parameters are deprecated and will be removed in the next edition - --> $DIR/future-incompatible-lint-group.rs:7:10 + --> $DIR/future-incompatible-lint-group.rs:11:10 | LL | fn f(u8) {} | ^^ help: try naming the parameter or explicitly ignoring it: `_: u8` @@ -8,14 +23,21 @@ LL | fn f(u8) {} = note: for more information, see issue #41686 = note: `#[warn(anonymous_parameters)]` on by default -error: this attribute can only be applied at the crate level - --> $DIR/future-incompatible-lint-group.rs:13:12 +error: aborting due to 1 previous error; 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +error: missing fragment specifier + --> $DIR/future-incompatible-lint-group.rs:6:19 | -LL | #![doc(test(some_test))] - | ^^^^^^^^^^^^^^^ +LL | macro_rules! m { ($i) => {} } + | ^^ | - = note: read for more information - = note: `#[deny(invalid_doc_attributes)]` on by default - -error: aborting due to 1 previous error; 1 warning emitted + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #40107 +note: the lint level is defined here + --> $DIR/future-incompatible-lint-group.rs:3:9 + | +LL | #![deny(future_incompatible)] + | ^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(missing_fragment_specifier)]` implied by `#[deny(future_incompatible)]` From 9759004d499190dd1099bec9ab4fa064336b7b08 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 1 May 2025 17:24:00 +0200 Subject: [PATCH 2/6] Allow `#![doc(test(attr(..)))]` at module level too --- compiler/rustc_passes/messages.ftl | 5 ++ compiler/rustc_passes/src/check_attr.rs | 51 ++++++++++++++++--- compiler/rustc_passes/src/errors.rs | 12 ++++- .../write-documentation/the-doc-attribute.md | 19 +++++++ tests/rustdoc-ui/lints/invalid-doc-attr.rs | 4 ++ .../rustdoc-ui/lints/invalid-doc-attr.stderr | 32 ++++++++---- tests/ui/rustdoc/doc-test-attr-pass.rs | 4 ++ 7 files changed, 107 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 6d815e510ea20..2f264ed4fc023 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 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 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 c68f8df49fc70..f1f309d67e2a2 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 00682a9c7a794..38b0db37e12b4 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, + pub sugg: Option, +} + +#[derive(LintDiagnostic)] +#[diag(passes_attr_mod_level)] +#[note] +pub(crate) struct AttrModLevelOnly { + #[subdiagnostic] + pub sugg: Option, } #[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 45146993371f9..afbcf4000c538 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 e1cc08ca24272..cd5ae44b1266d 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 7621999a8ca52..0fd55ff94d872 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 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 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 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 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 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 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 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 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 f0120b7c2d0b4..40ffd5b25723a 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() {} From 8768fc72f03d1729bf186c05dcabf8125cc09195 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 1 May 2025 18:15:04 +0200 Subject: [PATCH 3/6] Collect and use `#![doc(test(attr(..)))]` at module level too --- src/librustdoc/doctest.rs | 18 ++--- src/librustdoc/doctest/extracted.rs | 4 +- src/librustdoc/doctest/make.rs | 16 ++++- src/librustdoc/doctest/markdown.rs | 10 ++- src/librustdoc/doctest/runner.rs | 11 +++- src/librustdoc/doctest/rust.rs | 40 ++++++++++- src/librustdoc/doctest/tests.rs | 66 +++++++++++-------- src/librustdoc/html/markdown.rs | 4 +- .../rustdoc-ui/doctest/dead-code-module-2.rs | 27 ++++++++ .../doctest/dead-code-module-2.stdout | 30 +++++++++ tests/rustdoc-ui/doctest/dead-code-module.rs | 18 +++++ .../doctest/dead-code-module.stdout | 29 ++++++++ .../doctest/doc-test-attr-pass-module.rs | 11 ++++ 13 files changed, 231 insertions(+), 53 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/dead-code-module-2.rs create mode 100644 tests/rustdoc-ui/doctest/dead-code-module-2.stdout create mode 100644 tests/rustdoc-ui/doctest/dead-code-module.rs create mode 100644 tests/rustdoc-ui/doctest/dead-code-module.stdout create mode 100644 tests/rustdoc-ui/doctest/doc-test-attr-pass-module.rs diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 0cdf2f92a8917..571632f922bd4 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -45,8 +45,6 @@ pub(crate) struct GlobalTestOptions { /// Whether inserting extra indent spaces in code block, /// default is `false`, only `true` for generating code link of Rust playground pub(crate) insert_indent_space: bool, - /// Additional crate-level attributes to add to doctests. - pub(crate) attrs: Vec, /// Path to file containing arguments for the invocation of rustc. pub(crate) args_file: PathBuf, } @@ -413,12 +411,9 @@ fn scrape_test_config( attrs: &[hir::Attribute], args_file: PathBuf, ) -> GlobalTestOptions { - use rustc_ast_pretty::pprust; - let mut opts = GlobalTestOptions { crate_name, no_crate_inject: false, - attrs: Vec::new(), insert_indent_space: false, args_file, }; @@ -435,13 +430,7 @@ fn scrape_test_config( if attr.has_name(sym::no_crate_inject) { opts.no_crate_inject = true; } - if attr.has_name(sym::attr) - && let Some(l) = attr.meta_item_list() - { - for item in l { - opts.attrs.push(pprust::meta_list_item_to_string(item)); - } - } + // NOTE: `test(attr(..))` is handled when discovering the individual tests } opts @@ -889,6 +878,7 @@ pub(crate) struct ScrapedDocTest { langstr: LangString, text: String, name: String, + global_crate_attrs: Vec, } impl ScrapedDocTest { @@ -898,6 +888,7 @@ impl ScrapedDocTest { logical_path: Vec, langstr: LangString, text: String, + global_crate_attrs: Vec, ) -> Self { let mut item_path = logical_path.join("::"); item_path.retain(|c| c != ' '); @@ -907,7 +898,7 @@ impl ScrapedDocTest { let name = format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly()); - Self { filename, line, langstr, text, name } + Self { filename, line, langstr, text, name, global_crate_attrs } } fn edition(&self, opts: &RustdocOptions) -> Edition { self.langstr.edition.unwrap_or(opts.edition) @@ -991,6 +982,7 @@ impl CreateRunnableDocTests { &scraped_test.text, Some(&self.opts.crate_name), edition, + scraped_test.global_crate_attrs.clone(), self.can_merge_doctests, Some(test_id), Some(&scraped_test.langstr), diff --git a/src/librustdoc/doctest/extracted.rs b/src/librustdoc/doctest/extracted.rs index ce362eabfc4c9..22b0fa9d6876e 100644 --- a/src/librustdoc/doctest/extracted.rs +++ b/src/librustdoc/doctest/extracted.rs @@ -35,12 +35,14 @@ impl ExtractedDocTests { ) { let edition = scraped_test.edition(options); - let ScrapedDocTest { filename, line, langstr, text, name } = scraped_test; + let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs } = + scraped_test; let doctest = DocTestBuilder::new( &text, Some(&opts.crate_name), edition, + global_crate_attrs, false, None, Some(&langstr), diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index d4fbfb12582e7..fbad7f2969977 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -41,6 +41,7 @@ pub(crate) struct DocTestBuilder { pub(crate) supports_color: bool, pub(crate) already_has_extern_crate: bool, pub(crate) has_main_fn: bool, + pub(crate) global_crate_attrs: Vec, pub(crate) crate_attrs: String, /// If this is a merged doctest, it will be put into `everything_else`, otherwise it will /// put into `crate_attrs`. @@ -57,6 +58,7 @@ impl DocTestBuilder { source: &str, crate_name: Option<&str>, edition: Edition, + global_crate_attrs: Vec, can_merge_doctests: bool, // If `test_id` is `None`, it means we're generating code for a code example "run" link. test_id: Option, @@ -88,6 +90,7 @@ impl DocTestBuilder { // If the AST returned an error, we don't want this doctest to be merged with the // others. return Self::invalid( + Vec::new(), String::new(), String::new(), String::new(), @@ -104,12 +107,16 @@ impl DocTestBuilder { let can_be_merged = can_merge_doctests && !has_global_allocator && crate_attrs.is_empty() + // FIXME: We can probably merge tests which have the same global crate attrs, + // like we already do for the edition + && global_crate_attrs.is_empty() // If this is a merged doctest and a defined macro uses `$crate`, then the path will // not work, so better not put it into merged doctests. && !(has_macro_def && everything_else.contains("$crate")); Self { supports_color, has_main_fn, + global_crate_attrs, crate_attrs, maybe_crate_attrs, crates, @@ -122,6 +129,7 @@ impl DocTestBuilder { } fn invalid( + global_crate_attrs: Vec, crate_attrs: String, maybe_crate_attrs: String, crates: String, @@ -131,6 +139,7 @@ impl DocTestBuilder { Self { supports_color: false, has_main_fn: false, + global_crate_attrs, crate_attrs, maybe_crate_attrs, crates, @@ -160,7 +169,8 @@ impl DocTestBuilder { let mut line_offset = 0; let mut prog = String::new(); let everything_else = self.everything_else.trim(); - if opts.attrs.is_empty() { + + if self.global_crate_attrs.is_empty() { // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some // lints that are commonly triggered in doctests. The crate-level test attributes are // commonly used to make tests fail in case they trigger warnings, so having this there in @@ -169,8 +179,8 @@ impl DocTestBuilder { line_offset += 1; } - // Next, any attributes that came from the crate root via #![doc(test(attr(...)))]. - for attr in &opts.attrs { + // Next, any attributes that came from #![doc(test(attr(...)))]. + for attr in &self.global_crate_attrs { prog.push_str(&format!("#![{attr}]\n")); line_offset += 1; } diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index b3a3ce08a05a6..dc8a29cdbd83f 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -24,7 +24,14 @@ impl DocTestVisitor for MdCollector { let filename = self.filename.clone(); // First line of Markdown is line 1. let line = 1 + rel_line.offset(); - self.tests.push(ScrapedDocTest::new(filename, line, self.cur_path.clone(), config, test)); + self.tests.push(ScrapedDocTest::new( + filename, + line, + self.cur_path.clone(), + config, + test, + Vec::new(), + )); } fn visit_header(&mut self, name: &str, level: u32) { @@ -89,7 +96,6 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> { crate_name, no_crate_inject: true, insert_indent_space: false, - attrs: vec![], args_file, }; diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index 39a4f23560a7e..f0914474c7934 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -12,6 +12,7 @@ use crate::html::markdown::{Ignore, LangString}; /// Convenient type to merge compatible doctests into one. pub(crate) struct DocTestRunner { crate_attrs: FxIndexSet, + global_crate_attrs: FxIndexSet, ids: String, output: String, output_merged_tests: String, @@ -23,6 +24,7 @@ impl DocTestRunner { pub(crate) fn new() -> Self { Self { crate_attrs: FxIndexSet::default(), + global_crate_attrs: FxIndexSet::default(), ids: String::new(), output: String::new(), output_merged_tests: String::new(), @@ -46,6 +48,9 @@ impl DocTestRunner { for line in doctest.crate_attrs.split('\n') { self.crate_attrs.insert(line.to_string()); } + for line in &doctest.global_crate_attrs { + self.global_crate_attrs.insert(line.to_string()); + } } self.ids.push_str(&format!( "tests.push({}::TEST);\n", @@ -85,7 +90,7 @@ impl DocTestRunner { code_prefix.push('\n'); } - if opts.attrs.is_empty() { + if self.global_crate_attrs.is_empty() { // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some // lints that are commonly triggered in doctests. The crate-level test attributes are // commonly used to make tests fail in case they trigger warnings, so having this there in @@ -93,8 +98,8 @@ impl DocTestRunner { code_prefix.push_str("#![allow(unused)]\n"); } - // Next, any attributes that came from the crate root via #![doc(test(attr(...)))]. - for attr in &opts.attrs { + // Next, any attributes that came from #![doc(test(attr(...)))]. + for attr in &self.global_crate_attrs { code_prefix.push_str(&format!("#![{attr}]\n")); } diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 43dcfab880b5a..583c7be389557 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -3,6 +3,7 @@ use std::env; use std::sync::Arc; +use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit}; @@ -10,7 +11,7 @@ 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}; +use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym}; use super::{DocTestVisitor, ScrapedDocTest}; use crate::clean::{Attributes, extract_cfg_from_attrs}; @@ -21,6 +22,7 @@ struct RustCollector { tests: Vec, cur_path: Vec, position: Span, + global_crate_attrs: Vec, } impl RustCollector { @@ -54,6 +56,7 @@ impl DocTestVisitor for RustCollector { self.cur_path.clone(), config, test, + self.global_crate_attrs.clone(), )); } @@ -73,6 +76,7 @@ impl<'tcx> HirCollector<'tcx> { cur_path: vec![], position: DUMMY_SP, tests: vec![], + global_crate_attrs: Vec::new(), }; Self { codes, tcx, collector } } @@ -149,6 +153,40 @@ impl<'tcx> intravisit::Visitor<'tcx> for HirCollector<'tcx> { self.tcx } + fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _s: Span, hir_id: hir::HirId) { + let attrs = self.tcx.hir_attrs(hir_id); + + if !attrs.is_empty() { + // Try collecting `#![doc(test(attr(...)))]` from the attribute module + let old_len = self.collector.global_crate_attrs.len(); + for doc_test_attrs in 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(|i| pprust::meta_list_item_to_string(i)) + { + // Add the additional attributes to the global_crate_attrs vector + self.collector.global_crate_attrs.push(attr); + } + } + + let r = intravisit::walk_mod(self, m); + + // Restore global_crate_attrs to it's previous size/content + self.collector.global_crate_attrs.truncate(old_len); + r + } else { + intravisit::walk_mod(self, m) + } + } + fn visit_item(&mut self, item: &'tcx hir::Item<'_>) { let name = match &item.kind { hir::ItemKind::Impl(impl_) => { diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs index 49add73e9d64b..a44bc8bc4d3a9 100644 --- a/src/librustdoc/doctest/tests.rs +++ b/src/librustdoc/doctest/tests.rs @@ -9,12 +9,14 @@ fn make_test( crate_name: Option<&str>, dont_insert_main: bool, opts: &GlobalTestOptions, + global_crate_attrs: Vec<&str>, test_id: Option<&str>, ) -> (String, usize) { let doctest = DocTestBuilder::new( test_code, crate_name, DEFAULT_EDITION, + global_crate_attrs.into_iter().map(|a| a.to_string()).collect(), false, test_id.map(|s| s.to_string()), None, @@ -30,7 +32,6 @@ fn default_global_opts(crate_name: impl Into) -> GlobalTestOptions { crate_name: crate_name.into(), no_crate_inject: false, insert_indent_space: false, - attrs: vec![], args_file: PathBuf::new(), } } @@ -45,7 +46,7 @@ fn main() { assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -60,7 +61,7 @@ fn main() { assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -79,7 +80,7 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 3)); } @@ -96,7 +97,7 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -114,7 +115,7 @@ use std::*; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("std"), false, &opts, None); + let (output, len) = make_test(input, Some("std"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -133,7 +134,7 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -150,7 +151,7 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -158,8 +159,7 @@ assert_eq!(2+2, 4); fn make_test_opts_attrs() { // If you supplied some doctest attributes with `#![doc(test(attr(...)))]`, it will use // those instead of the stock `#![allow(unused)]`. - let mut opts = default_global_opts("asdf"); - opts.attrs.push("feature(sick_rad)".to_string()); + let opts = default_global_opts("asdf"); let input = "use asdf::qwop; assert_eq!(2+2, 4);"; let expected = "#![feature(sick_rad)] @@ -170,11 +170,10 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = + make_test(input, Some("asdf"), false, &opts, vec!["feature(sick_rad)"], None); assert_eq!((output, len), (expected, 3)); - // Adding more will also bump the returned line offset. - opts.attrs.push("feature(hella_dope)".to_string()); let expected = "#![feature(sick_rad)] #![feature(hella_dope)] #[allow(unused_extern_crates)] @@ -184,7 +183,18 @@ use asdf::qwop; assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test( + input, + Some("asdf"), + false, + &opts, + vec![ + "feature(sick_rad)", + // Adding more will also bump the returned line offset. + "feature(hella_dope)", + ], + None, + ); assert_eq!((output, len), (expected, 4)); } @@ -202,7 +212,7 @@ fn main() { assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -218,7 +228,7 @@ fn main() { assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 1)); } @@ -234,7 +244,7 @@ fn main() { assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -248,7 +258,7 @@ assert_eq!(2+2, 4);"; //Ceci n'est pas une `fn main` assert_eq!(2+2, 4);" .to_string(); - let (output, len) = make_test(input, None, true, &opts, None); + let (output, len) = make_test(input, None, true, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 1)); } @@ -266,7 +276,7 @@ assert_eq!(2+2, 4); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -286,7 +296,7 @@ assert_eq!(asdf::foo, 4); }" .to_string(); - let (output, len) = make_test(input, Some("asdf"), false, &opts, None); + let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 3)); } @@ -304,7 +314,7 @@ test_wrapper! { }" .to_string(); - let (output, len) = make_test(input, Some("my_crate"), false, &opts, None); + let (output, len) = make_test(input, Some("my_crate"), false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 1)); } @@ -324,7 +334,7 @@ io::stdin().read_line(&mut input)?; Ok::<(), io:Error>(()) } _inner().unwrap() }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -338,7 +348,7 @@ fn main() { #[allow(non_snake_case)] fn _doctest_main__some_unique_name() { assert_eq!(2+2, 4); } _doctest_main__some_unique_name() }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, Some("_some_unique_name")); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), Some("_some_unique_name")); assert_eq!((output, len), (expected, 2)); } @@ -357,7 +367,7 @@ fn main() { eprintln!(\"hello anan\"); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } @@ -377,7 +387,7 @@ fn main() { eprintln!(\"hello anan\"); }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 1)); } @@ -424,7 +434,7 @@ fn main() { }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); // And same, if there is a `main` function provided by the user, we ensure that it's @@ -444,7 +454,7 @@ fn main() {}"; fn main() {}" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 1)); } @@ -472,6 +482,6 @@ pub mod outer_module { } }" .to_string(); - let (output, len) = make_test(input, None, false, &opts, None); + let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); assert_eq!((output, len), (expected, 2)); } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index fc46293e7eaac..1f7d0b49c16e4 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -300,10 +300,10 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { crate_name: krate.map(String::from).unwrap_or_default(), no_crate_inject: false, insert_indent_space: true, - attrs: vec![], args_file: PathBuf::new(), }; - let doctest = doctest::DocTestBuilder::new(&test, krate, edition, false, None, None); + let doctest = + doctest::DocTestBuilder::new(&test, krate, edition, Vec::new(), false, None, None); let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate); let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" }; diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.rs b/tests/rustdoc-ui/doctest/dead-code-module-2.rs new file mode 100644 index 0000000000000..de7b11b91ec57 --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-module-2.rs @@ -0,0 +1,27 @@ +// Same test as dead-code-module but with 2 doc(test(attr())) at different levels. + +//@ edition: 2024 +//@ compile-flags:--test +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ failure-status: 101 + +#![doc(test(attr(allow(unused_variables))))] + +mod my_mod { + #![doc(test(attr(deny(warnings))))] + + /// Example + /// + /// ```rust,no_run + /// trait T { fn f(); } + /// ``` + pub fn f() {} +} + +/// Example +/// +/// ```rust,no_run +/// trait OnlyWarning { fn no_deny_warnings(); } +/// ``` +pub fn g() {} diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout new file mode 100644 index 0000000000000..36b8eaed22bb2 --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout @@ -0,0 +1,30 @@ + +running 2 tests +test $DIR/dead-code-module-2.rs - my_mod::f (line 16) - compile ... FAILED +test $DIR/dead-code-module-2.rs - g (line 24) - compile ... ok + +failures: + +---- $DIR/dead-code-module-2.rs - my_mod::f (line 16) stdout ---- +error: trait `T` is never used + --> $DIR/dead-code-module-2.rs:17:7 + | +LL | trait T { fn f(); } + | ^ + | +note: the lint level is defined here + --> $DIR/dead-code-module-2.rs:15:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/dead-code-module-2.rs - my_mod::f (line 16) + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/dead-code-module.rs b/tests/rustdoc-ui/doctest/dead-code-module.rs new file mode 100644 index 0000000000000..f825749a6a25a --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-module.rs @@ -0,0 +1,18 @@ +// Same test as dead-code but inside a module. + +//@ edition: 2024 +//@ compile-flags:--test +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ failure-status: 101 + +mod my_mod { + #![doc(test(attr(allow(unused_variables), deny(warnings))))] + + /// Example + /// + /// ```rust,no_run + /// trait T { fn f(); } + /// ``` + pub fn f() {} +} diff --git a/tests/rustdoc-ui/doctest/dead-code-module.stdout b/tests/rustdoc-ui/doctest/dead-code-module.stdout new file mode 100644 index 0000000000000..b5ccf225d25c7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-module.stdout @@ -0,0 +1,29 @@ + +running 1 test +test $DIR/dead-code-module.rs - my_mod::f (line 14) - compile ... FAILED + +failures: + +---- $DIR/dead-code-module.rs - my_mod::f (line 14) stdout ---- +error: trait `T` is never used + --> $DIR/dead-code-module.rs:15:7 + | +LL | trait T { fn f(); } + | ^ + | +note: the lint level is defined here + --> $DIR/dead-code-module.rs:13:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/dead-code-module.rs - my_mod::f (line 14) + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/doc-test-attr-pass-module.rs b/tests/rustdoc-ui/doctest/doc-test-attr-pass-module.rs new file mode 100644 index 0000000000000..e916ca41ea0eb --- /dev/null +++ b/tests/rustdoc-ui/doctest/doc-test-attr-pass-module.rs @@ -0,0 +1,11 @@ +//@ check-pass + +#![crate_type = "lib"] +#![deny(invalid_doc_attributes)] +#![doc(test(no_crate_inject))] + +mod my_mod { + #![doc(test(attr(deny(warnings))))] + + pub fn foo() {} +} From c1b07e972d31c08aef3b1cb26bdf2dd90c48a63a Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 1 May 2025 18:44:38 +0200 Subject: [PATCH 4/6] Allow `#![doc(test(attr(..)))]` doctests to be again merged together --- src/librustdoc/doctest.rs | 31 ++++++++++++++----- src/librustdoc/doctest/make.rs | 3 -- .../doctests-keep-binaries-2024/rmake.rs | 17 +++++++++- .../doctest/dead-code-module-2.stdout | 11 +++++-- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 571632f922bd4..e767ea3c97505 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -5,6 +5,7 @@ mod runner; mod rust; use std::fs::File; +use std::hash::{Hash, Hasher}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; @@ -14,7 +15,7 @@ use std::{panic, str}; pub(crate) use make::DocTestBuilder; pub(crate) use markdown::test as test_markdown; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHasher, FxIndexMap, FxIndexSet}; use rustc_errors::emitter::HumanReadableErrorType; use rustc_errors::{ColorConfig, DiagCtxtHandle}; use rustc_hir as hir; @@ -323,7 +324,7 @@ pub(crate) fn run_tests( rustdoc_options: &Arc, unused_extern_reports: &Arc>>, mut standalone_tests: Vec, - mergeable_tests: FxIndexMap>, + mergeable_tests: FxIndexMap>, // We pass this argument so we can drop it manually before using `exit`. mut temp_dir: Option, ) { @@ -338,7 +339,7 @@ pub(crate) fn run_tests( let mut ran_edition_tests = 0; let target_str = rustdoc_options.target.to_string(); - for (edition, mut doctests) in mergeable_tests { + for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests { if doctests.is_empty() { continue; } @@ -348,8 +349,8 @@ pub(crate) fn run_tests( let rustdoc_test_options = IndividualTestOptions::new( rustdoc_options, - &Some(format!("merged_doctest_{edition}")), - PathBuf::from(format!("doctest_{edition}.rs")), + &Some(format!("merged_doctest_{edition}_{global_crate_attrs_hash}")), + PathBuf::from(format!("doctest_{edition}_{global_crate_attrs_hash}.rs")), ); for (doctest, scraped_test) in &doctests { @@ -927,9 +928,15 @@ pub(crate) trait DocTestVisitor { fn visit_header(&mut self, _name: &str, _level: u32) {} } +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub(crate) struct MergeableTestKey { + edition: Edition, + global_crate_attrs_hash: u64, +} + struct CreateRunnableDocTests { standalone_tests: Vec, - mergeable_tests: FxIndexMap>, + mergeable_tests: FxIndexMap>, rustdoc_options: Arc, opts: GlobalTestOptions, @@ -997,7 +1004,17 @@ impl CreateRunnableDocTests { let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test); self.standalone_tests.push(test_desc); } else { - self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test)); + self.mergeable_tests + .entry(MergeableTestKey { + edition, + global_crate_attrs_hash: { + let mut hasher = FxHasher::default(); + scraped_test.global_crate_attrs.hash(&mut hasher); + hasher.finish() + }, + }) + .or_default() + .push((doctest, scraped_test)); } } diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index fbad7f2969977..ec57293afe7f8 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -107,9 +107,6 @@ impl DocTestBuilder { let can_be_merged = can_merge_doctests && !has_global_allocator && crate_attrs.is_empty() - // FIXME: We can probably merge tests which have the same global crate attrs, - // like we already do for the edition - && global_crate_attrs.is_empty() // If this is a merged doctest and a defined macro uses `$crate`, then the path will // not work, so better not put it into merged doctests. && !(has_macro_def && everything_else.contains("$crate")); diff --git a/tests/run-make/doctests-keep-binaries-2024/rmake.rs b/tests/run-make/doctests-keep-binaries-2024/rmake.rs index 3e8ffcbf24457..97324e1dcbc8e 100644 --- a/tests/run-make/doctests-keep-binaries-2024/rmake.rs +++ b/tests/run-make/doctests-keep-binaries-2024/rmake.rs @@ -16,7 +16,22 @@ fn setup_test_env(callback: F) { } fn check_generated_binaries() { - run("doctests/merged_doctest_2024/rust_out"); + let mut found_merged_doctest = false; + rfs::read_dir_entries("doctests/", |path| { + if path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.starts_with("merged_doctest_2024")) + { + found_merged_doctest = true; + let rust_out = path.join("rust_out"); + let rust_out = rust_out.to_string_lossy(); + run(&*rust_out); + } + }); + if !found_merged_doctest { + panic!("no directory starting with `merged_doctest_2024` found under `doctests/`"); + } } fn main() { diff --git a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout index 36b8eaed22bb2..d44068dcbf5d7 100644 --- a/tests/rustdoc-ui/doctest/dead-code-module-2.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-module-2.stdout @@ -1,8 +1,13 @@ -running 2 tests -test $DIR/dead-code-module-2.rs - my_mod::f (line 16) - compile ... FAILED +running 1 test test $DIR/dead-code-module-2.rs - g (line 24) - compile ... ok +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 1 test +test $DIR/dead-code-module-2.rs - my_mod::f (line 16) - compile ... FAILED + failures: ---- $DIR/dead-code-module-2.rs - my_mod::f (line 16) stdout ---- @@ -26,5 +31,5 @@ Couldn't compile the test. failures: $DIR/dead-code-module-2.rs - my_mod::f (line 16) -test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME From 4520d5e29113f042dcf3a029453ea3eabbbb0764 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 5 May 2025 22:50:40 +0200 Subject: [PATCH 5/6] Allow `#![doc(test(attr(..)))]` at every level --- compiler/rustc_passes/messages.ftl | 5 -- compiler/rustc_passes/src/check_attr.rs | 41 ++------------- compiler/rustc_passes/src/errors.rs | 12 +---- .../write-documentation/the-doc-attribute.md | 51 +++++++++---------- tests/rustdoc-ui/lints/invalid-doc-attr.rs | 4 -- .../rustdoc-ui/lints/invalid-doc-attr.stderr | 32 ++++-------- tests/ui/rustdoc/doc-test-attr-pass.rs | 38 ++++++++++++++ 7 files changed, 77 insertions(+), 106 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 2f264ed4fc023..6d815e510ea20 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -46,11 +46,6 @@ passes_attr_crate_level = .suggestion = to apply to the crate, use an inner attribute .note = read 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 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 f1f309d67e2a2..4c5e03c43191a 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::AttrCrateLevelSugg { + .then_some(errors::AttrCrateLevelOnlySugg { attr: attr.span().with_lo(bang_span).with_hi(bang_span), }); self.tcx.emit_node_span_lint( @@ -1266,46 +1266,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> { true } - /// 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, - ) { + fn check_test_attr(&self, attr: &Attribute, 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), _) => { - self.check_attr_mod_level(attr, meta, hir_id, target); + // Allowed everywhere like `#[doc]` } (Some(sym::no_crate_inject), _) => { self.check_attr_crate_level(attr, meta, hir_id); @@ -1396,7 +1363,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } Some(sym::test) => { - self.check_test_attr(attr, meta, hir_id, target); + self.check_test_attr(attr, meta, hir_id); } Some( diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 38b0db37e12b4..00682a9c7a794 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1890,20 +1890,12 @@ pub(crate) struct UnusedVarTryIgnoreSugg { #[note] pub(crate) struct AttrCrateLevelOnly { #[subdiagnostic] - pub sugg: Option, -} - -#[derive(LintDiagnostic)] -#[diag(passes_attr_mod_level)] -#[note] -pub(crate) struct AttrModLevelOnly { - #[subdiagnostic] - pub sugg: Option, + pub sugg: Option, } #[derive(Subdiagnostic)] #[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")] -pub(crate) struct AttrCrateLevelSugg { +pub(crate) struct AttrCrateLevelOnlySugg { #[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 afbcf4000c538..a70861faa761a 100644 --- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md +++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md @@ -141,34 +141,6 @@ 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 -example, if you want your doctests to fail if they have dead code, you could add this: - -```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 These forms of the `#[doc]` attribute are used on individual items, to control how @@ -300,3 +272,26 @@ To get around this limitation, we just add `#[doc(alias = "lib_name_do_something on the `do_something` method and then it's all good! Users can now look for `lib_name_do_something` in our crate directly and find `Obj::do_something`. + +### `test(attr(...))` + +This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For +example, if you want your doctests to fail if they have dead code, you could add this: + +```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` +``` diff --git a/tests/rustdoc-ui/lints/invalid-doc-attr.rs b/tests/rustdoc-ui/lints/invalid-doc-attr.rs index cd5ae44b1266d..e1cc08ca24272 100644 --- a/tests/rustdoc-ui/lints/invalid-doc-attr.rs +++ b/tests/rustdoc-ui/lints/invalid-doc-attr.rs @@ -4,10 +4,6 @@ #![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 0fd55ff94d872..7621999a8ca52 100644 --- a/tests/rustdoc-ui/lints/invalid-doc-attr.stderr +++ b/tests/rustdoc-ui/lints/invalid-doc-attr.stderr @@ -1,30 +1,18 @@ -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 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 + --> $DIR/invalid-doc-attr.rs:7:7 | LL | #[doc(test(no_crate_inject))] | ^^^^^^^^^^^^^^^^^^^^^ | = note: read 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:15:7 + --> $DIR/invalid-doc-attr.rs:11:7 | LL | #[doc(inline)] | ^^^^^^ only applicable on `use` items @@ -35,7 +23,7 @@ LL | pub fn foo() {} = note: read for more information error: this attribute can only be applied at the crate level - --> $DIR/invalid-doc-attr.rs:20:12 + --> $DIR/invalid-doc-attr.rs:16:12 | LL | #![doc(test(no_crate_inject))] | ^^^^^^^^^^^^^^^^^^^^^ @@ -43,7 +31,7 @@ LL | #![doc(test(no_crate_inject))] = note: read for more information error: conflicting doc inlining attributes - --> $DIR/invalid-doc-attr.rs:30:7 + --> $DIR/invalid-doc-attr.rs:26:7 | LL | #[doc(inline)] | ^^^^^^ this attribute... @@ -53,7 +41,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:36:7 + --> $DIR/invalid-doc-attr.rs:32:7 | LL | #[doc(masked)] | ^^^^^^ only applicable on `extern crate` items @@ -64,7 +52,7 @@ LL | pub struct Masked; = note: read for more information error: this attribute cannot be applied to an `extern crate self` item - --> $DIR/invalid-doc-attr.rs:40:7 + --> $DIR/invalid-doc-attr.rs:36:7 | LL | #[doc(masked)] | ^^^^^^ not applicable on `extern crate self` items @@ -81,7 +69,7 @@ LL | #![doc(masked)] = note: read for more information error: this attribute can only be applied at the crate level - --> $DIR/invalid-doc-attr.rs:23:11 + --> $DIR/invalid-doc-attr.rs:19:11 | LL | #[doc(test(no_crate_inject))] | ^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +77,7 @@ LL | #[doc(test(no_crate_inject))] = note: read for more information error: this attribute can only be applied to a `use` item - --> $DIR/invalid-doc-attr.rs:25:11 + --> $DIR/invalid-doc-attr.rs:21:11 | LL | #[doc(inline)] | ^^^^^^ only applicable on `use` items @@ -99,5 +87,5 @@ LL | pub fn baz() {} | = note: read for more information -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/rustdoc/doc-test-attr-pass.rs b/tests/ui/rustdoc/doc-test-attr-pass.rs index 40ffd5b25723a..60d1e3722bdff 100644 --- a/tests/ui/rustdoc/doc-test-attr-pass.rs +++ b/tests/ui/rustdoc/doc-test-attr-pass.rs @@ -10,4 +10,42 @@ mod test { #![doc(test(attr(allow(warnings))))] } +#[doc(test(attr(allow(dead_code))))] +static S: u32 = 5; + +#[doc(test(attr(allow(dead_code))))] +const C: u32 = 5; + +#[doc(test(attr(deny(dead_code))))] +struct A { + #[doc(test(attr(allow(dead_code))))] + field: u32 +} + +#[doc(test(attr(deny(dead_code))))] +union U { + #[doc(test(attr(allow(dead_code))))] + field: u32, + field2: u64, +} + +#[doc(test(attr(deny(dead_code))))] +enum Enum { + #[doc(test(attr(allow(dead_code))))] + Variant1, +} + +#[doc(test(attr(deny(dead_code))))] +impl A { + #[doc(test(attr(deny(dead_code))))] + fn method() {} +} + +#[doc(test(attr(deny(dead_code))))] +trait MyTrait { + #[doc(test(attr(deny(dead_code))))] + fn my_trait_fn(); +} + +#[doc(test(attr(deny(dead_code))))] pub fn foo() {} From 3187894296024e163c525805c25495e61784b83c Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 5 May 2025 23:07:29 +0200 Subject: [PATCH 6/6] Collect and use `#[doc(test(attr(..)))]` at every level --- src/librustdoc/doctest/rust.rs | 57 +++---- tests/rustdoc-ui/doctest/dead-code-items.rs | 116 ++++++++++++++ .../rustdoc-ui/doctest/dead-code-items.stdout | 146 ++++++++++++++++++ 3 files changed, 285 insertions(+), 34 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/dead-code-items.rs create mode 100644 tests/rustdoc-ui/doctest/dead-code-items.stdout diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 583c7be389557..50d6339caf3a6 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -106,6 +106,26 @@ impl HirCollector<'_> { return; } + // 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(|i| pprust::meta_list_item_to_string(i)) + { + // Add the additional attributes to the global_crate_attrs vector + self.collector.global_crate_attrs.push(attr); + } + } + let mut has_name = false; if let Some(name) = name { self.collector.cur_path.push(name); @@ -140,6 +160,9 @@ impl HirCollector<'_> { nested(self); + // Restore global_crate_attrs to it's previous size/content + self.collector.global_crate_attrs.truncate(old_global_crate_attrs_len); + if has_name { self.collector.cur_path.pop(); } @@ -153,40 +176,6 @@ impl<'tcx> intravisit::Visitor<'tcx> for HirCollector<'tcx> { self.tcx } - fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _s: Span, hir_id: hir::HirId) { - let attrs = self.tcx.hir_attrs(hir_id); - - if !attrs.is_empty() { - // Try collecting `#![doc(test(attr(...)))]` from the attribute module - let old_len = self.collector.global_crate_attrs.len(); - for doc_test_attrs in 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(|i| pprust::meta_list_item_to_string(i)) - { - // Add the additional attributes to the global_crate_attrs vector - self.collector.global_crate_attrs.push(attr); - } - } - - let r = intravisit::walk_mod(self, m); - - // Restore global_crate_attrs to it's previous size/content - self.collector.global_crate_attrs.truncate(old_len); - r - } else { - intravisit::walk_mod(self, m) - } - } - fn visit_item(&mut self, item: &'tcx hir::Item<'_>) { let name = match &item.kind { hir::ItemKind::Impl(impl_) => { diff --git a/tests/rustdoc-ui/doctest/dead-code-items.rs b/tests/rustdoc-ui/doctest/dead-code-items.rs new file mode 100644 index 0000000000000..015504cbcedbc --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-items.rs @@ -0,0 +1,116 @@ +// Same test as dead-code-module but with 2 doc(test(attr())) at different levels. + +//@ edition: 2024 +//@ compile-flags:--test --test-args=--test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ failure-status: 101 + +#![doc(test(attr(deny(warnings))))] + +#[doc(test(attr(allow(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// trait OnlyWarning { fn no_deny_warnings(); } +/// ``` +static S: u32 = 5; + +#[doc(test(attr(allow(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// let unused_error = 5; +/// +/// fn dead_code_but_no_error() {} +/// ``` +const C: u32 = 5; + +#[doc(test(attr(allow(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// trait OnlyWarningAtA { fn no_deny_warnings(); } +/// ``` +struct A { + #[doc(test(attr(deny(dead_code))))] + /// Example + /// + /// ```rust,no_run + /// trait DeadCodeInField {} + /// ``` + field: u32 +} + +#[doc(test(attr(allow(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// trait OnlyWarningAtU { fn no_deny_warnings(); } +/// ``` +union U { + #[doc(test(attr(deny(dead_code))))] + /// Example + /// + /// ```rust,no_run + /// trait DeadCodeInUnionField {} + /// ``` + field: u32, + /// Example + /// + /// ```rust,no_run + /// trait NotDeadCodeInUnionField {} + /// ``` + field2: u64, +} + +#[doc(test(attr(deny(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// let not_dead_code_but_unused = 5; +/// ``` +enum Enum { + #[doc(test(attr(allow(dead_code))))] + /// Example + /// + /// ```rust,no_run + /// trait NotDeadCodeInVariant {} + /// + /// fn main() { let unused_in_variant = 5; } + /// ``` + Variant1, +} + +#[doc(test(attr(allow(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// trait OnlyWarningAtImplA { fn no_deny_warnings(); } +/// ``` +impl A { + /// Example + /// + /// ```rust,no_run + /// trait NotDeadCodeInImplMethod {} + /// ``` + fn method() {} +} + +#[doc(test(attr(deny(dead_code))))] +/// Example +/// +/// ```rust,no_run +/// trait StillDeadCodeAtMyTrait { } +/// ``` +trait MyTrait { + #[doc(test(attr(allow(dead_code))))] + /// Example + /// + /// ```rust,no_run + /// trait NotDeadCodeAtImplFn {} + /// + /// fn main() { let unused_in_impl = 5; } + /// ``` + fn my_trait_fn(); +} diff --git a/tests/rustdoc-ui/doctest/dead-code-items.stdout b/tests/rustdoc-ui/doctest/dead-code-items.stdout new file mode 100644 index 0000000000000..4b9d8be94dd3c --- /dev/null +++ b/tests/rustdoc-ui/doctest/dead-code-items.stdout @@ -0,0 +1,146 @@ + +running 13 tests +test $DIR/dead-code-items.rs - A (line 32) - compile ... ok +test $DIR/dead-code-items.rs - A (line 88) - compile ... ok +test $DIR/dead-code-items.rs - A::field (line 39) - compile ... FAILED +test $DIR/dead-code-items.rs - A::method (line 94) - compile ... ok +test $DIR/dead-code-items.rs - C (line 22) - compile ... FAILED +test $DIR/dead-code-items.rs - Enum (line 70) - compile ... FAILED +test $DIR/dead-code-items.rs - Enum::Variant1 (line 77) - compile ... FAILED +test $DIR/dead-code-items.rs - MyTrait (line 103) - compile ... FAILED +test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) - compile ... FAILED +test $DIR/dead-code-items.rs - S (line 14) - compile ... ok +test $DIR/dead-code-items.rs - U (line 48) - compile ... ok +test $DIR/dead-code-items.rs - U::field (line 55) - compile ... FAILED +test $DIR/dead-code-items.rs - U::field2 (line 61) - compile ... ok + +failures: + +---- $DIR/dead-code-items.rs - A::field (line 39) stdout ---- +error: trait `DeadCodeInField` is never used + --> $DIR/dead-code-items.rs:40:7 + | +LL | trait DeadCodeInField {} + | ^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:38:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - C (line 22) stdout ---- +error: unused variable: `unused_error` + --> $DIR/dead-code-items.rs:23:5 + | +LL | let unused_error = 5; + | ^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_error` + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:20:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - Enum (line 70) stdout ---- +error: unused variable: `not_dead_code_but_unused` + --> $DIR/dead-code-items.rs:71:5 + | +LL | let not_dead_code_but_unused = 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_not_dead_code_but_unused` + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:68:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - Enum::Variant1 (line 77) stdout ---- +error: unused variable: `unused_in_variant` + --> $DIR/dead-code-items.rs:80:17 + | +LL | fn main() { let unused_in_variant = 5; } + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_variant` + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:75:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - MyTrait (line 103) stdout ---- +error: trait `StillDeadCodeAtMyTrait` is never used + --> $DIR/dead-code-items.rs:104:7 + | +LL | trait StillDeadCodeAtMyTrait { } + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:102:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) stdout ---- +error: unused variable: `unused_in_impl` + --> $DIR/dead-code-items.rs:113:17 + | +LL | fn main() { let unused_in_impl = 5; } + | ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_impl` + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:108:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]` + +error: aborting due to 1 previous error + +Couldn't compile the test. +---- $DIR/dead-code-items.rs - U::field (line 55) stdout ---- +error: trait `DeadCodeInUnionField` is never used + --> $DIR/dead-code-items.rs:56:7 + | +LL | trait DeadCodeInUnionField {} + | ^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/dead-code-items.rs:54:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/dead-code-items.rs - A::field (line 39) + $DIR/dead-code-items.rs - C (line 22) + $DIR/dead-code-items.rs - Enum (line 70) + $DIR/dead-code-items.rs - Enum::Variant1 (line 77) + $DIR/dead-code-items.rs - MyTrait (line 103) + $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) + $DIR/dead-code-items.rs - U::field (line 55) + +test result: FAILED. 6 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +