Skip to content

Commit 94222b7

Browse files
authored
Rollup merge of rust-lang#73945 - est31:unused_externs, r=Mark-Simulacrum
Add an unstable --json=unused-externs flag to print unused externs This adds an unstable flag to print a list of the extern names not used by cargo. This PR will enable cargo to collect unused dependencies from all units and provide warnings. The companion PR to cargo is: rust-lang/cargo#8437 The goal is eventual stabilization of this flag in rustc as well as in cargo. Discussion of this feature is mostly contained inside these threads: rust-lang#57274 rust-lang#72342 rust-lang#72603 The feature builds upon the internal datastructures added by rust-lang#72342 Externs are uniquely identified by name and the information is sufficient for cargo. If the mode is enabled, rustc will print json messages like: ``` {"unused_extern_names":["byteorder","openssl","webpki"]} ``` For a crate that got passed byteorder, openssl and webpki dependencies but needed none of them. ### Q: Why not pass -Wunused-crate-dependencies? A: See [ehuss's comment here](rust-lang#57274 (comment)) TLDR: it's cleaner. Rust's warning system wasn't built to be filtered or edited by cargo. Even a basic implementation of the feature would have to change the "n warnings emitted" line that rustc prints at the end. Cargo ideally wants to synthesize its own warnings anyways. For example, it would be hard for rustc to emit warnings like "dependency foo is only used by dev targets", suggesting to make it a dev-dependency instead. ### Q: Make rustc emit used or unused externs? A: Emitting used externs has the advantage that it simplifies cargo's collection job. However, emitting unused externs creates less data to be communicated between rustc and cargo. Often you want to paste a cargo command obtained from `cargo build -vv` for doing something completely unrelated. The message is emitted always, even if no warning or error is emitted. At that point, even this tiny difference in "noise" matters. That's why I went with emitting unused externs. ### Q: One json msg per extern or a collective json msg? A: Same as above, the data format should be concise. Having 30 lines for the 30 crates a crate uses would be disturbing to readers. Also it helps the cargo implementation to know that there aren't more unused deps coming. ### Q: Why use names of externs instead of e.g. paths? A: Names are both sufficient as well as neccessary to uniquely identify a passed `--extern` arg. Names are sufficient because you *must* pass a name when passing an `--extern` arg. Passing a path is optional on the other hand so rustc might also figure out a crate's location from the file system. You can also put multiple paths for the same extern name, via e.g. `--extern hello=/usr/lib/hello.rmeta --extern hello=/usr/local/lib/hello.rmeta`, but rustc will only ever use one of those paths. Also, paths don't identify a dependency uniquely as it is possible to have multiple different extern names point to the same path. So paths are ill-suited for identification. ### Q: What about 2015 edition crates? A: They are fully supported. Even on the 2015 edition, an explicit `--extern` flag is is required to enable `extern crate foo;` to work (outside of sysroot crates, which this flag doesn't warn about anyways). So the lint would still fire on 2015 edition crates if you haven't included a dependency specified in Cargo.toml using `extern crate foo;` or similar. The lint won't fire if your sole use in the crate is through a `extern crate foo;` statement, but that's not its job. For detecting unused `extern crate foo` statements, there is the `unused_extern_crates` lint which can be enabled by `#![warn(unused_extern_crates)]` or similar. cc `@jsgf` `@ehuss` `@petrochenkov` `@estebank`
2 parents f98135b + d018ef1 commit 94222b7

File tree

9 files changed

+218
-13
lines changed

9 files changed

+218
-13
lines changed

compiler/rustc_errors/src/emitter.rs

+3
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ pub trait Emitter {
195195

196196
fn emit_future_breakage_report(&mut self, _diags: Vec<(FutureBreakage, Diagnostic)>) {}
197197

198+
/// Emit list of unused externs
199+
fn emit_unused_externs(&mut self, _lint_level: &str, _unused_externs: &[&str]) {}
200+
198201
/// Checks if should show explanations about "rustc --explain"
199202
fn should_show_explain(&self) -> bool {
200203
true

compiler/rustc_errors/src/json.rs

+25
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,19 @@ impl Emitter for JsonEmitter {
159159
}
160160
}
161161

162+
fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
163+
let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
164+
let result = if self.pretty {
165+
writeln!(&mut self.dst, "{}", as_pretty_json(&data))
166+
} else {
167+
writeln!(&mut self.dst, "{}", as_json(&data))
168+
}
169+
.and_then(|_| self.dst.flush());
170+
if let Err(e) = result {
171+
panic!("failed to print unused externs: {:?}", e);
172+
}
173+
}
174+
162175
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
163176
Some(&self.sm)
164177
}
@@ -322,6 +335,18 @@ struct FutureIncompatReport {
322335
future_incompat_report: Vec<FutureBreakageItem>,
323336
}
324337

338+
// NOTE: Keep this in sync with the equivalent structs in rustdoc's
339+
// doctest component (as well as cargo).
340+
// We could unify this struct the one in rustdoc but they have different
341+
// ownership semantics, so doing so would create wasteful allocations.
342+
#[derive(Encodable)]
343+
struct UnusedExterns<'a, 'b, 'c> {
344+
/// The severity level of the unused dependencies lint
345+
lint_level: &'a str,
346+
/// List of unused externs by their names.
347+
unused_extern_names: &'b [&'c str],
348+
}
349+
325350
impl Diagnostic {
326351
fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
327352
let sugg = diag.suggestions.iter().map(|sugg| Diagnostic {

compiler/rustc_errors/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,10 @@ impl Handler {
765765
self.inner.borrow_mut().emitter.emit_future_breakage_report(diags)
766766
}
767767

768+
pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) {
769+
self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs)
770+
}
771+
768772
pub fn delay_as_bug(&self, diagnostic: Diagnostic) {
769773
self.inner.borrow_mut().delay_as_bug(diagnostic)
770774
}
@@ -839,6 +843,10 @@ impl HandlerInner {
839843
self.emitter.emit_artifact_notification(path, artifact_type);
840844
}
841845

846+
fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
847+
self.emitter.emit_unused_externs(lint_level, unused_externs);
848+
}
849+
842850
fn treat_err_as_bug(&self) -> bool {
843851
self.flags.treat_err_as_bug.map_or(false, |c| self.err_count() >= c.get())
844852
}

compiler/rustc_interface/src/passes.rs

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
1616
use rustc_hir::definitions::Definitions;
1717
use rustc_hir::Crate;
1818
use rustc_lint::LintStore;
19+
use rustc_metadata::creader::CStore;
1920
use rustc_middle::arena::Arena;
2021
use rustc_middle::dep_graph::DepGraph;
2122
use rustc_middle::middle;
@@ -831,6 +832,12 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> {
831832
});
832833

833834
sess.time("looking_for_derive_registrar", || proc_macro_decls::find(tcx));
835+
836+
let cstore = tcx
837+
.cstore_as_any()
838+
.downcast_ref::<CStore>()
839+
.expect("`tcx.cstore` is not a `CStore`");
840+
cstore.report_unused_deps(tcx);
834841
},
835842
{
836843
par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {

compiler/rustc_metadata/src/creader.rs

+34-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct CStore {
4646
/// This map is used to verify we get no hash conflicts between
4747
/// `StableCrateId` values.
4848
stable_crate_ids: FxHashMap<StableCrateId, CrateNum>,
49+
50+
/// Unused externs of the crate
51+
unused_externs: Vec<Symbol>,
4952
}
5053

5154
pub struct CrateLoader<'a> {
@@ -190,6 +193,27 @@ impl CStore {
190193
crate fn has_global_allocator(&self) -> bool {
191194
self.has_global_allocator
192195
}
196+
197+
pub fn report_unused_deps(&self, tcx: TyCtxt<'_>) {
198+
// We put the check for the option before the lint_level_at_node call
199+
// because the call mutates internal state and introducing it
200+
// leads to some ui tests failing.
201+
if !tcx.sess.opts.json_unused_externs {
202+
return;
203+
}
204+
let level = tcx
205+
.lint_level_at_node(lint::builtin::UNUSED_CRATE_DEPENDENCIES, rustc_hir::CRATE_HIR_ID)
206+
.0;
207+
if level != lint::Level::Allow {
208+
let unused_externs =
209+
self.unused_externs.iter().map(|ident| ident.to_ident_string()).collect::<Vec<_>>();
210+
let unused_externs = unused_externs.iter().map(String::as_str).collect::<Vec<&str>>();
211+
tcx.sess
212+
.parse_sess
213+
.span_diagnostic
214+
.emit_unused_externs(level.as_str(), &unused_externs);
215+
}
216+
}
193217
}
194218

195219
impl<'a> CrateLoader<'a> {
@@ -217,6 +241,7 @@ impl<'a> CrateLoader<'a> {
217241
allocator_kind: None,
218242
has_global_allocator: false,
219243
stable_crate_ids,
244+
unused_externs: Vec::new(),
220245
},
221246
used_extern_options: Default::default(),
222247
}
@@ -904,11 +929,17 @@ impl<'a> CrateLoader<'a> {
904929
// Don't worry about pathless `--extern foo` sysroot references
905930
continue;
906931
}
907-
if self.used_extern_options.contains(&Symbol::intern(name)) {
932+
let name_interned = Symbol::intern(name);
933+
if self.used_extern_options.contains(&name_interned) {
908934
continue;
909935
}
910936

911937
// Got a real unused --extern
938+
if self.sess.opts.json_unused_externs {
939+
self.cstore.unused_externs.push(name_interned);
940+
continue;
941+
}
942+
912943
let diag = match self.sess.opts.extern_dep_specs.get(name) {
913944
Some(loc) => BuiltinLintDiagnostics::ExternDepSpec(name.clone(), loc.into()),
914945
None => {
@@ -941,9 +972,9 @@ impl<'a> CrateLoader<'a> {
941972
self.inject_allocator_crate(krate);
942973
self.inject_panic_runtime(krate);
943974

944-
info!("{:?}", CrateDump(&self.cstore));
945-
946975
self.report_unused_deps(krate);
976+
977+
info!("{:?}", CrateDump(&self.cstore));
947978
}
948979

949980
pub fn process_extern_crate(

compiler/rustc_session/src/config.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ impl Externs {
456456
pub fn iter(&self) -> BTreeMapIter<'_, String, ExternEntry> {
457457
self.0.iter()
458458
}
459+
460+
pub fn len(&self) -> usize {
461+
self.0.len()
462+
}
459463
}
460464

461465
impl ExternEntry {
@@ -698,6 +702,7 @@ impl Default for Options {
698702
remap_path_prefix: Vec::new(),
699703
edition: DEFAULT_EDITION,
700704
json_artifact_notifications: false,
705+
json_unused_externs: false,
701706
pretty: None,
702707
}
703708
}
@@ -1196,15 +1201,23 @@ pub fn parse_color(matches: &getopts::Matches) -> ColorConfig {
11961201
}
11971202
}
11981203

1204+
/// Possible json config files
1205+
pub struct JsonConfig {
1206+
pub json_rendered: HumanReadableErrorType,
1207+
pub json_artifact_notifications: bool,
1208+
pub json_unused_externs: bool,
1209+
}
1210+
11991211
/// Parse the `--json` flag.
12001212
///
12011213
/// The first value returned is how to render JSON diagnostics, and the second
12021214
/// is whether or not artifact notifications are enabled.
1203-
pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool) {
1215+
pub fn parse_json(matches: &getopts::Matches) -> JsonConfig {
12041216
let mut json_rendered: fn(ColorConfig) -> HumanReadableErrorType =
12051217
HumanReadableErrorType::Default;
12061218
let mut json_color = ColorConfig::Never;
12071219
let mut json_artifact_notifications = false;
1220+
let mut json_unused_externs = false;
12081221
for option in matches.opt_strs("json") {
12091222
// For now conservatively forbid `--color` with `--json` since `--json`
12101223
// won't actually be emitting any colors and anything colorized is
@@ -1221,14 +1234,20 @@ pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool)
12211234
"diagnostic-short" => json_rendered = HumanReadableErrorType::Short,
12221235
"diagnostic-rendered-ansi" => json_color = ColorConfig::Always,
12231236
"artifacts" => json_artifact_notifications = true,
1237+
"unused-externs" => json_unused_externs = true,
12241238
s => early_error(
12251239
ErrorOutputType::default(),
12261240
&format!("unknown `--json` option `{}`", s),
12271241
),
12281242
}
12291243
}
12301244
}
1231-
(json_rendered(json_color), json_artifact_notifications)
1245+
1246+
JsonConfig {
1247+
json_rendered: json_rendered(json_color),
1248+
json_artifact_notifications,
1249+
json_unused_externs,
1250+
}
12321251
}
12331252

12341253
/// Parses the `--error-format` flag.
@@ -1806,7 +1825,8 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
18061825

18071826
let edition = parse_crate_edition(matches);
18081827

1809-
let (json_rendered, json_artifact_notifications) = parse_json(matches);
1828+
let JsonConfig { json_rendered, json_artifact_notifications, json_unused_externs } =
1829+
parse_json(matches);
18101830

18111831
let error_format = parse_error_format(matches, color, json_rendered);
18121832

@@ -1819,6 +1839,14 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
18191839
let mut debugging_opts = build_debugging_options(matches, error_format);
18201840
check_debug_option_stability(&debugging_opts, error_format, json_rendered);
18211841

1842+
if !debugging_opts.unstable_options && json_unused_externs {
1843+
early_error(
1844+
error_format,
1845+
"the `-Z unstable-options` flag must also be passed to enable \
1846+
the flag `--json=unused-externs`",
1847+
);
1848+
}
1849+
18221850
let output_types = parse_output_types(&debugging_opts, matches, error_format);
18231851

18241852
let mut cg = build_codegen_options(matches, error_format);
@@ -1979,6 +2007,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
19792007
remap_path_prefix,
19802008
edition,
19812009
json_artifact_notifications,
2010+
json_unused_externs,
19822011
pretty,
19832012
}
19842013
}

compiler/rustc_session/src/options.rs

+3
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ top_level_options!(
147147
// by the compiler.
148148
json_artifact_notifications: bool [TRACKED],
149149

150+
// `true` if we're emitting a JSON blob containing the unused externs
151+
json_unused_externs: bool [UNTRACKED],
152+
150153
pretty: Option<PpMode> [UNTRACKED],
151154
}
152155
);

src/librustdoc/config.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ crate struct Options {
154154
/// If this option is set to `true`, rustdoc will only run checks and not generate
155155
/// documentation.
156156
crate run_check: bool,
157+
/// Whether doctests should emit unused externs
158+
crate json_unused_externs: bool,
157159
}
158160

159161
impl fmt::Debug for Options {
@@ -352,7 +354,8 @@ impl Options {
352354
}
353355

354356
let color = config::parse_color(&matches);
355-
let (json_rendered, _artifacts) = config::parse_json(&matches);
357+
let config::JsonConfig { json_rendered, json_unused_externs, .. } =
358+
config::parse_json(&matches);
356359
let error_format = config::parse_error_format(&matches, color, json_rendered);
357360

358361
let codegen_options = build_codegen_options(matches, error_format);
@@ -687,6 +690,7 @@ impl Options {
687690
},
688691
crate_name,
689692
output_format,
693+
json_unused_externs,
690694
})
691695
}
692696

0 commit comments

Comments
 (0)