Skip to content

Commit a97b3bd

Browse files
authored
Unrolled build for #140560
Rollup merge of #140560 - Urgau:test_attr-module-level, r=GuillaumeGomez Allow `#![doc(test(attr(..)))]` everywhere This PR adds the ability to specify [`#![doc(test(attr(..)))]`](https://doc.rust-lang.org/nightly/rustdoc/write-documentation/the-doc-attribute.html#testattr) ~~at module level~~ everywhere in addition to allowing it at crate-root. This is motivated by a recent PR #140323 (by ````@tgross35)```` where we have to duplicate 2 attributes to every single `f16` and `f128` doctests, by allowing `#![doc(test(attr(..)))]` at module level (and everywhere else) we can omit them entirely and just have (in both module): ```rust #![doc(test(attr(feature(cfg_target_has_reliable_f16_f128))))] #![doc(test(attr(expect(internal_features))))] ``` Those new attributes are appended to the one found at crate-root or at a previous module. Those "global" attributes are compatible with merged doctests (they already were before). Given the small addition that this is, I'm proposing to insta-stabilize it, but I can feature-gate it if preferred. Best reviewed commit by commit. r? ````@GuillaumeGomez````
2 parents cdd545b + d96d3be commit a97b3bd

23 files changed

+704
-101
lines changed

compiler/rustc_passes/src/check_attr.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,13 +1266,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
12661266
true
12671267
}
12681268

1269-
/// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if
1270-
/// valid.
1271-
fn check_test_attr(&self, meta: &MetaItemInner, hir_id: HirId) {
1269+
/// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
1270+
fn check_test_attr(&self, attr: &Attribute, meta: &MetaItemInner, hir_id: HirId) {
12721271
if let Some(metas) = meta.meta_item_list() {
12731272
for i_meta in metas {
12741273
match (i_meta.name(), i_meta.meta_item()) {
1275-
(Some(sym::attr | sym::no_crate_inject), _) => {}
1274+
(Some(sym::attr), _) => {
1275+
// Allowed everywhere like `#[doc]`
1276+
}
1277+
(Some(sym::no_crate_inject), _) => {
1278+
self.check_attr_crate_level(attr, meta, hir_id);
1279+
}
12761280
(_, Some(m)) => {
12771281
self.tcx.emit_node_span_lint(
12781282
INVALID_DOC_ATTRIBUTES,
@@ -1359,9 +1363,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
13591363
}
13601364

13611365
Some(sym::test) => {
1362-
if self.check_attr_crate_level(attr, meta, hir_id) {
1363-
self.check_test_attr(meta, hir_id);
1364-
}
1366+
self.check_test_attr(attr, meta, hir_id);
13651367
}
13661368

13671369
Some(

src/doc/rustdoc/src/write-documentation/the-doc-attribute.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,6 @@ But if you include this:
143143

144144
it will not.
145145

146-
### `test(attr(...))`
147-
148-
This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For
149-
example, if you want your doctests to fail if they have dead code, you could add this:
150-
151-
```rust,no_run
152-
#![doc(test(attr(deny(dead_code))))]
153-
```
154-
155146
## At the item level
156147

157148
These forms of the `#[doc]` attribute are used on individual items, to control how
@@ -283,3 +274,26 @@ To get around this limitation, we just add `#[doc(alias = "lib_name_do_something
283274
on the `do_something` method and then it's all good!
284275
Users can now look for `lib_name_do_something` in our crate directly and find
285276
`Obj::do_something`.
277+
278+
### `test(attr(...))`
279+
280+
This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For
281+
example, if you want your doctests to fail if they have dead code, you could add this:
282+
283+
```rust,no_run
284+
#![doc(test(attr(deny(dead_code))))]
285+
286+
mod my_mod {
287+
#![doc(test(attr(allow(dead_code))))] // but allow `dead_code` for this module
288+
}
289+
```
290+
291+
`test(attr(..))` attributes are appended to the parent module's, they do not replace the current
292+
list of attributes. In the previous example, both attributes would be present:
293+
294+
```rust,no_run
295+
// For every doctest in `my_mod`
296+
297+
#![deny(dead_code)] // from the crate-root
298+
#![allow(dead_code)] // from `my_mod`
299+
```

src/librustdoc/doctest.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod runner;
55
mod rust;
66

77
use std::fs::File;
8+
use std::hash::{Hash, Hasher};
89
use std::io::{self, Write};
910
use std::path::{Path, PathBuf};
1011
use std::process::{self, Command, Stdio};
@@ -14,7 +15,7 @@ use std::{panic, str};
1415

1516
pub(crate) use make::{BuildDocTestBuilder, DocTestBuilder};
1617
pub(crate) use markdown::test as test_markdown;
17-
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
18+
use rustc_data_structures::fx::{FxHashMap, FxHasher, FxIndexMap, FxIndexSet};
1819
use rustc_errors::emitter::HumanReadableErrorType;
1920
use rustc_errors::{ColorConfig, DiagCtxtHandle};
2021
use rustc_hir as hir;
@@ -45,8 +46,6 @@ pub(crate) struct GlobalTestOptions {
4546
/// Whether inserting extra indent spaces in code block,
4647
/// default is `false`, only `true` for generating code link of Rust playground
4748
pub(crate) insert_indent_space: bool,
48-
/// Additional crate-level attributes to add to doctests.
49-
pub(crate) attrs: Vec<String>,
5049
/// Path to file containing arguments for the invocation of rustc.
5150
pub(crate) args_file: PathBuf,
5251
}
@@ -283,7 +282,7 @@ pub(crate) fn run_tests(
283282
rustdoc_options: &Arc<RustdocOptions>,
284283
unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>,
285284
mut standalone_tests: Vec<test::TestDescAndFn>,
286-
mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
285+
mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,
287286
// We pass this argument so we can drop it manually before using `exit`.
288287
mut temp_dir: Option<TempDir>,
289288
) {
@@ -298,7 +297,7 @@ pub(crate) fn run_tests(
298297
let mut ran_edition_tests = 0;
299298
let target_str = rustdoc_options.target.to_string();
300299

301-
for (edition, mut doctests) in mergeable_tests {
300+
for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests {
302301
if doctests.is_empty() {
303302
continue;
304303
}
@@ -308,8 +307,8 @@ pub(crate) fn run_tests(
308307

309308
let rustdoc_test_options = IndividualTestOptions::new(
310309
rustdoc_options,
311-
&Some(format!("merged_doctest_{edition}")),
312-
PathBuf::from(format!("doctest_{edition}.rs")),
310+
&Some(format!("merged_doctest_{edition}_{global_crate_attrs_hash}")),
311+
PathBuf::from(format!("doctest_{edition}_{global_crate_attrs_hash}.rs")),
313312
);
314313

315314
for (doctest, scraped_test) in &doctests {
@@ -371,12 +370,9 @@ fn scrape_test_config(
371370
attrs: &[hir::Attribute],
372371
args_file: PathBuf,
373372
) -> GlobalTestOptions {
374-
use rustc_ast_pretty::pprust;
375-
376373
let mut opts = GlobalTestOptions {
377374
crate_name,
378375
no_crate_inject: false,
379-
attrs: Vec::new(),
380376
insert_indent_space: false,
381377
args_file,
382378
};
@@ -393,13 +389,7 @@ fn scrape_test_config(
393389
if attr.has_name(sym::no_crate_inject) {
394390
opts.no_crate_inject = true;
395391
}
396-
if attr.has_name(sym::attr)
397-
&& let Some(l) = attr.meta_item_list()
398-
{
399-
for item in l {
400-
opts.attrs.push(pprust::meta_list_item_to_string(item));
401-
}
402-
}
392+
// NOTE: `test(attr(..))` is handled when discovering the individual tests
403393
}
404394

405395
opts
@@ -848,6 +838,7 @@ pub(crate) struct ScrapedDocTest {
848838
text: String,
849839
name: String,
850840
span: Span,
841+
global_crate_attrs: Vec<String>,
851842
}
852843

853844
impl ScrapedDocTest {
@@ -858,6 +849,7 @@ impl ScrapedDocTest {
858849
langstr: LangString,
859850
text: String,
860851
span: Span,
852+
global_crate_attrs: Vec<String>,
861853
) -> Self {
862854
let mut item_path = logical_path.join("::");
863855
item_path.retain(|c| c != ' ');
@@ -867,7 +859,7 @@ impl ScrapedDocTest {
867859
let name =
868860
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
869861

870-
Self { filename, line, langstr, text, name, span }
862+
Self { filename, line, langstr, text, name, span, global_crate_attrs }
871863
}
872864
fn edition(&self, opts: &RustdocOptions) -> Edition {
873865
self.langstr.edition.unwrap_or(opts.edition)
@@ -896,9 +888,15 @@ pub(crate) trait DocTestVisitor {
896888
fn visit_header(&mut self, _name: &str, _level: u32) {}
897889
}
898890

891+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
892+
pub(crate) struct MergeableTestKey {
893+
edition: Edition,
894+
global_crate_attrs_hash: u64,
895+
}
896+
899897
struct CreateRunnableDocTests {
900898
standalone_tests: Vec<test::TestDescAndFn>,
901-
mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
899+
mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,
902900

903901
rustdoc_options: Arc<RustdocOptions>,
904902
opts: GlobalTestOptions,
@@ -949,6 +947,7 @@ impl CreateRunnableDocTests {
949947
let edition = scraped_test.edition(&self.rustdoc_options);
950948
let doctest = BuildDocTestBuilder::new(&scraped_test.text)
951949
.crate_name(&self.opts.crate_name)
950+
.global_crate_attrs(scraped_test.global_crate_attrs.clone())
952951
.edition(edition)
953952
.can_merge_doctests(self.can_merge_doctests)
954953
.test_id(test_id)
@@ -965,7 +964,17 @@ impl CreateRunnableDocTests {
965964
let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test);
966965
self.standalone_tests.push(test_desc);
967966
} else {
968-
self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test));
967+
self.mergeable_tests
968+
.entry(MergeableTestKey {
969+
edition,
970+
global_crate_attrs_hash: {
971+
let mut hasher = FxHasher::default();
972+
scraped_test.global_crate_attrs.hash(&mut hasher);
973+
hasher.finish()
974+
},
975+
})
976+
.or_default()
977+
.push((doctest, scraped_test));
969978
}
970979
}
971980

src/librustdoc/doctest/extracted.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ impl ExtractedDocTests {
3535
) {
3636
let edition = scraped_test.edition(options);
3737

38-
let ScrapedDocTest { filename, line, langstr, text, name, .. } = scraped_test;
38+
let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs, .. } =
39+
scraped_test;
3940

4041
let doctest = BuildDocTestBuilder::new(&text)
4142
.crate_name(&opts.crate_name)
43+
.global_crate_attrs(global_crate_attrs)
4244
.edition(edition)
4345
.lang_str(&langstr)
4446
.build(None);
47+
4548
let (full_test_code, size) = doctest.generate_unique_doctest(
4649
&text,
4750
langstr.test_harness,

src/librustdoc/doctest/make.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub(crate) struct BuildDocTestBuilder<'a> {
4545
test_id: Option<String>,
4646
lang_str: Option<&'a LangString>,
4747
span: Span,
48+
global_crate_attrs: Vec<String>,
4849
}
4950

5051
impl<'a> BuildDocTestBuilder<'a> {
@@ -57,6 +58,7 @@ impl<'a> BuildDocTestBuilder<'a> {
5758
test_id: None,
5859
lang_str: None,
5960
span: DUMMY_SP,
61+
global_crate_attrs: Vec::new(),
6062
}
6163
}
6264

@@ -96,6 +98,12 @@ impl<'a> BuildDocTestBuilder<'a> {
9698
self
9799
}
98100

101+
#[inline]
102+
pub(crate) fn global_crate_attrs(mut self, global_crate_attrs: Vec<String>) -> Self {
103+
self.global_crate_attrs = global_crate_attrs;
104+
self
105+
}
106+
99107
pub(crate) fn build(self, dcx: Option<DiagCtxtHandle<'_>>) -> DocTestBuilder {
100108
let BuildDocTestBuilder {
101109
source,
@@ -106,6 +114,7 @@ impl<'a> BuildDocTestBuilder<'a> {
106114
test_id,
107115
lang_str,
108116
span,
117+
global_crate_attrs,
109118
} = self;
110119
let can_merge_doctests = can_merge_doctests
111120
&& lang_str.is_some_and(|lang_str| {
@@ -133,6 +142,7 @@ impl<'a> BuildDocTestBuilder<'a> {
133142
// If the AST returned an error, we don't want this doctest to be merged with the
134143
// others.
135144
return DocTestBuilder::invalid(
145+
Vec::new(),
136146
String::new(),
137147
String::new(),
138148
String::new(),
@@ -155,6 +165,7 @@ impl<'a> BuildDocTestBuilder<'a> {
155165
DocTestBuilder {
156166
supports_color,
157167
has_main_fn,
168+
global_crate_attrs,
158169
crate_attrs,
159170
maybe_crate_attrs,
160171
crates,
@@ -173,6 +184,7 @@ pub(crate) struct DocTestBuilder {
173184
pub(crate) supports_color: bool,
174185
pub(crate) already_has_extern_crate: bool,
175186
pub(crate) has_main_fn: bool,
187+
pub(crate) global_crate_attrs: Vec<String>,
176188
pub(crate) crate_attrs: String,
177189
/// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
178190
/// put into `crate_attrs`.
@@ -186,6 +198,7 @@ pub(crate) struct DocTestBuilder {
186198

187199
impl DocTestBuilder {
188200
fn invalid(
201+
global_crate_attrs: Vec<String>,
189202
crate_attrs: String,
190203
maybe_crate_attrs: String,
191204
crates: String,
@@ -195,6 +208,7 @@ impl DocTestBuilder {
195208
Self {
196209
supports_color: false,
197210
has_main_fn: false,
211+
global_crate_attrs,
198212
crate_attrs,
199213
maybe_crate_attrs,
200214
crates,
@@ -224,7 +238,8 @@ impl DocTestBuilder {
224238
let mut line_offset = 0;
225239
let mut prog = String::new();
226240
let everything_else = self.everything_else.trim();
227-
if opts.attrs.is_empty() {
241+
242+
if self.global_crate_attrs.is_empty() {
228243
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
229244
// lints that are commonly triggered in doctests. The crate-level test attributes are
230245
// commonly used to make tests fail in case they trigger warnings, so having this there in
@@ -233,8 +248,8 @@ impl DocTestBuilder {
233248
line_offset += 1;
234249
}
235250

236-
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
237-
for attr in &opts.attrs {
251+
// Next, any attributes that came from #![doc(test(attr(...)))].
252+
for attr in &self.global_crate_attrs {
238253
prog.push_str(&format!("#![{attr}]\n"));
239254
line_offset += 1;
240255
}

src/librustdoc/doctest/markdown.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ impl DocTestVisitor for MdCollector {
3131
config,
3232
test,
3333
DUMMY_SP,
34+
Vec::new(),
3435
));
3536
}
3637

@@ -96,7 +97,6 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
9697
crate_name,
9798
no_crate_inject: true,
9899
insert_indent_space: false,
99-
attrs: vec![],
100100
args_file,
101101
};
102102

0 commit comments

Comments
 (0)