diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index eca7487358ac..bff6aaab9b14 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -125,6 +125,11 @@ pub fn run(update_mode: UpdateMode) { update_mode, &gen_deprecated(deprecated_lints.iter()), ); + process_file( + "clippy_lints/src/lib.nightly_lints.rs", + update_mode, + &gen_nightly_lint_list(internal_lints.iter(), usable_lints.iter()), + ); let all_group_lints = usable_lints.iter().filter(|l| { matches!( @@ -320,6 +325,36 @@ fn gen_deprecated<'a>(lints: impl Iterator) -> String { output } +fn gen_nightly_lint_list<'a>( + internal_lints: impl Iterator, + usable_lints: impl Iterator, +) -> String { + let details: Vec<_> = internal_lints + .map(|l| (false, l)) + .chain(usable_lints.map(|l| (true, l))) + .filter(|(_, l)| l.version.as_ref().map_or(false, |v| v == "nightly")) + .map(|(p, l)| (p, &l.module, l.name.to_uppercase())) + .collect(); + + let mut output = GENERATED_FILE_COMMENT.to_string(); + output.push_str("clippy_utils::nightly::set_nightly_lints([\n"); + // The test lint "FOREVER_NIGHTLY_LINT" is in the `internal_warn` group which is + // not processed by `update_lints`. For testing purposes we still need the lint to be + // registered in the `nightly_lints` list. This manually adds this one lint. + output.push_str(" #[cfg(feature = \"internal\")]\n"); + output.push_str(" LintId::of(utils::internal_lints::FOREVER_NIGHTLY_LINT),\n"); + + for (is_public, module_name, lint_name) in details { + if !is_public { + output.push_str(" #[cfg(feature = \"internal\")]\n"); + } + output.push_str(&format!(" LintId::of({}::{}),\n", module_name, lint_name)); + } + output.push_str("])\n"); + + output +} + /// Generates the code for registering lints #[must_use] fn gen_register_lint_list<'a>( diff --git a/clippy_lints/src/lib.nightly_lints.rs b/clippy_lints/src/lib.nightly_lints.rs new file mode 100644 index 000000000000..fb5b89e5b96f --- /dev/null +++ b/clippy_lints/src/lib.nightly_lints.rs @@ -0,0 +1,12 @@ +// This file was generated by `cargo dev update_lints`. +// Use that command to update this file and do not edit by hand. +// Manual edits will be overwritten. + +clippy_utils::nightly::set_nightly_lints([ + #[cfg(feature = "internal")] + LintId::of(utils::internal_lints::FOREVER_NIGHTLY_LINT), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), + LintId::of(borrow_as_ptr::BORROW_AS_PTR), + LintId::of(manual_bits::MANUAL_BITS), + LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION), +]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index f2661a82c0d2..c1d9d18539bb 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -467,6 +467,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: register_removed_non_tool_lints(store); include!("lib.deprecated.rs"); + include!("lib.nightly_lints.rs"); include!("lib.register_lints.rs"); include!("lib.register_restriction.rs"); diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index ca222c3d6699..67b49e60ddd2 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -8,6 +8,8 @@ //! Thank you! //! ~The `INTERNAL_METADATA_COLLECTOR` lint +use crate::nightly::LintLevelProvider; + use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_hir::HirId; use rustc_lint::{LateContext, Lint, LintContext}; @@ -46,7 +48,11 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) { /// 17 | std::mem::forget(seven); /// | ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` -pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: &str) { +pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: &str) { + if crate::nightly::suppress_lint(cx, lint) { + return; + } + cx.struct_span_lint(lint, sp, |diag| { let mut diag = diag.build(msg); docs_link(&mut diag, lint); @@ -74,7 +80,7 @@ pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into( +pub fn span_lint_and_help<'a, T: LintContext + LintLevelProvider>( cx: &'a T, lint: &'static Lint, span: Span, @@ -117,7 +123,7 @@ pub fn span_lint_and_help<'a, T: LintContext>( /// 10 | forget(&SomeStruct); /// | ^^^^^^^^^^^ /// ``` -pub fn span_lint_and_note<'a, T: LintContext>( +pub fn span_lint_and_note<'a, T: LintContext + LintLevelProvider>( cx: &'a T, lint: &'static Lint, span: impl Into, @@ -125,6 +131,10 @@ pub fn span_lint_and_note<'a, T: LintContext>( note_span: Option, note: &str, ) { + if crate::nightly::suppress_lint(cx, lint) { + return; + } + cx.struct_span_lint(lint, span, |diag| { let mut diag = diag.build(msg); if let Some(note_span) = note_span { @@ -143,10 +153,14 @@ pub fn span_lint_and_note<'a, T: LintContext>( /// If you change the signature, remember to update the internal lint `CollapsibleCalls` pub fn span_lint_and_then(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F) where - C: LintContext, + C: LintContext + LintLevelProvider, S: Into, F: FnOnce(&mut DiagnosticBuilder<'_>), { + if crate::nightly::suppress_lint(cx, lint) { + return; + } + cx.struct_span_lint(lint, sp, |diag| { let mut diag = diag.build(msg); f(&mut diag); @@ -156,6 +170,10 @@ where } pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) { + if crate::nightly::suppress_lint(cx, lint) { + return; + } + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| { let mut diag = diag.build(msg); docs_link(&mut diag, lint); @@ -171,6 +189,10 @@ pub fn span_lint_hir_and_then( msg: &str, f: impl FnOnce(&mut DiagnosticBuilder<'_>), ) { + if crate::nightly::suppress_lint(cx, lint) { + return; + } + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| { let mut diag = diag.build(msg); f(&mut diag); @@ -199,7 +221,7 @@ pub fn span_lint_hir_and_then( /// = note: `-D fold-any` implied by `-D warnings` /// ``` #[cfg_attr(feature = "internal", allow(clippy::collapsible_span_lint_calls))] -pub fn span_lint_and_sugg<'a, T: LintContext>( +pub fn span_lint_and_sugg<'a, T: LintContext + LintLevelProvider>( cx: &'a T, lint: &'static Lint, sp: Span, diff --git a/clippy_utils/src/nightly.rs b/clippy_utils/src/nightly.rs index b5a4dbcc8b69..8d2ecfcd19ec 100644 --- a/clippy_utils/src/nightly.rs +++ b/clippy_utils/src/nightly.rs @@ -3,9 +3,13 @@ use std::lazy::SyncOnceCell; +use rustc_data_structures::stable_set::FxHashSet; +use rustc_lint::{EarlyContext, LateContext, Level, Lint, LintId}; +use rustc_middle::lint::{LevelAndSource, LintLevelSource}; use rustc_session::Session; static IS_NIGHTLY_RUN: SyncOnceCell = SyncOnceCell::new(); +static NIGHTLY_LINTS: SyncOnceCell> = SyncOnceCell::new(); /// This function is used to determine if nightly lints should be enabled or disabled /// in this Clippy run. @@ -33,3 +37,65 @@ pub fn eval_is_nightly_run(sess: &Session) { pub fn is_nightly_run() -> bool { *IS_NIGHTLY_RUN.get().unwrap_or(&false) } + +/// This function takes a list of all nightly lints that will be surpressed before +/// the emission if nightly lints are disabled. +/// +/// It's only allowed to call this once. This is done by [`clippy_lints::lib`] +#[doc(hidden)] +pub fn set_nightly_lints(lints: [LintId; N]) { + // The from trait for HashMaps is only implemented for the normal hasher. Here we have to add each + // item individually + let mut nightly_lints = FxHashSet::default(); + lints.iter().copied().for_each(|lint| { + nightly_lints.insert(lint); + }); + NIGHTLY_LINTS + .set(nightly_lints) + .expect("`NIGHTLY_LINTS` should only be set once."); +} + +/// Returns true if the lint is a registered nightly lint. Note that a lint will still be a +/// registered nightly lint if nightly lints are enabled as usual. +/// +/// Please use [`is_nightly_run`] to determine if Clippy's nightly features +/// should be enabled. +#[inline] +pub fn is_nightly_lint(lint: &'static Lint) -> bool { + NIGHTLY_LINTS + .get() + .map_or(false, |lints| lints.contains(&LintId::of(lint))) +} + +/// This function checks if the given lint is a nightly lint and should be suppressed in the current +/// context. +pub fn suppress_lint(cx: &T, lint: &'static Lint) -> bool { + if !is_nightly_run() && is_nightly_lint(lint) { + let (_, level_src) = cx.get_lint_level(lint); + if level_src == LintLevelSource::Default + || level_src == LintLevelSource::CommandLine(sym!(warnings), Level::Deny) + { + return true; + } + } + + false +} + +/// This trait is used to retrieve the lint level for the lint based on the +/// current linting context. +pub trait LintLevelProvider { + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource; +} + +impl LintLevelProvider for LateContext<'_> { + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs) + } +} + +impl LintLevelProvider for EarlyContext<'_> { + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + self.builder.lint_level(lint) + } +}