diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 7e8cbd00c22..866dae110cc 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -44,6 +44,7 @@ extern crate rustc_target;
 extern crate rustc_trait_selection;
 extern crate rustc_typeck;
 
+use crate::utils::parse_msrv;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_lint::LintId;
 use rustc_session::Session;
@@ -933,7 +934,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &zero_div_zero::ZERO_DIVIDED_BY_ZERO,
     ]);
     // end register lints, do not remove this comment, it’s used in `update_lints`
-
     store.register_late_pass(|| box await_holding_invalid::AwaitHolding);
     store.register_late_pass(|| box serde_api::SerdeAPI);
     store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
@@ -969,7 +969,23 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box strings::StringAdd);
     store.register_late_pass(|| box implicit_return::ImplicitReturn);
     store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
-    store.register_late_pass(|| box methods::Methods);
+
+    let parsed_msrv = conf.msrv.as_ref().and_then(|s| {
+        parse_msrv(s, None, None).or_else(|| {
+            sess.err(&format!("error reading Clippy's configuration file. `{}` is not a valid Rust version", s));
+            None
+        })
+    });
+
+    let msrv = parsed_msrv.clone();
+    store.register_late_pass(move || box methods::Methods::new(msrv.clone()));
+    let msrv = parsed_msrv.clone();
+    store.register_late_pass(move || box matches::Matches::new(msrv.clone()));
+    let msrv = parsed_msrv.clone();
+    store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv.clone()));
+    let msrv = parsed_msrv;
+    store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv.clone()));
+
     store.register_late_pass(|| box map_clone::MapClone);
     store.register_late_pass(|| box map_err_ignore::MapErrIgnore);
     store.register_late_pass(|| box shadow::Shadow);
@@ -983,7 +999,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     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));
-    store.register_late_pass(|| box matches::Matches::default());
     store.register_late_pass(|| box minmax::MinMaxPass);
     store.register_late_pass(|| box open_options::OpenOptions);
     store.register_late_pass(|| box zero_div_zero::ZeroDiv);
@@ -1144,7 +1159,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box if_let_mutex::IfLetMutex);
     store.register_late_pass(|| box mut_mutex_lock::MutMutexLock);
     store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
-    store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive);
     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);
@@ -1166,7 +1180,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box manual_ok_or::ManualOkOr);
     store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
     store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
-    store.register_late_pass(|| box manual_strip::ManualStrip);
     store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
     let disallowed_methods = conf.disallowed_methods.iter().cloned().collect::<FxHashSet<_>>();
     store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods));
diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs
index a1450b0d5fe..4762ba16ac7 100644
--- a/clippy_lints/src/manual_non_exhaustive.rs
+++ b/clippy_lints/src/manual_non_exhaustive.rs
@@ -1,11 +1,20 @@
-use crate::utils::{snippet_opt, span_lint_and_then};
+use crate::utils::{get_inner_attr, meets_msrv, snippet_opt, span_lint_and_then};
 use if_chain::if_chain;
 use rustc_ast::ast::{Attribute, Item, ItemKind, StructField, Variant, VariantData, VisibilityKind};
 use rustc_attr as attr;
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::{sym, Span};
+use semver::{Version, VersionReq};
+
+const MANUAL_NON_EXHAUSTIVE_MSRV: Version = Version {
+    major: 1,
+    minor: 40,
+    patch: 0,
+    pre: Vec::new(),
+    build: Vec::new(),
+};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for manual implementations of the non-exhaustive pattern.
@@ -55,10 +64,26 @@ declare_clippy_lint! {
     "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
 }
 
-declare_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
+#[derive(Clone)]
+pub struct ManualNonExhaustive {
+    msrv: Option<VersionReq>,
+}
+
+impl ManualNonExhaustive {
+    #[must_use]
+    pub fn new(msrv: Option<VersionReq>) -> Self {
+        Self { msrv }
+    }
+}
+
+impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
 
 impl EarlyLintPass for ManualNonExhaustive {
     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+        if !meets_msrv(self.msrv.as_ref(), &MANUAL_NON_EXHAUSTIVE_MSRV) {
+            return;
+        }
+
         match &item.kind {
             ItemKind::Enum(def, _) => {
                 check_manual_non_exhaustive_enum(cx, item, &def.variants);
@@ -73,6 +98,8 @@ impl EarlyLintPass for ManualNonExhaustive {
             _ => {},
         }
     }
+
+    extract_msrv_attr!(EarlyContext);
 }
 
 fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs
index 4afb0ab3bad..446641ca114 100644
--- a/clippy_lints/src/manual_strip.rs
+++ b/clippy_lints/src/manual_strip.rs
@@ -1,21 +1,31 @@
 use crate::consts::{constant, Constant};
 use crate::utils::usage::mutated_variables;
 use crate::utils::{
-    eq_expr_value, higher, match_def_path, multispan_sugg, paths, qpath_res, snippet, span_lint_and_then,
+    eq_expr_value, get_inner_attr, higher, match_def_path, meets_msrv, multispan_sugg, paths, qpath_res, snippet,
+    span_lint_and_then,
 };
 
 use if_chain::if_chain;
-use rustc_ast::ast::LitKind;
+use rustc_ast::ast::{Attribute, LitKind};
 use rustc_hir::def::Res;
 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
 use rustc_hir::BinOpKind;
 use rustc_hir::{BorrowKind, Expr, ExprKind};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::hir::map::Map;
 use rustc_middle::ty;
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::Spanned;
 use rustc_span::Span;
+use semver::{Version, VersionReq};
+
+const MANUAL_STRIP_MSRV: Version = Version {
+    major: 1,
+    minor: 45,
+    patch: 0,
+    pre: Vec::new(),
+    build: Vec::new(),
+};
 
 declare_clippy_lint! {
     /// **What it does:**
@@ -51,7 +61,18 @@ declare_clippy_lint! {
     "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
 }
 
-declare_lint_pass!(ManualStrip => [MANUAL_STRIP]);
+pub struct ManualStrip {
+    msrv: Option<VersionReq>,
+}
+
+impl ManualStrip {
+    #[must_use]
+    pub fn new(msrv: Option<VersionReq>) -> Self {
+        Self { msrv }
+    }
+}
+
+impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 enum StripKind {
@@ -61,6 +82,10 @@ enum StripKind {
 
 impl<'tcx> LateLintPass<'tcx> for ManualStrip {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+        if !meets_msrv(self.msrv.as_ref(), &MANUAL_STRIP_MSRV) {
+            return;
+        }
+
         if_chain! {
             if let Some((cond, then, _)) = higher::if_block(&expr);
             if let ExprKind::MethodCall(_, _, [target_arg, pattern], _) = cond.kind;
@@ -114,6 +139,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
             }
         }
     }
+
+    extract_msrv_attr!(LateContext);
 }
 
 // Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs
index af59917e801..5c38abbd9c6 100644
--- a/clippy_lints/src/matches.rs
+++ b/clippy_lints/src/matches.rs
@@ -2,14 +2,14 @@ use crate::consts::{constant, miri_to_const, Constant};
 use crate::utils::sugg::Sugg;
 use crate::utils::usage::is_unused;
 use crate::utils::{
-    expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
-    is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, multispan_sugg, remove_blocks, snippet,
-    snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
-    span_lint_and_then,
+    expr_block, get_arg_name, get_inner_attr, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of,
+    is_refutable, is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg,
+    remove_blocks, snippet, snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note,
+    span_lint_and_sugg, span_lint_and_then,
 };
 use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
 use if_chain::if_chain;
-use rustc_ast::ast::LitKind;
+use rustc_ast::ast::{Attribute, LitKind};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
 use rustc_hir::def::CtorKind;
@@ -23,6 +23,7 @@ use rustc_middle::ty::{self, Ty, TyS};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::{Span, Spanned};
 use rustc_span::{sym, Symbol};
+use semver::{Version, VersionReq};
 use std::cmp::Ordering;
 use std::collections::hash_map::Entry;
 use std::collections::Bound;
@@ -527,9 +528,20 @@ declare_clippy_lint! {
 
 #[derive(Default)]
 pub struct Matches {
+    msrv: Option<VersionReq>,
     infallible_destructuring_match_linted: bool,
 }
 
+impl Matches {
+    #[must_use]
+    pub fn new(msrv: Option<VersionReq>) -> Self {
+        Self {
+            msrv,
+            ..Matches::default()
+        }
+    }
+}
+
 impl_lint_pass!(Matches => [
     SINGLE_MATCH,
     MATCH_REF_PATS,
@@ -549,6 +561,14 @@ impl_lint_pass!(Matches => [
     MATCH_SAME_ARMS,
 ]);
 
+const MATCH_LIKE_MATCHES_MACRO_MSRV: Version = Version {
+    major: 1,
+    minor: 42,
+    patch: 0,
+    pre: Vec::new(),
+    build: Vec::new(),
+};
+
 impl<'tcx> LateLintPass<'tcx> for Matches {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
         if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) {
@@ -556,7 +576,12 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
         }
 
         redundant_pattern_match::check(cx, expr);
-        if !check_match_like_matches(cx, expr) {
+
+        if meets_msrv(self.msrv.as_ref(), &MATCH_LIKE_MATCHES_MACRO_MSRV) {
+            if !check_match_like_matches(cx, expr) {
+                lint_match_arms(cx, expr);
+            }
+        } else {
             lint_match_arms(cx, expr);
         }
 
@@ -640,6 +665,8 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
             }
         }
     }
+
+    extract_msrv_attr!(LateContext);
 }
 
 #[rustfmt::skip]
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index fa174404365..6b478986067 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -12,6 +12,7 @@ use std::iter;
 use bind_instead_of_map::BindInsteadOfMap;
 use if_chain::if_chain;
 use rustc_ast::ast;
+use rustc_ast::ast::Attribute;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
@@ -20,7 +21,7 @@ use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
 use rustc_middle::hir::map::Map;
 use rustc_middle::lint::in_external_macro;
 use rustc_middle::ty::{self, TraitRef, Ty, TyS};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::Span;
 use rustc_span::symbol::{sym, SymbolStr};
 
@@ -28,12 +29,14 @@ use crate::consts::{constant, Constant};
 use crate::utils::eager_or_lazy::is_lazyness_candidate;
 use crate::utils::usage::mutated_variables;
 use crate::utils::{
-    contains_ty, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro,
-    is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath,
-    match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, remove_blocks, return_ty,
-    single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
-    span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty_depth, SpanlessEq,
+    contains_ty, get_arg_name, get_inner_attr, get_parent_expr, get_trait_def_id, has_iter_method, higher,
+    implements_trait, in_macro, is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
+    match_def_path, match_qpath, match_trait_method, match_type, match_var, meets_msrv, method_calls,
+    method_chain_args, paths, remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability,
+    snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg,
+    walk_ptrs_ty_depth, SpanlessEq,
 };
+use semver::{Version, VersionReq};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s.
@@ -1404,7 +1407,18 @@ declare_clippy_lint! {
     "use `.collect()` instead of `::from_iter()`"
 }
 
-declare_lint_pass!(Methods => [
+pub struct Methods {
+    msrv: Option<VersionReq>,
+}
+
+impl Methods {
+    #[must_use]
+    pub fn new(msrv: Option<VersionReq>) -> Self {
+        Self { msrv }
+    }
+}
+
+impl_lint_pass!(Methods => [
     UNWRAP_USED,
     EXPECT_USED,
     SHOULD_IMPLEMENT_TRAIT,
@@ -1531,8 +1545,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
                 check_pointer_offset(cx, expr, arg_lists[0])
             },
             ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
-            ["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
-            ["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
+            ["map", "as_ref"] => {
+                lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref())
+            },
+            ["map", "as_mut"] => {
+                lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref())
+            },
             ["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
             ["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
             ["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
@@ -1738,6 +1756,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
             }
         }
     }
+
+    extract_msrv_attr!(LateContext);
 }
 
 /// Checks for the `OR_FUN_CALL` lint.
@@ -3453,6 +3473,14 @@ fn lint_suspicious_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
     );
 }
 
+const OPTION_AS_REF_DEREF_MSRV: Version = Version {
+    major: 1,
+    minor: 40,
+    patch: 0,
+    pre: Vec::new(),
+    build: Vec::new(),
+};
+
 /// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
 fn lint_option_as_ref_deref<'tcx>(
     cx: &LateContext<'tcx>,
@@ -3460,7 +3488,12 @@ fn lint_option_as_ref_deref<'tcx>(
     as_ref_args: &[hir::Expr<'_>],
     map_args: &[hir::Expr<'_>],
     is_mut: bool,
+    msrv: Option<&VersionReq>,
 ) {
+    if !meets_msrv(msrv, &OPTION_AS_REF_DEREF_MSRV) {
+        return;
+    }
+
     let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
 
     let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]);
diff --git a/clippy_lints/src/utils/attrs.rs b/clippy_lints/src/utils/attrs.rs
index e6d41341a55..aed6ee5dc57 100644
--- a/clippy_lints/src/utils/attrs.rs
+++ b/clippy_lints/src/utils/attrs.rs
@@ -21,6 +21,7 @@ pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
         DeprecationStatus::Replaced("cognitive_complexity"),
     ),
     ("dump", DeprecationStatus::None),
+    ("msrv", DeprecationStatus::None),
 ];
 
 pub struct LimitStack {
@@ -123,6 +124,22 @@ fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'
     }
 }
 
+pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
+    let mut unique_attr = None;
+    for attr in get_attr(sess, attrs, name) {
+        match attr.style {
+            ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
+            ast::AttrStyle::Inner => {
+                sess.span_err(attr.span, &format!("`{}` is defined multiple times", name));
+            },
+            ast::AttrStyle::Outer => {
+                sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
+            },
+        }
+    }
+    unique_attr
+}
+
 /// Return true if the attributes contain any of `proc_macro`,
 /// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
 pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 0ac8fff69f0..fc6304118d9 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -106,6 +106,8 @@ macro_rules! define_Conf {
 
 pub use self::helpers::Conf;
 define_Conf! {
+    /// Lint: MANUAL_NON_EXHAUSTIVE, MANUAL_STRIP, OPTION_AS_REF_DEREF, MATCH_LIKE_MATCHES_MACRO. The minimum rust version that the project supports
+    (msrv, "msrv": Option<String>, None),
     /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
     (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
     /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs
index e9c71e23a67..f8e88512048 100644
--- a/clippy_lints/src/utils/mod.rs
+++ b/clippy_lints/src/utils/mod.rs
@@ -51,6 +51,7 @@ use rustc_lint::{LateContext, Level, Lint, LintContext};
 use rustc_middle::hir::map::Map;
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
 use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable};
+use rustc_session::Session;
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::source_map::original_sp;
 use rustc_span::sym as rustc_sym;
@@ -58,10 +59,58 @@ use rustc_span::symbol::{self, kw, Symbol};
 use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
 use rustc_target::abi::Integer;
 use rustc_trait_selection::traits::query::normalize::AtExt;
+use semver::{Version, VersionReq};
 use smallvec::SmallVec;
 
 use crate::consts::{constant, Constant};
 
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<VersionReq> {
+    if let Ok(version) = VersionReq::parse(msrv) {
+        return Some(version);
+    } else if let Some(sess) = sess {
+        if let Some(span) = span {
+            sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
+        }
+    }
+    None
+}
+
+pub fn meets_msrv(msrv: Option<&VersionReq>, lint_msrv: &Version) -> bool {
+    msrv.map_or(true, |msrv| !msrv.matches(lint_msrv))
+}
+
+#[macro_export]
+macro_rules! extract_msrv_attr {
+    (LateContext) => {
+        fn enter_lint_attrs(&mut self, cx: &rustc_lint::LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+            match get_inner_attr(cx.sess(), attrs, "msrv") {
+                Some(msrv_attr) => {
+                    if let Some(msrv) = msrv_attr.value_str() {
+                        self.msrv = crate::utils::parse_msrv(&msrv.to_string(), Some(cx.sess()), Some(msrv_attr.span));
+                    } else {
+                        cx.sess().span_err(msrv_attr.span, "bad clippy attribute");
+                    }
+                },
+                _ => (),
+            }
+        }
+    };
+    (EarlyContext) => {
+        fn enter_lint_attrs(&mut self, cx: &rustc_lint::EarlyContext<'tcx>, attrs: &'tcx [Attribute]) {
+            match get_inner_attr(cx.sess, attrs, "msrv") {
+                Some(msrv_attr) => {
+                    if let Some(msrv) = msrv_attr.value_str() {
+                        self.msrv = crate::utils::parse_msrv(&msrv.to_string(), Some(cx.sess), Some(msrv_attr.span));
+                    } else {
+                        cx.sess.span_err(msrv_attr.span, "bad clippy attribute");
+                    }
+                },
+                _ => (),
+            }
+        }
+    };
+}
+
 /// Returns `true` if the two spans come from differing expansions (i.e., one is
 /// from a macro and one isn't).
 #[must_use]
diff --git a/tests/ui-toml/invalid_min_rust_version/clippy.toml b/tests/ui-toml/invalid_min_rust_version/clippy.toml
new file mode 100644
index 00000000000..088b12b2dac
--- /dev/null
+++ b/tests/ui-toml/invalid_min_rust_version/clippy.toml
@@ -0,0 +1 @@
+msrv = "invalid.version"
diff --git a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs
new file mode 100644
index 00000000000..2ebf28645e5
--- /dev/null
+++ b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs
@@ -0,0 +1,3 @@
+#![allow(clippy::redundant_clone)]
+
+fn main() {}
diff --git a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr
new file mode 100644
index 00000000000..e9d8fd2e0f5
--- /dev/null
+++ b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr
@@ -0,0 +1,4 @@
+error: error reading Clippy's configuration file. `invalid.version` is not a valid Rust version
+
+error: aborting due to previous error
+
diff --git a/tests/ui-toml/min_rust_version/clippy.toml b/tests/ui-toml/min_rust_version/clippy.toml
new file mode 100644
index 00000000000..8e17d8074c4
--- /dev/null
+++ b/tests/ui-toml/min_rust_version/clippy.toml
@@ -0,0 +1 @@
+msrv = "1.0.0"
diff --git a/tests/ui-toml/min_rust_version/min_rust_version.rs b/tests/ui-toml/min_rust_version/min_rust_version.rs
new file mode 100644
index 00000000000..bc41efa42a1
--- /dev/null
+++ b/tests/ui-toml/min_rust_version/min_rust_version.rs
@@ -0,0 +1,68 @@
+#![allow(clippy::redundant_clone)]
+#![warn(clippy::manual_non_exhaustive)]
+
+use std::ops::Deref;
+
+mod enums {
+    enum E {
+        A,
+        B,
+        #[doc(hidden)]
+        _C,
+    }
+
+    // user forgot to remove the marker
+    #[non_exhaustive]
+    enum Ep {
+        A,
+        B,
+        #[doc(hidden)]
+        _C,
+    }
+}
+
+fn option_as_ref_deref() {
+    let mut opt = Some(String::from("123"));
+
+    let _ = opt.as_ref().map(String::as_str);
+    let _ = opt.as_ref().map(|x| x.as_str());
+    let _ = opt.as_mut().map(String::as_mut_str);
+    let _ = opt.as_mut().map(|x| x.as_mut_str());
+}
+
+fn match_like_matches() {
+    let _y = match Some(5) {
+        Some(0) => true,
+        _ => false,
+    };
+}
+
+fn match_same_arms() {
+    match (1, 2, 3) {
+        (1, .., 3) => 42,
+        (.., 3) => 42, //~ ERROR match arms have same body
+        _ => 0,
+    };
+}
+
+fn match_same_arms2() {
+    let _ = match Some(42) {
+        Some(_) => 24,
+        None => 24, //~ ERROR match arms have same body
+    };
+}
+
+fn manual_strip_msrv() {
+    let s = "hello, world!";
+    if s.starts_with("hello, ") {
+        assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+    }
+}
+
+fn main() {
+    option_as_ref_deref();
+    match_like_matches();
+    match_same_arms();
+    match_same_arms2();
+    manual_strip_msrv();
+}
diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
index a58e7e918e2..af3d9ecf6e8 100644
--- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
+++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
@@ -1,4 +1,4 @@
-error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `third-party` at line 5 column 1
+error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `third-party` at line 5 column 1
 
 error: aborting due to previous error
 
diff --git a/tests/ui/min_rust_version_attr.rs b/tests/ui/min_rust_version_attr.rs
new file mode 100644
index 00000000000..8ed483a3ac6
--- /dev/null
+++ b/tests/ui/min_rust_version_attr.rs
@@ -0,0 +1,51 @@
+#![allow(clippy::redundant_clone)]
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "1.0.0"]
+
+use std::ops::Deref;
+
+fn option_as_ref_deref() {
+    let mut opt = Some(String::from("123"));
+
+    let _ = opt.as_ref().map(String::as_str);
+    let _ = opt.as_ref().map(|x| x.as_str());
+    let _ = opt.as_mut().map(String::as_mut_str);
+    let _ = opt.as_mut().map(|x| x.as_mut_str());
+}
+
+fn match_like_matches() {
+    let _y = match Some(5) {
+        Some(0) => true,
+        _ => false,
+    };
+}
+
+fn match_same_arms() {
+    match (1, 2, 3) {
+        (1, .., 3) => 42,
+        (.., 3) => 42, //~ ERROR match arms have same body
+        _ => 0,
+    };
+}
+
+fn match_same_arms2() {
+    let _ = match Some(42) {
+        Some(_) => 24,
+        None => 24, //~ ERROR match arms have same body
+    };
+}
+
+fn manual_strip_msrv() {
+    let s = "hello, world!";
+    if s.starts_with("hello, ") {
+        assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+    }
+}
+
+fn main() {
+    option_as_ref_deref();
+    match_like_matches();
+    match_same_arms();
+    match_same_arms2();
+    manual_strip_msrv();
+}
diff --git a/tests/ui/min_rust_version_invalid_attr.rs b/tests/ui/min_rust_version_invalid_attr.rs
new file mode 100644
index 00000000000..f20841891a7
--- /dev/null
+++ b/tests/ui/min_rust_version_invalid_attr.rs
@@ -0,0 +1,4 @@
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "invalid.version"]
+
+fn main() {}
diff --git a/tests/ui/min_rust_version_invalid_attr.stderr b/tests/ui/min_rust_version_invalid_attr.stderr
new file mode 100644
index 00000000000..6ff88ca56f8
--- /dev/null
+++ b/tests/ui/min_rust_version_invalid_attr.stderr
@@ -0,0 +1,8 @@
+error: `invalid.version` is not a valid Rust version
+  --> $DIR/min_rust_version_invalid_attr.rs:2:1
+   |
+LL | #![clippy::msrv = "invalid.version"]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/tests/ui/min_rust_version_no_patch.rs b/tests/ui/min_rust_version_no_patch.rs
new file mode 100644
index 00000000000..515fe8f95e9
--- /dev/null
+++ b/tests/ui/min_rust_version_no_patch.rs
@@ -0,0 +1,14 @@
+#![allow(clippy::redundant_clone)]
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "^1.0"]
+
+fn manual_strip_msrv() {
+    let s = "hello, world!";
+    if s.starts_with("hello, ") {
+        assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+    }
+}
+
+fn main() {
+    manual_strip_msrv()
+}
diff --git a/tests/ui/min_rust_version_outer_attr.rs b/tests/ui/min_rust_version_outer_attr.rs
new file mode 100644
index 00000000000..551948bd72e
--- /dev/null
+++ b/tests/ui/min_rust_version_outer_attr.rs
@@ -0,0 +1,4 @@
+#![feature(custom_inner_attributes)]
+
+#[clippy::msrv = "invalid.version"]
+fn main() {}
diff --git a/tests/ui/min_rust_version_outer_attr.stderr b/tests/ui/min_rust_version_outer_attr.stderr
new file mode 100644
index 00000000000..579ee7a87d2
--- /dev/null
+++ b/tests/ui/min_rust_version_outer_attr.stderr
@@ -0,0 +1,8 @@
+error: `msrv` cannot be an outer attribute
+  --> $DIR/min_rust_version_outer_attr.rs:3:1
+   |
+LL | #[clippy::msrv = "invalid.version"]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+