Skip to content

Commit

Permalink
Add --extern-loc to augment unused crate dependency diagnostics
Browse files Browse the repository at this point in the history
This allows a build system to indicate a location in its own dependency
specification files (eg Cargo's `Cargo.toml`) which can be reported
along side any unused crate dependency.

This supports several types of location:
 - 'json' - provide some json-structured data, which is included in the json diagnostics
     in a `tool_metadata` field
 - 'raw' - emit the provided string into the output. This also appears as a json string in
     `tool_metadata`.

If no `--extern-location` is explicitly provided then a default json entry of the form
`"tool_metadata":{"name":<cratename>,"path":<cratepath>}` is emitted.
  • Loading branch information
jsgf committed Feb 7, 2021
1 parent 9778068 commit 82ccb65
Show file tree
Hide file tree
Showing 44 changed files with 512 additions and 47 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock
Expand Up @@ -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"
Expand Down Expand Up @@ -3914,6 +3914,7 @@ dependencies = [
"rustc_index",
"rustc_middle",
"rustc_parse_format",
"rustc_serialize",
"rustc_session",
"rustc_span",
"rustc_target",
Expand Down
24 changes: 24 additions & 0 deletions compiler/rustc_errors/src/diagnostic.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -303,6 +305,7 @@ impl Diagnostic {
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
tool_metadata: Default::default(),
});
self
}
Expand All @@ -328,6 +331,7 @@ impl Diagnostic {
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
tool_metadata: Default::default(),
});
self
}
Expand All @@ -354,6 +358,7 @@ impl Diagnostic {
msg: msg.to_owned(),
style: SuggestionStyle::CompletelyHidden,
applicability,
tool_metadata: Default::default(),
});
self
}
Expand Down Expand Up @@ -408,6 +413,7 @@ impl Diagnostic {
msg: msg.to_owned(),
style,
applicability,
tool_metadata: Default::default(),
});
self
}
Expand Down Expand Up @@ -446,6 +452,7 @@ impl Diagnostic {
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
applicability,
tool_metadata: Default::default(),
});
self
}
Expand Down Expand Up @@ -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() {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_errors/src/json.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -180,6 +181,8 @@ struct Diagnostic {
children: Vec<Diagnostic>,
/// The message as rustc would render it.
rendered: Option<String>,
/// Extra tool metadata
tool_metadata: ToolMetadata,
}

#[derive(Encodable)]
Expand Down Expand Up @@ -269,6 +272,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
Expand Down Expand Up @@ -312,6 +316,7 @@ impl Diagnostic {
.chain(sugg)
.collect(),
rendered: Some(output),
tool_metadata: ToolMetadata::default(),
}
}

Expand All @@ -327,6 +332,7 @@ impl Diagnostic {
.unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
children: vec![],
rendered: None,
tool_metadata: ToolMetadata::default(),
}
}
}
Expand Down
35 changes: 34 additions & 1 deletion compiler/rustc_errors/src/lib.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -73,6 +76,35 @@ impl SuggestionStyle {
}
}

#[derive(Clone, Debug, PartialEq, Default)]
pub struct ToolMetadata(pub Option<Json>);

impl ToolMetadata {
fn new(json: Json) -> Self {
ToolMetadata(Some(json))
}
}

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
Expand Down Expand Up @@ -106,6 +138,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)]
Expand Down Expand Up @@ -775,7 +809,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();
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/Cargo.toml
Expand Up @@ -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" }
31 changes: 29 additions & 2 deletions compiler/rustc_lint/src/context.rs
Expand Up @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand All @@ -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
Expand Down
27 changes: 23 additions & 4 deletions compiler/rustc_metadata/src/creader.rs
Expand Up @@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -881,8 +900,8 @@ impl<'a> CrateLoader<'a> {
name,
self.local_crate_name,
name),
diag,
);
}
}
}

Expand Down

0 comments on commit 82ccb65

Please sign in to comment.