Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 0b7a598

Browse files
committedFeb 8, 2021
Auto merge of rust-lang#72603 - jsgf:extern-loc, r=nikomatsakis
Implement `--extern-location` This PR implements `--extern-location` as a followup to rust-lang#72342 as part of the implementation of rust-lang#57274. The goal of this PR is to allow rustc, in coordination with the build system, to present a useful diagnostic about how to remove an unnecessary dependency from a dependency specification file (eg Cargo.toml). EDIT: Updated to current PR state. The location is specified for each named crate - that is, for a given `--extern foo[=path]` there can also be `--extern-location foo=<location>`. It supports ~~three~~ two styles of location: ~~1. `--extern-location foo=file:<path>:<line>` - a file path and line specification 1. `--extern-location foo=span:<path>:<start>:<end>` - a span specified as a file and start and end byte offsets~~ 1. `--extern-location foo=raw:<anything>` - a raw string which is included in the output 1. `--extern-location foo=json:<anything>` - an arbitrary Json structure which is emitted via Json diagnostics in a `tool_metadata` field. ~~1 & 2 are turned into an internal `Span`, so long as the path exists and is readable, and the location is meaningful (within the file, etc). This is used as the `Span` for a fix suggestion which is reported like other fix suggestions.~~ `raw` and `json` are for the case where the location isn't best expressed as a file and location within that file. For example, it could be a rule name and the name of a dependency within that rule. `rustc` makes no attempt to parse the raw string, and simply includes it in the output diagnostic text. `json` is only included in json diagnostics. `raw` is emitted as text and also as a json string in `tool_metadata`. If no `--extern-location` option is specified then it will emit a default json structure consisting of `{"name": name, "path": path}` corresponding to the name and path in `--extern name=path`. This is a prototype/RFC to make some of the earlier conversations more concrete. It doesn't stand on its own - it's only useful if implemented by Cargo and other build systems. There's also a ton of implementation details which I'd appreciate a second eye on as well. ~~**NOTE** The first commit in this PR is rust-lang#72342 and should be ignored for the purposes of review. The first commit is a very simplistic implementation which is basically raw-only, presented as a MVP. The second implements the full thing, and subsequent commits are incremental fixes.~~ cc `@ehuss` `@est31` `@petrochenkov` `@estebank`
2 parents bb587b1 + 91d8c3b commit 0b7a598

37 files changed

+534
-10
lines changed
 

‎Cargo.lock

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,9 +726,9 @@ dependencies = [
726726

727727
[[package]]
728728
name = "const_fn"
729-
version = "0.4.2"
729+
version = "0.4.3"
730730
source = "registry+https://github.com/rust-lang/crates.io-index"
731-
checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
731+
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
732732

733733
[[package]]
734734
name = "constant_time_eq"
@@ -3914,6 +3914,7 @@ dependencies = [
39143914
"rustc_index",
39153915
"rustc_middle",
39163916
"rustc_parse_format",
3917+
"rustc_serialize",
39173918
"rustc_session",
39183919
"rustc_span",
39193920
"rustc_target",

‎compiler/rustc_errors/src/diagnostic.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use crate::Level;
44
use crate::Substitution;
55
use crate::SubstitutionPart;
66
use crate::SuggestionStyle;
7+
use crate::ToolMetadata;
78
use rustc_lint_defs::Applicability;
9+
use rustc_serialize::json::Json;
810
use rustc_span::{MultiSpan, Span, DUMMY_SP};
911
use std::fmt;
1012

@@ -303,6 +305,7 @@ impl Diagnostic {
303305
msg: msg.to_owned(),
304306
style: SuggestionStyle::ShowCode,
305307
applicability,
308+
tool_metadata: Default::default(),
306309
});
307310
self
308311
}
@@ -328,6 +331,7 @@ impl Diagnostic {
328331
msg: msg.to_owned(),
329332
style: SuggestionStyle::ShowCode,
330333
applicability,
334+
tool_metadata: Default::default(),
331335
});
332336
self
333337
}
@@ -354,6 +358,7 @@ impl Diagnostic {
354358
msg: msg.to_owned(),
355359
style: SuggestionStyle::CompletelyHidden,
356360
applicability,
361+
tool_metadata: Default::default(),
357362
});
358363
self
359364
}
@@ -408,6 +413,7 @@ impl Diagnostic {
408413
msg: msg.to_owned(),
409414
style,
410415
applicability,
416+
tool_metadata: Default::default(),
411417
});
412418
self
413419
}
@@ -446,6 +452,7 @@ impl Diagnostic {
446452
msg: msg.to_owned(),
447453
style: SuggestionStyle::ShowCode,
448454
applicability,
455+
tool_metadata: Default::default(),
449456
});
450457
self
451458
}
@@ -515,6 +522,23 @@ impl Diagnostic {
515522
self
516523
}
517524

525+
/// Adds a suggestion intended only for a tool. The intent is that the metadata encodes
526+
/// the suggestion in a tool-specific way, as it may not even directly involve Rust code.
527+
pub fn tool_only_suggestion_with_metadata(
528+
&mut self,
529+
msg: &str,
530+
applicability: Applicability,
531+
tool_metadata: Json,
532+
) {
533+
self.suggestions.push(CodeSuggestion {
534+
substitutions: vec![],
535+
msg: msg.to_owned(),
536+
style: SuggestionStyle::CompletelyHidden,
537+
applicability,
538+
tool_metadata: ToolMetadata::new(tool_metadata),
539+
})
540+
}
541+
518542
pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
519543
self.span = sp.into();
520544
if let Some(span) = self.span.primary_span() {

‎compiler/rustc_errors/src/json.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_span::source_map::{FilePathMapping, SourceMap};
1414
use crate::emitter::{Emitter, HumanReadableErrorType};
1515
use crate::registry::Registry;
1616
use crate::DiagnosticId;
17+
use crate::ToolMetadata;
1718
use crate::{CodeSuggestion, SubDiagnostic};
1819
use rustc_lint_defs::{Applicability, FutureBreakage};
1920

@@ -26,6 +27,7 @@ use std::sync::{Arc, Mutex};
2627
use std::vec;
2728

2829
use rustc_serialize::json::{as_json, as_pretty_json};
30+
use rustc_serialize::{Encodable, Encoder};
2931

3032
#[cfg(test)]
3133
mod tests;
@@ -168,7 +170,8 @@ impl Emitter for JsonEmitter {
168170

169171
// The following data types are provided just for serialisation.
170172

171-
#[derive(Encodable)]
173+
// NOTE: this has a manual implementation of Encodable which needs to be updated in
174+
// parallel.
172175
struct Diagnostic {
173176
/// The primary error message.
174177
message: String,
@@ -180,6 +183,65 @@ struct Diagnostic {
180183
children: Vec<Diagnostic>,
181184
/// The message as rustc would render it.
182185
rendered: Option<String>,
186+
/// Extra tool metadata
187+
tool_metadata: ToolMetadata,
188+
}
189+
190+
macro_rules! encode_fields {
191+
(
192+
$enc:expr, // encoder
193+
$idx:expr, // starting field index
194+
$struct:expr, // struct we're serializing
195+
$struct_name:ident, // struct name
196+
[ $($name:ident),+$(,)? ], // fields to encode
197+
[ $($ignore:ident),+$(,)? ] // fields we're skipping
198+
) => {
199+
{
200+
// Pattern match to make sure all fields are accounted for
201+
let $struct_name { $($name,)+ $($ignore: _,)+ } = $struct;
202+
let mut idx = $idx;
203+
$(
204+
$enc.emit_struct_field(
205+
stringify!($name),
206+
idx,
207+
|enc| $name.encode(enc),
208+
)?;
209+
idx += 1;
210+
)+
211+
idx
212+
}
213+
};
214+
}
215+
216+
// Special-case encoder to skip tool_metadata if not set
217+
impl<E: Encoder> Encodable<E> for Diagnostic {
218+
fn encode(&self, s: &mut E) -> Result<(), E::Error> {
219+
s.emit_struct("diagnostic", 7, |s| {
220+
let mut idx = 0;
221+
222+
idx = encode_fields!(
223+
s,
224+
idx,
225+
self,
226+
Self,
227+
[message, code, level, spans, children, rendered],
228+
[tool_metadata]
229+
);
230+
if self.tool_metadata.is_set() {
231+
idx = encode_fields!(
232+
s,
233+
idx,
234+
self,
235+
Self,
236+
[tool_metadata],
237+
[message, code, level, spans, children, rendered]
238+
);
239+
}
240+
241+
let _ = idx;
242+
Ok(())
243+
})
244+
}
183245
}
184246

185247
#[derive(Encodable)]
@@ -269,6 +331,7 @@ impl Diagnostic {
269331
spans: DiagnosticSpan::from_suggestion(sugg, je),
270332
children: vec![],
271333
rendered: None,
334+
tool_metadata: sugg.tool_metadata.clone(),
272335
});
273336

274337
// generate regular command line output and store it in the json
@@ -312,6 +375,7 @@ impl Diagnostic {
312375
.chain(sugg)
313376
.collect(),
314377
rendered: Some(output),
378+
tool_metadata: ToolMetadata::default(),
315379
}
316380
}
317381

@@ -327,6 +391,7 @@ impl Diagnostic {
327391
.unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
328392
children: vec![],
329393
rendered: None,
394+
tool_metadata: ToolMetadata::default(),
330395
}
331396
}
332397
}

‎compiler/rustc_errors/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ use rustc_data_structures::sync::{self, Lock, Lrc};
2323
use rustc_data_structures::AtomicRef;
2424
use rustc_lint_defs::FutureBreakage;
2525
pub use rustc_lint_defs::{pluralize, Applicability};
26+
use rustc_serialize::json::Json;
27+
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
2628
use rustc_span::source_map::SourceMap;
2729
use rustc_span::{Loc, MultiSpan, Span};
2830

2931
use std::borrow::Cow;
32+
use std::hash::{Hash, Hasher};
3033
use std::panic;
3134
use std::path::Path;
3235
use std::{error, fmt};
@@ -73,6 +76,39 @@ impl SuggestionStyle {
7376
}
7477
}
7578

79+
#[derive(Clone, Debug, PartialEq, Default)]
80+
pub struct ToolMetadata(pub Option<Json>);
81+
82+
impl ToolMetadata {
83+
fn new(json: Json) -> Self {
84+
ToolMetadata(Some(json))
85+
}
86+
87+
fn is_set(&self) -> bool {
88+
self.0.is_some()
89+
}
90+
}
91+
92+
impl Hash for ToolMetadata {
93+
fn hash<H: Hasher>(&self, _state: &mut H) {}
94+
}
95+
96+
// Doesn't really need to round-trip
97+
impl<D: Decoder> Decodable<D> for ToolMetadata {
98+
fn decode(_d: &mut D) -> Result<Self, D::Error> {
99+
Ok(ToolMetadata(None))
100+
}
101+
}
102+
103+
impl<S: Encoder> Encodable<S> for ToolMetadata {
104+
fn encode(&self, e: &mut S) -> Result<(), S::Error> {
105+
match &self.0 {
106+
None => e.emit_unit(),
107+
Some(json) => json.encode(e),
108+
}
109+
}
110+
}
111+
76112
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
77113
pub struct CodeSuggestion {
78114
/// Each substitute can have multiple variants due to multiple
@@ -106,6 +142,8 @@ pub struct CodeSuggestion {
106142
/// which are useful for users but not useful for
107143
/// tools like rustfix
108144
pub applicability: Applicability,
145+
/// Tool-specific metadata
146+
pub tool_metadata: ToolMetadata,
109147
}
110148

111149
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
@@ -775,7 +813,6 @@ impl HandlerInner {
775813
}
776814

777815
let already_emitted = |this: &mut Self| {
778-
use std::hash::Hash;
779816
let mut hasher = StableHasher::new();
780817
diagnostic.hash(&mut hasher);
781818
let diagnostic_hash = hasher.finish();

‎compiler/rustc_lint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ rustc_data_structures = { path = "../rustc_data_structures" }
1919
rustc_feature = { path = "../rustc_feature" }
2020
rustc_index = { path = "../rustc_index" }
2121
rustc_session = { path = "../rustc_session" }
22+
rustc_serialize = { path = "../rustc_serialize" }
2223
rustc_trait_selection = { path = "../rustc_trait_selection" }
2324
rustc_parse_format = { path = "../rustc_parse_format" }

‎compiler/rustc_lint/src/context.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use crate::passes::{EarlyLintPassObject, LateLintPassObject};
2121
use rustc_ast as ast;
2222
use rustc_data_structures::fx::FxHashMap;
2323
use rustc_data_structures::sync;
24-
use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err, Applicability};
24+
use rustc_errors::{
25+
add_elided_lifetime_in_path_suggestion, struct_span_err, Applicability, SuggestionStyle,
26+
};
2527
use rustc_hir as hir;
2628
use rustc_hir::def::Res;
2729
use rustc_hir::def_id::{CrateNum, DefId};
@@ -32,7 +34,8 @@ use rustc_middle::middle::stability;
3234
use rustc_middle::ty::layout::{LayoutError, TyAndLayout};
3335
use rustc_middle::ty::print::with_no_trimmed_paths;
3436
use rustc_middle::ty::{self, print::Printer, subst::GenericArg, Ty, TyCtxt};
35-
use rustc_session::lint::BuiltinLintDiagnostics;
37+
use rustc_serialize::json::Json;
38+
use rustc_session::lint::{BuiltinLintDiagnostics, ExternDepSpec};
3639
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
3740
use rustc_session::Session;
3841
use rustc_session::SessionLintStore;
@@ -639,6 +642,30 @@ pub trait LintContext: Sized {
639642
BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
640643
db.span_label(span, "the attribute is introduced here");
641644
}
645+
BuiltinLintDiagnostics::ExternDepSpec(krate, loc) => {
646+
let json = match loc {
647+
ExternDepSpec::Json(json) => {
648+
db.help(&format!("remove unnecessary dependency `{}`", krate));
649+
json
650+
}
651+
ExternDepSpec::Raw(raw) => {
652+
db.help(&format!("remove unnecessary dependency `{}` at `{}`", krate, raw));
653+
db.span_suggestion_with_style(
654+
DUMMY_SP,
655+
"raw extern location",
656+
raw.clone(),
657+
Applicability::Unspecified,
658+
SuggestionStyle::CompletelyHidden,
659+
);
660+
Json::String(raw)
661+
}
662+
};
663+
db.tool_only_suggestion_with_metadata(
664+
"json extern location",
665+
Applicability::Unspecified,
666+
json
667+
);
668+
}
642669
}
643670
// Rewrap `db`, and pass control to the user.
644671
decorate(LintDiagnosticBuilder::new(db));

‎compiler/rustc_lint_defs/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate rustc_macros;
44
pub use self::Level::*;
55
use rustc_ast::node_id::{NodeId, NodeMap};
66
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
7+
use rustc_serialize::json::Json;
78
use rustc_span::edition::Edition;
89
use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
910
use rustc_target::spec::abi::Abi;
@@ -239,6 +240,13 @@ impl<HCX> ToStableHashKey<HCX> for LintId {
239240
}
240241
}
241242

243+
// Duplicated from rustc_session::config::ExternDepSpec to avoid cyclic dependency
244+
#[derive(PartialEq)]
245+
pub enum ExternDepSpec {
246+
Json(Json),
247+
Raw(String),
248+
}
249+
242250
// This could be a closure, but then implementing derive trait
243251
// becomes hacky (and it gets allocated).
244252
#[derive(PartialEq)]
@@ -257,6 +265,7 @@ pub enum BuiltinLintDiagnostics {
257265
UnusedDocComment(Span),
258266
PatternsInFnsWithoutBody(Span, Ident),
259267
LegacyDeriveHelpers(Span),
268+
ExternDepSpec(String, ExternDepSpec),
260269
}
261270

262271
/// Lints that are buffered up early on in the `Session` before the

‎compiler/rustc_metadata/src/creader.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ use rustc_index::vec::IndexVec;
1616
use rustc_middle::middle::cstore::{CrateDepKind, CrateSource, ExternCrate};
1717
use rustc_middle::middle::cstore::{ExternCrateSource, MetadataLoaderDyn};
1818
use rustc_middle::ty::TyCtxt;
19+
use rustc_serialize::json::ToJson;
1920
use rustc_session::config::{self, CrateType, ExternLocation};
20-
use rustc_session::lint;
21+
use rustc_session::lint::{self, BuiltinLintDiagnostics, ExternDepSpec};
2122
use rustc_session::output::validate_crate_name;
2223
use rustc_session::search_paths::PathKind;
2324
use rustc_session::{CrateDisambiguator, Session};
@@ -27,6 +28,7 @@ use rustc_span::{Span, DUMMY_SP};
2728
use rustc_target::spec::{PanicStrategy, TargetTriple};
2829

2930
use proc_macro::bridge::client::ProcMacro;
31+
use std::collections::BTreeMap;
3032
use std::path::Path;
3133
use std::{cmp, env};
3234
use tracing::{debug, info};
@@ -871,8 +873,25 @@ impl<'a> CrateLoader<'a> {
871873
// Don't worry about pathless `--extern foo` sysroot references
872874
continue;
873875
}
874-
if !self.used_extern_options.contains(&Symbol::intern(name)) {
875-
self.sess.parse_sess.buffer_lint(
876+
if self.used_extern_options.contains(&Symbol::intern(name)) {
877+
continue;
878+
}
879+
880+
// Got a real unused --extern
881+
let diag = match self.sess.opts.extern_dep_specs.get(name) {
882+
Some(loc) => BuiltinLintDiagnostics::ExternDepSpec(name.clone(), loc.into()),
883+
None => {
884+
// If we don't have a specific location, provide a json encoding of the `--extern`
885+
// option.
886+
let meta: BTreeMap<String, String> =
887+
std::iter::once(("name".to_string(), name.to_string())).collect();
888+
BuiltinLintDiagnostics::ExternDepSpec(
889+
name.clone(),
890+
ExternDepSpec::Json(meta.to_json()),
891+
)
892+
}
893+
};
894+
self.sess.parse_sess.buffer_lint_with_diagnostic(
876895
lint::builtin::UNUSED_CRATE_DEPENDENCIES,
877896
span,
878897
ast::CRATE_NODE_ID,
@@ -881,8 +900,8 @@ impl<'a> CrateLoader<'a> {
881900
name,
882901
self.local_crate_name,
883902
name),
903+
diag,
884904
);
885-
}
886905
}
887906
}
888907

‎compiler/rustc_session/src/config.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
1515
use rustc_target::abi::{Align, TargetDataLayout};
1616
use rustc_target::spec::{SplitDebuginfo, Target, TargetTriple};
1717

18+
use rustc_serialize::json;
19+
1820
use crate::parse::CrateConfig;
1921
use rustc_feature::UnstableFeatures;
2022
use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST};
@@ -408,6 +410,9 @@ impl OutputTypes {
408410
#[derive(Clone)]
409411
pub struct Externs(BTreeMap<String, ExternEntry>);
410412

413+
#[derive(Clone)]
414+
pub struct ExternDepSpecs(BTreeMap<String, ExternDepSpec>);
415+
411416
#[derive(Clone, Debug)]
412417
pub struct ExternEntry {
413418
pub location: ExternLocation,
@@ -439,6 +444,27 @@ pub enum ExternLocation {
439444
ExactPaths(BTreeSet<CanonicalizedPath>),
440445
}
441446

447+
/// Supplied source location of a dependency - for example in a build specification
448+
/// file like Cargo.toml. We support several syntaxes: if it makes sense to reference
449+
/// a file and line, then the build system can specify that. On the other hand, it may
450+
/// make more sense to have an arbitrary raw string.
451+
#[derive(Clone, PartialEq)]
452+
pub enum ExternDepSpec {
453+
/// Raw string
454+
Raw(String),
455+
/// Raw data in json format
456+
Json(json::Json),
457+
}
458+
459+
impl<'a> From<&'a ExternDepSpec> for rustc_lint_defs::ExternDepSpec {
460+
fn from(from: &'a ExternDepSpec) -> Self {
461+
match from {
462+
ExternDepSpec::Raw(s) => rustc_lint_defs::ExternDepSpec::Raw(s.clone()),
463+
ExternDepSpec::Json(json) => rustc_lint_defs::ExternDepSpec::Json(json.clone()),
464+
}
465+
}
466+
}
467+
442468
impl Externs {
443469
pub fn new(data: BTreeMap<String, ExternEntry>) -> Externs {
444470
Externs(data)
@@ -466,6 +492,25 @@ impl ExternEntry {
466492
}
467493
}
468494

495+
impl ExternDepSpecs {
496+
pub fn new(data: BTreeMap<String, ExternDepSpec>) -> ExternDepSpecs {
497+
ExternDepSpecs(data)
498+
}
499+
500+
pub fn get(&self, key: &str) -> Option<&ExternDepSpec> {
501+
self.0.get(key)
502+
}
503+
}
504+
505+
impl fmt::Display for ExternDepSpec {
506+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
507+
match self {
508+
ExternDepSpec::Raw(raw) => fmt.write_str(raw),
509+
ExternDepSpec::Json(json) => json::as_json(json).fmt(fmt),
510+
}
511+
}
512+
}
513+
469514
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
470515
pub enum PrintRequest {
471516
FileNames,
@@ -679,6 +724,7 @@ impl Default for Options {
679724
cg: basic_codegen_options(),
680725
error_format: ErrorOutputType::default(),
681726
externs: Externs(BTreeMap::new()),
727+
extern_dep_specs: ExternDepSpecs(BTreeMap::new()),
682728
crate_name: None,
683729
alt_std_name: None,
684730
libs: Vec::new(),
@@ -1105,6 +1151,12 @@ pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
11051151
"Specify where an external rust library is located",
11061152
"NAME[=PATH]",
11071153
),
1154+
opt::multi_s(
1155+
"",
1156+
"extern-location",
1157+
"Location where an external crate dependency is specified",
1158+
"NAME=LOCATION",
1159+
),
11081160
opt::opt_s("", "sysroot", "Override the system root", "PATH"),
11091161
opt::multi("Z", "", "Set internal debugging options", "FLAG"),
11101162
opt::opt_s(
@@ -1727,6 +1779,68 @@ pub fn parse_externs(
17271779
Externs(externs)
17281780
}
17291781

1782+
fn parse_extern_dep_specs(
1783+
matches: &getopts::Matches,
1784+
debugging_opts: &DebuggingOptions,
1785+
error_format: ErrorOutputType,
1786+
) -> ExternDepSpecs {
1787+
let is_unstable_enabled = debugging_opts.unstable_options;
1788+
let mut map = BTreeMap::new();
1789+
1790+
for arg in matches.opt_strs("extern-location") {
1791+
if !is_unstable_enabled {
1792+
early_error(
1793+
error_format,
1794+
"`--extern-location` option is unstable: set `-Z unstable-options`",
1795+
);
1796+
}
1797+
1798+
let mut parts = arg.splitn(2, '=');
1799+
let name = parts.next().unwrap_or_else(|| {
1800+
early_error(error_format, "`--extern-location` value must not be empty")
1801+
});
1802+
let loc = parts.next().unwrap_or_else(|| {
1803+
early_error(
1804+
error_format,
1805+
&format!("`--extern-location`: specify location for extern crate `{}`", name),
1806+
)
1807+
});
1808+
1809+
let locparts: Vec<_> = loc.split(":").collect();
1810+
let spec = match &locparts[..] {
1811+
["raw", ..] => {
1812+
// Don't want `:` split string
1813+
let raw = loc.splitn(2, ':').nth(1).unwrap_or_else(|| {
1814+
early_error(error_format, "`--extern-location`: missing `raw` location")
1815+
});
1816+
ExternDepSpec::Raw(raw.to_string())
1817+
}
1818+
["json", ..] => {
1819+
// Don't want `:` split string
1820+
let raw = loc.splitn(2, ':').nth(1).unwrap_or_else(|| {
1821+
early_error(error_format, "`--extern-location`: missing `json` location")
1822+
});
1823+
let json = json::from_str(raw).unwrap_or_else(|_| {
1824+
early_error(
1825+
error_format,
1826+
&format!("`--extern-location`: malformed json location `{}`", raw),
1827+
)
1828+
});
1829+
ExternDepSpec::Json(json)
1830+
}
1831+
[bad, ..] => early_error(
1832+
error_format,
1833+
&format!("unknown location type `{}`: use `raw` or `json`", bad),
1834+
),
1835+
[] => early_error(error_format, "missing location specification"),
1836+
};
1837+
1838+
map.insert(name.to_string(), spec);
1839+
}
1840+
1841+
ExternDepSpecs::new(map)
1842+
}
1843+
17301844
fn parse_remap_path_prefix(
17311845
matches: &getopts::Matches,
17321846
error_format: ErrorOutputType,
@@ -1888,6 +2002,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
18882002
}
18892003

18902004
let externs = parse_externs(matches, &debugging_opts, error_format);
2005+
let extern_dep_specs = parse_extern_dep_specs(matches, &debugging_opts, error_format);
18912006

18922007
let crate_name = matches.opt_str("crate-name");
18932008

@@ -1924,6 +2039,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
19242039
error_format,
19252040
externs,
19262041
unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()),
2042+
extern_dep_specs,
19272043
crate_name,
19282044
alt_std_name: None,
19292045
libs,

‎compiler/rustc_session/src/options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ top_level_options!(
112112
borrowck_mode: BorrowckMode [UNTRACKED],
113113
cg: CodegenOptions [TRACKED],
114114
externs: Externs [UNTRACKED],
115+
extern_dep_specs: ExternDepSpecs [UNTRACKED],
115116
crate_name: Option<String> [TRACKED],
116117
// An optional name to use as the crate for std during std injection,
117118
// written `extern crate name as std`. Defaults to `std`. Used by
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# `extern-location`
2+
3+
MCP for this feature: [#303]
4+
5+
[#303]: https://github.com/rust-lang/compiler-team/issues/303
6+
7+
------------------------
8+
9+
The `unused-extern-crates` lint reports when a crate was specified on the rustc
10+
command-line with `--extern name=path` but no symbols were referenced in it.
11+
This is useful to know, but it's hard to map that back to a specific place a user
12+
or tool could fix (ie, to remove the unused dependency).
13+
14+
The `--extern-location` flag allows the build system to associate a location with
15+
the `--extern` option, which is then emitted as part of the diagnostics. This location
16+
is abstract and just round-tripped through rustc; the compiler never attempts to
17+
interpret it in any way.
18+
19+
There are two supported forms of location: a bare string, or a blob of json:
20+
- `--extern-location foo=raw:Makefile:123` would associate the raw string `Makefile:123`
21+
- `--extern-location 'bar=json:{"target":"//my_project:library","dep":"//common:serde"}` would
22+
associate the json structure with `--extern bar=<path>`, indicating which dependency of
23+
which rule introduced the unused extern crate.
24+
25+
This primarily intended to be used with tooling - for example a linter which can automatically
26+
remove unused dependencies - rather than being directly presented to users.
27+
28+
`raw` locations are presented as part of the normal rendered diagnostics and included in
29+
the json form. `json` locations are only included in the json form of diagnostics,
30+
as a `tool_metadata` field. For `raw` locations `tool_metadata` is simply a json string,
31+
whereas `json` allows the rustc invoker to fully control its form and content.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// --extern-location with bad location type
2+
3+
// aux-crate:bar=bar.rs
4+
// compile-flags:--extern-location bar=badloc:in-the-test-file
5+
6+
#![warn(unused_crate_dependencies)]
7+
8+
fn main() {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: unknown location type `badloc`: use `raw` or `json`
2+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Default extern location from name and path if one isn't specified
2+
3+
// check-pass
4+
// aux-crate:bar=bar.rs
5+
// compile-flags:--error-format json
6+
7+
#![warn(unused_crate_dependencies)]
8+
//~^ WARNING external crate `bar` unused in
9+
10+
fn main() {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{"message":"external crate `bar` unused in `extern_loc_defl_json`: remove the dependency or add `use bar as _;`","code":{"code":"unused_crate_dependencies","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/extern-loc-defl-json.rs","byte_start":146,"byte_end":146,"line_start":7,"line_end":7,"column_start":1,"column_end":1,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":1,"highlight_end":1}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"$DIR/extern-loc-defl-json.rs","byte_start":154,"byte_end":179,"line_start":7,"line_end":7,"column_start":9,"column_end":34,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":9,"highlight_end":34}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"remove unnecessary dependency `bar`","code":null,"level":"help","spans":[],"children":[],"rendered":null},{"message":"json extern location","code":null,"level":"help","spans":[],"children":[],"rendered":null,"tool_metadata":{"name":"bar"}}],"rendered":"warning: external crate `bar` unused in `extern_loc_defl_json`: remove the dependency or add `use bar as _;`
2+
--> $DIR/extern-loc-defl-json.rs:7:1
3+
|
4+
LL | #![warn(unused_crate_dependencies)]
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/extern-loc-defl-json.rs:7:9
9+
|
10+
LL | #![warn(unused_crate_dependencies)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar`
13+
14+
"}
15+
{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
16+
17+
"}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// --extern-location with a raw reference
2+
3+
// aux-crate:bar=bar.rs
4+
// compile-flags:--extern-location bar=json:[{"malformed
5+
6+
#![warn(unused_crate_dependencies)]
7+
8+
fn main() {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: `--extern-location`: malformed json location `[{"malformed`
2+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// --extern-location with a raw reference
2+
3+
// check-pass
4+
// aux-crate:bar=bar.rs
5+
// compile-flags:--extern-location bar=json:{"key":123,"value":{}} --error-format json
6+
7+
#![warn(unused_crate_dependencies)]
8+
//~^ WARNING external crate `bar` unused in
9+
10+
fn main() {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{"message":"external crate `bar` unused in `extern_loc_json_json`: remove the dependency or add `use bar as _;`","code":{"code":"unused_crate_dependencies","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/extern-loc-json-json.rs","byte_start":169,"byte_end":169,"line_start":7,"line_end":7,"column_start":1,"column_end":1,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":1,"highlight_end":1}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"$DIR/extern-loc-json-json.rs","byte_start":177,"byte_end":202,"line_start":7,"line_end":7,"column_start":9,"column_end":34,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":9,"highlight_end":34}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"remove unnecessary dependency `bar`","code":null,"level":"help","spans":[],"children":[],"rendered":null},{"message":"json extern location","code":null,"level":"help","spans":[],"children":[],"rendered":null,"tool_metadata":{"key":123,"value":{}}}],"rendered":"warning: external crate `bar` unused in `extern_loc_json_json`: remove the dependency or add `use bar as _;`
2+
--> $DIR/extern-loc-json-json.rs:7:1
3+
|
4+
LL | #![warn(unused_crate_dependencies)]
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/extern-loc-json-json.rs:7:9
9+
|
10+
LL | #![warn(unused_crate_dependencies)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar`
13+
14+
"}
15+
{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
16+
17+
"}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// --extern-location with a raw reference
2+
3+
// check-pass
4+
// aux-crate:bar=bar.rs
5+
// compile-flags:--extern-location bar=json:{"key":123,"value":{}}
6+
7+
#![warn(unused_crate_dependencies)]
8+
//~^ WARNING external crate `bar` unused in
9+
10+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
warning: external crate `bar` unused in `extern_loc_json`: remove the dependency or add `use bar as _;`
2+
--> $DIR/extern-loc-json.rs:7:1
3+
|
4+
LL | #![warn(unused_crate_dependencies)]
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/extern-loc-json.rs:7:9
9+
|
10+
LL | #![warn(unused_crate_dependencies)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar`
13+
14+
warning: 1 warning emitted
15+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// --extern-location with a raw reference
2+
3+
// aux-crate:bar=bar.rs
4+
// compile-flags:--extern-location bar
5+
6+
#![warn(unused_crate_dependencies)]
7+
8+
fn main() {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: `--extern-location`: specify location for extern crate `bar`
2+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// --extern-location with no type
2+
3+
// aux-crate:bar=bar.rs
4+
// compile-flags:--extern-location bar=missing-loc-type
5+
6+
#![warn(unused_crate_dependencies)]
7+
8+
fn main() {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: unknown location type `missing-loc-type`: use `raw` or `json`
2+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// --extern-location with a raw reference
2+
3+
// check-pass
4+
// aux-crate:bar=bar.rs
5+
// compile-flags:--extern-location bar=raw:in-the-test-file --error-format json
6+
7+
#![warn(unused_crate_dependencies)]
8+
//~^ WARNING external crate `bar` unused in
9+
10+
fn main() {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{"message":"external crate `bar` unused in `extern_loc_raw_json`: remove the dependency or add `use bar as _;`","code":{"code":"unused_crate_dependencies","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/extern-loc-raw-json.rs","byte_start":162,"byte_end":162,"line_start":7,"line_end":7,"column_start":1,"column_end":1,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":1,"highlight_end":1}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"$DIR/extern-loc-raw-json.rs","byte_start":170,"byte_end":195,"line_start":7,"line_end":7,"column_start":9,"column_end":34,"is_primary":true,"text":[{"text":"#![warn(unused_crate_dependencies)]","highlight_start":9,"highlight_end":34}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"remove unnecessary dependency `bar` at `in-the-test-file`","code":null,"level":"help","spans":[],"children":[],"rendered":null},{"message":"raw extern location","code":null,"level":"help","spans":[{"file_name":"$DIR/extern-loc-raw-json.rs","byte_start":0,"byte_end":0,"line_start":1,"line_end":1,"column_start":1,"column_end":1,"is_primary":true,"text":[],"label":null,"suggested_replacement":"in-the-test-file","suggestion_applicability":"Unspecified","expansion":null}],"children":[],"rendered":null},{"message":"json extern location","code":null,"level":"help","spans":[],"children":[],"rendered":null,"tool_metadata":"in-the-test-file"}],"rendered":"warning: external crate `bar` unused in `extern_loc_raw_json`: remove the dependency or add `use bar as _;`
2+
--> $DIR/extern-loc-raw-json.rs:7:1
3+
|
4+
LL | #![warn(unused_crate_dependencies)]
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/extern-loc-raw-json.rs:7:9
9+
|
10+
LL | #![warn(unused_crate_dependencies)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar` at `in-the-test-file`
13+
14+
"}
15+
{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
16+
17+
"}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// --extern-location with a raw reference
2+
3+
// aux-crate:bar=bar.rs
4+
// compile-flags:--extern-location bar=raw
5+
6+
#![warn(unused_crate_dependencies)]
7+
8+
fn main() {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: `--extern-location`: missing `raw` location
2+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// --extern-location with a raw reference
2+
3+
// check-pass
4+
// aux-crate:bar=bar.rs
5+
// compile-flags:--extern-location bar=raw:in-the-test-file
6+
7+
#![warn(unused_crate_dependencies)]
8+
//~^ WARNING external crate `bar` unused in
9+
10+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
warning: external crate `bar` unused in `extern_loc_raw`: remove the dependency or add `use bar as _;`
2+
--> $DIR/extern-loc-raw.rs:7:1
3+
|
4+
LL | #![warn(unused_crate_dependencies)]
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/extern-loc-raw.rs:7:9
9+
|
10+
LL | #![warn(unused_crate_dependencies)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar` at `in-the-test-file`
13+
14+
warning: 1 warning emitted
15+

‎src/test/ui/unused-crate-deps/libfib.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ LL | pub fn fib(n: u32) -> Vec<u32> {
55
| ^
66
|
77
= note: requested on the command line with `-W unused-crate-dependencies`
8+
= help: remove unnecessary dependency `bar`
89

910
warning: 1 warning emitted
1011

‎src/test/ui/unused-crate-deps/test.mk

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Everyone uses make for building Rust
2+
3+
foo: bar.rlib
4+
$(RUSTC) --crate-type bin --extern bar=bar.rlib
5+
6+
%.rlib: %.rs
7+
$(RUSTC) --crate-type lib $<

‎src/test/ui/unused-crate-deps/unused-aliases.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ note: the lint level is defined here
99
|
1010
LL | #![warn(unused_crate_dependencies)]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `barbar`
1213

1314
warning: 1 warning emitted
1415

‎src/test/ui/unused-crate-deps/warn-attr.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ note: the lint level is defined here
99
|
1010
LL | #![warn(unused_crate_dependencies)]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove unnecessary dependency `bar`
1213

1314
warning: 1 warning emitted
1415

‎src/test/ui/unused-crate-deps/warn-cmdline-static.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ LL | fn main() {}
55
| ^
66
|
77
= note: requested on the command line with `-W unused-crate-dependencies`
8+
= help: remove unnecessary dependency `bar`
89

910
warning: 1 warning emitted
1011

‎src/test/ui/unused-crate-deps/warn-cmdline.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ LL | fn main() {}
55
| ^
66
|
77
= note: requested on the command line with `-W unused-crate-dependencies`
8+
= help: remove unnecessary dependency `bar`
89

910
warning: 1 warning emitted
1011

0 commit comments

Comments
 (0)
This repository has been archived.