Skip to content

Commit 9d9705f

Browse files
committed
Collect and use #![doc(test(attr(..)))] at module level too
1 parent 80c6a08 commit 9d9705f

File tree

13 files changed

+229
-52
lines changed

13 files changed

+229
-52
lines changed

src/librustdoc/doctest.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ pub(crate) struct GlobalTestOptions {
4545
/// Whether inserting extra indent spaces in code block,
4646
/// default is `false`, only `true` for generating code link of Rust playground
4747
pub(crate) insert_indent_space: bool,
48-
/// Additional crate-level attributes to add to doctests.
49-
pub(crate) attrs: Vec<String>,
5048
/// Path to file containing arguments for the invocation of rustc.
5149
pub(crate) args_file: PathBuf,
5250
}
@@ -371,12 +369,9 @@ fn scrape_test_config(
371369
attrs: &[hir::Attribute],
372370
args_file: PathBuf,
373371
) -> GlobalTestOptions {
374-
use rustc_ast_pretty::pprust;
375-
376372
let mut opts = GlobalTestOptions {
377373
crate_name,
378374
no_crate_inject: false,
379-
attrs: Vec::new(),
380375
insert_indent_space: false,
381376
args_file,
382377
};
@@ -393,13 +388,7 @@ fn scrape_test_config(
393388
if attr.has_name(sym::no_crate_inject) {
394389
opts.no_crate_inject = true;
395390
}
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-
}
391+
// NOTE: `test(attr(..))` is handled when discovering the individual tests
403392
}
404393

405394
opts
@@ -848,6 +837,7 @@ pub(crate) struct ScrapedDocTest {
848837
text: String,
849838
name: String,
850839
span: Span,
840+
global_crate_attrs: Vec<String>,
851841
}
852842

853843
impl ScrapedDocTest {
@@ -858,6 +848,7 @@ impl ScrapedDocTest {
858848
langstr: LangString,
859849
text: String,
860850
span: Span,
851+
global_crate_attrs: Vec<String>,
861852
) -> Self {
862853
let mut item_path = logical_path.join("::");
863854
item_path.retain(|c| c != ' ');
@@ -867,7 +858,7 @@ impl ScrapedDocTest {
867858
let name =
868859
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
869860

870-
Self { filename, line, langstr, text, name, span }
861+
Self { filename, line, langstr, text, name, span, global_crate_attrs }
871862
}
872863
fn edition(&self, opts: &RustdocOptions) -> Edition {
873864
self.langstr.edition.unwrap_or(opts.edition)
@@ -949,6 +940,7 @@ impl CreateRunnableDocTests {
949940
let edition = scraped_test.edition(&self.rustdoc_options);
950941
let doctest = BuildDocTestBuilder::new(&scraped_test.text)
951942
.crate_name(&self.opts.crate_name)
943+
.global_crate_attrs(scraped_test.global_crate_attrs.clone())
952944
.edition(edition)
953945
.can_merge_doctests(self.can_merge_doctests)
954946
.test_id(test_id)

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

src/librustdoc/doctest/runner.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::html::markdown::{Ignore, LangString};
1212
/// Convenient type to merge compatible doctests into one.
1313
pub(crate) struct DocTestRunner {
1414
crate_attrs: FxIndexSet<String>,
15+
global_crate_attrs: FxIndexSet<String>,
1516
ids: String,
1617
output: String,
1718
output_merged_tests: String,
@@ -23,6 +24,7 @@ impl DocTestRunner {
2324
pub(crate) fn new() -> Self {
2425
Self {
2526
crate_attrs: FxIndexSet::default(),
27+
global_crate_attrs: FxIndexSet::default(),
2628
ids: String::new(),
2729
output: String::new(),
2830
output_merged_tests: String::new(),
@@ -46,6 +48,9 @@ impl DocTestRunner {
4648
for line in doctest.crate_attrs.split('\n') {
4749
self.crate_attrs.insert(line.to_string());
4850
}
51+
for line in &doctest.global_crate_attrs {
52+
self.global_crate_attrs.insert(line.to_string());
53+
}
4954
}
5055
self.ids.push_str(&format!(
5156
"tests.push({}::TEST);\n",
@@ -85,16 +90,16 @@ impl DocTestRunner {
8590
code_prefix.push('\n');
8691
}
8792

88-
if opts.attrs.is_empty() {
93+
if self.global_crate_attrs.is_empty() {
8994
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
9095
// lints that are commonly triggered in doctests. The crate-level test attributes are
9196
// commonly used to make tests fail in case they trigger warnings, so having this there in
9297
// that case may cause some tests to pass when they shouldn't have.
9398
code_prefix.push_str("#![allow(unused)]\n");
9499
}
95100

96-
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
97-
for attr in &opts.attrs {
101+
// Next, any attributes that came from #![doc(test(attr(...)))].
102+
for attr in &self.global_crate_attrs {
98103
code_prefix.push_str(&format!("#![{attr}]\n"));
99104
}
100105

src/librustdoc/doctest/rust.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ use std::cell::Cell;
44
use std::env;
55
use std::sync::Arc;
66

7+
use rustc_ast_pretty::pprust;
78
use rustc_data_structures::fx::FxHashSet;
89
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
910
use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
1011
use rustc_middle::hir::nested_filter;
1112
use rustc_middle::ty::TyCtxt;
1213
use rustc_resolve::rustdoc::span_of_fragments;
1314
use rustc_span::source_map::SourceMap;
14-
use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span};
15+
use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym};
1516

1617
use super::{DocTestVisitor, ScrapedDocTest};
1718
use crate::clean::{Attributes, extract_cfg_from_attrs};
@@ -22,6 +23,7 @@ struct RustCollector {
2223
tests: Vec<ScrapedDocTest>,
2324
cur_path: Vec<String>,
2425
position: Span,
26+
global_crate_attrs: Vec<String>,
2527
}
2628

2729
impl RustCollector {
@@ -75,6 +77,7 @@ impl DocTestVisitor for RustCollector {
7577
config,
7678
test,
7779
span,
80+
self.global_crate_attrs.clone(),
7881
));
7982
}
8083

@@ -94,6 +97,7 @@ impl<'tcx> HirCollector<'tcx> {
9497
cur_path: vec![],
9598
position: DUMMY_SP,
9699
tests: vec![],
100+
global_crate_attrs: Vec::new(),
97101
};
98102
Self { codes, tcx, collector }
99103
}
@@ -170,6 +174,40 @@ impl<'tcx> intravisit::Visitor<'tcx> for HirCollector<'tcx> {
170174
self.tcx
171175
}
172176

177+
fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _s: Span, hir_id: hir::HirId) {
178+
let attrs = self.tcx.hir_attrs(hir_id);
179+
180+
if !attrs.is_empty() {
181+
// Try collecting `#![doc(test(attr(...)))]` from the attribute module
182+
let old_len = self.collector.global_crate_attrs.len();
183+
for doc_test_attrs in attrs
184+
.iter()
185+
.filter(|a| a.has_name(sym::doc))
186+
.flat_map(|a| a.meta_item_list().unwrap_or_default())
187+
.filter(|a| a.has_name(sym::test))
188+
{
189+
let Some(doc_test_attrs) = doc_test_attrs.meta_item_list() else { continue };
190+
for attr in doc_test_attrs
191+
.iter()
192+
.filter(|a| a.has_name(sym::attr))
193+
.flat_map(|a| a.meta_item_list().unwrap_or_default())
194+
.map(|i| pprust::meta_list_item_to_string(i))
195+
{
196+
// Add the additional attributes to the global_crate_attrs vector
197+
self.collector.global_crate_attrs.push(attr);
198+
}
199+
}
200+
201+
let r = intravisit::walk_mod(self, m);
202+
203+
// Restore global_crate_attrs to it's previous size/content
204+
self.collector.global_crate_attrs.truncate(old_len);
205+
r
206+
} else {
207+
intravisit::walk_mod(self, m)
208+
}
209+
}
210+
173211
fn visit_item(&mut self, item: &'tcx hir::Item<'_>) {
174212
let name = match &item.kind {
175213
hir::ItemKind::Impl(impl_) => {

0 commit comments

Comments
 (0)