about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-02-08 02:23:17 +0000
committerbors <bors@rust-lang.org>2021-02-08 02:23:17 +0000
commit0b7a598e12649d7ab2415a82cbc3fea879fa9dab (patch)
tree186f81bd7d9aa52d578c253d084d0ce7a188e649
parentbb587b1a1737738658d2eaecd4c8c1cab555257a (diff)
parent91d8c3b521b95b64eec3329c2ddbddb2315024d9 (diff)
downloadrust-0b7a598e12649d7ab2415a82cbc3fea879fa9dab.tar.gz
rust-0b7a598e12649d7ab2415a82cbc3fea879fa9dab.zip
Auto merge of #72603 - jsgf:extern-loc, r=nikomatsakis
Implement `--extern-location`

This PR implements `--extern-location` as a followup to #72342 as part of the implementation of #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 #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`
-rw-r--r--Cargo.lock5
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs24
-rw-r--r--compiler/rustc_errors/src/json.rs67
-rw-r--r--compiler/rustc_errors/src/lib.rs39
-rw-r--r--compiler/rustc_lint/Cargo.toml1
-rw-r--r--compiler/rustc_lint/src/context.rs31
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs9
-rw-r--r--compiler/rustc_metadata/src/creader.rs27
-rw-r--r--compiler/rustc_session/src/config.rs116
-rw-r--r--compiler/rustc_session/src/options.rs1
-rw-r--r--src/doc/unstable-book/src/compiler-flags/extern-location.md31
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-bad-loctype.rs8
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-bad-loctype.stderr2
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-defl-json.rs10
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-defl-json.stderr17
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json-bad-json.rs8
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json-bad-json.stderr2
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json-json.rs10
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json-json.stderr17
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json.rs10
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-json.stderr15
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-missing-loc.rs8
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-missing-loc.stderr2
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-missing-loctype.rs8
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-missing-loctype.stderr2
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw-json.rs10
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw-json.stderr17
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.rs8
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.stderr2
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw.rs10
-rw-r--r--src/test/ui/unused-crate-deps/extern-loc-raw.stderr15
-rw-r--r--src/test/ui/unused-crate-deps/libfib.stderr1
-rw-r--r--src/test/ui/unused-crate-deps/test.mk7
-rw-r--r--src/test/ui/unused-crate-deps/unused-aliases.stderr1
-rw-r--r--src/test/ui/unused-crate-deps/warn-attr.stderr1
-rw-r--r--src/test/ui/unused-crate-deps/warn-cmdline-static.stderr1
-rw-r--r--src/test/ui/unused-crate-deps/warn-cmdline.stderr1
37 files changed, 534 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6f29120455e..fd23b2764c0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -726,9 +726,9 @@ dependencies = [
 
 [[package]]
 name = "const_fn"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
 
 [[package]]
 name = "constant_time_eq"
@@ -3914,6 +3914,7 @@ dependencies = [
  "rustc_index",
  "rustc_middle",
  "rustc_parse_format",
+ "rustc_serialize",
  "rustc_session",
  "rustc_span",
  "rustc_target",
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index e61476bf23e..ef4a45cab41 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -4,7 +4,9 @@ use crate::Level;
 use crate::Substitution;
 use crate::SubstitutionPart;
 use crate::SuggestionStyle;
+use crate::ToolMetadata;
 use rustc_lint_defs::Applicability;
+use rustc_serialize::json::Json;
 use rustc_span::{MultiSpan, Span, DUMMY_SP};
 use std::fmt;
 
@@ -303,6 +305,7 @@ impl Diagnostic {
             msg: msg.to_owned(),
             style: SuggestionStyle::ShowCode,
             applicability,
+            tool_metadata: Default::default(),
         });
         self
     }
@@ -328,6 +331,7 @@ impl Diagnostic {
             msg: msg.to_owned(),
             style: SuggestionStyle::ShowCode,
             applicability,
+            tool_metadata: Default::default(),
         });
         self
     }
@@ -354,6 +358,7 @@ impl Diagnostic {
             msg: msg.to_owned(),
             style: SuggestionStyle::CompletelyHidden,
             applicability,
+            tool_metadata: Default::default(),
         });
         self
     }
@@ -408,6 +413,7 @@ impl Diagnostic {
             msg: msg.to_owned(),
             style,
             applicability,
+            tool_metadata: Default::default(),
         });
         self
     }
@@ -446,6 +452,7 @@ impl Diagnostic {
             msg: msg.to_owned(),
             style: SuggestionStyle::ShowCode,
             applicability,
+            tool_metadata: Default::default(),
         });
         self
     }
@@ -515,6 +522,23 @@ impl Diagnostic {
         self
     }
 
+    /// Adds a suggestion intended only for a tool. The intent is that the metadata encodes
+    /// the suggestion in a tool-specific way, as it may not even directly involve Rust code.
+    pub fn tool_only_suggestion_with_metadata(
+        &mut self,
+        msg: &str,
+        applicability: Applicability,
+        tool_metadata: Json,
+    ) {
+        self.suggestions.push(CodeSuggestion {
+            substitutions: vec![],
+            msg: msg.to_owned(),
+            style: SuggestionStyle::CompletelyHidden,
+            applicability,
+            tool_metadata: ToolMetadata::new(tool_metadata),
+        })
+    }
+
     pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
         self.span = sp.into();
         if let Some(span) = self.span.primary_span() {
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index d57beb1148a..c27b39a9d62 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -14,6 +14,7 @@ use rustc_span::source_map::{FilePathMapping, SourceMap};
 use crate::emitter::{Emitter, HumanReadableErrorType};
 use crate::registry::Registry;
 use crate::DiagnosticId;
+use crate::ToolMetadata;
 use crate::{CodeSuggestion, SubDiagnostic};
 use rustc_lint_defs::{Applicability, FutureBreakage};
 
@@ -26,6 +27,7 @@ use std::sync::{Arc, Mutex};
 use std::vec;
 
 use rustc_serialize::json::{as_json, as_pretty_json};
+use rustc_serialize::{Encodable, Encoder};
 
 #[cfg(test)]
 mod tests;
@@ -168,7 +170,8 @@ impl Emitter for JsonEmitter {
 
 // The following data types are provided just for serialisation.
 
-#[derive(Encodable)]
+// NOTE: this has a manual implementation of Encodable which needs to be updated in
+// parallel.
 struct Diagnostic {
     /// The primary error message.
     message: String,
@@ -180,6 +183,65 @@ struct Diagnostic {
     children: Vec<Diagnostic>,
     /// The message as rustc would render it.
     rendered: Option<String>,
+    /// Extra tool metadata
+    tool_metadata: ToolMetadata,
+}
+
+macro_rules! encode_fields {
+    (
+        $enc:expr,                  // encoder
+        $idx:expr,                  // starting field index
+        $struct:expr,               // struct we're serializing
+        $struct_name:ident,         // struct name
+        [ $($name:ident),+$(,)? ],  // fields to encode
+        [ $($ignore:ident),+$(,)? ] // fields we're skipping
+    ) => {
+        {
+            // Pattern match to make sure all fields are accounted for
+            let $struct_name { $($name,)+ $($ignore: _,)+ } = $struct;
+            let mut idx = $idx;
+            $(
+                $enc.emit_struct_field(
+                    stringify!($name),
+                    idx,
+                    |enc| $name.encode(enc),
+                )?;
+                idx += 1;
+            )+
+            idx
+        }
+    };
+}
+
+// Special-case encoder to skip tool_metadata if not set
+impl<E: Encoder> Encodable<E> for Diagnostic {
+    fn encode(&self, s: &mut E) -> Result<(), E::Error> {
+        s.emit_struct("diagnostic", 7, |s| {
+            let mut idx = 0;
+
+            idx = encode_fields!(
+                s,
+                idx,
+                self,
+                Self,
+                [message, code, level, spans, children, rendered],
+                [tool_metadata]
+            );
+            if self.tool_metadata.is_set() {
+                idx = encode_fields!(
+                    s,
+                    idx,
+                    self,
+                    Self,
+                    [tool_metadata],
+                    [message, code, level, spans, children, rendered]
+                );
+            }
+
+            let _ = idx;
+            Ok(())
+        })
+    }
 }
 
 #[derive(Encodable)]
@@ -269,6 +331,7 @@ impl Diagnostic {
             spans: DiagnosticSpan::from_suggestion(sugg, je),
             children: vec![],
             rendered: None,
+            tool_metadata: sugg.tool_metadata.clone(),
         });
 
         // generate regular command line output and store it in the json
@@ -312,6 +375,7 @@ impl Diagnostic {
                 .chain(sugg)
                 .collect(),
             rendered: Some(output),
+            tool_metadata: ToolMetadata::default(),
         }
     }
 
@@ -327,6 +391,7 @@ impl Diagnostic {
                 .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
             children: vec![],
             rendered: None,
+            tool_metadata: ToolMetadata::default(),
         }
     }
 }
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index aa882332099..9800ed9bfa9 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -23,10 +23,13 @@ use rustc_data_structures::sync::{self, Lock, Lrc};
 use rustc_data_structures::AtomicRef;
 use rustc_lint_defs::FutureBreakage;
 pub use rustc_lint_defs::{pluralize, Applicability};
+use rustc_serialize::json::Json;
+use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use rustc_span::source_map::SourceMap;
 use rustc_span::{Loc, MultiSpan, Span};
 
 use std::borrow::Cow;
+use std::hash::{Hash, Hasher};
 use std::panic;
 use std::path::Path;
 use std::{error, fmt};
@@ -73,6 +76,39 @@ impl SuggestionStyle {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Default)]
+pub struct ToolMetadata(pub Option<Json>);
+
+impl ToolMetadata {
+    fn new(json: Json) -> Self {
+        ToolMetadata(Some(json))
+    }
+
+    fn is_set(&self) -> bool {
+        self.0.is_some()
+    }
+}
+
+impl Hash for ToolMetadata {
+    fn hash<H: Hasher>(&self, _state: &mut H) {}
+}
+
+// Doesn't really need to round-trip
+impl<D: Decoder> Decodable<D> for ToolMetadata {
+    fn decode(_d: &mut D) -> Result<Self, D::Error> {
+        Ok(ToolMetadata(None))
+    }
+}
+
+impl<S: Encoder> Encodable<S> for ToolMetadata {
+    fn encode(&self, e: &mut S) -> Result<(), S::Error> {
+        match &self.0 {
+            None => e.emit_unit(),
+            Some(json) => json.encode(e),
+        }
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
 pub struct CodeSuggestion {
     /// Each substitute can have multiple variants due to multiple
@@ -106,6 +142,8 @@ pub struct CodeSuggestion {
     /// which are useful for users but not useful for
     /// tools like rustfix
     pub applicability: Applicability,
+    /// Tool-specific metadata
+    pub tool_metadata: ToolMetadata,
 }
 
 #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
@@ -775,7 +813,6 @@ impl HandlerInner {
         }
 
         let already_emitted = |this: &mut Self| {
-            use std::hash::Hash;
             let mut hasher = StableHasher::new();
             diagnostic.hash(&mut hasher);
             let diagnostic_hash = hasher.finish();
diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml
index c56eb09b634..90badd3d573 100644
--- a/compiler/rustc_lint/Cargo.toml
+++ b/compiler/rustc_lint/Cargo.toml
@@ -19,5 +19,6 @@ rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_feature = { path = "../rustc_feature" }
 rustc_index = { path = "../rustc_index" }
 rustc_session = { path = "../rustc_session" }
+rustc_serialize = { path = "../rustc_serialize" }
 rustc_trait_selection = { path = "../rustc_trait_selection" }
 rustc_parse_format = { path = "../rustc_parse_format" }
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 254220839aa..b8db51f590d 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -21,7 +21,9 @@ use crate::passes::{EarlyLintPassObject, LateLintPassObject};
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync;
-use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err, Applicability};
+use rustc_errors::{
+    add_elided_lifetime_in_path_suggestion, struct_span_err, Applicability, SuggestionStyle,
+};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{CrateNum, DefId};
@@ -32,7 +34,8 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::layout::{LayoutError, TyAndLayout};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self, print::Printer, subst::GenericArg, Ty, TyCtxt};
-use rustc_session::lint::BuiltinLintDiagnostics;
+use rustc_serialize::json::Json;
+use rustc_session::lint::{BuiltinLintDiagnostics, ExternDepSpec};
 use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
 use rustc_session::Session;
 use rustc_session::SessionLintStore;
@@ -639,6 +642,30 @@ pub trait LintContext: Sized {
                 BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
                     db.span_label(span, "the attribute is introduced here");
                 }
+                BuiltinLintDiagnostics::ExternDepSpec(krate, loc) => {
+                    let json = match loc {
+                        ExternDepSpec::Json(json) => {
+                            db.help(&format!("remove unnecessary dependency `{}`", krate));
+                            json
+                        }
+                        ExternDepSpec::Raw(raw) => {
+                            db.help(&format!("remove unnecessary dependency `{}` at `{}`", krate, raw));
+                            db.span_suggestion_with_style(
+                                DUMMY_SP,
+                                "raw extern location",
+                                raw.clone(),
+                                Applicability::Unspecified,
+                                SuggestionStyle::CompletelyHidden,
+                            );
+                            Json::String(raw)
+                        }
+                    };
+                    db.tool_only_suggestion_with_metadata(
+                        "json extern location",
+                        Applicability::Unspecified,
+                        json
+                    );
+                }
             }
             // Rewrap `db`, and pass control to the user.
             decorate(LintDiagnosticBuilder::new(db));
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 594e2cbd3ae..4c7d3f6c8c0 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -4,6 +4,7 @@ extern crate rustc_macros;
 pub use self::Level::*;
 use rustc_ast::node_id::{NodeId, NodeMap};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
+use rustc_serialize::json::Json;
 use rustc_span::edition::Edition;
 use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
 use rustc_target::spec::abi::Abi;
@@ -239,6 +240,13 @@ impl<HCX> ToStableHashKey<HCX> for LintId {
     }
 }
 
+// Duplicated from rustc_session::config::ExternDepSpec to avoid cyclic dependency
+#[derive(PartialEq)]
+pub enum ExternDepSpec {
+    Json(Json),
+    Raw(String),
+}
+
 // This could be a closure, but then implementing derive trait
 // becomes hacky (and it gets allocated).
 #[derive(PartialEq)]
@@ -257,6 +265,7 @@ pub enum BuiltinLintDiagnostics {
     UnusedDocComment(Span),
     PatternsInFnsWithoutBody(Span, Ident),
     LegacyDeriveHelpers(Span),
+    ExternDepSpec(String, ExternDepSpec),
 }
 
 /// Lints that are buffered up early on in the `Session` before the
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index e3fbd1a2b29..63c6f369eb6 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -16,8 +16,9 @@ use rustc_index::vec::IndexVec;
 use rustc_middle::middle::cstore::{CrateDepKind, CrateSource, ExternCrate};
 use rustc_middle::middle::cstore::{ExternCrateSource, MetadataLoaderDyn};
 use rustc_middle::ty::TyCtxt;
+use rustc_serialize::json::ToJson;
 use rustc_session::config::{self, CrateType, ExternLocation};
-use rustc_session::lint;
+use rustc_session::lint::{self, BuiltinLintDiagnostics, ExternDepSpec};
 use rustc_session::output::validate_crate_name;
 use rustc_session::search_paths::PathKind;
 use rustc_session::{CrateDisambiguator, Session};
@@ -27,6 +28,7 @@ use rustc_span::{Span, DUMMY_SP};
 use rustc_target::spec::{PanicStrategy, TargetTriple};
 
 use proc_macro::bridge::client::ProcMacro;
+use std::collections::BTreeMap;
 use std::path::Path;
 use std::{cmp, env};
 use tracing::{debug, info};
@@ -871,8 +873,25 @@ impl<'a> CrateLoader<'a> {
                 // Don't worry about pathless `--extern foo` sysroot references
                 continue;
             }
-            if !self.used_extern_options.contains(&Symbol::intern(name)) {
-                self.sess.parse_sess.buffer_lint(
+            if self.used_extern_options.contains(&Symbol::intern(name)) {
+                continue;
+            }
+
+            // Got a real unused --extern
+            let diag = match self.sess.opts.extern_dep_specs.get(name) {
+                Some(loc) => BuiltinLintDiagnostics::ExternDepSpec(name.clone(), loc.into()),
+                None => {
+                    // If we don't have a specific location, provide a json encoding of the `--extern`
+                    // option.
+                    let meta: BTreeMap<String, String> =
+                        std::iter::once(("name".to_string(), name.to_string())).collect();
+                    BuiltinLintDiagnostics::ExternDepSpec(
+                        name.clone(),
+                        ExternDepSpec::Json(meta.to_json()),
+                    )
+                }
+            };
+            self.sess.parse_sess.buffer_lint_with_diagnostic(
                     lint::builtin::UNUSED_CRATE_DEPENDENCIES,
                     span,
                     ast::CRATE_NODE_ID,
@@ -881,8 +900,8 @@ impl<'a> CrateLoader<'a> {
                         name,
                         self.local_crate_name,
                         name),
+                    diag,
                 );
-            }
         }
     }
 
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 9d73c3b4424..e9ea0ab6f98 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -15,6 +15,8 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_target::abi::{Align, TargetDataLayout};
 use rustc_target::spec::{SplitDebuginfo, Target, TargetTriple};
 
+use rustc_serialize::json;
+
 use crate::parse::CrateConfig;
 use rustc_feature::UnstableFeatures;
 use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST};
@@ -408,6 +410,9 @@ impl OutputTypes {
 #[derive(Clone)]
 pub struct Externs(BTreeMap<String, ExternEntry>);
 
+#[derive(Clone)]
+pub struct ExternDepSpecs(BTreeMap<String, ExternDepSpec>);
+
 #[derive(Clone, Debug)]
 pub struct ExternEntry {
     pub location: ExternLocation,
@@ -439,6 +444,27 @@ pub enum ExternLocation {
     ExactPaths(BTreeSet<CanonicalizedPath>),
 }
 
+/// Supplied source location of a dependency - for example in a build specification
+/// file like Cargo.toml. We support several syntaxes: if it makes sense to reference
+/// a file and line, then the build system can specify that. On the other hand, it may
+/// make more sense to have an arbitrary raw string.
+#[derive(Clone, PartialEq)]
+pub enum ExternDepSpec {
+    /// Raw string
+    Raw(String),
+    /// Raw data in json format
+    Json(json::Json),
+}
+
+impl<'a> From<&'a ExternDepSpec> for rustc_lint_defs::ExternDepSpec {
+    fn from(from: &'a ExternDepSpec) -> Self {
+        match from {
+            ExternDepSpec::Raw(s) => rustc_lint_defs::ExternDepSpec::Raw(s.clone()),
+            ExternDepSpec::Json(json) => rustc_lint_defs::ExternDepSpec::Json(json.clone()),
+        }
+    }
+}
+
 impl Externs {
     pub fn new(data: BTreeMap<String, ExternEntry>) -> Externs {
         Externs(data)
@@ -466,6 +492,25 @@ impl ExternEntry {
     }
 }
 
+impl ExternDepSpecs {
+    pub fn new(data: BTreeMap<String, ExternDepSpec>) -> ExternDepSpecs {
+        ExternDepSpecs(data)
+    }
+
+    pub fn get(&self, key: &str) -> Option<&ExternDepSpec> {
+        self.0.get(key)
+    }
+}
+
+impl fmt::Display for ExternDepSpec {
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ExternDepSpec::Raw(raw) => fmt.write_str(raw),
+            ExternDepSpec::Json(json) => json::as_json(json).fmt(fmt),
+        }
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub enum PrintRequest {
     FileNames,
@@ -679,6 +724,7 @@ impl Default for Options {
             cg: basic_codegen_options(),
             error_format: ErrorOutputType::default(),
             externs: Externs(BTreeMap::new()),
+            extern_dep_specs: ExternDepSpecs(BTreeMap::new()),
             crate_name: None,
             alt_std_name: None,
             libs: Vec::new(),
@@ -1105,6 +1151,12 @@ pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
             "Specify where an external rust library is located",
             "NAME[=PATH]",
         ),
+        opt::multi_s(
+            "",
+            "extern-location",
+            "Location where an external crate dependency is specified",
+            "NAME=LOCATION",
+        ),
         opt::opt_s("", "sysroot", "Override the system root", "PATH"),
         opt::multi("Z", "", "Set internal debugging options", "FLAG"),
         opt::opt_s(
@@ -1727,6 +1779,68 @@ pub fn parse_externs(
     Externs(externs)
 }
 
+fn parse_extern_dep_specs(
+    matches: &getopts::Matches,
+    debugging_opts: &DebuggingOptions,
+    error_format: ErrorOutputType,
+) -> ExternDepSpecs {
+    let is_unstable_enabled = debugging_opts.unstable_options;
+    let mut map = BTreeMap::new();
+
+    for arg in matches.opt_strs("extern-location") {
+        if !is_unstable_enabled {
+            early_error(
+                error_format,
+                "`--extern-location` option is unstable: set `-Z unstable-options`",
+            );
+        }
+
+        let mut parts = arg.splitn(2, '=');
+        let name = parts.next().unwrap_or_else(|| {
+            early_error(error_format, "`--extern-location` value must not be empty")
+        });
+        let loc = parts.next().unwrap_or_else(|| {
+            early_error(
+                error_format,
+                &format!("`--extern-location`: specify location for extern crate `{}`", name),
+            )
+        });
+
+        let locparts: Vec<_> = loc.split(":").collect();
+        let spec = match &locparts[..] {
+            ["raw", ..] => {
+                // Don't want `:` split string
+                let raw = loc.splitn(2, ':').nth(1).unwrap_or_else(|| {
+                    early_error(error_format, "`--extern-location`: missing `raw` location")
+                });
+                ExternDepSpec::Raw(raw.to_string())
+            }
+            ["json", ..] => {
+                // Don't want `:` split string
+                let raw = loc.splitn(2, ':').nth(1).unwrap_or_else(|| {
+                    early_error(error_format, "`--extern-location`: missing `json` location")
+                });
+                let json = json::from_str(raw).unwrap_or_else(|_| {
+                    early_error(
+                        error_format,
+                        &format!("`--extern-location`: malformed json location `{}`", raw),
+                    )
+                });
+                ExternDepSpec::Json(json)
+            }
+            [bad, ..] => early_error(
+                error_format,
+                &format!("unknown location type `{}`: use `raw` or `json`", bad),
+            ),
+            [] => early_error(error_format, "missing location specification"),
+        };
+
+        map.insert(name.to_string(), spec);
+    }
+
+    ExternDepSpecs::new(map)
+}
+
 fn parse_remap_path_prefix(
     matches: &getopts::Matches,
     error_format: ErrorOutputType,
@@ -1888,6 +2002,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
     }
 
     let externs = parse_externs(matches, &debugging_opts, error_format);
+    let extern_dep_specs = parse_extern_dep_specs(matches, &debugging_opts, error_format);
 
     let crate_name = matches.opt_str("crate-name");
 
@@ -1924,6 +2039,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
         error_format,
         externs,
         unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()),
+        extern_dep_specs,
         crate_name,
         alt_std_name: None,
         libs,
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 779e0421636..f78df8a7e29 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -112,6 +112,7 @@ top_level_options!(
         borrowck_mode: BorrowckMode [UNTRACKED],
         cg: CodegenOptions [TRACKED],
         externs: Externs [UNTRACKED],
+        extern_dep_specs: ExternDepSpecs [UNTRACKED],
         crate_name: Option<String> [TRACKED],
         // An optional name to use as the crate for std during std injection,
         // written `extern crate name as std`. Defaults to `std`. Used by
diff --git a/src/doc/unstable-book/src/compiler-flags/extern-location.md b/src/doc/unstable-book/src/compiler-flags/extern-location.md
new file mode 100644
index 00000000000..1c80d5426bf
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/extern-location.md
@@ -0,0 +1,31 @@
+# `extern-location`
+
+MCP for this feature: [#303]
+
+[#303]: https://github.com/rust-lang/compiler-team/issues/303
+
+------------------------
+
+The `unused-extern-crates` lint reports when a crate was specified on the rustc
+command-line with `--extern name=path` but no symbols were referenced in it.
+This is useful to know, but it's hard to map that back to a specific place a user
+or tool could fix (ie, to remove the unused dependency).
+
+The `--extern-location` flag allows the build system to associate a location with
+the `--extern` option, which is then emitted as part of the diagnostics. This location
+is abstract and just round-tripped through rustc; the compiler never attempts to
+interpret it in any way.
+
+There are two supported forms of location: a bare string, or a blob of json:
+- `--extern-location foo=raw:Makefile:123` would associate the raw string `Makefile:123`
+- `--extern-location 'bar=json:{"target":"//my_project:library","dep":"//common:serde"}` would
+  associate the json structure with `--extern bar=<path>`, indicating which dependency of
+  which rule introduced the unused extern crate.
+
+This primarily intended to be used with tooling - for example a linter which can automatically
+remove unused dependencies - rather than being directly presented to users.
+
+`raw` locations are presented as part of the normal rendered diagnostics and included in
+the json form. `json` locations are only included in the json form of diagnostics,
+as a `tool_metadata` field. For `raw` locations `tool_metadata` is simply a json string,
+whereas `json` allows the rustc invoker to fully control its form and content.
diff --git a/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.rs b/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.rs
new file mode 100644
index 00000000000..3e1527e2c2e
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.rs
@@ -0,0 +1,8 @@
+// --extern-location with bad location type
+
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=badloc:in-the-test-file
+
+#![warn(unused_crate_dependencies)]
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.stderr b/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.stderr
new file mode 100644
index 00000000000..12378f12557
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-bad-loctype.stderr
@@ -0,0 +1,2 @@
+error: unknown location type `badloc`: use `raw` or `json`
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-defl-json.rs b/src/test/ui/unused-crate-deps/extern-loc-defl-json.rs
new file mode 100644
index 00000000000..a023f535b81
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-defl-json.rs
@@ -0,0 +1,10 @@
+// Default extern location from name and path if one isn't specified
+
+// check-pass
+// aux-crate:bar=bar.rs
+// compile-flags:--error-format json
+
+#![warn(unused_crate_dependencies)]
+//~^ WARNING external crate `bar` unused in
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-defl-json.stderr b/src/test/ui/unused-crate-deps/extern-loc-defl-json.stderr
new file mode 100644
index 00000000000..cee3f6c1495
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-defl-json.stderr
@@ -0,0 +1,17 @@
+{"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 _;`
+  --> $DIR/extern-loc-defl-json.rs:7:1
+   |
+LL | #![warn(unused_crate_dependencies)]
+   | ^
+   |
+note: the lint level is defined here
+  --> $DIR/extern-loc-defl-json.rs:7:9
+   |
+LL | #![warn(unused_crate_dependencies)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar`
+
+"}
+{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
+
+"}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.rs b/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.rs
new file mode 100644
index 00000000000..6fdf710a126
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.rs
@@ -0,0 +1,8 @@
+// --extern-location with a raw reference
+
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=json:[{"malformed
+
+#![warn(unused_crate_dependencies)]
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.stderr b/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.stderr
new file mode 100644
index 00000000000..20d606372e0
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json-bad-json.stderr
@@ -0,0 +1,2 @@
+error: `--extern-location`: malformed json location `[{"malformed`
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json-json.rs b/src/test/ui/unused-crate-deps/extern-loc-json-json.rs
new file mode 100644
index 00000000000..02a9869151f
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json-json.rs
@@ -0,0 +1,10 @@
+// --extern-location with a raw reference
+
+// check-pass
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=json:{"key":123,"value":{}} --error-format json
+
+#![warn(unused_crate_dependencies)]
+//~^ WARNING external crate `bar` unused in
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json-json.stderr b/src/test/ui/unused-crate-deps/extern-loc-json-json.stderr
new file mode 100644
index 00000000000..5fc8397e469
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json-json.stderr
@@ -0,0 +1,17 @@
+{"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 _;`
+  --> $DIR/extern-loc-json-json.rs:7:1
+   |
+LL | #![warn(unused_crate_dependencies)]
+   | ^
+   |
+note: the lint level is defined here
+  --> $DIR/extern-loc-json-json.rs:7:9
+   |
+LL | #![warn(unused_crate_dependencies)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar`
+
+"}
+{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
+
+"}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json.rs b/src/test/ui/unused-crate-deps/extern-loc-json.rs
new file mode 100644
index 00000000000..212610d532e
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json.rs
@@ -0,0 +1,10 @@
+// --extern-location with a raw reference
+
+// check-pass
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=json:{"key":123,"value":{}}
+
+#![warn(unused_crate_dependencies)]
+//~^ WARNING external crate `bar` unused in
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-json.stderr b/src/test/ui/unused-crate-deps/extern-loc-json.stderr
new file mode 100644
index 00000000000..a6bbc0da1c6
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-json.stderr
@@ -0,0 +1,15 @@
+warning: external crate `bar` unused in `extern_loc_json`: remove the dependency or add `use bar as _;`
+  --> $DIR/extern-loc-json.rs:7:1
+   |
+LL | #![warn(unused_crate_dependencies)]
+   | ^
+   |
+note: the lint level is defined here
+  --> $DIR/extern-loc-json.rs:7:9
+   |
+LL | #![warn(unused_crate_dependencies)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar`
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-missing-loc.rs b/src/test/ui/unused-crate-deps/extern-loc-missing-loc.rs
new file mode 100644
index 00000000000..9339a004d3b
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-missing-loc.rs
@@ -0,0 +1,8 @@
+// --extern-location with a raw reference
+
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar
+
+#![warn(unused_crate_dependencies)]
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-missing-loc.stderr b/src/test/ui/unused-crate-deps/extern-loc-missing-loc.stderr
new file mode 100644
index 00000000000..4584fbfb67f
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-missing-loc.stderr
@@ -0,0 +1,2 @@
+error: `--extern-location`: specify location for extern crate `bar`
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.rs b/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.rs
new file mode 100644
index 00000000000..4768365a653
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.rs
@@ -0,0 +1,8 @@
+// --extern-location with no type
+
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=missing-loc-type
+
+#![warn(unused_crate_dependencies)]
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.stderr b/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.stderr
new file mode 100644
index 00000000000..d0c36ebeb14
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-missing-loctype.stderr
@@ -0,0 +1,2 @@
+error: unknown location type `missing-loc-type`: use `raw` or `json`
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw-json.rs b/src/test/ui/unused-crate-deps/extern-loc-raw-json.rs
new file mode 100644
index 00000000000..207615ccc87
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw-json.rs
@@ -0,0 +1,10 @@
+// --extern-location with a raw reference
+
+// check-pass
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=raw:in-the-test-file --error-format json
+
+#![warn(unused_crate_dependencies)]
+//~^ WARNING external crate `bar` unused in
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw-json.stderr b/src/test/ui/unused-crate-deps/extern-loc-raw-json.stderr
new file mode 100644
index 00000000000..25f099927fd
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw-json.stderr
@@ -0,0 +1,17 @@
+{"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 _;`
+  --> $DIR/extern-loc-raw-json.rs:7:1
+   |
+LL | #![warn(unused_crate_dependencies)]
+   | ^
+   |
+note: the lint level is defined here
+  --> $DIR/extern-loc-raw-json.rs:7:9
+   |
+LL | #![warn(unused_crate_dependencies)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar` at `in-the-test-file`
+
+"}
+{"message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 1 warning emitted
+
+"}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.rs b/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.rs
new file mode 100644
index 00000000000..65b64268394
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.rs
@@ -0,0 +1,8 @@
+// --extern-location with a raw reference
+
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=raw
+
+#![warn(unused_crate_dependencies)]
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.stderr b/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.stderr
new file mode 100644
index 00000000000..4b51266e4f6
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw-missing-loc.stderr
@@ -0,0 +1,2 @@
+error: `--extern-location`: missing `raw` location
+
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw.rs b/src/test/ui/unused-crate-deps/extern-loc-raw.rs
new file mode 100644
index 00000000000..fc3fed1e10e
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw.rs
@@ -0,0 +1,10 @@
+// --extern-location with a raw reference
+
+// check-pass
+// aux-crate:bar=bar.rs
+// compile-flags:--extern-location bar=raw:in-the-test-file
+
+#![warn(unused_crate_dependencies)]
+//~^ WARNING external crate `bar` unused in
+
+fn main() {}
diff --git a/src/test/ui/unused-crate-deps/extern-loc-raw.stderr b/src/test/ui/unused-crate-deps/extern-loc-raw.stderr
new file mode 100644
index 00000000000..2cdd0055866
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/extern-loc-raw.stderr
@@ -0,0 +1,15 @@
+warning: external crate `bar` unused in `extern_loc_raw`: remove the dependency or add `use bar as _;`
+  --> $DIR/extern-loc-raw.rs:7:1
+   |
+LL | #![warn(unused_crate_dependencies)]
+   | ^
+   |
+note: the lint level is defined here
+  --> $DIR/extern-loc-raw.rs:7:9
+   |
+LL | #![warn(unused_crate_dependencies)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar` at `in-the-test-file`
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/unused-crate-deps/libfib.stderr b/src/test/ui/unused-crate-deps/libfib.stderr
index 15833126bd6..479f51bff46 100644
--- a/src/test/ui/unused-crate-deps/libfib.stderr
+++ b/src/test/ui/unused-crate-deps/libfib.stderr
@@ -5,6 +5,7 @@ LL | pub fn fib(n: u32) -> Vec<u32> {
    | ^
    |
    = note: requested on the command line with `-W unused-crate-dependencies`
+   = help: remove unnecessary dependency `bar`
 
 warning: 1 warning emitted
 
diff --git a/src/test/ui/unused-crate-deps/test.mk b/src/test/ui/unused-crate-deps/test.mk
new file mode 100644
index 00000000000..0b98b4e44fb
--- /dev/null
+++ b/src/test/ui/unused-crate-deps/test.mk
@@ -0,0 +1,7 @@
+# Everyone uses make for building Rust
+
+foo: bar.rlib
+	$(RUSTC) --crate-type bin --extern bar=bar.rlib
+
+%.rlib: %.rs
+	$(RUSTC) --crate-type lib $<
diff --git a/src/test/ui/unused-crate-deps/unused-aliases.stderr b/src/test/ui/unused-crate-deps/unused-aliases.stderr
index c8c6c4507b0..1142d156d0e 100644
--- a/src/test/ui/unused-crate-deps/unused-aliases.stderr
+++ b/src/test/ui/unused-crate-deps/unused-aliases.stderr
@@ -9,6 +9,7 @@ note: the lint level is defined here
    |
 LL | #![warn(unused_crate_dependencies)]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `barbar`
 
 warning: 1 warning emitted
 
diff --git a/src/test/ui/unused-crate-deps/warn-attr.stderr b/src/test/ui/unused-crate-deps/warn-attr.stderr
index 0d38315704b..29667d9525c 100644
--- a/src/test/ui/unused-crate-deps/warn-attr.stderr
+++ b/src/test/ui/unused-crate-deps/warn-attr.stderr
@@ -9,6 +9,7 @@ note: the lint level is defined here
    |
 LL | #![warn(unused_crate_dependencies)]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: remove unnecessary dependency `bar`
 
 warning: 1 warning emitted
 
diff --git a/src/test/ui/unused-crate-deps/warn-cmdline-static.stderr b/src/test/ui/unused-crate-deps/warn-cmdline-static.stderr
index 65956461d64..2c0c9215129 100644
--- a/src/test/ui/unused-crate-deps/warn-cmdline-static.stderr
+++ b/src/test/ui/unused-crate-deps/warn-cmdline-static.stderr
@@ -5,6 +5,7 @@ LL | fn main() {}
    | ^
    |
    = note: requested on the command line with `-W unused-crate-dependencies`
+   = help: remove unnecessary dependency `bar`
 
 warning: 1 warning emitted
 
diff --git a/src/test/ui/unused-crate-deps/warn-cmdline.stderr b/src/test/ui/unused-crate-deps/warn-cmdline.stderr
index ea675ba9a1e..2cd49218f5a 100644
--- a/src/test/ui/unused-crate-deps/warn-cmdline.stderr
+++ b/src/test/ui/unused-crate-deps/warn-cmdline.stderr
@@ -5,6 +5,7 @@ LL | fn main() {}
    | ^
    |
    = note: requested on the command line with `-W unused-crate-dependencies`
+   = help: remove unnecessary dependency `bar`
 
 warning: 1 warning emitted