Skip to content

Commit 13371b5

Browse files
committed
Make doctests collect and emit the unused externs
1 parent 2d52006 commit 13371b5

File tree

3 files changed

+88
-7
lines changed

3 files changed

+88
-7
lines changed

compiler/rustc_session/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,10 @@ impl Externs {
485485
pub fn iter(&self) -> BTreeMapIter<'_, String, ExternEntry> {
486486
self.0.iter()
487487
}
488+
489+
pub fn len(&self) -> usize {
490+
self.0.len()
491+
}
488492
}
489493

490494
impl ExternEntry {

src/librustdoc/config.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ crate struct Options {
153153
/// If this option is set to `true`, rustdoc will only run checks and not generate
154154
/// documentation.
155155
crate run_check: bool,
156+
/// Whether doctests should emit unused externs
157+
crate json_unused_externs: bool,
156158
}
157159

158160
impl fmt::Debug for Options {
@@ -323,7 +325,8 @@ impl Options {
323325
}
324326

325327
let color = config::parse_color(&matches);
326-
let config::JsonConfig { json_rendered, .. } = config::parse_json(&matches);
328+
let config::JsonConfig { json_rendered, json_unused_externs, .. } =
329+
config::parse_json(&matches);
327330
let error_format = config::parse_error_format(&matches, color, json_rendered);
328331

329332
let codegen_options = build_codegen_options(matches, error_format);
@@ -644,6 +647,7 @@ impl Options {
644647
},
645648
crate_name,
646649
output_format,
650+
json_unused_externs,
647651
})
648652
}
649653

src/librustdoc/doctest.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_ast as ast;
2-
use rustc_data_structures::fx::FxHashMap;
2+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
33
use rustc_data_structures::sync::Lrc;
44
use rustc_errors::{ColorConfig, ErrorReported};
55
use rustc_hir as hir;
@@ -23,6 +23,8 @@ use std::panic;
2323
use std::path::PathBuf;
2424
use std::process::{self, Command, Stdio};
2525
use std::str;
26+
use std::sync::atomic::{AtomicUsize, Ordering};
27+
use std::sync::{Arc, Mutex};
2628

2729
use crate::clean::Attributes;
2830
use crate::config::Options;
@@ -103,8 +105,10 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
103105

104106
let mut test_args = options.test_args.clone();
105107
let display_warnings = options.display_warnings;
108+
let externs = options.externs.clone();
109+
let json_unused_externs = options.json_unused_externs;
106110

107-
let tests = interface::run_compiler(config, |compiler| {
111+
let res = interface::run_compiler(config, |compiler| {
108112
compiler.enter(|queries| {
109113
let lower_to_hir = queries.lower_to_hir()?;
110114

@@ -147,12 +151,15 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
147151
});
148152
compiler.session().abort_if_errors();
149153

150-
let ret: Result<_, ErrorReported> = Ok(collector.tests);
154+
let unused_extern_reports = collector.unused_extern_reports.clone();
155+
let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
156+
let ret: Result<_, ErrorReported> =
157+
Ok((collector.tests, unused_extern_reports, compiling_test_count));
151158
ret
152159
})
153160
});
154-
let tests = match tests {
155-
Ok(tests) => tests,
161+
let (tests, unused_extern_reports, compiling_test_count) = match res {
162+
Ok(res) => res,
156163
Err(ErrorReported) => return Err(ErrorReported),
157164
};
158165

@@ -164,6 +171,29 @@ crate fn run(options: Options) -> Result<(), ErrorReported> {
164171
Some(testing::Options::new().display_output(display_warnings)),
165172
);
166173

174+
// Collect and warn about unused externs, but only if we've gotten
175+
// reports for each doctest
176+
if json_unused_externs {
177+
let unused_extern_reports: Vec<_> =
178+
std::mem::take(&mut unused_extern_reports.lock().unwrap());
179+
if unused_extern_reports.len() == compiling_test_count {
180+
let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>();
181+
let mut unused_extern_names = unused_extern_reports
182+
.iter()
183+
.map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>())
184+
.fold(extern_names, |uextsa, uextsb| {
185+
uextsa.intersection(&uextsb).map(|v| *v).collect::<FxHashSet<&String>>()
186+
})
187+
.iter()
188+
.map(|v| (*v).clone())
189+
.collect::<Vec<String>>();
190+
unused_extern_names.sort();
191+
let unused_extern_json =
192+
serde_json::to_string(&UnusedExterns { unused_extern_names }).unwrap();
193+
eprintln!("{}", unused_extern_json);
194+
}
195+
}
196+
167197
Ok(())
168198
}
169199

@@ -233,6 +263,12 @@ impl DirState {
233263
}
234264
}
235265

266+
#[derive(serde::Serialize, serde::Deserialize)]
267+
struct UnusedExterns {
268+
/// List of unused externs by their names.
269+
unused_extern_names: Vec<String>,
270+
}
271+
236272
fn run_test(
237273
test: &str,
238274
cratename: &str,
@@ -251,6 +287,7 @@ fn run_test(
251287
outdir: DirState,
252288
path: PathBuf,
253289
test_id: &str,
290+
report_unused_externs: impl Fn(UnusedExterns),
254291
) -> Result<(), TestFailure> {
255292
let (test, line_offset, supports_color) =
256293
make_test(test, Some(cratename), as_test_harness, opts, edition, Some(test_id));
@@ -276,6 +313,11 @@ fn run_test(
276313
if as_test_harness {
277314
compiler.arg("--test");
278315
}
316+
if options.json_unused_externs && !compile_fail {
317+
compiler.arg("--error-format=json");
318+
compiler.arg("--json").arg("unused-externs");
319+
compiler.arg("-Z").arg("unstable-options");
320+
}
279321
for lib_str in &options.lib_strs {
280322
compiler.arg("-L").arg(&lib_str);
281323
}
@@ -335,7 +377,26 @@ fn run_test(
335377
eprint!("{}", self.0);
336378
}
337379
}
338-
let out = str::from_utf8(&output.stderr).unwrap();
380+
let mut out_lines = str::from_utf8(&output.stderr)
381+
.unwrap()
382+
.lines()
383+
.filter(|l| {
384+
if let Ok(uext) = serde_json::from_str::<UnusedExterns>(l) {
385+
report_unused_externs(uext);
386+
false
387+
} else {
388+
true
389+
}
390+
})
391+
.collect::<Vec<_>>();
392+
393+
// Add a \n to the end to properly terminate the last line,
394+
// but only if there was output to be printed
395+
if out_lines.len() > 0 {
396+
out_lines.push("");
397+
}
398+
399+
let out = out_lines.join("\n");
339400
let _bomb = Bomb(&out);
340401
match (output.status.success(), compile_fail) {
341402
(true, true) => {
@@ -719,6 +780,8 @@ crate struct Collector {
719780
source_map: Option<Lrc<SourceMap>>,
720781
filename: Option<PathBuf>,
721782
visited_tests: FxHashMap<(String, usize), usize>,
783+
unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
784+
compiling_test_count: AtomicUsize,
722785
}
723786

724787
impl Collector {
@@ -743,6 +806,8 @@ impl Collector {
743806
source_map,
744807
filename,
745808
visited_tests: FxHashMap::default(),
809+
unused_extern_reports: Default::default(),
810+
compiling_test_count: AtomicUsize::new(0),
746811
}
747812
}
748813

@@ -789,6 +854,10 @@ impl Tester for Collector {
789854
let runtool_args = self.options.runtool_args.clone();
790855
let target = self.options.target.clone();
791856
let target_str = target.to_string();
857+
let unused_externs = self.unused_extern_reports.clone();
858+
if !config.compile_fail {
859+
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
860+
}
792861

793862
// FIXME(#44940): if doctests ever support path remapping, then this filename
794863
// needs to be the result of `SourceMap::span_to_unmapped_path`.
@@ -844,6 +913,9 @@ impl Tester for Collector {
844913
test_type: testing::TestType::DocTest,
845914
},
846915
testfn: testing::DynTestFn(box move || {
916+
let report_unused_externs = |uext| {
917+
unused_externs.lock().unwrap().push(uext);
918+
};
847919
let res = run_test(
848920
&test,
849921
&cratename,
@@ -862,6 +934,7 @@ impl Tester for Collector {
862934
outdir,
863935
path,
864936
&test_id,
937+
report_unused_externs,
865938
);
866939

867940
if let Err(err) = res {

0 commit comments

Comments
 (0)