diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 38a11b427c1f..f0629f7591f7 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -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 diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index db8f459ef045..db6e9298e235 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -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, diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 619726f0d5d8..9d046bdef1cf 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -137,5 +137,5 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option = 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) } )* diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs index 4a1da0f50cc2..9ddbc496e6d4 100644 --- a/compiler/rustc_interface/src/callbacks.rs +++ b/compiler/rustc_interface/src/callbacks.rs @@ -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(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 _)); } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 8d498b32cd8a..c1df8aac6f31 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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" diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index a71373c62f38..882e85924778 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -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); }) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 27f2a534adb5..68819445a06c 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -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::>(); + + 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 { diff --git a/compiler/rustc_query_impl/src/dep_kind_vtables.rs b/compiler/rustc_query_impl/src/dep_kind_vtables.rs index fa82a0413b1a..847bec8c640f 100644 --- a/compiler/rustc_query_impl/src/dep_kind_vtables.rs +++ b/compiler/rustc_query_impl/src/dep_kind_vtables.rs @@ -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, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index dc18b05c7576..30840a487273 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -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>, } #[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);