diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 28c1a54d2c5..54bc69e058b 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -6,9 +6,12 @@ use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; -use crate::utils::{snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; +use crate::utils::{meets_msrv, snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; + +const CHECKED_CONVERSIONS_MSRV: RustcVersion = RustcVersion::new(1, 34, 0); declare_clippy_lint! { /// **What it does:** Checks for explicit bounds checking when casting. @@ -39,10 +42,25 @@ "`try_from` could replace manual bounds checking when casting" } -declare_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); +pub struct CheckedConversions { + msrv: Option, +} + +impl CheckedConversions { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); impl<'tcx> LateLintPass<'tcx> for CheckedConversions { fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { + if !meets_msrv(self.msrv.as_ref(), &CHECKED_CONVERSIONS_MSRV) { + return; + } + let result = if_chain! { if !in_external_macro(cx.sess(), item.span); if let ExprKind::Binary(op, ref left, ref right) = &item.kind; @@ -74,6 +92,8 @@ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { } } } + + extract_msrv_attr!(LateContext); } /// Searches for a single check from unsigned to _ is done diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 5fca088c9b4..f06926fa91d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1005,6 +1005,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box matches::Matches::new(msrv)); store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv)); store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv)); + store.register_early_pass(move || box redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)); + store.register_early_pass(move || box redundant_field_names::RedundantFieldNames::new(msrv)); + store.register_late_pass(move || box checked_conversions::CheckedConversions::new(msrv)); + store.register_late_pass(move || box mem_replace::MemReplace::new(msrv)); + store.register_late_pass(move || box ranges::Ranges::new(msrv)); + store.register_late_pass(move || box use_self::UseSelf::new(msrv)); + store.register_late_pass(move || box missing_const_for_fn::MissingConstForFn::new(msrv)); + store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount); store.register_late_pass(|| box map_clone::MapClone); store.register_late_pass(|| box map_err_ignore::MapErrIgnore); @@ -1015,7 +1023,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box main_recursion::MainRecursion::default()); store.register_late_pass(|| box lifetimes::Lifetimes); store.register_late_pass(|| box entry::HashMapPass); - store.register_late_pass(|| box ranges::Ranges); store.register_late_pass(|| box types::Casts); let type_complexity_threshold = conf.type_complexity_threshold; store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold)); @@ -1060,7 +1067,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box neg_multiply::NegMultiply); store.register_late_pass(|| box mem_discriminant::MemDiscriminant); store.register_late_pass(|| box mem_forget::MemForget); - store.register_late_pass(|| box mem_replace::MemReplace); store.register_late_pass(|| box arithmetic::Arithmetic::default()); store.register_late_pass(|| box assign_ops::AssignOps); store.register_late_pass(|| box let_if_seq::LetIfSeq); @@ -1082,7 +1088,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box pass_by_ref_or_value); store.register_late_pass(|| box ref_option_ref::RefOptionRef); store.register_late_pass(|| box try_err::TryErr); - store.register_late_pass(|| box use_self::UseSelf); store.register_late_pass(|| box bytecount::ByteCount); store.register_late_pass(|| box infinite_iter::InfiniteIter); store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody); @@ -1108,10 +1113,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box unnecessary_wraps::UnnecessaryWraps); store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); - store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); store.register_late_pass(|| box transmuting_null::TransmutingNull); store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite); - store.register_late_pass(|| box checked_conversions::CheckedConversions); store.register_late_pass(|| box integer_division::IntegerDivision); store.register_late_pass(|| box inherent_to_string::InherentToString); let max_trait_bounds = conf.max_trait_bounds; @@ -1140,7 +1143,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box redundant_else::RedundantElse); store.register_late_pass(|| box create_dir::CreateDir); store.register_early_pass(|| box needless_arbitrary_self_type::NeedlessArbitrarySelfType); - store.register_early_pass(|| box redundant_static_lifetimes::RedundantStaticLifetimes); store.register_late_pass(|| box cargo_common_metadata::CargoCommonMetadata); store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions); store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies); @@ -1180,7 +1182,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box mut_mutex_lock::MutMutexLock); store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); - store.register_early_pass(|| box redundant_field_names::RedundantFieldNames); store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero); store.register_late_pass(|| box panic_in_result_fn::PanicInResultFn); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index bb0acecc5a9..19087b02077 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,13 +1,14 @@ use crate::utils::{ - in_macro, match_def_path, match_qpath, paths, snippet, snippet_with_applicability, span_lint_and_help, + in_macro, match_def_path, match_qpath, meets_msrv, paths, snippet, snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::Span; use rustc_span::symbol::sym; @@ -94,7 +95,7 @@ "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`" } -declare_lint_pass!(MemReplace => +impl_lint_pass!(MemReplace => [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { @@ -224,6 +225,19 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr< } } +const MEM_REPLACE_WITH_DEFAULT_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); + +pub struct MemReplace { + msrv: Option, +} + +impl MemReplace { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + impl<'tcx> LateLintPass<'tcx> for MemReplace { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if_chain! { @@ -236,8 +250,11 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { then { check_replace_option_with_none(cx, src, dest, expr.span); check_replace_with_uninit(cx, src, dest, expr.span); - check_replace_with_default(cx, src, dest, expr.span); + if meets_msrv(self.msrv.as_ref(), &MEM_REPLACE_WITH_DEFAULT_MSRV) { + check_replace_with_default(cx, src, dest, expr.span); + } } } } + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8002c27a5e9..5133f31e0e7 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1487,7 +1487,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { ["expect", ..] => lint_expect(cx, expr, arg_lists[0]), ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), ["unwrap_or_else", "map"] => { - if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]) { + if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0], self.msrv.as_ref()) { unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"); } }, @@ -1509,7 +1509,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]), ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), - ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]), + ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1], self.msrv.as_ref()), ["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]), ["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]), ["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]), @@ -2733,6 +2733,8 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map } } +const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); + /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s /// Return true if lint triggered fn lint_map_unwrap_or_else<'tcx>( @@ -2740,7 +2742,11 @@ fn lint_map_unwrap_or_else<'tcx>( expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>], unwrap_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, ) -> bool { + if !meets_msrv(msrv, &MAP_UNWRAP_OR_MSRV) { + return false; + } // lint if the caller of `map()` is an `Option` let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::result_type); @@ -2923,9 +2929,20 @@ fn lint_filter_map<'tcx>( } } +const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0); + /// lint use of `filter_map().next()` for `Iterators` -fn lint_filter_map_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) { +fn lint_filter_map_next<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + filter_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, +) { if match_trait_method(cx, expr, &paths::ITERATOR) { + if !meets_msrv(msrv, &FILTER_MAP_NEXT_MSRV) { + return; + } + let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ `.find_map(..)` instead."; let filter_snippet = snippet(cx, filter_args[1].span, ".."); diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 38e2ce563ee..6ebeaced62a 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -1,14 +1,19 @@ use crate::utils::qualify_min_const_fn::is_min_const_fn; -use crate::utils::{fn_has_unsatisfiable_preds, has_drop, is_entrypoint_fn, span_lint, trait_ref_of_method}; +use crate::utils::{ + fn_has_unsatisfiable_preds, has_drop, is_entrypoint_fn, meets_msrv, span_lint, trait_ref_of_method, +}; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::Span; use rustc_typeck::hir_ty_to_ty; +const MISSING_CONST_FOR_FN_MSRV: RustcVersion = RustcVersion::new(1, 37, 0); + declare_clippy_lint! { /// **What it does:** /// @@ -69,7 +74,18 @@ "Lint functions definitions that could be made `const fn`" } -declare_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); +impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); + +pub struct MissingConstForFn { + msrv: Option, +} + +impl MissingConstForFn { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { fn check_fn( @@ -81,6 +97,10 @@ fn check_fn( span: Span, hir_id: HirId, ) { + if !meets_msrv(self.msrv.as_ref(), &MISSING_CONST_FOR_FN_MSRV) { + return; + } + let def_id = cx.tcx.hir().local_def_id(hir_id); if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) { @@ -126,6 +146,7 @@ fn check_fn( span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); } } + extract_msrv_attr!(LateContext); } /// Returns true if any of the method parameters is a type that implements `Drop`. The method diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 4b514bbd42c..f9173808089 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -3,9 +3,10 @@ use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; use rustc_span::sym; use rustc_span::symbol::Ident; @@ -13,8 +14,8 @@ use crate::utils::sugg::Sugg; use crate::utils::{ - get_parent_expr, is_integer_const, single_segment_path, snippet, snippet_opt, snippet_with_applicability, - span_lint, span_lint_and_sugg, span_lint_and_then, + get_parent_expr, is_integer_const, meets_msrv, single_segment_path, snippet, snippet_opt, + snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then, }; use crate::utils::{higher, SpanlessEq}; @@ -160,7 +161,20 @@ "manually reimplementing {`Range`, `RangeInclusive`}`::contains`" } -declare_lint_pass!(Ranges => [ +const MANUAL_RANGE_CONTAINS_MSRV: RustcVersion = RustcVersion::new(1, 35, 0); + +pub struct Ranges { + msrv: Option, +} + +impl Ranges { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(Ranges => [ RANGE_ZIP_WITH_LEN, RANGE_PLUS_ONE, RANGE_MINUS_ONE, @@ -175,7 +189,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { check_range_zip_with_len(cx, path, args, expr.span); }, ExprKind::Binary(ref op, ref l, ref r) => { - check_possible_range_contains(cx, op.node, l, r, expr.span); + if meets_msrv(self.msrv.as_ref(), &MANUAL_RANGE_CONTAINS_MSRV) { + check_possible_range_contains(cx, op.node, l, r, expr.span); + } }, _ => {}, } @@ -184,6 +200,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { check_inclusive_range_minus_one(cx, expr); check_reversed_empty_range(cx, expr); } + extract_msrv_attr!(LateContext); } fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, span: Span) { diff --git a/clippy_lints/src/redundant_field_names.rs b/clippy_lints/src/redundant_field_names.rs index 2a81170e49e..38dcf7a192c 100644 --- a/clippy_lints/src/redundant_field_names.rs +++ b/clippy_lints/src/redundant_field_names.rs @@ -1,9 +1,12 @@ -use crate::utils::span_lint_and_sugg; +use crate::utils::{meets_msrv, span_lint_and_sugg}; use rustc_ast::ast::{Expr, ExprKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +const REDUNDANT_FIELD_NAMES_MSRV: RustcVersion = RustcVersion::new(1, 17, 0); declare_clippy_lint! { /// **What it does:** Checks for fields in struct literals where shorthands @@ -33,10 +36,25 @@ "checks for fields in struct literals where shorthands could be used" } -declare_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]); +pub struct RedundantFieldNames { + msrv: Option, +} + +impl RedundantFieldNames { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]); impl EarlyLintPass for RedundantFieldNames { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if !meets_msrv(self.msrv.as_ref(), &REDUNDANT_FIELD_NAMES_MSRV) { + return; + } + if in_external_macro(cx.sess, expr.span) { return; } @@ -64,4 +82,5 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { } } } + extract_msrv_attr!(EarlyContext); } diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index 7bbcc67aa2d..fcfa3c12755 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -1,8 +1,11 @@ -use crate::utils::{snippet, span_lint_and_then}; +use crate::utils::{meets_msrv, snippet, span_lint_and_then}; use rustc_ast::ast::{Item, ItemKind, Ty, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +const REDUNDANT_STATIC_LIFETIMES_MSRV: RustcVersion = RustcVersion::new(1, 17, 0); declare_clippy_lint! { /// **What it does:** Checks for constants and statics with an explicit `'static` lifetime. @@ -29,7 +32,18 @@ "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them." } -declare_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]); +pub struct RedundantStaticLifetimes { + msrv: Option, +} + +impl RedundantStaticLifetimes { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]); impl RedundantStaticLifetimes { // Recursively visit types @@ -84,6 +98,10 @@ fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { impl EarlyLintPass for RedundantStaticLifetimes { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if !meets_msrv(self.msrv.as_ref(), &REDUNDANT_STATIC_LIFETIMES_MSRV) { + return; + } + if !item.span.from_expansion() { if let ItemKind::Const(_, ref var_type, _) = item.kind { self.visit_type(var_type, cx, "constants have by default a `'static` lifetime"); @@ -96,4 +114,6 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { } } } + + extract_msrv_attr!(EarlyContext); } diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 5ac4797680b..3b23f885e08 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -12,11 +12,12 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_middle::ty::{DefIdTree, Ty}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::kw; use rustc_typeck::hir_ty_to_ty; -use crate::utils::{differing_macro_contexts, span_lint_and_sugg}; +use crate::utils::{differing_macro_contexts, meets_msrv, span_lint_and_sugg}; declare_clippy_lint! { /// **What it does:** Checks for unnecessary repetition of structure name when a @@ -53,7 +54,7 @@ "unnecessary structure name repetition whereas `Self` is applicable" } -declare_lint_pass!(UseSelf => [USE_SELF]); +impl_lint_pass!(UseSelf => [USE_SELF]); const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element"; @@ -157,8 +158,25 @@ fn check_trait_method_impl_decl<'tcx>( } } +const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0); + +pub struct UseSelf { + msrv: Option, +} + +impl UseSelf { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) { + return; + } + if in_external_macro(cx.sess(), item.span) { return; } @@ -204,6 +222,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { } } } + extract_msrv_attr!(LateContext); } struct UseSelfVisitor<'a, 'tcx> { diff --git a/doc/adding_lints.md b/doc/adding_lints.md index b1dacfc9c6d..a723b0a4c20 100644 --- a/doc/adding_lints.md +++ b/doc/adding_lints.md @@ -226,13 +226,13 @@ store.register_early_pass(|| box foo_functions::FooFunctions); ``` As one may expect, there is a corresponding `register_late_pass` method -available as well. Without a call to one of `register_early_pass` or +available as well. Without a call to one of `register_early_pass` or `register_late_pass`, the lint pass in question will not be run. -One reason that `cargo dev` does not automate this step is that multiple lints +One reason that `cargo dev` does not automate this step is that multiple lints can use the same lint pass, so registering the lint pass may already be done when adding a new lint. Another reason that this step is not automated is that -the order that the passes are registered determines the order the passes +the order that the passes are registered determines the order the passes actually run, which in turn affects the order that any emitted lints are output in. @@ -380,6 +380,57 @@ pass. [`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn [ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html +## Specifying the lint's minimum supported Rust version (msrv) + +Projects supporting older versions of Rust would need to disable a lint if it targets features +present in later versions. Support for this can be added by specifying an msrv in your lint like so, + +```rust +const MANUAL_STRIP_MSRV: RustcVersion = RustcVersion::new(1, 45, 0); +``` + +The project's msrv will also have to be an attribute in the lint so you'll have to add a struct +and constructor for your lint. The project's msrv needs to be passed when the lint is registered +in `lib.rs` + +```rust +pub struct ManualStrip { + msrv: Option, +} + +impl ManualStrip { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} +``` + +The project's msrv can then be matched against the lint's msrv in the LintPass using the `meets_msrv` utility +function. + +``` rust +if !meets_msrv(self.msrv.as_ref(), &MANUAL_STRIP_MSRV) { + return; +} +``` + +The project's msrv can also be specified as an inner attribute, which overrides the value from +`clippy.toml`. This can be accounted for using the `extract_msrv_attr!(LintContext)` macro and passing +LateContext/EarlyContext. + +```rust +impl<'tcx> LateLintPass<'tcx> for ManualStrip { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + ... + } + extract_msrv_attr!(LateContext); +} +``` + +Once the msrv is added to the lint, a relevant test case should be added to `tests/ui/min_rust_version_attr.rs` +which verifies that the lint isn't emitted if the project's msrv is lower. + ## Author lint If you have trouble implementing your lint, there is also the internal `author` diff --git a/tests/ui/min_rust_version_attr.rs b/tests/ui/min_rust_version_attr.rs index 1026cc40d3b..3848bca3207 100644 --- a/tests/ui/min_rust_version_attr.rs +++ b/tests/ui/min_rust_version_attr.rs @@ -2,7 +2,7 @@ #![feature(custom_inner_attributes)] #![clippy::msrv = "1.0.0"] -use std::ops::Deref; +use std::ops::{Deref, RangeFrom}; fn option_as_ref_deref() { let mut opt = Some(String::from("123")); @@ -42,12 +42,94 @@ pub fn manual_strip_msrv() { } } +pub fn redundant_fieldnames() { + let start = 0; + let _ = RangeFrom { start: start }; +} + +pub fn redundant_static_lifetime() { + const VAR_ONE: &'static str = "Test constant #1"; +} + +pub fn checked_conversion() { + let value: i64 = 42; + let _ = value <= (u32::max_value() as i64) && value >= 0; + let _ = value <= (u32::MAX as i64) && value >= 0; +} + +pub fn filter_map_next() { + let a = ["1", "lol", "3", "NaN", "5"]; + + #[rustfmt::skip] + let _: Option = vec![1, 2, 3, 4, 5, 6] + .into_iter() + .filter_map(|x| { + if x == 2 { + Some(x * 2) + } else { + None + } + }) + .next(); +} + +#[allow(clippy::no_effect)] +#[allow(clippy::short_circuit_statement)] +#[allow(clippy::unnecessary_operation)] +pub fn manual_range_contains() { + let x = 5; + x >= 8 && x < 12; +} + +pub fn use_self() { + struct Foo {} + + impl Foo { + fn new() -> Foo { + Foo {} + } + fn test() -> Foo { + Foo::new() + } + } +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::replace(&mut s, String::default()); +} + +fn map_unwrap_or() { + let opt = Some(1); + + // Check for `option.map(_).unwrap_or(_)` use. + // Single line case. + let _ = opt + .map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); +} + +// Could be const +fn missing_const_for_fn() -> i32 { + 1 +} + fn main() { + filter_map_next(); + checked_conversion(); + redundant_fieldnames(); + redundant_static_lifetime(); option_as_ref_deref(); match_like_matches(); match_same_arms(); match_same_arms2(); manual_strip_msrv(); + manual_range_contains(); + use_self(); + replace_with_default(); + map_unwrap_or(); + missing_const_for_fn(); } mod meets_msrv { diff --git a/tests/ui/min_rust_version_attr.stderr b/tests/ui/min_rust_version_attr.stderr index 3e1af046e7a..34805263104 100644 --- a/tests/ui/min_rust_version_attr.stderr +++ b/tests/ui/min_rust_version_attr.stderr @@ -1,12 +1,12 @@ error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:60:24 + --> $DIR/min_rust_version_attr.rs:142:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::manual-strip` implied by `-D warnings` note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:59:9 + --> $DIR/min_rust_version_attr.rs:141:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,13 +17,13 @@ LL | assert_eq!(.to_uppercase(), "WORLD!"); | error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:72:24 + --> $DIR/min_rust_version_attr.rs:154:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:71:9 + --> $DIR/min_rust_version_attr.rs:153:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^