mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Report unused features
This commit is contained in:
@@ -338,7 +338,11 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send))
|
||||
}
|
||||
}
|
||||
|
||||
Some(Linker::codegen_and_build_linker(tcx, &*compiler.codegen_backend))
|
||||
let linker = Linker::codegen_and_build_linker(tcx, &*compiler.codegen_backend);
|
||||
|
||||
tcx.report_unused_features();
|
||||
|
||||
Some(linker)
|
||||
});
|
||||
|
||||
// Linking is done outside the `compiler.enter()` so that the
|
||||
|
||||
@@ -1330,7 +1330,7 @@ pub struct BuiltinAttribute {
|
||||
safety: AttributeSafety::Normal,
|
||||
template: template!(NameValueStr: "name"),
|
||||
duplicates: ErrorFollowing,
|
||||
gate: Gated{
|
||||
gate: Gated {
|
||||
feature: sym::rustc_attrs,
|
||||
message: "use of an internal attribute",
|
||||
check: Features::rustc_attrs,
|
||||
|
||||
@@ -137,5 +137,5 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option<NonZero<u
|
||||
pub use removed::REMOVED_LANG_FEATURES;
|
||||
pub use unstable::{
|
||||
DEPENDENT_FEATURES, EnabledLangFeature, EnabledLibFeature, Features, INCOMPATIBLE_FEATURES,
|
||||
UNSTABLE_LANG_FEATURES,
|
||||
TRACK_FEATURE, UNSTABLE_LANG_FEATURES,
|
||||
};
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use rustc_data_structures::AtomicRef;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_span::{Span, Symbol, sym};
|
||||
|
||||
use super::{Feature, to_nonzero};
|
||||
|
||||
fn default_track_feature(_: Symbol) {}
|
||||
|
||||
/// Recording used features in the dependency graph so incremental can
|
||||
/// replay used features when needed.
|
||||
pub static TRACK_FEATURE: AtomicRef<fn(Symbol)> = AtomicRef::new(&(default_track_feature as _));
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum FeatureStatus {
|
||||
Default,
|
||||
@@ -103,7 +110,12 @@ pub fn enabled_features_iter_stable_order(
|
||||
|
||||
/// Is the given feature enabled (via `#[feature(...)]`)?
|
||||
pub fn enabled(&self, feature: Symbol) -> bool {
|
||||
self.enabled_features.contains(&feature)
|
||||
if self.enabled_features.contains(&feature) {
|
||||
TRACK_FEATURE(feature);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +136,7 @@ macro_rules! declare_features {
|
||||
impl Features {
|
||||
$(
|
||||
pub fn $feature(&self) -> bool {
|
||||
self.enabled_features.contains(&sym::$feature)
|
||||
self.enabled(sym::$feature)
|
||||
}
|
||||
)*
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
use std::fmt;
|
||||
|
||||
use rustc_errors::DiagInner;
|
||||
use rustc_middle::dep_graph::TaskDepsRef;
|
||||
use rustc_middle::dep_graph::{DepNodeIndex, QuerySideEffect, TaskDepsRef};
|
||||
use rustc_middle::ty::tls;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
fn track_span_parent(def_id: rustc_span::def_id::LocalDefId) {
|
||||
tls::with_context_opt(|icx| {
|
||||
@@ -51,6 +52,25 @@ fn track_diagnostic<R>(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner) -> R)
|
||||
})
|
||||
}
|
||||
|
||||
fn track_feature(feature: Symbol) {
|
||||
tls::with_context_opt(|icx| {
|
||||
let Some(icx) = icx else {
|
||||
return;
|
||||
};
|
||||
let tcx = icx.tcx;
|
||||
|
||||
if let Some(dep_node_index) = tcx.sess.used_features.lock().get(&feature).copied() {
|
||||
tcx.dep_graph.read_index(DepNodeIndex::from_u32(dep_node_index));
|
||||
} else {
|
||||
let dep_node_index = tcx
|
||||
.dep_graph
|
||||
.encode_side_effect(tcx, QuerySideEffect::CheckFeature { symbol: feature });
|
||||
tcx.sess.used_features.lock().insert(feature, dep_node_index.as_u32());
|
||||
tcx.dep_graph.read_index(dep_node_index);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This is a callback from `rustc_hir` as it cannot access the implicit state
|
||||
/// in `rustc_middle` otherwise.
|
||||
fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@@ -70,4 +90,5 @@ pub fn setup_callbacks() {
|
||||
rustc_span::SPAN_TRACK.swap(&(track_span_parent as fn(_)));
|
||||
rustc_hir::def_id::DEF_ID_DEBUG.swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
|
||||
rustc_errors::TRACK_DIAGNOSTIC.swap(&(track_diagnostic as _));
|
||||
rustc_feature::TRACK_FEATURE.swap(&(track_feature as _));
|
||||
}
|
||||
|
||||
@@ -1088,11 +1088,6 @@
|
||||
/// crate-level [`feature` attributes].
|
||||
///
|
||||
/// [`feature` attributes]: https://doc.rust-lang.org/nightly/unstable-book/
|
||||
///
|
||||
/// Note: This lint is currently not functional, see [issue #44232] for
|
||||
/// more details.
|
||||
///
|
||||
/// [issue #44232]: https://github.com/rust-lang/rust/issues/44232
|
||||
pub UNUSED_FEATURES,
|
||||
Warn,
|
||||
"unused features found in crate-level `#[feature]` directives"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
use rustc_macros::{Decodable, Encodable};
|
||||
use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::Symbol;
|
||||
use tracing::{debug, instrument};
|
||||
#[cfg(debug_assertions)]
|
||||
use {super::debug::EdgeFilter, std::env};
|
||||
@@ -45,6 +46,11 @@ pub enum QuerySideEffect {
|
||||
/// the query as green, as that query will have the side
|
||||
/// effect dep node as a dependency.
|
||||
Diagnostic(DiagInner),
|
||||
/// Records the feature used during query execution.
|
||||
/// This feature will be inserted into `sess.used_features`
|
||||
/// if we mark the query as green, as that query will have
|
||||
/// the side effect dep node as a dependency.
|
||||
CheckFeature { symbol: Symbol },
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct DepGraph {
|
||||
@@ -514,29 +520,40 @@ pub fn read_index(&self, dep_node_index: DepNodeIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
/// This encodes a diagnostic by creating a node with an unique index and associating
|
||||
/// `diagnostic` with it, for use in the next session.
|
||||
/// This encodes a side effect by creating a node with an unique index and associating
|
||||
/// it with the node, for use in the next session.
|
||||
#[inline]
|
||||
pub fn record_diagnostic<'tcx>(&self, tcx: TyCtxt<'tcx>, diagnostic: &DiagInner) {
|
||||
if let Some(ref data) = self.data {
|
||||
read_deps(|task_deps| match task_deps {
|
||||
TaskDepsRef::EvalAlways | TaskDepsRef::Ignore => return,
|
||||
TaskDepsRef::Forbid | TaskDepsRef::Allow(..) => {
|
||||
self.read_index(data.encode_diagnostic(tcx, diagnostic));
|
||||
let dep_node_index = data
|
||||
.encode_side_effect(tcx, QuerySideEffect::Diagnostic(diagnostic.clone()));
|
||||
self.read_index(dep_node_index);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
/// This forces a diagnostic node green by running its side effect. `prev_index` would
|
||||
/// refer to a node created used `encode_diagnostic` in the previous session.
|
||||
/// This forces a side effect node green by running its side effect. `prev_index` would
|
||||
/// refer to a node created used `encode_side_effect` in the previous session.
|
||||
#[inline]
|
||||
pub fn force_diagnostic_node<'tcx>(
|
||||
pub fn force_side_effect<'tcx>(&self, tcx: TyCtxt<'tcx>, prev_index: SerializedDepNodeIndex) {
|
||||
if let Some(ref data) = self.data {
|
||||
data.force_side_effect(tcx, prev_index);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encode_side_effect<'tcx>(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
prev_index: SerializedDepNodeIndex,
|
||||
) {
|
||||
side_effect: QuerySideEffect,
|
||||
) -> DepNodeIndex {
|
||||
if let Some(ref data) = self.data {
|
||||
data.force_diagnostic_node(tcx, prev_index);
|
||||
data.encode_side_effect(tcx, side_effect)
|
||||
} else {
|
||||
self.next_virtual_depnode_index()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,10 +690,14 @@ pub fn mark_debug_loaded_from_disk(&self, dep_node: DepNode) {
|
||||
self.debug_loaded_from_disk.lock().insert(dep_node);
|
||||
}
|
||||
|
||||
/// This encodes a diagnostic by creating a node with an unique index and associating
|
||||
/// `diagnostic` with it, for use in the next session.
|
||||
/// This encodes a side effect by creating a node with an unique index and associating
|
||||
/// it with the node, for use in the next session.
|
||||
#[inline]
|
||||
fn encode_diagnostic<'tcx>(&self, tcx: TyCtxt<'tcx>, diagnostic: &DiagInner) -> DepNodeIndex {
|
||||
fn encode_side_effect<'tcx>(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
side_effect: QuerySideEffect,
|
||||
) -> DepNodeIndex {
|
||||
// Use `send_new` so we get an unique index, even though the dep node is not.
|
||||
let dep_node_index = self.current.encoder.send_new(
|
||||
DepNode {
|
||||
@@ -684,28 +705,21 @@ fn encode_diagnostic<'tcx>(&self, tcx: TyCtxt<'tcx>, diagnostic: &DiagInner) ->
|
||||
key_fingerprint: PackedFingerprint::from(Fingerprint::ZERO),
|
||||
},
|
||||
Fingerprint::ZERO,
|
||||
// We want the side effect node to always be red so it will be forced and emit the
|
||||
// diagnostic.
|
||||
// We want the side effect node to always be red so it will be forced and run the
|
||||
// side effect.
|
||||
std::iter::once(DepNodeIndex::FOREVER_RED_NODE).collect(),
|
||||
);
|
||||
let side_effect = QuerySideEffect::Diagnostic(diagnostic.clone());
|
||||
tcx.store_side_effect(dep_node_index, side_effect);
|
||||
dep_node_index
|
||||
}
|
||||
|
||||
/// This forces a diagnostic node green by running its side effect. `prev_index` would
|
||||
/// refer to a node created used `encode_diagnostic` in the previous session.
|
||||
/// This forces a side effect node green by running its side effect. `prev_index` would
|
||||
/// refer to a node created used `encode_side_effect` in the previous session.
|
||||
#[inline]
|
||||
fn force_diagnostic_node<'tcx>(&self, tcx: TyCtxt<'tcx>, prev_index: SerializedDepNodeIndex) {
|
||||
fn force_side_effect<'tcx>(&self, tcx: TyCtxt<'tcx>, prev_index: SerializedDepNodeIndex) {
|
||||
with_deps(TaskDepsRef::Ignore, || {
|
||||
let side_effect = tcx.load_side_effect(prev_index).unwrap();
|
||||
|
||||
match &side_effect {
|
||||
QuerySideEffect::Diagnostic(diagnostic) => {
|
||||
tcx.dcx().emit_diagnostic(diagnostic.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Use `send_and_color` as `promote_node_and_deps_to_current` expects all
|
||||
// green dependencies. `send_and_color` will also prevent multiple nodes
|
||||
// being encoded for concurrent calls.
|
||||
@@ -720,6 +734,16 @@ fn force_diagnostic_node<'tcx>(&self, tcx: TyCtxt<'tcx>, prev_index: SerializedD
|
||||
std::iter::once(DepNodeIndex::FOREVER_RED_NODE).collect(),
|
||||
true,
|
||||
);
|
||||
|
||||
match &side_effect {
|
||||
QuerySideEffect::Diagnostic(diagnostic) => {
|
||||
tcx.dcx().emit_diagnostic(diagnostic.clone());
|
||||
}
|
||||
QuerySideEffect::CheckFeature { symbol } => {
|
||||
tcx.sess.used_features.lock().insert(*symbol, dep_node_index.as_u32());
|
||||
}
|
||||
}
|
||||
|
||||
// This will just overwrite the same value for concurrent calls.
|
||||
tcx.store_side_effect(dep_node_index, side_effect);
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
use rustc_hir::intravisit::VisitorExt;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_hir::limit::Limit;
|
||||
use rustc_hir::{self as hir, HirId, Node, TraitCandidate, find_attr};
|
||||
use rustc_hir::{self as hir, CRATE_HIR_ID, HirId, Node, TraitCandidate, find_attr};
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
|
||||
use rustc_session::Session;
|
||||
@@ -1688,6 +1688,36 @@ pub fn finish(self) {
|
||||
self.sess.dcx().emit_fatal(crate::error::FailedWritingFile { path: &path, error });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_unused_features(self) {
|
||||
// Collect first to avoid holding the lock while linting.
|
||||
let used_features = self.sess.used_features.lock();
|
||||
let unused_features = self
|
||||
.features()
|
||||
.enabled_features_iter_stable_order()
|
||||
.filter(|(f, _)| {
|
||||
!used_features.contains_key(f)
|
||||
// FIXME: `restricted_std` is used to tell a standard library built
|
||||
// for a platform that it doesn't know how to support. But it
|
||||
// could only gate a private mod (see `__restricted_std_workaround`)
|
||||
// with `cfg(not(restricted_std))`, so it cannot be recorded as used
|
||||
// in downstream crates. It should never be linted, but should we
|
||||
// hack this in the linter to ignore it?
|
||||
&& f.as_str() != "restricted_std"
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (feature, span) in unused_features {
|
||||
self.node_span_lint(
|
||||
rustc_session::lint::builtin::UNUSED_FEATURES,
|
||||
CRATE_HIR_ID,
|
||||
span,
|
||||
|lint| {
|
||||
lint.primary_message(format!("feature `{}` is declared but not used", feature));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! nop_lift {
|
||||
|
||||
@@ -42,7 +42,7 @@ pub(crate) fn SideEffect<'tcx>() -> DepKindVTable<'tcx> {
|
||||
is_eval_always: false,
|
||||
key_fingerprint_style: KeyFingerprintStyle::Unit,
|
||||
force_from_dep_node_fn: Some(|tcx, _, prev_index| {
|
||||
tcx.dep_graph.force_diagnostic_node(tcx, prev_index);
|
||||
tcx.dep_graph.force_side_effect(tcx, prev_index);
|
||||
true
|
||||
}),
|
||||
promote_from_disk_fn: None,
|
||||
|
||||
@@ -166,6 +166,11 @@ pub struct Session {
|
||||
/// Used by `-Zmir-opt-bisect-limit` to assign an index to each
|
||||
/// optimization-pass execution candidate during this compilation.
|
||||
pub mir_opt_bisect_eval_count: AtomicUsize,
|
||||
|
||||
/// Enabled features that are used in the current compilation.
|
||||
///
|
||||
/// The value is the `DepNodeIndex` of the node encodes the used feature.
|
||||
pub used_features: Lock<FxHashMap<Symbol, u32>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -1096,6 +1101,7 @@ pub fn build_session(
|
||||
replaced_intrinsics: FxHashSet::default(), // filled by `run_compiler`
|
||||
thin_lto_supported: true, // filled by `run_compiler`
|
||||
mir_opt_bisect_eval_count: AtomicUsize::new(0),
|
||||
used_features: Lock::default(),
|
||||
};
|
||||
|
||||
validate_commandline_args_with_session_available(&sess);
|
||||
|
||||
Reference in New Issue
Block a user