11use rustc_ast as ast;
2- use rustc_data_structures:: fx:: FxHashMap ;
2+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
33use rustc_data_structures:: sync:: Lrc ;
44use rustc_errors:: { ColorConfig , ErrorReported } ;
55use rustc_hir as hir;
@@ -23,6 +23,8 @@ use std::panic;
2323use std:: path:: PathBuf ;
2424use std:: process:: { self , Command , Stdio } ;
2525use std:: str;
26+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
27+ use std:: sync:: { Arc , Mutex } ;
2628
2729use crate :: clean:: Attributes ;
2830use 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+
236272fn 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
724787impl 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