about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTimo <30553356+y21@users.noreply.github.com>2025-02-28 20:48:55 +0000
committerGitHub <noreply@github.com>2025-02-28 20:48:55 +0000
commit9f9a822509e5ad3e560cbbe830d1013f936fca28 (patch)
treee4f6389817f4326dd6491cda2fd9cd4f47d5436a
parent6f66a6037e4246305559e503a53fd42b83d809b3 (diff)
parent5b0004c45fae362f50e9e587f7a16249501d07f8 (diff)
Check for MSRV attributes in late passes using the HIR (#13821)
Closes https://github.com/rust-lang/rust-clippy/issues/13169

Late lints now use a parent iter to check for `#[clippy::msrv]`
attributes instead of keeping track with `extract_msrv_attr`. This is
required for incremental lints since they run per module instead of per
crate so don't visit all the necessary attributes

As a basic optimisation if no `#[clippy::msrv]` attributes are
discovered in early passes the HIR access is skipped completely and just
the configured MSRV is used, for most code bases this will be the case

changelog: none
-rw-r--r--book/src/development/adding_lints.md31
-rw-r--r--clippy_config/src/conf.rs2
-rw-r--r--clippy_dev/src/main.rs6
-rw-r--r--clippy_dev/src/new_lint.rs82
-rw-r--r--clippy_lints/src/almost_complete_range.rs8
-rw-r--r--clippy_lints/src/approx_const.rs8
-rw-r--r--clippy_lints/src/assigning_clones.rs8
-rw-r--r--clippy_lints/src/attrs/deprecated_cfg_attr.rs4
-rw-r--r--clippy_lints/src/attrs/mod.rs22
-rw-r--r--clippy_lints/src/attrs/repr_attributes.rs11
-rw-r--r--clippy_lints/src/booleans.rs22
-rw-r--r--clippy_lints/src/casts/borrow_as_ptr.rs4
-rw-r--r--clippy_lints/src/casts/cast_abs_to_unsigned.rs6
-rw-r--r--clippy_lints/src/casts/cast_lossless.rs6
-rw-r--r--clippy_lints/src/casts/cast_slice_different_sizes.rs9
-rw-r--r--clippy_lints/src/casts/cast_slice_from_raw_parts.rs6
-rw-r--r--clippy_lints/src/casts/mod.rs24
-rw-r--r--clippy_lints/src/casts/ptr_as_ptr.rs7
-rw-r--r--clippy_lints/src/casts/ptr_cast_constness.rs4
-rw-r--r--clippy_lints/src/checked_conversions.rs8
-rw-r--r--clippy_lints/src/derivable_impls.rs8
-rw-r--r--clippy_lints/src/format_args.rs10
-rw-r--r--clippy_lints/src/from_over_into.rs10
-rw-r--r--clippy_lints/src/functions/mod.rs10
-rw-r--r--clippy_lints/src/functions/result.rs10
-rw-r--r--clippy_lints/src/if_then_some_else_none.rs10
-rw-r--r--clippy_lints/src/implicit_saturating_sub.rs16
-rw-r--r--clippy_lints/src/incompatible_msrv.rs39
-rw-r--r--clippy_lints/src/index_refutable_slice.rs6
-rw-r--r--clippy_lints/src/instant_subtraction.rs8
-rw-r--r--clippy_lints/src/legacy_numeric_constants.rs12
-rw-r--r--clippy_lints/src/lifetimes.rs27
-rw-r--r--clippy_lints/src/lines_filter_map_ok.rs10
-rw-r--r--clippy_lints/src/loops/explicit_iter_loop.rs13
-rw-r--r--clippy_lints/src/loops/manual_flatten.rs4
-rw-r--r--clippy_lints/src/loops/manual_slice_fill.rs8
-rw-r--r--clippy_lints/src/loops/mod.rs12
-rw-r--r--clippy_lints/src/loops/same_item_push.rs6
-rw-r--r--clippy_lints/src/manual_bits.rs8
-rw-r--r--clippy_lints/src/manual_clamp.rs14
-rw-r--r--clippy_lints/src/manual_div_ceil.rs11
-rw-r--r--clippy_lints/src/manual_float_methods.rs8
-rw-r--r--clippy_lints/src/manual_hash_one.rs8
-rw-r--r--clippy_lints/src/manual_is_ascii_check.rs10
-rw-r--r--clippy_lints/src/manual_let_else.rs2
-rw-r--r--clippy_lints/src/manual_main_separator_str.rs8
-rw-r--r--clippy_lints/src/manual_non_exhaustive.rs6
-rw-r--r--clippy_lints/src/manual_option_as_slice.rs45
-rw-r--r--clippy_lints/src/manual_rem_euclid.rs10
-rw-r--r--clippy_lints/src/manual_retain.rs26
-rw-r--r--clippy_lints/src/manual_strip.rs9
-rw-r--r--clippy_lints/src/matches/collapsible_match.rs8
-rw-r--r--clippy_lints/src/matches/mod.rs28
-rw-r--r--clippy_lints/src/matches/redundant_guards.rs6
-rw-r--r--clippy_lints/src/mem_replace.rs20
-rw-r--r--clippy_lints/src/methods/cloned_instead_of_copied.rs6
-rw-r--r--clippy_lints/src/methods/err_expect.rs7
-rw-r--r--clippy_lints/src/methods/filter_map_next.rs4
-rw-r--r--clippy_lints/src/methods/io_other_error.rs6
-rw-r--r--clippy_lints/src/methods/is_digit_ascii_radix.rs10
-rw-r--r--clippy_lints/src/methods/iter_kv_map.rs4
-rw-r--r--clippy_lints/src/methods/manual_c_str_literals.rs8
-rw-r--r--clippy_lints/src/methods/manual_inspect.rs6
-rw-r--r--clippy_lints/src/methods/manual_is_variant_and.rs4
-rw-r--r--clippy_lints/src/methods/manual_repeat_n.rs6
-rw-r--r--clippy_lints/src/methods/manual_try_fold.rs4
-rw-r--r--clippy_lints/src/methods/map_clone.rs6
-rw-r--r--clippy_lints/src/methods/map_unwrap_or.rs4
-rw-r--r--clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs6
-rw-r--r--clippy_lints/src/methods/mod.rs64
-rw-r--r--clippy_lints/src/methods/option_as_ref_deref.rs8
-rw-r--r--clippy_lints/src/methods/option_map_unwrap_or.rs8
-rw-r--r--clippy_lints/src/methods/path_ends_with_ext.rs4
-rw-r--r--clippy_lints/src/methods/str_splitn.rs4
-rw-r--r--clippy_lints/src/methods/string_lit_chars_any.rs6
-rw-r--r--clippy_lints/src/methods/unnecessary_map_or.rs6
-rw-r--r--clippy_lints/src/methods/unnecessary_to_owned.rs6
-rw-r--r--clippy_lints/src/methods/useless_nonzero_new_unchecked.rs6
-rw-r--r--clippy_lints/src/missing_const_for_fn.rs26
-rw-r--r--clippy_lints/src/missing_const_for_thread_local.rs16
-rw-r--r--clippy_lints/src/needless_borrows_for_generic_args.rs10
-rw-r--r--clippy_lints/src/non_std_lazy_statics.rs34
-rw-r--r--clippy_lints/src/operators/manual_midpoint.rs10
-rw-r--r--clippy_lints/src/operators/mod.rs6
-rw-r--r--clippy_lints/src/question_mark.rs3
-rw-r--r--clippy_lints/src/ranges.rs7
-rw-r--r--clippy_lints/src/redundant_field_names.rs9
-rw-r--r--clippy_lints/src/redundant_static_lifetimes.rs8
-rw-r--r--clippy_lints/src/repeat_vec_with_capacity.rs13
-rw-r--r--clippy_lints/src/std_instead_of_core.rs12
-rw-r--r--clippy_lints/src/string_patterns.rs12
-rw-r--r--clippy_lints/src/trait_bounds.rs8
-rw-r--r--clippy_lints/src/transmute/mod.rs16
-rw-r--r--clippy_lints/src/transmute/transmute_float_to_int.rs4
-rw-r--r--clippy_lints/src/transmute/transmute_int_to_float.rs4
-rw-r--r--clippy_lints/src/transmute/transmute_num_to_bytes.rs5
-rw-r--r--clippy_lints/src/transmute/transmute_ptr_to_ptr.rs6
-rw-r--r--clippy_lints/src/transmute/transmute_ptr_to_ref.rs6
-rw-r--r--clippy_lints/src/tuple_array_conversions.rs8
-rw-r--r--clippy_lints/src/unnested_or_patterns.rs8
-rw-r--r--clippy_lints/src/unused_trait_names.rs10
-rw-r--r--clippy_lints/src/use_self.rs10
-rw-r--r--clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs13
-rw-r--r--clippy_lints/src/vec.rs6
-rw-r--r--clippy_utils/src/lib.rs17
-rw-r--r--clippy_utils/src/msrvs.rs135
-rw-r--r--clippy_utils/src/paths.rs3
-rw-r--r--clippy_utils/src/qualify_min_const_fn.rs123
-rw-r--r--tests/ui-internal/invalid_msrv_attr_impl.fixed11
-rw-r--r--tests/ui-internal/invalid_msrv_attr_impl.rs8
-rw-r--r--tests/ui-internal/invalid_msrv_attr_impl.stderr24
-rw-r--r--tests/ui/msrv_attributes_without_early_lints.rs14
112 files changed, 672 insertions, 850 deletions
diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md
index 60135e96c5a..0b9010f0107 100644
--- a/book/src/development/adding_lints.md
+++ b/book/src/development/adding_lints.md
@@ -460,7 +460,7 @@ pub struct ManualStrip {
 
 impl ManualStrip {
     pub fn new(conf: &'static Conf) -> Self {
-        Self { msrv: conf.msrv.clone() }
+        Self { msrv: conf.msrv }
     }
 }
 ```
@@ -469,24 +469,13 @@ The project's MSRV can then be matched against the feature MSRV in the LintPass
 using the `Msrv::meets` method.
 
 ``` rust
-if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
+if !self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) {
     return;
 }
 ```
 
-The project's MSRV can also be specified as an 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,ignore
-impl<'tcx> LateLintPass<'tcx> for ManualStrip {
-    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
-        ...
-    }
-    extract_msrv_attr!(LateContext);
-}
-```
+Early lint passes should instead use `MsrvStack` coupled with
+`extract_msrv_attr!()`
 
 Once the `msrv` is added to the lint, a relevant test case should be added to
 the lint's test file, `tests/ui/manual_strip.rs` in this example. It should
@@ -512,8 +501,16 @@ in `clippy_config/src/conf.rs`:
 
 ```rust
 define_Conf! {
-    /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
-    (msrv: Option<String> = None),
+    #[lints(
+        allow_attributes,
+        allow_attributes_without_reason,
+        ..
+        <the newly added lint name>,
+        ..
+        unused_trait_names,
+        use_self,
+    )]
+    msrv: Msrv = Msrv::default(),
     ...
 }
 ```
diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs
index e4571204f46..a61acbaa96b 100644
--- a/clippy_config/src/conf.rs
+++ b/clippy_config/src/conf.rs
@@ -670,7 +670,7 @@ define_Conf! {
         unused_trait_names,
         use_self,
     )]
-    msrv: Msrv = Msrv::empty(),
+    msrv: Msrv = Msrv::default(),
     /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
     #[lints(large_types_passed_by_value)]
     pass_by_value_size_limit: u64 = 256,
diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs
index fcdee073f88..074dea4ab77 100644
--- a/clippy_dev/src/main.rs
+++ b/clippy_dev/src/main.rs
@@ -35,7 +35,7 @@ fn main() {
             category,
             r#type,
             msrv,
-        } => match new_lint::create(&pass, &name, &category, r#type.as_deref(), msrv) {
+        } => match new_lint::create(pass, &name, &category, r#type.as_deref(), msrv) {
             Ok(()) => update_lints::update(utils::UpdateMode::Change),
             Err(e) => eprintln!("Unable to create lint: {e}"),
         },
@@ -147,9 +147,9 @@ enum DevCommand {
     #[command(name = "new_lint")]
     /// Create a new lint and run `cargo dev update_lints`
     NewLint {
-        #[arg(short, long, value_parser = ["early", "late"], conflicts_with = "type", default_value = "late")]
+        #[arg(short, long, conflicts_with = "type", default_value = "late")]
         /// Specify whether the lint runs during the early or late pass
-        pass: String,
+        pass: new_lint::Pass,
         #[arg(
             short,
             long,
diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs
index cf6e4424566..96e12706c9e 100644
--- a/clippy_dev/src/new_lint.rs
+++ b/clippy_dev/src/new_lint.rs
@@ -1,13 +1,28 @@
 use crate::utils::{clippy_project_root, clippy_version};
+use clap::ValueEnum;
 use indoc::{formatdoc, writedoc};
-use std::fmt::Write as _;
+use std::fmt::{self, Write as _};
 use std::fs::{self, OpenOptions};
-use std::io::prelude::*;
+use std::io::{self, Write as _};
 use std::path::{Path, PathBuf};
-use std::{fmt, io};
+
+#[derive(Clone, Copy, PartialEq, ValueEnum)]
+pub enum Pass {
+    Early,
+    Late,
+}
+
+impl fmt::Display for Pass {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(match self {
+            Pass::Early => "early",
+            Pass::Late => "late",
+        })
+    }
+}
 
 struct LintData<'a> {
-    pass: &'a str,
+    pass: Pass,
     name: &'a str,
     category: &'a str,
     ty: Option<&'a str>,
@@ -35,7 +50,7 @@ impl<T> Context for io::Result<T> {
 /// # Errors
 ///
 /// This function errors out if the files couldn't be created or written to.
-pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> {
+pub fn create(pass: Pass, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> {
     if category == "cargo" && ty.is_none() {
         // `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo`
         ty = Some("cargo");
@@ -56,7 +71,7 @@ pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv
         add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?;
     }
 
-    if pass == "early" {
+    if pass == Pass::Early {
         println!(
             "\n\
             NOTE: Use a late pass unless you need something specific from\n\
@@ -136,23 +151,17 @@ fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
     let mut lib_rs = fs::read_to_string(path).context("reading")?;
 
     let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
+    let ctor_arg = if lint.pass == Pass::Late { "_" } else { "" };
+    let lint_pass = lint.pass;
+    let module_name = lint.name;
+    let camel_name = to_camel_case(lint.name);
 
     let new_lint = if enable_msrv {
         format!(
             "store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf)));\n    ",
-            lint_pass = lint.pass,
-            ctor_arg = if lint.pass == "late" { "_" } else { "" },
-            module_name = lint.name,
-            camel_name = to_camel_case(lint.name),
         )
     } else {
-        format!(
-            "store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n    ",
-            lint_pass = lint.pass,
-            ctor_arg = if lint.pass == "late" { "_" } else { "" },
-            module_name = lint.name,
-            camel_name = to_camel_case(lint.name),
-        )
+        format!("store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n    ",)
     };
 
     lib_rs.insert_str(comment_start, &new_lint);
@@ -242,11 +251,16 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
     let mut result = String::new();
 
     let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
-        "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
-        "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
-        _ => {
-            unreachable!("`pass_type` should only ever be `early` or `late`!");
-        },
+        Pass::Early => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
+        Pass::Late => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
+    };
+    let (msrv_ty, msrv_ctor, extract_msrv) = match lint.pass {
+        Pass::Early => (
+            "MsrvStack",
+            "MsrvStack::new(conf.msrv)",
+            "\n    extract_msrv_attr!();\n",
+        ),
+        Pass::Late => ("Msrv", "conf.msrv", ""),
     };
 
     let lint_name = lint.name;
@@ -258,10 +272,10 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
         let _: fmt::Result = writedoc!(
             result,
             r"
-            use clippy_utils::msrvs::{{self, Msrv}};
+            use clippy_utils::msrvs::{{self, {msrv_ty}}};
             use clippy_config::Conf;
             {pass_import}
-            use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
+            use rustc_lint::{{{context_import}, {pass_type}}};
             use rustc_session::impl_lint_pass;
 
         "
@@ -285,20 +299,18 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
             result,
             r"
             pub struct {name_camel} {{
-                msrv: Msrv,
+                msrv: {msrv_ty},
             }}
 
             impl {name_camel} {{
                 pub fn new(conf: &'static Conf) -> Self {{
-                    Self {{ msrv: conf.msrv.clone() }}
+                    Self {{ msrv: {msrv_ctor} }}
                 }}
             }}
 
             impl_lint_pass!({name_camel} => [{name_upper}]);
 
-            impl {pass_type}{pass_lifetimes} for {name_camel} {{
-                extract_msrv_attr!({context_import});
-            }}
+            impl {pass_type}{pass_lifetimes} for {name_camel} {{{extract_msrv}}}
 
             // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed.
             // TODO: Update msrv config comment in `clippy_config/src/conf.rs`
@@ -375,9 +387,9 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
 
     let mod_file_path = ty_dir.join("mod.rs");
     let context_import = setup_mod_file(&mod_file_path, lint)?;
-    let pass_lifetimes = match context_import {
-        "LateContext" => "<'_>",
-        _ => "",
+    let (pass_lifetimes, msrv_ty, msrv_ref, msrv_cx) = match context_import {
+        "LateContext" => ("<'_>", "Msrv", "", "cx, "),
+        _ => ("", "MsrvStack", "&", ""),
     };
 
     let name_upper = lint.name.to_uppercase();
@@ -387,14 +399,14 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
         let _: fmt::Result = writedoc!(
             lint_file_contents,
             r#"
-                use clippy_utils::msrvs::{{self, Msrv}};
+                use clippy_utils::msrvs::{{self, {msrv_ty}}};
                 use rustc_lint::{{{context_import}, LintContext}};
 
                 use super::{name_upper};
 
                 // TODO: Adjust the parameters as necessary
-                pub(super) fn check(cx: &{context_import}{pass_lifetimes}, msrv: &Msrv) {{
-                    if !msrv.meets(todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
+                pub(super) fn check(cx: &{context_import}{pass_lifetimes}, msrv: {msrv_ref}{msrv_ty}) {{
+                    if !msrv.meets({msrv_cx}todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
                         return;
                     }}
                     todo!();
diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs
index 0f7f779e8ea..4f55968d562 100644
--- a/clippy_lints/src/almost_complete_range.rs
+++ b/clippy_lints/src/almost_complete_range.rs
@@ -1,6 +1,6 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, MsrvStack};
 use clippy_utils::source::{trim_span, walk_span_to_context};
 use rustc_ast::ast::{Expr, ExprKind, LitKind, Pat, PatKind, RangeEnd, RangeLimits};
 use rustc_errors::Applicability;
@@ -31,12 +31,12 @@ declare_clippy_lint! {
 impl_lint_pass!(AlmostCompleteRange => [ALMOST_COMPLETE_RANGE]);
 
 pub struct AlmostCompleteRange {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 impl AlmostCompleteRange {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -96,7 +96,7 @@ impl EarlyLintPass for AlmostCompleteRange {
         }
     }
 
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
 }
 
 fn is_incomplete_range(start: &Expr, end: &Expr) -> bool {
diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs
index fbcd49f0018..9ae746c13b2 100644
--- a/clippy_lints/src/approx_const.rs
+++ b/clippy_lints/src/approx_const.rs
@@ -69,9 +69,7 @@ pub struct ApproxConstant {
 
 impl ApproxConstant {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -89,8 +87,6 @@ impl LateLintPass<'_> for ApproxConstant {
             _ => (),
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 impl ApproxConstant {
@@ -98,7 +94,7 @@ impl ApproxConstant {
         let s = s.as_str();
         if s.parse::<f64>().is_ok() {
             for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
-                if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(msrv)) {
+                if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) {
                     span_lint_and_help(
                         cx,
                         APPROX_CONSTANT,
diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs
index 348495f97a2..ab34af7c317 100644
--- a/clippy_lints/src/assigning_clones.rs
+++ b/clippy_lints/src/assigning_clones.rs
@@ -59,9 +59,7 @@ pub struct AssigningClones {
 
 impl AssigningClones {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -90,7 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
                 sym::clone if is_diag_trait_item(cx, fn_id, sym::Clone) => CloneTrait::Clone,
                 _ if fn_name.as_str() == "to_owned"
                     && is_diag_trait_item(cx, fn_id, sym::ToOwned)
-                    && self.msrv.meets(msrvs::CLONE_INTO) =>
+                    && self.msrv.meets(cx, msrvs::CLONE_INTO) =>
                 {
                     CloneTrait::ToOwned
                 },
@@ -143,8 +141,6 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Checks if the data being cloned borrows from the place that is being assigned to:
diff --git a/clippy_lints/src/attrs/deprecated_cfg_attr.rs b/clippy_lints/src/attrs/deprecated_cfg_attr.rs
index 3a462018e3e..cd38aed26a3 100644
--- a/clippy_lints/src/attrs/deprecated_cfg_attr.rs
+++ b/clippy_lints/src/attrs/deprecated_cfg_attr.rs
@@ -1,12 +1,12 @@
 use super::{Attribute, DEPRECATED_CFG_ATTR, DEPRECATED_CLIPPY_CFG_ATTR, unnecessary_clippy_cfg};
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, MsrvStack};
 use rustc_ast::AttrStyle;
 use rustc_errors::Applicability;
 use rustc_lint::EarlyContext;
 use rustc_span::sym;
 
-pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
+pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) {
     // check cfg_attr
     if attr.has_name(sym::cfg_attr)
         && let Some(items) = attr.meta_item_list()
diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs
index e0aab8c95a8..2b59c218d57 100644
--- a/clippy_lints/src/attrs/mod.rs
+++ b/clippy_lints/src/attrs/mod.rs
@@ -14,7 +14,7 @@ mod useless_attribute;
 mod utils;
 
 use clippy_config::Conf;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, Msrv, MsrvStack};
 use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
 use rustc_hir::{ImplItem, Item, TraitItem};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
@@ -459,9 +459,7 @@ impl_lint_pass!(Attributes => [
 
 impl Attributes {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -471,7 +469,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
         if is_relevant_item(cx, item) {
             inline_always::check(cx, item.span, item.ident.name, attrs);
         }
-        repr_attributes::check(cx, item.span, attrs, &self.msrv);
+        repr_attributes::check(cx, item.span, attrs, self.msrv);
     }
 
     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
@@ -485,18 +483,16 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
             inline_always::check(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 pub struct EarlyAttributes {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl EarlyAttributes {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -515,17 +511,17 @@ impl EarlyLintPass for EarlyAttributes {
         non_minimal_cfg::check(cx, attr);
     }
 
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
 }
 
 pub struct PostExpansionEarlyAttributes {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl PostExpansionEarlyAttributes {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -589,5 +585,5 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
         duplicated_attributes::check(cx, &item.attrs);
     }
 
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
 }
diff --git a/clippy_lints/src/attrs/repr_attributes.rs b/clippy_lints/src/attrs/repr_attributes.rs
index 3efb8bd3ff0..e5cfbaf952a 100644
--- a/clippy_lints/src/attrs/repr_attributes.rs
+++ b/clippy_lints/src/attrs/repr_attributes.rs
@@ -4,17 +4,11 @@ use rustc_lint::LateContext;
 use rustc_span::Span;
 
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::msrvs;
+use clippy_utils::msrvs::{self, Msrv};
 
 use super::REPR_PACKED_WITHOUT_ABI;
 
-pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute], msrv: &msrvs::Msrv) {
-    if msrv.meets(msrvs::REPR_RUST) {
-        check_packed(cx, item_span, attrs);
-    }
-}
-
-fn check_packed(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) {
+pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute], msrv: Msrv) {
     if let Some(reprs) = find_attr!(attrs, AttributeKind::Repr(r) => r) {
         let packed_span = reprs
             .iter()
@@ -25,6 +19,7 @@ fn check_packed(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) {
             && !reprs
                 .iter()
                 .any(|(x, _)| *x == ReprAttr::ReprC || *x == ReprAttr::ReprRust)
+            && msrv.meets(cx, msrvs::REPR_RUST)
         {
             span_lint_and_then(
                 cx,
diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs
index f57f56f3efd..48b5d4da888 100644
--- a/clippy_lints/src/booleans.rs
+++ b/clippy_lints/src/booleans.rs
@@ -85,9 +85,7 @@ pub struct NonminimalBool {
 
 impl NonminimalBool {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -103,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
         _: Span,
         _: LocalDefId,
     ) {
-        NonminimalBoolVisitor { cx, msrv: &self.msrv }.visit_body(body);
+        NonminimalBoolVisitor { cx, msrv: self.msrv }.visit_body(body);
     }
 
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
@@ -120,8 +118,6 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
             _ => {},
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
@@ -198,7 +194,7 @@ fn check_inverted_bool_in_condition(
     );
 }
 
-fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
+fn check_simplify_not(cx: &LateContext<'_>, msrv: Msrv, expr: &Expr<'_>) {
     if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
         && !expr.span.from_expansion()
         && !inner.span.from_expansion()
@@ -234,7 +230,7 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
 
 struct NonminimalBoolVisitor<'a, 'tcx> {
     cx: &'a LateContext<'tcx>,
-    msrv: &'a Msrv,
+    msrv: Msrv,
 }
 
 use quine_mc_cluskey::Bool;
@@ -327,7 +323,7 @@ impl<'v> Hir2Qmm<'_, '_, 'v> {
 struct SuggestContext<'a, 'tcx, 'v> {
     terminals: &'v [&'v Expr<'v>],
     cx: &'a LateContext<'tcx>,
-    msrv: &'a Msrv,
+    msrv: Msrv,
     output: String,
 }
 
@@ -398,7 +394,7 @@ impl SuggestContext<'_, '_, '_> {
     }
 }
 
-fn simplify_not(cx: &LateContext<'_>, curr_msrv: &Msrv, expr: &Expr<'_>) -> Option<String> {
+fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Option<String> {
     match &expr.kind {
         ExprKind::Binary(binop, lhs, rhs) => {
             if !implements_ord(cx, lhs) {
@@ -440,7 +436,9 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: &Msrv, expr: &Expr<'_>) -> Opti
                 .iter()
                 .copied()
                 .flat_map(|(msrv, a, b)| vec![(msrv, a, b), (msrv, b, a)])
-                .find(|&(msrv, a, _)| msrv.is_none_or(|msrv| curr_msrv.meets(msrv)) && a == path.ident.name.as_str())
+                .find(|&(msrv, a, _)| {
+                    a == path.ident.name.as_str() && msrv.is_none_or(|msrv| curr_msrv.meets(cx, msrv))
+                })
                 .and_then(|(_, _, neg_method)| {
                     let negated_args = args
                         .iter()
@@ -469,7 +467,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: &Msrv, expr: &Expr<'_>) -> Opti
     }
 }
 
-fn suggest(cx: &LateContext<'_>, msrv: &Msrv, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
+fn suggest(cx: &LateContext<'_>, msrv: Msrv, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
     let mut suggest_context = SuggestContext {
         terminals,
         cx,
diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs
index 6057144bc6a..d143629da3a 100644
--- a/clippy_lints/src/casts/borrow_as_ptr.rs
+++ b/clippy_lints/src/casts/borrow_as_ptr.rs
@@ -16,7 +16,7 @@ pub(super) fn check<'tcx>(
     expr: &'tcx Expr<'_>,
     cast_expr: &'tcx Expr<'_>,
     cast_to: &'tcx Ty<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     if matches!(cast_to.kind, TyKind::Ptr(_))
         && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
@@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(
             return false;
         }
 
-        let (suggestion, span) = if msrv.meets(msrvs::RAW_REF_OP) {
+        let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
             let operator_kind = match mutability {
                 Mutability::Not => "const",
                 Mutability::Mut => "mut",
diff --git a/clippy_lints/src/casts/cast_abs_to_unsigned.rs b/clippy_lints/src/casts/cast_abs_to_unsigned.rs
index ae433773193..8b3529e84fc 100644
--- a/clippy_lints/src/casts/cast_abs_to_unsigned.rs
+++ b/clippy_lints/src/casts/cast_abs_to_unsigned.rs
@@ -14,13 +14,13 @@ pub(super) fn check(
     cast_expr: &Expr<'_>,
     cast_from: Ty<'_>,
     cast_to: Ty<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if msrv.meets(msrvs::UNSIGNED_ABS)
-        && let ty::Int(from) = cast_from.kind()
+    if let ty::Int(from) = cast_from.kind()
         && let ty::Uint(to) = cast_to.kind()
         && let ExprKind::MethodCall(method_path, receiver, [], _) = cast_expr.kind
         && method_path.ident.name.as_str() == "abs"
+        && msrv.meets(cx, msrvs::UNSIGNED_ABS)
     {
         let span = if from.bit_width() == to.bit_width() {
             expr.span
diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs
index c326a0d935c..3ae43732dc0 100644
--- a/clippy_lints/src/casts/cast_lossless.rs
+++ b/clippy_lints/src/casts/cast_lossless.rs
@@ -19,7 +19,7 @@ pub(super) fn check(
     cast_from: Ty<'_>,
     cast_to: Ty<'_>,
     cast_to_hir: &rustc_hir::Ty<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if !should_lint(cx, cast_from, cast_to, msrv) {
         return;
@@ -70,7 +70,7 @@ pub(super) fn check(
     );
 }
 
-fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
+fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: Msrv) -> bool {
     // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
     if is_in_const_context(cx) {
         return false;
@@ -96,7 +96,7 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &
             };
             !is_isize_or_usize(cast_from) && from_nbits < to_nbits
         },
-        (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(msrvs::FROM_BOOL) => true,
+        (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(cx, msrvs::FROM_BOOL) => true,
         (_, _) => {
             matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
         },
diff --git a/clippy_lints/src/casts/cast_slice_different_sizes.rs b/clippy_lints/src/casts/cast_slice_different_sizes.rs
index 030c2d322db..c48f253606d 100644
--- a/clippy_lints/src/casts/cast_slice_different_sizes.rs
+++ b/clippy_lints/src/casts/cast_slice_different_sizes.rs
@@ -9,12 +9,7 @@ use rustc_middle::ty::{self, Ty, TypeAndMut};
 
 use super::CAST_SLICE_DIFFERENT_SIZES;
 
-pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv) {
-    // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
-    if !msrv.meets(msrvs::PTR_SLICE_RAW_PARTS) {
-        return;
-    }
-
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) {
     // if this cast is the child of another cast expression then don't emit something for it, the full
     // chain will be analyzed
     if is_child_of_cast(cx, expr) {
@@ -30,7 +25,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
         if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
             let from_size = from_layout.size.bytes();
             let to_size = to_layout.size.bytes();
-            if from_size != to_size && from_size != 0 && to_size != 0 {
+            if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
                 span_lint_and_then(
                     cx,
                     CAST_SLICE_DIFFERENT_SIZES,
diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
index c3bc5c0c9f2..46b0c88d3fe 100644
--- a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
+++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
@@ -23,9 +23,8 @@ fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> {
     }
 }
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: &Msrv) {
-    if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS)
-        && let ty::RawPtr(ptrty, _) = cast_to.kind()
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: Msrv) {
+    if let ty::RawPtr(ptrty, _) = cast_to.kind()
         && let ty::Slice(_) = ptrty.kind()
         && let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind
         && let ExprKind::Path(ref qpath) = fun.kind
@@ -33,6 +32,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
         && let Some(rpk) = raw_parts_kind(cx, fun_def_id)
         && let ctxt = expr.span.ctxt()
         && cast_expr.span.ctxt() == ctxt
+        && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS)
     {
         let func = match rpk {
             RawPartsKind::Immutable => "from_raw_parts",
diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs
index 3701f9eb5e8..dc2a1fa85bf 100644
--- a/clippy_lints/src/casts/mod.rs
+++ b/clippy_lints/src/casts/mod.rs
@@ -765,9 +765,7 @@ pub struct Casts {
 
 impl Casts {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -817,8 +815,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
             if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) {
                 return;
             }
-            cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, &self.msrv);
-            ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, &self.msrv);
+            cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv);
+            ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
             as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
             fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
             fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
@@ -831,29 +829,27 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
                     cast_possible_wrap::check(cx, expr, cast_from, cast_to);
                     cast_precision_loss::check(cx, expr, cast_from, cast_to);
                     cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to);
-                    cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, &self.msrv);
+                    cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
                     cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to);
                 }
-                cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, &self.msrv);
+                cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, self.msrv);
                 cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from);
             }
 
             as_underscore::check(cx, expr, cast_to_hir);
             as_pointer_underscore::check(cx, cast_to, cast_to_hir);
 
-            let was_borrow_as_ptr_emitted = self.msrv.meets(msrvs::BORROW_AS_PTR)
-                && borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, &self.msrv);
-            if self.msrv.meets(msrvs::PTR_FROM_REF) && !was_borrow_as_ptr_emitted {
+            let was_borrow_as_ptr_emitted = self.msrv.meets(cx, msrvs::BORROW_AS_PTR)
+                && borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv);
+            if !was_borrow_as_ptr_emitted && self.msrv.meets(cx, msrvs::PTR_FROM_REF) {
                 ref_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
             }
         }
 
         cast_ptr_alignment::check(cx, expr);
         char_lit_as_u8::check(cx, expr);
-        ptr_as_ptr::check(cx, expr, &self.msrv);
-        cast_slice_different_sizes::check(cx, expr, &self.msrv);
+        ptr_as_ptr::check(cx, expr, self.msrv);
+        cast_slice_different_sizes::check(cx, expr, self.msrv);
         ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs
index bdc389d39dd..d57e391b55d 100644
--- a/clippy_lints/src/casts/ptr_as_ptr.rs
+++ b/clippy_lints/src/casts/ptr_as_ptr.rs
@@ -26,11 +26,7 @@ impl OmitFollowedCastReason<'_> {
     }
 }
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) {
-    if !msrv.meets(msrvs::POINTER_CAST) {
-        return;
-    }
-
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
     if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind
         && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr))
         && let ty::RawPtr(_, from_mutbl) = cast_from.kind()
@@ -40,6 +36,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) {
         // The `U` in `pointer::cast` have to be `Sized`
         // as explained here: https://github.com/rust-lang/rust/issues/60602.
         && to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
+        && msrv.meets(cx, msrvs::POINTER_CAST)
     {
         let mut app = Applicability::MachineApplicable;
         let turbofish = match &cast_to_hir_ty.kind {
diff --git a/clippy_lints/src/casts/ptr_cast_constness.rs b/clippy_lints/src/casts/ptr_cast_constness.rs
index 945c05ee943..cad9c1df273 100644
--- a/clippy_lints/src/casts/ptr_cast_constness.rs
+++ b/clippy_lints/src/casts/ptr_cast_constness.rs
@@ -16,7 +16,7 @@ pub(super) fn check<'tcx>(
     cast_expr: &Expr<'_>,
     cast_from: Ty<'tcx>,
     cast_to: Ty<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let ty::RawPtr(from_ty, from_mutbl) = cast_from.kind()
         && let ty::RawPtr(to_ty, to_mutbl) = cast_to.kind()
@@ -52,7 +52,7 @@ pub(super) fn check<'tcx>(
             return;
         }
 
-        if msrv.meets(msrvs::POINTER_CAST_CONSTNESS) {
+        if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) {
             let sugg = Sugg::hir(cx, cast_expr, "_");
             let constness = match *to_mutbl {
                 Mutability::Not => "const",
diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs
index 9516af7334d..b36c8662289 100644
--- a/clippy_lints/src/checked_conversions.rs
+++ b/clippy_lints/src/checked_conversions.rs
@@ -39,9 +39,7 @@ pub struct CheckedConversions {
 
 impl CheckedConversions {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -65,7 +63,6 @@ impl LateLintPass<'_> for CheckedConversions {
             }
             && !item.span.in_external_macro(cx.sess().source_map())
             && !is_in_const_context(cx)
-            && self.msrv.meets(msrvs::TRY_FROM)
             && let Some(cv) = match op2 {
                 // todo: check for case signed -> larger unsigned == only x >= 0
                 None => check_upper_bound(lt1, gt1).filter(|cv| cv.cvt == ConversionType::FromUnsigned),
@@ -79,6 +76,7 @@ impl LateLintPass<'_> for CheckedConversions {
                 },
             }
             && let Some(to_type) = cv.to_type
+            && self.msrv.meets(cx, msrvs::TRY_FROM)
         {
             let mut applicability = Applicability::MachineApplicable;
             let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
@@ -93,8 +91,6 @@ impl LateLintPass<'_> for CheckedConversions {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Contains the result of a tried conversion check
diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs
index bb445e0155f..66a3e5e3d3c 100644
--- a/clippy_lints/src/derivable_impls.rs
+++ b/clippy_lints/src/derivable_impls.rs
@@ -62,9 +62,7 @@ pub struct DerivableImpls {
 
 impl DerivableImpls {
     pub fn new(conf: &'static Conf) -> Self {
-        DerivableImpls {
-            msrv: conf.msrv.clone(),
-        }
+        DerivableImpls { msrv: conf.msrv }
     }
 }
 
@@ -205,11 +203,9 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
         {
             if adt_def.is_struct() {
                 check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
-            } else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
+            } else if adt_def.is_enum() && self.msrv.meets(cx, msrvs::DEFAULT_ENUM_ATTRIBUTE) {
                 check_enum(cx, item, func_expr, adt_def);
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs
index 1ba355938b6..fc5f76179f9 100644
--- a/clippy_lints/src/format_args.rs
+++ b/clippy_lints/src/format_args.rs
@@ -215,7 +215,7 @@ impl<'tcx> FormatArgs<'tcx> {
         let ty_msrv_map = make_ty_msrv_map(tcx);
         Self {
             format_args,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             ignore_mixed: conf.allow_mixed_uninlined_format_args,
             ty_msrv_map,
         }
@@ -240,13 +240,11 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
 
             linter.check_templates();
 
-            if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
+            if self.msrv.meets(cx, msrvs::FORMAT_ARGS_CAPTURE) {
                 linter.check_uninlined_args();
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 struct FormatArgsExpr<'a, 'tcx> {
@@ -542,7 +540,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
         let ty = ty.peel_refs();
 
         if let Some(msrv) = self.ty_msrv_map.get(&ty)
-            && msrv.is_none_or(|msrv| self.msrv.meets(msrv))
+            && msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
         {
             return true;
         }
@@ -553,7 +551,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
             && implements_trait(self.cx, ty, deref_trait_id, &[])
             && let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, "Target")
             && let Some(msrv) = self.ty_msrv_map.get(&target_ty)
-            && msrv.is_none_or(|msrv| self.msrv.meets(msrv))
+            && msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
         {
             return true;
         }
diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs
index 41bf6e81916..6da5567d9c7 100644
--- a/clippy_lints/src/from_over_into.rs
+++ b/clippy_lints/src/from_over_into.rs
@@ -58,9 +58,7 @@ pub struct FromOverInto {
 
 impl FromOverInto {
     pub fn new(conf: &'static Conf) -> Self {
-        FromOverInto {
-            msrv: conf.msrv.clone(),
-        }
+        FromOverInto { msrv: conf.msrv }
     }
 }
 
@@ -77,12 +75,12 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto {
             && let Some(into_trait_seg) = hir_trait_ref.path.segments.last()
             // `impl Into<target_ty> for self_ty`
             && let Some(GenericArgs { args: [GenericArg::Type(target_ty)], .. }) = into_trait_seg.args
-            && self.msrv.meets(msrvs::RE_REBALANCING_COHERENCE)
             && span_is_local(item.span)
             && let Some(middle_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id)
-                                                  .map(ty::EarlyBinder::instantiate_identity)
+            .map(ty::EarlyBinder::instantiate_identity)
             && cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id)
             && !matches!(middle_trait_ref.args.type_at(1).kind(), ty::Alias(ty::Opaque, _))
+            && self.msrv.meets(cx, msrvs::RE_REBALANCING_COHERENCE)
         {
             span_lint_and_then(
                 cx,
@@ -114,8 +112,6 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Finds the occurrences of `Self` and `self`
diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs
index 243eb5cbfd4..5f3fc5100e7 100644
--- a/clippy_lints/src/functions/mod.rs
+++ b/clippy_lints/src/functions/mod.rs
@@ -471,7 +471,7 @@ impl Functions {
                 .iter()
                 .flat_map(|p| def_path_def_ids(tcx, &p.split("::").collect::<Vec<_>>()))
                 .collect(),
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -521,12 +521,12 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
 
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
         must_use::check_item(cx, item);
-        result::check_item(cx, item, self.large_error_threshold, &self.msrv);
+        result::check_item(cx, item, self.large_error_threshold, self.msrv);
     }
 
     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
         must_use::check_impl_item(cx, item);
-        result::check_impl_item(cx, item, self.large_error_threshold, &self.msrv);
+        result::check_impl_item(cx, item, self.large_error_threshold, self.msrv);
         impl_trait_in_params::check_impl_item(cx, item);
         renamed_function_params::check_impl_item(cx, item, &self.trait_ids);
     }
@@ -535,10 +535,8 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
         too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
         not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
         must_use::check_trait_item(cx, item);
-        result::check_trait_item(cx, item, self.large_error_threshold, &self.msrv);
+        result::check_trait_item(cx, item, self.large_error_threshold, self.msrv);
         impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
         ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api);
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs
index 74d365a7255..cade56f5822 100644
--- a/clippy_lints/src/functions/result.rs
+++ b/clippy_lints/src/functions/result.rs
@@ -34,7 +34,7 @@ fn result_err_ty<'tcx>(
     }
 }
 
-pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64, msrv: &Msrv) {
+pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64, msrv: Msrv) {
     if let hir::ItemKind::Fn { ref sig, .. } = item.kind
         && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
     {
@@ -50,7 +50,7 @@ pub(super) fn check_impl_item<'tcx>(
     cx: &LateContext<'tcx>,
     item: &hir::ImplItem<'tcx>,
     large_err_threshold: u64,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     // Don't lint if method is a trait's implementation, we can't do anything about those
     if let hir::ImplItemKind::Fn(ref sig, _) = item.kind
@@ -69,7 +69,7 @@ pub(super) fn check_trait_item<'tcx>(
     cx: &LateContext<'tcx>,
     item: &hir::TraitItem<'tcx>,
     large_err_threshold: u64,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
@@ -82,8 +82,8 @@ pub(super) fn check_trait_item<'tcx>(
     }
 }
 
-fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span, msrv: &Msrv) {
-    if err_ty.is_unit() && (!is_no_std_crate(cx) || msrv.meets(msrvs::ERROR_IN_CORE)) {
+fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span, msrv: Msrv) {
+    if err_ty.is_unit() && (!is_no_std_crate(cx) || msrv.meets(cx, msrvs::ERROR_IN_CORE)) {
         span_lint_and_help(
             cx,
             RESULT_UNIT_ERR,
diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs
index 28e6344186f..fbbd33efd02 100644
--- a/clippy_lints/src/if_then_some_else_none.rs
+++ b/clippy_lints/src/if_then_some_else_none.rs
@@ -54,9 +54,7 @@ pub struct IfThenSomeElseNone {
 
 impl IfThenSomeElseNone {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -79,10 +77,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
             && !is_else_clause(cx.tcx, expr)
             && !is_in_const_context(cx)
             && !expr.span.in_external_macro(cx.sess().source_map())
-            && self.msrv.meets(msrvs::BOOL_THEN)
+            && self.msrv.meets(cx, msrvs::BOOL_THEN)
             && !contains_return(then_block.stmts)
         {
-            let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) {
+            let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
                 "then_some"
             } else {
                 "then"
@@ -120,6 +118,4 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs
index 152d506a7c0..f2d16ff2e56 100644
--- a/clippy_lints/src/implicit_saturating_sub.rs
+++ b/clippy_lints/src/implicit_saturating_sub.rs
@@ -83,9 +83,7 @@ impl_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB, INVERTED_SATU
 
 impl ImplicitSaturatingSub {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -108,12 +106,10 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
             && let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
         {
             check_manual_check(
-                cx, expr, cond_op, cond_left, cond_right, if_block, else_block, &self.msrv,
+                cx, expr, cond_op, cond_left, cond_right, if_block, else_block, self.msrv,
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -125,7 +121,7 @@ fn check_manual_check<'tcx>(
     right_hand: &Expr<'tcx>,
     if_block: &Expr<'tcx>,
     else_block: &Expr<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     let ty = cx.typeck_results().expr_ty(left_hand);
     if ty.is_numeric() && !ty.is_signed() {
@@ -178,7 +174,7 @@ fn check_gt(
     little_var: &Expr<'_>,
     if_block: &Expr<'_>,
     else_block: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
     is_composited: bool,
 ) {
     if let Some(big_var) = Var::new(big_var)
@@ -221,7 +217,7 @@ fn check_subtraction(
     little_var: Var,
     if_block: &Expr<'_>,
     else_block: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
     is_composited: bool,
 ) {
     let if_block = peel_blocks(if_block);
@@ -258,7 +254,7 @@ fn check_subtraction(
             // if `snippet_opt` fails, it won't try the next conditions.
             if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
                 && let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
-                && (!is_in_const_context(cx) || msrv.meets(msrvs::SATURATING_SUB_CONST))
+                && (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
             {
                 let sugg = format!(
                     "{}{big_var_snippet}.saturating_sub({little_var_snippet}){}",
diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs
index 26df41e42a6..12dfb14c454 100644
--- a/clippy_lints/src/incompatible_msrv.rs
+++ b/clippy_lints/src/incompatible_msrv.rs
@@ -50,7 +50,7 @@ impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
 impl IncompatibleMsrv {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             is_above_msrv: FxHashMap::default(),
             check_in_tests: conf.check_incompatible_msrv_in_tests,
         }
@@ -88,39 +88,30 @@ impl IncompatibleMsrv {
             // We don't check local items since their MSRV is supposed to always be valid.
             return;
         }
-        let version = self.get_def_id_version(cx.tcx, def_id);
-        if self.msrv.meets(version) || (!self.check_in_tests && is_in_test(cx.tcx, node)) {
-            return;
-        }
         if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {
             // Desugared expressions get to cheat and stability is ignored.
             // Intentionally not using `.from_expansion()`, since we do still care about macro expansions
             return;
         }
-        self.emit_lint_for(cx, span, version);
-    }
-
-    fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
-        span_lint(
-            cx,
-            INCOMPATIBLE_MSRV,
-            span,
-            format!(
-                "current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
-                self.msrv
-            ),
-        );
+        if (self.check_in_tests || !is_in_test(cx.tcx, node))
+            && let Some(current) = self.msrv.current(cx)
+            && let version = self.get_def_id_version(cx.tcx, def_id)
+            && version > current
+        {
+            span_lint(
+                cx,
+                INCOMPATIBLE_MSRV,
+                span,
+                format!(
+                    "current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`"
+                ),
+            );
+        }
     }
 }
 
 impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
-    extract_msrv_attr!(LateContext);
-
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
-        if self.msrv.current().is_none() {
-            // If there is no MSRV, then no need to check anything...
-            return;
-        }
         match expr.kind {
             ExprKind::MethodCall(_, _, _, span) => {
                 if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs
index deac51ab4c4..d53e139de01 100644
--- a/clippy_lints/src/index_refutable_slice.rs
+++ b/clippy_lints/src/index_refutable_slice.rs
@@ -62,7 +62,7 @@ impl IndexRefutableSlice {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             max_suggested_slice: conf.max_suggested_slice_pattern_length,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -74,19 +74,17 @@ impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
         if let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
             && (!expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some())
             && !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
-            && self.msrv.meets(msrvs::SLICE_PATTERNS)
             && let found_slices = find_slice_values(cx, let_pat)
             && !found_slices.is_empty()
             && let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then)
             && !filtered_slices.is_empty()
+            && self.msrv.meets(cx, msrvs::SLICE_PATTERNS)
         {
             for slice in filtered_slices.values() {
                 lint_slice(cx, slice);
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap<HirId, SliceLintInformation> {
diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs
index f4e41dc826b..4ae1119ab3a 100644
--- a/clippy_lints/src/instant_subtraction.rs
+++ b/clippy_lints/src/instant_subtraction.rs
@@ -70,9 +70,7 @@ pub struct InstantSubtraction {
 
 impl InstantSubtraction {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -99,14 +97,12 @@ impl LateLintPass<'_> for InstantSubtraction {
                 print_manual_instant_elapsed_sugg(cx, expr, sugg);
             } else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration)
                 && !expr.span.from_expansion()
-                && self.msrv.meets(msrvs::TRY_FROM)
+                && self.msrv.meets(cx, msrvs::TRY_FROM)
             {
                 print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
diff --git a/clippy_lints/src/legacy_numeric_constants.rs b/clippy_lints/src/legacy_numeric_constants.rs
index 6f2ce04e8f8..3939318bee6 100644
--- a/clippy_lints/src/legacy_numeric_constants.rs
+++ b/clippy_lints/src/legacy_numeric_constants.rs
@@ -39,9 +39,7 @@ pub struct LegacyNumericConstants {
 
 impl LegacyNumericConstants {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -52,9 +50,9 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
         // Integer modules are "TBD" deprecated, and the contents are too,
         // so lint on the `use` statement directly.
         if let ItemKind::Use(path, kind @ (UseKind::Single | UseKind::Glob)) = item.kind
-            && self.msrv.meets(msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
             && !item.span.in_external_macro(cx.sess().source_map())
             && let Some(def_id) = path.res[0].opt_def_id()
+            && self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
         {
             let module = if is_integer_module(cx, def_id) {
                 true
@@ -137,8 +135,8 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
             return;
         };
 
-        if self.msrv.meets(msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
-            && !expr.span.in_external_macro(cx.sess().source_map())
+        if !expr.span.in_external_macro(cx.sess().source_map())
+            && self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
             && !is_from_proc_macro(cx, expr)
         {
             span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| {
@@ -151,8 +149,6 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
             });
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn is_integer_module(cx: &LateContext<'_>, did: DefId) -> bool {
diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs
index 7589ab1229a..3dd2de1fafc 100644
--- a/clippy_lints/src/lifetimes.rs
+++ b/clippy_lints/src/lifetimes.rs
@@ -130,9 +130,7 @@ pub struct Lifetimes {
 
 impl Lifetimes {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -151,7 +149,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
             ..
         } = item.kind
         {
-            check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, &self.msrv);
+            check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv);
         } else if let ItemKind::Impl(impl_) = item.kind {
             if !item.span.from_expansion() {
                 report_extra_impl_lifetimes(cx, impl_);
@@ -170,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
                 item.generics,
                 item.span,
                 report_extra_lifetimes,
-                &self.msrv,
+                self.msrv,
             );
         }
     }
@@ -181,11 +179,9 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
                 TraitFn::Required(sig) => (None, Some(sig)),
                 TraitFn::Provided(id) => (Some(id), None),
             };
-            check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, &self.msrv);
+            check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, self.msrv);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -197,7 +193,7 @@ fn check_fn_inner<'tcx>(
     generics: &'tcx Generics<'_>,
     span: Span,
     report_extra_lifetimes: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if span.in_external_macro(cx.sess().source_map()) || has_where_lifetimes(cx, generics) {
         return;
@@ -270,7 +266,7 @@ fn could_use_elision<'tcx>(
     body: Option<BodyId>,
     trait_sig: Option<&[Ident]>,
     named_generics: &'tcx [GenericParam<'_>],
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> Option<(Vec<LocalDefId>, Vec<Lifetime>)> {
     // There are two scenarios where elision works:
     // * no output references, all input references have different LT
@@ -388,17 +384,12 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefI
 }
 
 // elision doesn't work for explicit self types before Rust 1.81, see rust-lang/rust#69064
-fn non_elidable_self_type<'tcx>(
-    cx: &LateContext<'tcx>,
-    func: &FnDecl<'tcx>,
-    ident: Option<Ident>,
-    msrv: &Msrv,
-) -> bool {
-    if !msrv.meets(msrvs::EXPLICIT_SELF_TYPE_ELISION)
-        && let Some(ident) = ident
+fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>, msrv: Msrv) -> bool {
+    if let Some(ident) = ident
         && ident.name == kw::SelfLower
         && !func.implicit_self.has_implicit_self()
         && let Some(self_ty) = func.inputs.first()
+        && !msrv.meets(cx, msrvs::EXPLICIT_SELF_TYPE_ELISION)
     {
         let mut visitor = RefVisitor::new(cx);
         visitor.visit_ty_unambig(self_ty);
diff --git a/clippy_lints/src/lines_filter_map_ok.rs b/clippy_lints/src/lines_filter_map_ok.rs
index 08548f56400..d8af44233d3 100644
--- a/clippy_lints/src/lines_filter_map_ok.rs
+++ b/clippy_lints/src/lines_filter_map_ok.rs
@@ -15,9 +15,7 @@ pub struct LinesFilterMapOk {
 
 impl LinesFilterMapOk {
     pub fn new(conf: &Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -74,13 +72,13 @@ impl_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
 
 impl LateLintPass<'_> for LinesFilterMapOk {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        if self.msrv.meets(msrvs::MAP_WHILE)
-            && let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind
+        if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind
             && is_trait_method(cx, expr, sym::Iterator)
             && let fm_method_str = fm_method.ident.as_str()
             && matches!(fm_method_str, "filter_map" | "flat_map" | "flatten")
             && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines)
             && should_lint(cx, fm_args, fm_method_str)
+            && self.msrv.meets(cx, msrvs::MAP_WHILE)
         {
             span_lint_and_then(
                 cx,
@@ -101,8 +99,6 @@ impl LateLintPass<'_> for LinesFilterMapOk {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> bool {
diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs
index 06cf901bfb2..412c78cc804 100644
--- a/clippy_lints/src/loops/explicit_iter_loop.rs
+++ b/clippy_lints/src/loops/explicit_iter_loop.rs
@@ -17,7 +17,7 @@ pub(super) fn check(
     cx: &LateContext<'_>,
     self_arg: &Expr<'_>,
     call_expr: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
     enforce_iter_loop_reborrow: bool,
 ) {
     let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr, enforce_iter_loop_reborrow, msrv) else {
@@ -26,10 +26,11 @@ pub(super) fn check(
 
     if let ty::Array(_, count) = *ty.peel_refs().kind() {
         if !ty.is_ref() {
-            if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
+            if !msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) {
                 return;
             }
-        } else if count.try_to_target_usize(cx.tcx).is_none_or(|x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) {
+        } else if count.try_to_target_usize(cx.tcx).is_none_or(|x| x > 32) && !msrv.meets(cx, msrvs::ARRAY_IMPL_ANY_LEN)
+        {
             return;
         }
     }
@@ -106,7 +107,7 @@ fn is_ref_iterable<'tcx>(
     self_arg: &Expr<'_>,
     call_expr: &Expr<'_>,
     enforce_iter_loop_reborrow: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> Option<(AdjustKind, Ty<'tcx>)> {
     let typeck = cx.typeck_results();
     if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
@@ -126,8 +127,8 @@ fn is_ref_iterable<'tcx>(
         let self_ty = typeck.expr_ty(self_arg);
         let self_is_copy = is_copy(cx, self_ty);
 
-        if !msrv.meets(msrvs::BOX_INTO_ITER)
-            && is_type_lang_item(cx, self_ty.peel_refs(), rustc_hir::LangItem::OwnedBox)
+        if is_type_lang_item(cx, self_ty.peel_refs(), rustc_hir::LangItem::OwnedBox)
+            && !msrv.meets(cx, msrvs::BOX_INTO_ITER)
         {
             return None;
         }
diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs
index ffeb7e889c2..9b6f97b9a2e 100644
--- a/clippy_lints/src/loops/manual_flatten.rs
+++ b/clippy_lints/src/loops/manual_flatten.rs
@@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(
     arg: &'tcx Expr<'_>,
     body: &'tcx Expr<'_>,
     span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     let inner_expr = peel_blocks_with_stmt(body);
     if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None, .. })
@@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
         && (some_ctor || ok_ctor)
         // Ensure expr in `if let` is not used afterwards
         && !is_local_used(cx, if_then, pat_hir_id)
-        && msrv.meets(msrvs::ITER_FLATTEN)
+        && msrv.meets(cx, msrvs::ITER_FLATTEN)
     {
         let if_let_type = if some_ctor { "Some" } else { "Ok" };
         // Prepare the error message
diff --git a/clippy_lints/src/loops/manual_slice_fill.rs b/clippy_lints/src/loops/manual_slice_fill.rs
index a9764178862..343f7c5d2d1 100644
--- a/clippy_lints/src/loops/manual_slice_fill.rs
+++ b/clippy_lints/src/loops/manual_slice_fill.rs
@@ -24,12 +24,8 @@ pub(super) fn check<'tcx>(
     arg: &'tcx Expr<'_>,
     body: &'tcx Expr<'_>,
     expr: &'tcx Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if !msrv.meets(msrvs::SLICE_FILL) {
-        return;
-    }
-
     // `for _ in 0..slice.len() { slice[_] = value; }`
     if let Some(higher::Range {
         start: Some(start),
@@ -61,6 +57,7 @@ pub(super) fn check<'tcx>(
         && let ExprKind::Path(Resolved(_, idx_path)) = idx.kind
         && let Res::Local(idx_hir) = idx_path.res
         && !is_local_used(cx, assignval, idx_hir)
+        && msrv.meets(cx, msrvs::SLICE_FILL)
     {
         sugg(cx, body, expr, slice.span, assignval.span);
     }
@@ -81,6 +78,7 @@ pub(super) fn check<'tcx>(
         // The `fill` method cannot be used if the slice's element type does not implement the `Clone` trait.
         && let Some(clone_trait) = cx.tcx.lang_items().clone_trait()
         && implements_trait(cx, cx.typeck_results().expr_ty(recv), clone_trait, &[])
+        && msrv.meets(cx, msrvs::SLICE_FILL)
     {
         sugg(cx, body, expr, recv_path.span, assignval.span);
     }
diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs
index ffe7566f5fb..ed725a03989 100644
--- a/clippy_lints/src/loops/mod.rs
+++ b/clippy_lints/src/loops/mod.rs
@@ -747,7 +747,7 @@ pub struct Loops {
 impl Loops {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             enforce_iter_loop_reborrow: conf.enforce_iter_loop_reborrow,
         }
     }
@@ -832,8 +832,6 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
             manual_while_let_some::check(cx, condition, body, span);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 impl Loops {
@@ -850,7 +848,7 @@ impl Loops {
     ) {
         let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
         if !is_manual_memcpy_triggered {
-            manual_slice_fill::check(cx, pat, arg, body, expr, &self.msrv);
+            manual_slice_fill::check(cx, pat, arg, body, expr, self.msrv);
             needless_range_loop::check(cx, pat, arg, body, expr);
             explicit_counter_loop::check(cx, pat, arg, body, expr, label);
         }
@@ -858,8 +856,8 @@ impl Loops {
         for_kv_map::check(cx, pat, arg, body);
         mut_range_bound::check(cx, arg, body);
         single_element_loop::check(cx, pat, arg, body, expr);
-        same_item_push::check(cx, pat, arg, body, expr, &self.msrv);
-        manual_flatten::check(cx, pat, arg, body, span, &self.msrv);
+        same_item_push::check(cx, pat, arg, body, expr, self.msrv);
+        manual_flatten::check(cx, pat, arg, body, span, self.msrv);
         manual_find::check(cx, pat, arg, body, span, expr);
         unused_enumerate_index::check(cx, pat, arg, body);
     }
@@ -868,7 +866,7 @@ impl Loops {
         if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
             match method.ident.as_str() {
                 "iter" | "iter_mut" => {
-                    explicit_iter_loop::check(cx, self_arg, arg, &self.msrv, self.enforce_iter_loop_reborrow);
+                    explicit_iter_loop::check(cx, self_arg, arg, self.msrv, self.enforce_iter_loop_reborrow);
                 },
                 "into_iter" => {
                     explicit_into_iter_loop::check(cx, self_arg, arg);
diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs
index c27e930c99a..661b4b590d8 100644
--- a/clippy_lints/src/loops/same_item_push.rs
+++ b/clippy_lints/src/loops/same_item_push.rs
@@ -20,14 +20,14 @@ pub(super) fn check<'tcx>(
     _: &'tcx Expr<'_>,
     body: &'tcx Expr<'_>,
     _: &'tcx Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext, msrv: &Msrv) {
+    fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext, msrv: Msrv) {
         let mut app = Applicability::Unspecified;
         let vec_str = snippet_with_context(cx, vec.span, ctxt, "", &mut app).0;
         let item_str = snippet_with_context(cx, pushed_item.span, ctxt, "", &mut app).0;
 
-        let secondary_help = if msrv.meets(msrvs::REPEAT_N)
+        let secondary_help = if msrv.meets(cx, msrvs::REPEAT_N)
             && let Some(std_or_core) = std_or_core(cx)
         {
             format!("or `{vec_str}.extend({std_or_core}::iter::repeat_n({item_str}, SIZE))`")
diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs
index 4a34a334cf2..39c4857b3e8 100644
--- a/clippy_lints/src/manual_bits.rs
+++ b/clippy_lints/src/manual_bits.rs
@@ -40,9 +40,7 @@ pub struct ManualBits {
 
 impl ManualBits {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -53,7 +51,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
         if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
             && let BinOpKind::Mul = &bin_op.node
             && !expr.span.from_expansion()
-            && self.msrv.meets(msrvs::INTEGER_BITS)
             && let ctxt = expr.span.ctxt()
             && left_expr.span.ctxt() == ctxt
             && right_expr.span.ctxt() == ctxt
@@ -61,6 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
             && matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_))
             && let ExprKind::Lit(lit) = &other_expr.kind
             && let LitKind::Int(Pu128(8), _) = lit.node
+            && self.msrv.meets(cx, msrvs::INTEGER_BITS)
         {
             let mut app = Applicability::MachineApplicable;
             let ty_snip = snippet_with_context(cx, real_ty_span, ctxt, "..", &mut app).0;
@@ -77,8 +75,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn get_one_size_of_ty<'tcx>(
diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs
index 484a7ba256b..50c8331eeba 100644
--- a/clippy_lints/src/manual_clamp.rs
+++ b/clippy_lints/src/manual_clamp.rs
@@ -99,9 +99,7 @@ pub struct ManualClamp {
 
 impl ManualClamp {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -144,30 +142,28 @@ struct InputMinMax<'tcx> {
 
 impl<'tcx> LateLintPass<'tcx> for ManualClamp {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
-        if !self.msrv.meets(msrvs::CLAMP) {
-            return;
-        }
         if !expr.span.from_expansion() && !is_in_const_context(cx) {
             let suggestion = is_if_elseif_else_pattern(cx, expr)
                 .or_else(|| is_max_min_pattern(cx, expr))
                 .or_else(|| is_call_max_min_pattern(cx, expr))
                 .or_else(|| is_match_pattern(cx, expr))
                 .or_else(|| is_if_elseif_pattern(cx, expr));
-            if let Some(suggestion) = suggestion {
+            if let Some(suggestion) = suggestion
+                && self.msrv.meets(cx, msrvs::CLAMP)
+            {
                 maybe_emit_suggestion(cx, &suggestion);
             }
         }
     }
 
     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
-        if !self.msrv.meets(msrvs::CLAMP) || is_in_const_context(cx) {
+        if is_in_const_context(cx) || !self.msrv.meets(cx, msrvs::CLAMP) {
             return;
         }
         for suggestion in is_two_if_pattern(cx, block) {
             maybe_emit_suggestion(cx, &suggestion);
         }
     }
-    extract_msrv_attr!(LateContext);
 }
 
 fn maybe_emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggestion<'tcx>) {
diff --git a/clippy_lints/src/manual_div_ceil.rs b/clippy_lints/src/manual_div_ceil.rs
index 04357cdd8f6..9c1419175d5 100644
--- a/clippy_lints/src/manual_div_ceil.rs
+++ b/clippy_lints/src/manual_div_ceil.rs
@@ -49,9 +49,7 @@ pub struct ManualDivCeil {
 impl ManualDivCeil {
     #[must_use]
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -59,10 +57,6 @@ impl_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]);
 
 impl<'tcx> LateLintPass<'tcx> for ManualDivCeil {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
-        if !self.msrv.meets(msrvs::MANUAL_DIV_CEIL) {
-            return;
-        }
-
         let mut applicability = Applicability::MachineApplicable;
 
         if let ExprKind::Binary(div_op, div_lhs, div_rhs) = expr.kind
@@ -70,6 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualDivCeil {
             && check_int_ty_and_feature(cx, div_lhs)
             && check_int_ty_and_feature(cx, div_rhs)
             && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = div_lhs.kind
+            && self.msrv.meets(cx, msrvs::MANUAL_DIV_CEIL)
         {
             // (x + (y - 1)) / y
             if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind
@@ -122,8 +117,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualDivCeil {
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Checks if two expressions represent non-zero integer literals such that `small_expr + 1 ==
diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs
index 2a5aa12d126..bd2785fea27 100644
--- a/clippy_lints/src/manual_float_methods.rs
+++ b/clippy_lints/src/manual_float_methods.rs
@@ -90,9 +90,7 @@ pub struct ManualFloatMethods {
 
 impl ManualFloatMethods {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -144,7 +142,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
             && !expr.span.in_external_macro(cx.sess().source_map())
             && (
                 is_not_const(cx.tcx, cx.tcx.hir_enclosing_body_owner(expr.hir_id).into())
-                    || self.msrv.meets(msrvs::CONST_FLOAT_CLASSIFY)
+                    || self.msrv.meets(cx, msrvs::CONST_FLOAT_CLASSIFY)
             )
             && let [first, second, const_1, const_2] = exprs
             && let ecx = ConstEvalCtxt::new(cx)
@@ -202,8 +200,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
             });
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn is_infinity(constant: &Constant<'_>) -> bool {
diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs
index 7e092d11f1b..f71264a93ca 100644
--- a/clippy_lints/src/manual_hash_one.rs
+++ b/clippy_lints/src/manual_hash_one.rs
@@ -53,9 +53,7 @@ pub struct ManualHashOne {
 
 impl ManualHashOne {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -98,7 +96,7 @@ impl LateLintPass<'_> for ManualHashOne {
             && let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind
             && seg.ident.name.as_str() == "finish"
 
-            && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE)
+            && self.msrv.meets(cx, msrvs::BUILD_HASHER_HASH_ONE)
         {
             span_lint_hir_and_then(
                 cx,
@@ -129,6 +127,4 @@ impl LateLintPass<'_> for ManualHashOne {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs
index 38106277a88..faf01a276a1 100644
--- a/clippy_lints/src/manual_is_ascii_check.rs
+++ b/clippy_lints/src/manual_is_ascii_check.rs
@@ -64,9 +64,7 @@ pub struct ManualIsAsciiCheck {
 
 impl ManualIsAsciiCheck {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -91,11 +89,11 @@ enum CharRange {
 
 impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
-        if !self.msrv.meets(msrvs::IS_ASCII_DIGIT) {
+        if !self.msrv.meets(cx, msrvs::IS_ASCII_DIGIT) {
             return;
         }
 
-        if is_in_const_context(cx) && !self.msrv.meets(msrvs::IS_ASCII_DIGIT_CONST) {
+        if is_in_const_context(cx) && !self.msrv.meets(cx, msrvs::IS_ASCII_DIGIT_CONST) {
             return;
         }
 
@@ -119,8 +117,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
             check_is_ascii(cx, expr.span, arg, &range, ty_sugg);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn get_ty_sugg<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'_>) -> Option<(Span, Ty<'tcx>)> {
diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs
index 3643b8c4425..47939767212 100644
--- a/clippy_lints/src/manual_let_else.rs
+++ b/clippy_lints/src/manual_let_else.rs
@@ -53,8 +53,8 @@ impl<'tcx> QuestionMark {
             && local.ty.is_none()
             && init.span.eq_ctxt(stmt.span)
             && let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init)
-            && self.msrv.meets(msrvs::LET_ELSE)
             && !stmt.span.in_external_macro(cx.sess().source_map())
+            && self.msrv.meets(cx, msrvs::LET_ELSE)
         {
             match if_let_or_match {
                 IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else, ..) => {
diff --git a/clippy_lints/src/manual_main_separator_str.rs b/clippy_lints/src/manual_main_separator_str.rs
index b7563a2508d..f54ccf2c87b 100644
--- a/clippy_lints/src/manual_main_separator_str.rs
+++ b/clippy_lints/src/manual_main_separator_str.rs
@@ -39,9 +39,7 @@ pub struct ManualMainSeparatorStr {
 
 impl ManualMainSeparatorStr {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -55,10 +53,10 @@ impl LateLintPass<'_> for ManualMainSeparatorStr {
             && let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind
             && let Res::Def(DefKind::Const, receiver_def_id) = path.res
             && is_trait_method(cx, target, sym::ToString)
-            && self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR)
             && cx.tcx.is_diagnostic_item(sym::path_main_separator, receiver_def_id)
             && let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind()
             && ty.is_str()
+            && self.msrv.meets(cx, msrvs::PATH_MAIN_SEPARATOR_STR)
         {
             span_lint_and_sugg(
                 cx,
@@ -71,6 +69,4 @@ impl LateLintPass<'_> for ManualMainSeparatorStr {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs
index 83d8a509390..496e0660d4f 100644
--- a/clippy_lints/src/manual_non_exhaustive.rs
+++ b/clippy_lints/src/manual_non_exhaustive.rs
@@ -71,7 +71,7 @@ pub struct ManualNonExhaustive {
 impl ManualNonExhaustive {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             constructed_enum_variants: FxHashSet::default(),
             potential_enums: Vec::new(),
         }
@@ -82,7 +82,7 @@ impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
 
 impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
-        if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) || !cx.effective_visibilities.is_exported(item.owner_id.def_id) {
+        if !cx.effective_visibilities.is_exported(item.owner_id.def_id) || !self.msrv.meets(cx, msrvs::NON_EXHAUSTIVE) {
             return;
         }
 
@@ -171,6 +171,4 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs
index e4360518b66..8dee29b2a0b 100644
--- a/clippy_lints/src/manual_option_as_slice.rs
+++ b/clippy_lints/src/manual_option_as_slice.rs
@@ -1,5 +1,6 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::msrvs::Msrv;
 use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs};
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
@@ -40,31 +41,21 @@ declare_clippy_lint! {
 }
 
 pub struct ManualOptionAsSlice {
-    msrv: msrvs::Msrv,
+    msrv: Msrv,
 }
 
 impl ManualOptionAsSlice {
     pub fn new(conf: &Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
 impl_lint_pass!(ManualOptionAsSlice => [MANUAL_OPTION_AS_SLICE]);
 
 impl LateLintPass<'_> for ManualOptionAsSlice {
-    extract_msrv_attr!(LateContext);
-
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         let span = expr.span;
-        if span.from_expansion()
-            || !self.msrv.meets(if clippy_utils::is_in_const_context(cx) {
-                msrvs::CONST_OPTION_AS_SLICE
-            } else {
-                msrvs::OPTION_AS_SLICE
-            })
-        {
+        if span.from_expansion() {
             return;
         }
         match expr.kind {
@@ -72,7 +63,7 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
                 if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1)
                     || is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2)
                 {
-                    check_as_ref(cx, scrutinee, span);
+                    check_as_ref(cx, scrutinee, span, self.msrv);
                 }
             },
             ExprKind::If(cond, then, Some(other)) => {
@@ -81,23 +72,23 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
                     && check_some_body(cx, binding, then)
                     && is_empty_slice(cx, other.peel_blocks())
                 {
-                    check_as_ref(cx, let_expr.init, span);
+                    check_as_ref(cx, let_expr.init, span, self.msrv);
                 }
             },
             ExprKind::MethodCall(seg, callee, [], _) => {
                 if seg.ident.name.as_str() == "unwrap_or_default" {
-                    check_map(cx, callee, span);
+                    check_map(cx, callee, span, self.msrv);
                 }
             },
             ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() {
                 "unwrap_or" => {
                     if is_empty_slice(cx, or) {
-                        check_map(cx, callee, span);
+                        check_map(cx, callee, span, self.msrv);
                     }
                 },
                 "unwrap_or_else" => {
                     if returns_empty_slice(cx, or) {
-                        check_map(cx, callee, span);
+                        check_map(cx, callee, span, self.msrv);
                     }
                 },
                 _ => {},
@@ -105,12 +96,12 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
             ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() {
                 "map_or" => {
                     if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
-                        check_as_ref(cx, callee, span);
+                        check_as_ref(cx, callee, span, self.msrv);
                     }
                 },
                 "map_or_else" => {
                     if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
-                        check_as_ref(cx, callee, span);
+                        check_as_ref(cx, callee, span, self.msrv);
                     }
                 },
                 _ => {},
@@ -120,20 +111,28 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
     }
 }
 
-fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span) {
+fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span, msrv: Msrv) {
     if let ExprKind::MethodCall(seg, callee, [mapping], _) = map.kind
         && seg.ident.name == sym::map
         && is_slice_from_ref(cx, mapping)
     {
-        check_as_ref(cx, callee, span);
+        check_as_ref(cx, callee, span, msrv);
     }
 }
 
-fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span) {
+fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) {
     if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind
         && seg.ident.name == sym::as_ref
         && let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind()
         && cx.tcx.is_diagnostic_item(sym::Option, adtdef.did())
+        && msrv.meets(
+            cx,
+            if clippy_utils::is_in_const_context(cx) {
+                msrvs::CONST_OPTION_AS_SLICE
+            } else {
+                msrvs::OPTION_AS_SLICE
+            },
+        )
     {
         if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) {
             span_lint_and_sugg(
diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs
index 469b4b7cf89..41e07e26bff 100644
--- a/clippy_lints/src/manual_rem_euclid.rs
+++ b/clippy_lints/src/manual_rem_euclid.rs
@@ -39,9 +39,7 @@ pub struct ManualRemEuclid {
 
 impl ManualRemEuclid {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -60,8 +58,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
             && add_lhs.span.ctxt() == ctxt
             && add_rhs.span.ctxt() == ctxt
             && !expr.span.in_external_macro(cx.sess().source_map())
-            && self.msrv.meets(msrvs::REM_EUCLID)
-            && (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !is_in_const_context(cx))
             && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
             && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs)
             && let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind
@@ -73,6 +69,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
             && const2 == const3
             && rem2_lhs.span.ctxt() == ctxt
             && rem2_rhs.span.ctxt() == ctxt
+            && self.msrv.meets(cx, msrvs::REM_EUCLID)
+            && (self.msrv.meets(cx, msrvs::REM_EUCLID_CONST) || !is_in_const_context(cx))
         {
             // Apply only to params or locals with annotated types
             match cx.tcx.parent_hir_node(hir_id) {
@@ -99,8 +97,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 // Checks if either the left or right expressions can be an unsigned int constant and returns that
diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs
index 0a4e756096e..16dd1ad4e47 100644
--- a/clippy_lints/src/manual_retain.rs
+++ b/clippy_lints/src/manual_retain.rs
@@ -50,9 +50,7 @@ pub struct ManualRetain {
 
 impl ManualRetain {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -66,13 +64,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
             && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
             && cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
         {
-            check_into_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
-            check_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
-            check_to_owned(cx, left_expr, target_expr, expr.span, &self.msrv);
+            check_into_iter(cx, left_expr, target_expr, expr.span, self.msrv);
+            check_iter(cx, left_expr, target_expr, expr.span, self.msrv);
+            check_to_owned(cx, left_expr, target_expr, expr.span, self.msrv);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn check_into_iter(
@@ -80,7 +76,7 @@ fn check_into_iter(
     left_expr: &hir::Expr<'_>,
     target_expr: &hir::Expr<'_>,
     parent_expr_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
         && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
@@ -123,7 +119,7 @@ fn check_iter(
     left_expr: &hir::Expr<'_>,
     target_expr: &hir::Expr<'_>,
     parent_expr_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
         && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
@@ -181,10 +177,9 @@ fn check_to_owned(
     left_expr: &hir::Expr<'_>,
     target_expr: &hir::Expr<'_>,
     parent_expr_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if msrv.meets(msrvs::STRING_RETAIN)
-        && let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
+    if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
         && let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
         && cx.tcx.is_diagnostic_item(sym::to_owned_method, to_owned_def_id)
         && let hir::ExprKind::MethodCall(_, chars_expr, [_], _) = &filter_expr.kind
@@ -200,6 +195,7 @@ fn check_to_owned(
         && let hir::ExprKind::Closure(closure) = closure_expr.kind
         && let filter_body = cx.tcx.hir_body(closure.body)
         && let [filter_params] = filter_body.params
+        && msrv.meets(cx, msrvs::STRING_RETAIN)
     {
         if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
             make_span_lint_and_sugg(
@@ -253,7 +249,7 @@ fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool {
         .any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id))
 }
 
-fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv) -> bool {
+fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool {
     let ty = cx.typeck_results().expr_ty(expr).peel_refs();
     let required = match get_type_diagnostic_name(cx, ty) {
         Some(sym::BinaryHeap) => msrvs::BINARY_HEAP_RETAIN,
@@ -264,7 +260,7 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
         Some(sym::Vec | sym::VecDeque) => return true,
         _ => return false,
     };
-    msrv.meets(required)
+    msrv.meets(cx, required)
 }
 
 fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs
index daed88b492e..9e911e61f19 100644
--- a/clippy_lints/src/manual_strip.rs
+++ b/clippy_lints/src/manual_strip.rs
@@ -56,9 +56,7 @@ pub struct ManualStrip {
 
 impl ManualStrip {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -75,7 +73,6 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
         if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
             && let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
             && let ExprKind::Path(target_path) = &target_arg.kind
-            && self.msrv.meets(msrvs::STR_STRIP_PREFIX)
             && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
         {
             let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) {
@@ -98,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
             }
 
             let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then);
-            if !strippings.is_empty() {
+            if !strippings.is_empty() && self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) {
                 let kind_word = match strip_kind {
                     StripKind::Prefix => "prefix",
                     StripKind::Suffix => "suffix",
@@ -156,8 +153,6 @@ 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/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs
index 97e8423695d..6f446bf9565 100644
--- a/clippy_lints/src/matches/collapsible_match.rs
+++ b/clippy_lints/src/matches/collapsible_match.rs
@@ -14,7 +14,7 @@ use rustc_span::Span;
 
 use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
 
-pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: &Msrv) {
+pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
     if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
         for arm in arms {
             check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv);
@@ -27,7 +27,7 @@ pub(super) fn check_if_let<'tcx>(
     pat: &'tcx Pat<'_>,
     body: &'tcx Expr<'_>,
     else_expr: Option<&'tcx Expr<'_>>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     check_arm(cx, false, pat, body, None, else_expr, msrv);
 }
@@ -39,7 +39,7 @@ fn check_arm<'tcx>(
     outer_then_body: &'tcx Expr<'tcx>,
     outer_guard: Option<&'tcx Expr<'tcx>>,
     outer_else_body: Option<&'tcx Expr<'tcx>>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     let inner_expr = peel_blocks_with_stmt(outer_then_body);
     if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr)
@@ -60,7 +60,7 @@ fn check_arm<'tcx>(
         // match expression must be a local binding
         // match <local> { .. }
         && let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee))
-        && !pat_contains_disallowed_or(inner_then_pat, msrv)
+        && !pat_contains_disallowed_or(cx, inner_then_pat, msrv)
         // the binding must come from the pattern of the containing match arm
         // ..<local>.. => match <local> { .. }
         && let (Some(binding_span), is_innermost_parent_pat_struct)
diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs
index 9ca914af281..35caa7d1f3a 100644
--- a/clippy_lints/src/matches/mod.rs
+++ b/clippy_lints/src/matches/mod.rs
@@ -1014,7 +1014,7 @@ pub struct Matches {
 impl Matches {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             infallible_destructuring_match_linted: false,
         }
     }
@@ -1073,7 +1073,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                 significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source);
             }
 
-            collapsible_match::check_match(cx, arms, &self.msrv);
+            collapsible_match::check_match(cx, arms, self.msrv);
             if !from_expansion {
                 // These don't depend on a relationship between multiple arms
                 match_wild_err_arm::check(cx, ex, arms);
@@ -1086,7 +1086,9 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
 
             if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) {
                 if source == MatchSource::Normal {
-                    if !(self.msrv.meets(msrvs::MATCHES_MACRO) && match_like_matches::check_match(cx, expr, ex, arms)) {
+                    if !(self.msrv.meets(cx, msrvs::MATCHES_MACRO)
+                        && match_like_matches::check_match(cx, expr, ex, arms))
+                    {
                         match_same_arms::check(cx, arms);
                     }
 
@@ -1120,7 +1122,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                     needless_match::check_match(cx, ex, arms, expr);
                     match_on_vec_items::check(cx, ex);
                     match_str_case_mismatch::check(cx, ex, arms);
-                    redundant_guards::check(cx, arms, &self.msrv);
+                    redundant_guards::check(cx, arms, self.msrv);
 
                     if !is_in_const_context(cx) {
                         manual_unwrap_or::check_match(cx, expr, ex, arms);
@@ -1138,11 +1140,11 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
             }
         } else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
-            collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, &self.msrv);
+            collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv);
             significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else);
             if !from_expansion {
                 if let Some(else_expr) = if_let.if_else {
-                    if self.msrv.meets(msrvs::MATCHES_MACRO) {
+                    if self.msrv.meets(cx, msrvs::MATCHES_MACRO) {
                         match_like_matches::check_if_let(
                             cx,
                             expr,
@@ -1208,8 +1210,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
     fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
         rest_pat_in_fully_bound_struct::check(cx, pat);
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Checks if there are any arms with a `#[cfg(..)]` attribute.
@@ -1274,16 +1274,12 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar
 }
 
 /// Checks if `pat` contains OR patterns that cannot be nested due to a too low MSRV.
-fn pat_contains_disallowed_or(pat: &Pat<'_>, msrv: &Msrv) -> bool {
-    if msrv.meets(msrvs::OR_PATTERNS) {
-        return false;
-    }
-
-    let mut result = false;
+fn pat_contains_disallowed_or(cx: &LateContext<'_>, pat: &Pat<'_>, msrv: Msrv) -> bool {
+    let mut contains_or = false;
     pat.walk(|p| {
         let is_or = matches!(p.kind, PatKind::Or(_));
-        result |= is_or;
+        contains_or |= is_or;
         !is_or
     });
-    result
+    contains_or && !msrv.meets(cx, msrvs::OR_PATTERNS)
 }
diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs
index dfc0513add9..ab53ad98572 100644
--- a/clippy_lints/src/matches/redundant_guards.rs
+++ b/clippy_lints/src/matches/redundant_guards.rs
@@ -16,7 +16,7 @@ use std::ops::ControlFlow;
 
 use super::{REDUNDANT_GUARDS, pat_contains_disallowed_or};
 
-pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: &Msrv) {
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: Msrv) {
     for outer_arm in arms {
         let Some(guard) = outer_arm.guard else {
             continue;
@@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
         if let ExprKind::Match(scrutinee, [arm, _], MatchSource::Normal) = guard.kind
             && matching_root_macro_call(cx, guard.span, sym::matches_macro).is_some()
             && let Some(binding) = get_pat_binding(cx, scrutinee, outer_arm)
-            && !pat_contains_disallowed_or(arm.pat, msrv)
+            && !pat_contains_disallowed_or(cx, arm.pat, msrv)
         {
             let pat_span = match (arm.pat.kind, binding.byref_ident) {
                 (PatKind::Ref(pat, _), Some(_)) => pat.span,
@@ -45,7 +45,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
         // `Some(x) if let Some(2) = x`
         else if let ExprKind::Let(let_expr) = guard.kind
             && let Some(binding) = get_pat_binding(cx, let_expr.init, outer_arm)
-            && !pat_contains_disallowed_or(let_expr.pat, msrv)
+            && !pat_contains_disallowed_or(cx, let_expr.pat, msrv)
         {
             let pat_span = match (let_expr.pat.kind, binding.byref_ident) {
                 (PatKind::Ref(pat, _), Some(_)) => pat.span,
diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs
index 2fe5f6a3a37..a0919947b3f 100644
--- a/clippy_lints/src/mem_replace.rs
+++ b/clippy_lints/src/mem_replace.rs
@@ -160,11 +160,11 @@ fn check_replace_option_with_some(
     src: &Expr<'_>,
     dest: &Expr<'_>,
     expr_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
-    if msrv.meets(msrvs::OPTION_REPLACE)
-        && let ExprKind::Call(src_func, [src_arg]) = src.kind
+    if let ExprKind::Call(src_func, [src_arg]) = src.kind
         && is_res_lang_ctor(cx, path_res(cx, src_func), OptionSome)
+        && msrv.meets(cx, msrvs::OPTION_REPLACE)
     {
         // We do not have to check for a `const` context here, because `core::mem::replace()` and
         // `Option::replace()` have been const-stabilized simultaneously in version 1.83.0.
@@ -250,15 +250,16 @@ fn check_replace_with_default(
     src: &Expr<'_>,
     dest: &Expr<'_>,
     expr: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
-    if msrv.meets(msrvs::MEM_TAKE) && is_expr_used_or_unified(cx.tcx, expr)
+    if is_expr_used_or_unified(cx.tcx, expr)
         // disable lint for primitives
         && let expr_type = cx.typeck_results().expr_ty_adjusted(src)
         && !is_non_aggregate_primitive_type(expr_type)
         && is_default_equivalent(cx, src)
         && !expr.span.in_external_macro(cx.tcx.sess.source_map())
         && let Some(top_crate) = std_or_core(cx)
+        && msrv.meets(cx, msrvs::MEM_TAKE)
     {
         span_lint_and_then(
             cx,
@@ -292,9 +293,7 @@ pub struct MemReplace {
 
 impl MemReplace {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -308,12 +307,11 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
         {
             // Check that second argument is `Option::None`
             if !check_replace_option_with_none(cx, src, dest, expr.span)
-                && !check_replace_option_with_some(cx, src, dest, expr.span, &self.msrv)
-                && !check_replace_with_default(cx, src, dest, expr, &self.msrv)
+                && !check_replace_option_with_some(cx, src, dest, expr.span, self.msrv)
+                && !check_replace_with_default(cx, src, dest, expr, self.msrv)
             {
                 check_replace_with_uninit(cx, src, dest, expr.span);
             }
         }
     }
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints/src/methods/cloned_instead_of_copied.rs
index 223a960b800..f50fb627b89 100644
--- a/clippy_lints/src/methods/cloned_instead_of_copied.rs
+++ b/clippy_lints/src/methods/cloned_instead_of_copied.rs
@@ -10,16 +10,16 @@ use rustc_span::{Span, sym};
 
 use super::CLONED_INSTEAD_OF_COPIED;
 
-pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: &Msrv) {
+pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Msrv) {
     let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
     let inner_ty = match recv_ty.kind() {
         // `Option<T>` -> `T`
         ty::Adt(adt, subst)
-            if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && msrv.meets(msrvs::OPTION_COPIED) =>
+            if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && msrv.meets(cx, msrvs::OPTION_COPIED) =>
         {
             subst.type_at(0)
         },
-        _ if is_trait_method(cx, expr, sym::Iterator) && msrv.meets(msrvs::ITERATOR_COPIED) => {
+        _ if is_trait_method(cx, expr, sym::Iterator) && msrv.meets(cx, msrvs::ITERATOR_COPIED) => {
             match get_iterator_item_ty(cx, recv_ty) {
                 // <T as Iterator>::Item
                 Some(ty) => ty,
diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs
index f2786efa44c..91ddaca07d8 100644
--- a/clippy_lints/src/methods/err_expect.rs
+++ b/clippy_lints/src/methods/err_expect.rs
@@ -14,19 +14,16 @@ pub(super) fn check(
     recv: &rustc_hir::Expr<'_>,
     expect_span: Span,
     err_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
-        // Test the version to make sure the lint can be showed (expect_err has been
-        // introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
-        && msrv.meets(msrvs::EXPECT_ERR)
-
         // Grabs the `Result<T, E>` type
         && let result_type = cx.typeck_results().expr_ty(recv)
         // Tests if the T type in a `Result<T, E>` is not None
         && let Some(data_type) = get_data_type(cx, result_type)
         // Tests if the T type in a `Result<T, E>` implements debug
         && has_debug_impl(cx, data_type)
+        && msrv.meets(cx, msrvs::EXPECT_ERR)
     {
         span_lint_and_sugg(
             cx,
diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints/src/methods/filter_map_next.rs
index 3f89e593148..9f3c346042f 100644
--- a/clippy_lints/src/methods/filter_map_next.rs
+++ b/clippy_lints/src/methods/filter_map_next.rs
@@ -14,10 +14,10 @@ pub(super) fn check<'tcx>(
     expr: &'tcx hir::Expr<'_>,
     recv: &'tcx hir::Expr<'_>,
     arg: &'tcx hir::Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if is_trait_method(cx, expr, sym::Iterator) {
-        if !msrv.meets(msrvs::ITERATOR_FIND_MAP) {
+        if !msrv.meets(cx, msrvs::ITERATOR_FIND_MAP) {
             return;
         }
 
diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints/src/methods/io_other_error.rs
index e0ce013ca7b..4659e9e163f 100644
--- a/clippy_lints/src/methods/io_other_error.rs
+++ b/clippy_lints/src/methods/io_other_error.rs
@@ -1,10 +1,10 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::msrvs::{IO_ERROR_OTHER, Msrv};
+use clippy_utils::msrvs::{self, Msrv};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, QPath};
 use rustc_lint::LateContext;
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args: &[Expr<'_>], msrv: Msrv) {
     if let [error_kind, error] = args
         && !expr.span.from_expansion()
         && !error_kind.span.from_expansion()
@@ -15,7 +15,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args
             &clippy_utils::paths::IO_ERRORKIND_OTHER,
         )
         && let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind
-        && msrv.meets(IO_ERROR_OTHER)
+        && msrv.meets(cx, msrvs::IO_ERROR_OTHER)
     {
         span_lint_and_then(
             cx,
diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs
index d8bb9e377a0..9c32e9ac539 100644
--- a/clippy_lints/src/methods/is_digit_ascii_radix.rs
+++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs
@@ -12,12 +12,8 @@ pub(super) fn check<'tcx>(
     expr: &'tcx Expr<'_>,
     self_arg: &'tcx Expr<'_>,
     radix: &'tcx Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if !msrv.meets(msrvs::IS_ASCII_DIGIT) {
-        return;
-    }
-
     if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
         return;
     }
@@ -30,6 +26,10 @@ pub(super) fn check<'tcx>(
         };
         let mut applicability = Applicability::MachineApplicable;
 
+        if !msrv.meets(cx, msrvs::IS_ASCII_DIGIT) {
+            return;
+        }
+
         span_lint_and_sugg(
             cx,
             IS_DIGIT_ASCII_RADIX,
diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs
index 518041177e9..94415fc9106 100644
--- a/clippy_lints/src/methods/iter_kv_map.rs
+++ b/clippy_lints/src/methods/iter_kv_map.rs
@@ -20,9 +20,9 @@ pub(super) fn check<'tcx>(
     expr: &'tcx Expr<'tcx>,  // .iter().map(|(_, v_| v))
     recv: &'tcx Expr<'tcx>,  // hashmap
     m_arg: &'tcx Expr<'tcx>, // |(_, v)| v
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if map_type == "into_iter" && !msrv.meets(msrvs::INTO_KEYS) {
+    if map_type == "into_iter" && !msrv.meets(cx, msrvs::INTO_KEYS) {
         return;
     }
     if !expr.span.from_expansion()
diff --git a/clippy_lints/src/methods/manual_c_str_literals.rs b/clippy_lints/src/methods/manual_c_str_literals.rs
index e1ebca0b09d..0274e31b4c3 100644
--- a/clippy_lints/src/methods/manual_c_str_literals.rs
+++ b/clippy_lints/src/methods/manual_c_str_literals.rs
@@ -22,7 +22,7 @@ pub(super) fn check_as_ptr<'tcx>(
     cx: &LateContext<'tcx>,
     expr: &'tcx Expr<'tcx>,
     receiver: &'tcx Expr<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let ExprKind::Lit(lit) = receiver.kind
         && let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
@@ -32,7 +32,7 @@ pub(super) fn check_as_ptr<'tcx>(
             |parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
         )
         && let Some(sugg) = rewrite_as_cstr(cx, lit.span)
-        && msrv.meets(msrvs::C_STR_LITERALS)
+        && msrv.meets(cx, msrvs::C_STR_LITERALS)
     {
         span_lint_and_sugg(
             cx,
@@ -65,11 +65,11 @@ fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
 /// - `CStr::from_bytes_with_nul(..)`
 /// - `CStr::from_bytes_with_nul_unchecked(..)`
 /// - `CStr::from_ptr(..)`
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: Msrv) {
     if let Some(fn_name) = is_c_str_function(cx, func)
         && let [arg] = args
         && cx.tcx.sess.edition() >= Edition2021
-        && msrv.meets(msrvs::C_STR_LITERALS)
+        && msrv.meets(cx, msrvs::C_STR_LITERALS)
     {
         match fn_name.as_str() {
             name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs
index 09ccb386a20..173ebcb7020 100644
--- a/clippy_lints/src/methods/manual_inspect.rs
+++ b/clippy_lints/src/methods/manual_inspect.rs
@@ -14,14 +14,14 @@ use rustc_span::{DUMMY_SP, Span, Symbol, sym};
 use super::MANUAL_INSPECT;
 
 #[expect(clippy::too_many_lines)]
-pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: &Msrv) {
+pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: Msrv) {
     if let ExprKind::Closure(c) = arg.kind
         && matches!(c.kind, ClosureKind::Closure)
         && let typeck = cx.typeck_results()
         && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id)
         && (is_diag_trait_item(cx, fn_id, sym::Iterator)
-            || (msrv.meets(msrvs::OPTION_RESULT_INSPECT)
-                && (is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result))))
+            || ((is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result))
+                && msrv.meets(cx, msrvs::OPTION_RESULT_INSPECT)))
         && let body = cx.tcx.hir_body(c.body)
         && let [param] = body.params
         && let PatKind::Binding(BindingMode(ByRef::No, Mutability::Not), arg_id, _, None) = param.pat.kind
diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs
index 90e502f244f..40aad03960c 100644
--- a/clippy_lints/src/methods/manual_is_variant_and.rs
+++ b/clippy_lints/src/methods/manual_is_variant_and.rs
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
     map_recv: &'tcx rustc_hir::Expr<'_>,
     map_arg: &'tcx rustc_hir::Expr<'_>,
     map_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     // Don't lint if:
 
@@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
     }
 
     // 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
-    if !msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
+    if !msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
         return;
     }
 
diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints/src/methods/manual_repeat_n.rs
index 6e09bf132aa..83b57cca17b 100644
--- a/clippy_lints/src/methods/manual_repeat_n.rs
+++ b/clippy_lints/src/methods/manual_repeat_n.rs
@@ -14,16 +14,16 @@ pub(super) fn check<'tcx>(
     expr: &'tcx Expr<'tcx>,
     repeat_expr: &Expr<'_>,
     take_arg: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if msrv.meets(msrvs::REPEAT_N)
-        && !expr.span.from_expansion()
+    if !expr.span.from_expansion()
         && is_trait_method(cx, expr, sym::Iterator)
         && let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind
         && let Some(def_id) = fn_def_id(cx, repeat_expr)
         && cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id)
         && !expr_use_ctxt(cx, expr).is_ty_unified
         && let Some(std_or_core) = std_or_core(cx)
+        && msrv.meets(cx, msrvs::REPEAT_N)
     {
         let mut app = Applicability::MachineApplicable;
         span_lint_and_sugg(
diff --git a/clippy_lints/src/methods/manual_try_fold.rs b/clippy_lints/src/methods/manual_try_fold.rs
index a56378b5b73..23dba47f60f 100644
--- a/clippy_lints/src/methods/manual_try_fold.rs
+++ b/clippy_lints/src/methods/manual_try_fold.rs
@@ -17,10 +17,9 @@ pub(super) fn check<'tcx>(
     init: &Expr<'_>,
     acc: &Expr<'_>,
     fold_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if !fold_span.in_external_macro(cx.sess().source_map())
-        && msrv.meets(msrvs::ITERATOR_TRY_FOLD)
         && is_trait_method(cx, expr, sym::Iterator)
         && let init_ty = cx.typeck_results().expr_ty(init)
         && let Some(try_trait) = cx.tcx.lang_items().try_trait()
@@ -29,6 +28,7 @@ pub(super) fn check<'tcx>(
         && let ExprKind::Path(qpath) = path.kind
         && let Res::Def(DefKind::Ctor(_, _), _) = cx.qpath_res(&qpath, path.hir_id)
         && let ExprKind::Closure(closure) = acc.kind
+        && msrv.meets(cx, msrvs::ITERATOR_TRY_FOLD)
         && !is_from_proc_macro(cx, expr)
         && let Some(args_snip) = closure
             .fn_arg_span
diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs
index b2705e1ffc2..128b3695f48 100644
--- a/clippy_lints/src/methods/map_clone.rs
+++ b/clippy_lints/src/methods/map_clone.rs
@@ -41,7 +41,7 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) ->
     true
 }
 
-pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
+pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: Msrv) {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
         && should_run_lint(cx, e, method_id)
     {
@@ -169,10 +169,10 @@ fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) {
     );
 }
 
-fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) {
+fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: Msrv) {
     let mut applicability = Applicability::MachineApplicable;
 
-    let (message, sugg_method) = if is_copy && msrv.meets(msrvs::ITERATOR_COPIED) {
+    let (message, sugg_method) = if is_copy && msrv.meets(cx, msrvs::ITERATOR_COPIED) {
         ("you are using an explicit closure for copying elements", "copied")
     } else {
         ("you are using an explicit closure for cloning elements", "cloned")
diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs
index 428da0cf107..df5a0de3392 100644
--- a/clippy_lints/src/methods/map_unwrap_or.rs
+++ b/clippy_lints/src/methods/map_unwrap_or.rs
@@ -19,13 +19,13 @@ pub(super) fn check<'tcx>(
     recv: &'tcx hir::Expr<'_>,
     map_arg: &'tcx hir::Expr<'_>,
     unwrap_arg: &'tcx hir::Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     // lint if the caller of `map()` is an `Option` or a `Result`.
     let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
     let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
 
-    if is_result && !msrv.meets(msrvs::RESULT_MAP_OR_ELSE) {
+    if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) {
         return false;
     }
 
diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs
index 35dd7c082c9..6cf0936c598 100644
--- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs
+++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs
@@ -62,7 +62,7 @@ pub(super) fn check(
     ex: &Expr<'_>,
     receiver: &Expr<'_>,
     arg: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
     method_call_span: Span,
 ) {
     let mut applicability = Applicability::MaybeIncorrect;
@@ -82,7 +82,7 @@ pub(super) fn check(
         let use_take;
 
         if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
-            if msrv.meets(msrvs::REPEAT_N) {
+            if msrv.meets(cx, msrvs::REPEAT_N) {
                 method_to_use_name = "repeat_n";
                 let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
                 new_span = (arg.span, format!("{body_snippet}, {count}"));
@@ -93,7 +93,7 @@ pub(super) fn check(
                 new_span = (arg.span, body_snippet.to_string());
                 use_take = true;
             }
-        } else if msrv.meets(msrvs::REPEAT_WITH) {
+        } else if msrv.meets(cx, msrvs::REPEAT_WITH) {
             method_to_use_name = "repeat_with";
             new_span = (param.span, String::new());
             use_take = true;
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index 0c154b296bc..94d3657d9f1 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -4503,7 +4503,7 @@ impl Methods {
 
         Self {
             avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             allow_expect_in_tests: conf.allow_expect_in_tests,
             allow_unwrap_in_tests: conf.allow_unwrap_in_tests,
             allow_expect_in_consts: conf.allow_expect_in_consts,
@@ -4688,9 +4688,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
             ExprKind::Call(func, args) => {
                 from_iter_instead_of_collect::check(cx, expr, args, func);
                 unnecessary_fallible_conversions::check_function(cx, expr, func);
-                manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
-                useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
-                io_other_error::check(cx, expr, func, args, &self.msrv);
+                manual_c_str_literals::check(cx, expr, func, args, self.msrv);
+                useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv);
+                io_other_error::check(cx, expr, func, args, self.msrv);
             },
             ExprKind::MethodCall(method_call, receiver, args, _) => {
                 let method_span = method_call.ident.span;
@@ -4709,7 +4709,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
                 inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);
                 single_char_add_str::check(cx, expr, receiver, args);
                 into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
-                unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, &self.msrv);
+                unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv);
             },
             ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
                 let mut info = BinaryExprInfo {
@@ -4855,8 +4855,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 impl Methods {
@@ -4914,7 +4912,7 @@ impl Methods {
                                 && let body = cx.tcx.hir_body(arg.body)
                                 && let [param] = body.params =>
                         {
-                            string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
+                            string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), self.msrv);
                         },
                         Some(("map", _, [map_arg], _, map_call_span)) => {
                             map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any");
@@ -4938,12 +4936,12 @@ impl Methods {
                     sliced_string_as_bytes::check(cx, expr, recv);
                 },
                 ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
-                ("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
+                ("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, self.msrv),
                 ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
                 ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
                 ("bytes", []) => unbuffered_bytes::check(cx, expr, recv),
                 ("cloned", []) => {
-                    cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
+                    cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv);
                     option_as_ref_cloned::check(cx, recv, span);
                 },
                 ("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
@@ -4957,7 +4955,7 @@ impl Methods {
                             format_collect::check(cx, expr, m_arg, m_ident_span);
                         },
                         Some(("take", take_self_arg, [take_arg], _, _)) => {
-                            if self.msrv.meets(msrvs::STR_REPEAT) {
+                            if self.msrv.meets(cx, msrvs::STR_REPEAT) {
                                 manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
                             }
                         },
@@ -4996,13 +4994,13 @@ impl Methods {
                     if let ExprKind::MethodCall(.., span) = expr.kind {
                         case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
                     }
-                    path_ends_with_ext::check(cx, recv, arg, expr, &self.msrv, &self.allowed_dotfiles);
+                    path_ends_with_ext::check(cx, recv, arg, expr, self.msrv, &self.allowed_dotfiles);
                 },
                 ("expect", [_]) => {
                     match method_call(recv) {
                         Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
                         Some(("err", recv, [], err_span, _)) => {
-                            err_expect::check(cx, expr, recv, span, err_span, &self.msrv);
+                            err_expect::check(cx, expr, recv, span, err_span, self.msrv);
                         },
                         _ => unwrap_expect_used::check(
                             cx,
@@ -5044,7 +5042,7 @@ impl Methods {
                             false,
                         );
                     }
-                    if self.msrv.meets(msrvs::ITER_FLATTEN) {
+                    if self.msrv.meets(cx, msrvs::ITER_FLATTEN) {
                         // use the sourcemap to get the span of the closure
                         iter_filter::check(cx, expr, arg, span);
                     }
@@ -5092,7 +5090,7 @@ impl Methods {
                     _ => {},
                 },
                 ("fold", [init, acc]) => {
-                    manual_try_fold::check(cx, expr, init, acc, call_span, &self.msrv);
+                    manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv);
                     unnecessary_fold::check(cx, expr, init, acc, span);
                 },
                 ("for_each", [arg]) => {
@@ -5133,7 +5131,7 @@ impl Methods {
                     is_empty::check(cx, expr, recv);
                 },
                 ("is_file", []) => filetype_is_file::check(cx, expr, recv),
-                ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
+                ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
                 ("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
                 ("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
                 ("iter" | "iter_mut" | "into_iter", []) => {
@@ -5170,11 +5168,11 @@ impl Methods {
                 (name @ ("map" | "map_err"), [m_arg]) => {
                     if name == "map" {
                         unused_enumerate_index::check(cx, expr, recv, m_arg);
-                        map_clone::check(cx, expr, recv, m_arg, &self.msrv);
-                        map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span);
+                        map_clone::check(cx, expr, recv, m_arg, self.msrv);
+                        map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span);
                         match method_call(recv) {
                             Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
-                                iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv);
+                                iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv);
                             },
                             Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
                                 cx,
@@ -5191,8 +5189,8 @@ impl Methods {
                     }
                     if let Some((name, recv2, args, span2, _)) = method_call(recv) {
                         match (name, args) {
-                            ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, &self.msrv),
-                            ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, &self.msrv),
+                            ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
+                            ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
                             ("filter", [f_arg]) => {
                                 filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
                             },
@@ -5203,7 +5201,7 @@ impl Methods {
                         }
                     }
                     map_identity::check(cx, expr, recv, m_arg, name, span);
-                    manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv);
+                    manual_inspect::check(cx, expr, m_arg, name, span, self.msrv);
                     crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
                 },
                 ("map_break" | "map_continue", [m_arg]) => {
@@ -5212,7 +5210,7 @@ impl Methods {
                 ("map_or", [def, map]) => {
                     option_map_or_none::check(cx, expr, recv, def, map);
                     manual_ok_or::check(cx, expr, recv, def, map);
-                    unnecessary_map_or::check(cx, expr, recv, def, map, span, &self.msrv);
+                    unnecessary_map_or::check(cx, expr, recv, def, map, span, self.msrv);
                 },
                 ("map_or_else", [def, map]) => {
                     result_map_or_else_none::check(cx, expr, recv, def, map);
@@ -5230,7 +5228,7 @@ impl Methods {
                                 false,
                             ),
                             ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
-                            ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, &self.msrv),
+                            ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
                             ("iter", []) => iter_next_slice::check(cx, expr, recv2),
                             ("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
                             ("skip_while", [_]) => skip_while_next::check(cx, expr),
@@ -5286,7 +5284,7 @@ impl Methods {
                     no_effect_replace::check(cx, expr, arg1, arg2);
 
                     // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint
-                    if self.msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY)
+                    if self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY)
                         && name == "replace"
                         && let Some(("replace", ..)) = method_call(recv)
                     {
@@ -5297,10 +5295,10 @@ impl Methods {
                     vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
                 },
                 ("seek", [arg]) => {
-                    if self.msrv.meets(msrvs::SEEK_FROM_CURRENT) {
+                    if self.msrv.meets(cx, msrvs::SEEK_FROM_CURRENT) {
                         seek_from_current::check(cx, expr, recv, arg);
                     }
-                    if self.msrv.meets(msrvs::SEEK_REWIND) {
+                    if self.msrv.meets(cx, msrvs::SEEK_REWIND) {
                         seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
                     }
                 },
@@ -5334,7 +5332,7 @@ impl Methods {
                 ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
                     if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) {
                         suspicious_splitn::check(cx, name, expr, recv, count);
-                        str_splitn::check(cx, name, expr, recv, pat_arg, count, &self.msrv);
+                        str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
                     }
                 },
                 ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
@@ -5345,7 +5343,7 @@ impl Methods {
                 ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
                 ("take", [arg]) => {
                     iter_out_of_bounds::check_take(cx, expr, recv, arg);
-                    manual_repeat_n::check(cx, expr, recv, arg, &self.msrv);
+                    manual_repeat_n::check(cx, expr, recv, arg, self.msrv);
                     if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
                         iter_overeager_cloned::check(
                             cx,
@@ -5359,7 +5357,7 @@ impl Methods {
                 },
                 ("take", []) => needless_option_take::check(cx, expr, recv),
                 ("then", [arg]) => {
-                    if !self.msrv.meets(msrvs::BOOL_THEN_SOME) {
+                    if !self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
                         return;
                     }
                     unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
@@ -5420,7 +5418,7 @@ impl Methods {
                             manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
                         },
                         Some(("map", m_recv, [m_arg], span, _)) => {
-                            option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, &self.msrv);
+                            option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
                         },
                         Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => {
                             obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg, then_method, "unwrap_or");
@@ -5431,7 +5429,7 @@ impl Methods {
                 },
                 ("unwrap_or_default", []) => {
                     if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
-                        manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
+                        manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
                     }
                     unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
                 },
@@ -5441,7 +5439,7 @@ impl Methods {
                 ("unwrap_or_else", [u_arg]) => {
                     match method_call(recv) {
                         Some(("map", recv, [map_arg], _, _))
-                            if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
+                            if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
                         Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => {
                             obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg, then_method, "unwrap_or_else");
                         },
diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs
index 469fcccbe4f..63ee922acfa 100644
--- a/clippy_lints/src/methods/option_as_ref_deref.rs
+++ b/clippy_lints/src/methods/option_as_ref_deref.rs
@@ -18,12 +18,8 @@ pub(super) fn check(
     as_ref_recv: &hir::Expr<'_>,
     map_arg: &hir::Expr<'_>,
     is_mut: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if !msrv.meets(msrvs::OPTION_AS_DEREF) {
-        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_recv);
@@ -93,7 +89,7 @@ pub(super) fn check(
         _ => false,
     };
 
-    if is_deref {
+    if is_deref && msrv.meets(cx, msrvs::OPTION_AS_DEREF) {
         let current_method = if is_mut {
             format!(".as_mut().map({})", snippet(cx, map_arg.span, ".."))
         } else {
diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs
index b1107d8cc72..4ba8e010904 100644
--- a/clippy_lints/src/methods/option_map_unwrap_or.rs
+++ b/clippy_lints/src/methods/option_map_unwrap_or.rs
@@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
     unwrap_recv: &rustc_hir::Expr<'_>,
     unwrap_arg: &'tcx rustc_hir::Expr<'_>,
     map_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     // lint if the caller of `map()` is an `Option`
     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
@@ -71,9 +71,9 @@ pub(super) fn check<'tcx>(
         }
 
         // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
-        let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
-            && matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
-            if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
+        let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
+            if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
+            && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
 
         let mut applicability = Applicability::MachineApplicable;
         // get snippet for unwrap_or()
diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints/src/methods/path_ends_with_ext.rs
index b3811a335e1..d3f513e7abd 100644
--- a/clippy_lints/src/methods/path_ends_with_ext.rs
+++ b/clippy_lints/src/methods/path_ends_with_ext.rs
@@ -20,7 +20,7 @@ pub(super) fn check(
     recv: &Expr<'_>,
     path: &Expr<'_>,
     expr: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
     allowed_dotfiles: &FxHashSet<&'static str>,
 ) {
     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::Path)
@@ -33,7 +33,7 @@ pub(super) fn check(
         && path.chars().all(char::is_alphanumeric)
     {
         let mut sugg = snippet(cx, recv.span, "..").into_owned();
-        if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
+        if msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
             let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
         } else {
             let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index 8389c2e3f98..4ccefb7ec9d 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -23,7 +23,7 @@ pub(super) fn check(
     self_arg: &Expr<'_>,
     pat_arg: &Expr<'_>,
     count: u128,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
         return;
@@ -33,7 +33,7 @@ pub(super) fn check(
         IterUsageKind::Nth(n) => count > n + 1,
         IterUsageKind::NextTuple => count > 2,
     };
-    let manual = count == 2 && msrv.meets(msrvs::STR_SPLIT_ONCE);
+    let manual = count == 2 && msrv.meets(cx, msrvs::STR_SPLIT_ONCE);
 
     match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir_parent_iter(expr.hir_id)) {
         Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
diff --git a/clippy_lints/src/methods/string_lit_chars_any.rs b/clippy_lints/src/methods/string_lit_chars_any.rs
index cb719b34b1f..f0f9d30d300 100644
--- a/clippy_lints/src/methods/string_lit_chars_any.rs
+++ b/clippy_lints/src/methods/string_lit_chars_any.rs
@@ -17,10 +17,9 @@ pub(super) fn check<'tcx>(
     recv: &Expr<'_>,
     param: &'tcx Param<'tcx>,
     body: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
-    if msrv.meets(msrvs::MATCHES_MACRO)
-        && is_trait_method(cx, expr, sym::Iterator)
+    if is_trait_method(cx, expr, sym::Iterator)
         && let PatKind::Binding(_, arg, _, _) = param.pat.kind
         && let ExprKind::Lit(lit_kind) = recv.kind
         && let LitKind::Str(val, _) = lit_kind.node
@@ -33,6 +32,7 @@ pub(super) fn check<'tcx>(
             (false, true) => lhs,
             _ => return,
         }
+        && msrv.meets(cx, msrvs::MATCHES_MACRO)
         && !is_from_proc_macro(cx, expr)
         && let Some(scrutinee_snip) = scrutinee.span.get_source_text(cx)
     {
diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs
index 3a669fca177..d7bd522ddab 100644
--- a/clippy_lints/src/methods/unnecessary_map_or.rs
+++ b/clippy_lints/src/methods/unnecessary_map_or.rs
@@ -42,7 +42,7 @@ pub(super) fn check<'a>(
     def: &Expr<'_>,
     map: &Expr<'_>,
     method_span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     let ExprKind::Lit(def_kind) = def.kind else {
         return;
@@ -119,14 +119,14 @@ pub(super) fn check<'a>(
         .into_string();
 
         (vec![(expr.span, sugg)], "a standard comparison", app)
-    } else if !def_bool && msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
+    } else if !def_bool && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
         let suggested_name = variant.method_name();
         (
             vec![(method_span, suggested_name.into()), (ext_def_span, String::default())],
             suggested_name,
             Applicability::MachineApplicable,
         )
-    } else if def_bool && matches!(variant, Variant::Some) && msrv.meets(msrvs::IS_NONE_OR) {
+    } else if def_bool && matches!(variant, Variant::Some) && msrv.meets(cx, msrvs::IS_NONE_OR) {
         (
             vec![(method_span, "is_none_or".into()), (ext_def_span, String::default())],
             "is_none_or",
diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs
index ea134c05705..a71b3659fd2 100644
--- a/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -31,7 +31,7 @@ pub fn check<'tcx>(
     method_name: Symbol,
     receiver: &'tcx Expr<'_>,
     args: &'tcx [Expr<'_>],
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
         && args.is_empty()
@@ -207,7 +207,7 @@ fn check_into_iter_call_arg(
     expr: &Expr<'_>,
     method_name: Symbol,
     receiver: &Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     if let Some(parent) = get_parent_expr(cx, expr)
         && let Some(callee_def_id) = fn_def_id(cx, parent)
@@ -224,7 +224,7 @@ fn check_into_iter_call_arg(
             return true;
         }
 
-        let cloned_or_copied = if is_copy(cx, item_ty) && msrv.meets(msrvs::ITERATOR_COPIED) {
+        let cloned_or_copied = if is_copy(cx, item_ty) && msrv.meets(cx, msrvs::ITERATOR_COPIED) {
             "copied"
         } else {
             "cloned"
diff --git a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs
index 0bd50429c09..22df1f3f485 100644
--- a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs
+++ b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs
@@ -10,13 +10,13 @@ use rustc_span::sym;
 
 use super::USELESS_NONZERO_NEW_UNCHECKED;
 
-pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: &Msrv) {
-    if msrv.meets(msrvs::CONST_UNWRAP)
-        && let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: Msrv) {
+    if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind
         && segment.ident.name == sym::new_unchecked
         && let [init_arg] = args
         && is_inside_always_const_context(cx.tcx, expr.hir_id)
         && is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero)
+        && msrv.meets(cx, msrvs::CONST_UNWRAP)
     {
         let mut app = Applicability::MachineApplicable;
         let ty_str = snippet_with_applicability(cx, ty.span, "_", &mut app);
diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs
index bc60fa507b1..38a19dd2999 100644
--- a/clippy_lints/src/missing_const_for_fn.rs
+++ b/clippy_lints/src/missing_const_for_fn.rs
@@ -80,9 +80,7 @@ pub struct MissingConstForFn {
 
 impl MissingConstForFn {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -101,10 +99,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
             return;
         }
 
-        if !self.msrv.meets(msrvs::CONST_IF_MATCH) {
-            return;
-        }
-
         if span.in_external_macro(cx.tcx.sess.source_map()) || is_entrypoint_fn(cx, def_id.to_def_id()) {
             return;
         }
@@ -123,7 +117,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
                     .iter()
                     .any(|param| matches!(param.kind, GenericParamKind::Const { .. }));
 
-                if already_const(header) || has_const_generic_params || !could_be_const_with_abi(&self.msrv, header.abi)
+                if already_const(header)
+                    || has_const_generic_params
+                    || !could_be_const_with_abi(cx, self.msrv, header.abi)
                 {
                     return;
                 }
@@ -152,13 +148,17 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
             }
         }
 
+        if !self.msrv.meets(cx, msrvs::CONST_IF_MATCH) {
+            return;
+        }
+
         if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) {
             return;
         }
 
         let mir = cx.tcx.optimized_mir(def_id);
 
-        if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv)
+        if let Ok(()) = is_min_const_fn(cx, mir, self.msrv)
             && let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) =
                 cx.tcx.hir_node_by_def_id(def_id)
         {
@@ -173,8 +173,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
             });
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 // We don't have to lint on something that's already `const`
@@ -183,13 +181,13 @@ fn already_const(header: hir::FnHeader) -> bool {
     header.constness == Constness::Const
 }
 
-fn could_be_const_with_abi(msrv: &Msrv, abi: ExternAbi) -> bool {
+fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: Msrv, abi: ExternAbi) -> bool {
     match abi {
         ExternAbi::Rust => true,
         // `const extern "C"` was stabilized after 1.62.0
-        ExternAbi::C { unwind: false } => msrv.meets(msrvs::CONST_EXTERN_C_FN),
+        ExternAbi::C { unwind: false } => msrv.meets(cx, msrvs::CONST_EXTERN_C_FN),
         // Rest ABIs are still unstable and need the `const_extern_fn` feature enabled.
-        _ => msrv.meets(msrvs::CONST_EXTERN_FN),
+        _ => msrv.meets(cx, msrvs::CONST_EXTERN_FN),
     }
 }
 
diff --git a/clippy_lints/src/missing_const_for_thread_local.rs b/clippy_lints/src/missing_const_for_thread_local.rs
index d4181c677af..ea74940828a 100644
--- a/clippy_lints/src/missing_const_for_thread_local.rs
+++ b/clippy_lints/src/missing_const_for_thread_local.rs
@@ -49,9 +49,7 @@ pub struct MissingConstForThreadLocal {
 
 impl MissingConstForThreadLocal {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -91,11 +89,11 @@ fn is_unreachable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
 }
 
 #[inline]
-fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: &Msrv) -> bool {
+fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: Msrv) -> bool {
     // Building MIR for `fn`s with unsatisfiable preds results in ICE.
     if !fn_has_unsatisfiable_preds(cx, defid)
         && let mir = cx.tcx.optimized_mir(defid)
-        && let Ok(()) = is_min_const_fn(cx.tcx, mir, msrv)
+        && let Ok(()) = is_min_const_fn(cx, mir, msrv)
     {
         return true;
     }
@@ -113,8 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal {
         local_defid: rustc_span::def_id::LocalDefId,
     ) {
         let defid = local_defid.to_def_id();
-        if self.msrv.meets(msrvs::THREAD_LOCAL_CONST_INIT)
-            && is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false)
+        if is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false)
             // Some implementations of `thread_local!` include an initializer fn.
             // In the case of a const initializer, the init fn is also const,
             // so we can skip the lint in that case. This occurs only on some
@@ -131,11 +128,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal {
             // https://github.com/rust-lang/rust-clippy/issues/12637
             // we ensure that this is reachable before we check in mir
             && !is_unreachable(cx, ret_expr)
-            && initializer_can_be_made_const(cx, defid, &self.msrv)
+            && initializer_can_be_made_const(cx, defid, self.msrv)
             // we know that the function is const-qualifiable, so now
             // we need only to get the initializer expression to span-lint it.
             && let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
             && initializer_snippet != "thread_local! { ... }"
+            && self.msrv.meets(cx, msrvs::THREAD_LOCAL_CONST_INIT)
         {
             span_lint_and_sugg(
                 cx,
@@ -148,6 +146,4 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs
index ea1d7e5d438..f686cc912dd 100644
--- a/clippy_lints/src/needless_borrows_for_generic_args.rs
+++ b/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -72,7 +72,7 @@ impl NeedlessBorrowsForGenericArgs<'_> {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             possible_borrowers: Vec::new(),
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -114,7 +114,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
                 i,
                 param_ty,
                 expr,
-                &self.msrv,
+                self.msrv,
             )
             && count != 0
         {
@@ -142,8 +142,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
             self.possible_borrowers.pop();
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn path_has_args(p: &QPath<'_>) -> bool {
@@ -172,7 +170,7 @@ fn needless_borrow_count<'tcx>(
     arg_index: usize,
     param_ty: ParamTy,
     mut expr: &Expr<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> usize {
     let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
     let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
@@ -273,7 +271,7 @@ fn needless_borrow_count<'tcx>(
                 && let ty::Param(param_ty) = trait_predicate.self_ty().kind()
                 && let GenericArgKind::Type(ty) = args_with_referent_ty[param_ty.index as usize].unpack()
                 && ty.is_array()
-                && !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
+                && !msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR)
             {
                 return false;
             }
diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs
index 774a182d089..a82365f9431 100644
--- a/clippy_lints/src/non_std_lazy_statics.rs
+++ b/clippy_lints/src/non_std_lazy_statics.rs
@@ -1,8 +1,8 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
-use clippy_utils::msrvs::Msrv;
+use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::visitors::for_each_expr;
-use clippy_utils::{def_path_def_ids, fn_def_id, path_def_id};
+use clippy_utils::{def_path_def_ids, fn_def_id, is_no_std_crate, path_def_id};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
@@ -75,7 +75,7 @@ impl NonStdLazyStatic {
     #[must_use]
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             lazy_static_lazy_static: Vec::new(),
             once_cell_crate: Vec::new(),
             once_cell_sync_lazy: Vec::new(),
@@ -89,23 +89,12 @@ impl NonStdLazyStatic {
 
 impl_lint_pass!(NonStdLazyStatic => [NON_STD_LAZY_STATICS]);
 
-/// Return if current MSRV does not meet the requirement for `lazy_cell` feature,
-/// or current context has `no_std` attribute.
-macro_rules! ensure_prerequisite {
-    ($msrv:expr, $cx:ident) => {
-        if !$msrv.meets(clippy_utils::msrvs::LAZY_CELL) || clippy_utils::is_no_std_crate($cx) {
-            return;
-        }
-    };
+fn can_use_lazy_cell(cx: &LateContext<'_>, msrv: Msrv) -> bool {
+    msrv.meets(cx, msrvs::LAZY_CELL) && !is_no_std_crate(cx)
 }
 
 impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
-    extract_msrv_attr!(LateContext);
-
     fn check_crate(&mut self, cx: &LateContext<'hir>) {
-        // Do not lint if current crate does not support `LazyLock`.
-        ensure_prerequisite!(self.msrv, cx);
-
         // Fetch def_ids for external paths
         self.lazy_static_lazy_static = def_path_def_ids(cx.tcx, &["lazy_static", "lazy_static"]).collect();
         self.once_cell_sync_lazy = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy"]).collect();
@@ -123,11 +112,10 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
     }
 
     fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) {
-        ensure_prerequisite!(self.msrv, cx);
-
         if let ItemKind::Static(..) = item.kind
             && let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span)
             && self.lazy_static_lazy_static.contains(&macro_call.def_id)
+            && can_use_lazy_cell(cx, self.msrv)
         {
             span_lint(
                 cx,
@@ -142,14 +130,14 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
             return;
         }
 
-        if let Some(lazy_info) = LazyInfo::from_item(self, cx, item) {
+        if let Some(lazy_info) = LazyInfo::from_item(self, cx, item)
+            && can_use_lazy_cell(cx, self.msrv)
+        {
             self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info);
         }
     }
 
     fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &Expr<'hir>) {
-        ensure_prerequisite!(self.msrv, cx);
-
         // All functions in the `FUNCTION_REPLACEMENTS` have only one args
         if let ExprKind::Call(callee, [arg]) = expr.kind
             && let Some(call_def_id) = fn_def_id(cx, expr)
@@ -163,8 +151,6 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
     }
 
     fn check_ty(&mut self, cx: &LateContext<'hir>, ty: &'hir rustc_hir::Ty<'hir, rustc_hir::AmbigArg>) {
-        ensure_prerequisite!(self.msrv, cx);
-
         // Record if types from `once_cell` besides `sync::Lazy` are used.
         if let rustc_hir::TyKind::Path(qpath) = ty.peel_refs().kind
             && let Some(ty_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id()
@@ -178,8 +164,6 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
     }
 
     fn check_crate_post(&mut self, cx: &LateContext<'hir>) {
-        ensure_prerequisite!(self.msrv, cx);
-
         if !self.uses_other_once_cell_types {
             for (_, lazy_info) in &self.lazy_type_defs {
                 lazy_info.lint(cx, &self.sugg_map);
diff --git a/clippy_lints/src/operators/manual_midpoint.rs b/clippy_lints/src/operators/manual_midpoint.rs
index 61ef5670a5a..81721a9f2af 100644
--- a/clippy_lints/src/operators/manual_midpoint.rs
+++ b/clippy_lints/src/operators/manual_midpoint.rs
@@ -16,7 +16,7 @@ pub(super) fn check<'tcx>(
     op: BinOpKind,
     left: &'tcx Expr<'_>,
     right: &'tcx Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) {
     if !left.span.from_expansion()
         && !right.span.from_expansion()
@@ -29,7 +29,7 @@ pub(super) fn check<'tcx>(
         && left_ty == right_ty
         // Do not lint on `(_+1)/2` and `(1+_)/2`, it is likely a `div_ceil()` operation
         && !is_integer_literal(ll_expr, 1) && !is_integer_literal(lr_expr, 1)
-        && is_midpoint_implemented(left_ty, msrv)
+        && is_midpoint_implemented(cx, left_ty, msrv)
     {
         let mut app = Applicability::MachineApplicable;
         let left_sugg = Sugg::hir_with_context(cx, ll_expr, expr.span.ctxt(), "..", &mut app);
@@ -55,10 +55,10 @@ fn add_operands<'e, 'tcx>(expr: &'e Expr<'tcx>) -> Option<(&'e Expr<'tcx>, &'e E
     }
 }
 
-fn is_midpoint_implemented(ty: Ty<'_>, msrv: &Msrv) -> bool {
+fn is_midpoint_implemented(cx: &LateContext<'_>, ty: Ty<'_>, msrv: Msrv) -> bool {
     match ty.kind() {
-        ty::Uint(_) | ty::Float(_) => msrv.meets(msrvs::UINT_FLOAT_MIDPOINT),
-        ty::Int(_) => msrv.meets(msrvs::INT_MIDPOINT),
+        ty::Uint(_) | ty::Float(_) => msrv.meets(cx, msrvs::UINT_FLOAT_MIDPOINT),
+        ty::Int(_) => msrv.meets(cx, msrvs::INT_MIDPOINT),
         _ => false,
     }
 }
diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs
index 43b657ea7ff..80459945094 100644
--- a/clippy_lints/src/operators/mod.rs
+++ b/clippy_lints/src/operators/mod.rs
@@ -872,7 +872,7 @@ impl Operators {
             arithmetic_context: numeric_arithmetic::Context::default(),
             verbose_bit_mask_threshold: conf.verbose_bit_mask_threshold,
             modulo_arithmetic_allow_comparison_to_zero: conf.allow_comparison_to_zero,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -922,7 +922,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
                     identity_op::check(cx, e, op.node, lhs, rhs);
                     needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
                     ptr_eq::check(cx, e, op.node, lhs, rhs);
-                    manual_midpoint::check(cx, e, op.node, lhs, rhs, &self.msrv);
+                    manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
                 }
                 self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
                 bit_mask::check(cx, e, op.node, lhs, rhs);
@@ -973,8 +973,6 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
     fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &Body<'_>) {
         self.arithmetic_context.body_post(cx, b);
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn macro_with_not_op(e: &Expr<'_>) -> bool {
diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs
index ffc3b86c502..4f5f3eb6c15 100644
--- a/clippy_lints/src/question_mark.rs
+++ b/clippy_lints/src/question_mark.rs
@@ -68,7 +68,7 @@ impl_lint_pass!(QuestionMark => [QUESTION_MARK, MANUAL_LET_ELSE]);
 impl QuestionMark {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             matches_behaviour: conf.matches_for_let_else,
             try_block_depth_stack: Vec::new(),
             inferred_ret_closure_stack: 0,
@@ -543,5 +543,4 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
                 .expect("blocks are always part of bodies and must have a depth") -= 1;
         }
     }
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs
index 1b0c0a4956f..cc423eca74f 100644
--- a/clippy_lints/src/ranges.rs
+++ b/clippy_lints/src/ranges.rs
@@ -166,9 +166,7 @@ pub struct Ranges {
 
 impl Ranges {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -182,7 +180,7 @@ impl_lint_pass!(Ranges => [
 impl<'tcx> LateLintPass<'tcx> for Ranges {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
         if let ExprKind::Binary(ref op, l, r) = expr.kind {
-            if self.msrv.meets(msrvs::RANGE_CONTAINS) {
+            if self.msrv.meets(cx, msrvs::RANGE_CONTAINS) {
                 check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
             }
         }
@@ -191,7 +189,6 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
         check_inclusive_range_minus_one(cx, expr);
         check_reversed_empty_range(cx, expr);
     }
-    extract_msrv_attr!(LateContext);
 }
 
 fn check_possible_range_contains(
diff --git a/clippy_lints/src/redundant_field_names.rs b/clippy_lints/src/redundant_field_names.rs
index 707abc008a8..feefe10f57d 100644
--- a/clippy_lints/src/redundant_field_names.rs
+++ b/clippy_lints/src/redundant_field_names.rs
@@ -1,6 +1,6 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, MsrvStack};
 use rustc_ast::ast::{Expr, ExprKind};
 use rustc_errors::Applicability;
 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
@@ -36,13 +36,13 @@ declare_clippy_lint! {
 }
 
 pub struct RedundantFieldNames {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl RedundantFieldNames {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -80,5 +80,6 @@ impl EarlyLintPass for RedundantFieldNames {
             }
         }
     }
-    extract_msrv_attr!(EarlyContext);
+
+    extract_msrv_attr!();
 }
diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs
index 06c85433806..b4e1f70d153 100644
--- a/clippy_lints/src/redundant_static_lifetimes.rs
+++ b/clippy_lints/src/redundant_static_lifetimes.rs
@@ -1,6 +1,6 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, MsrvStack};
 use clippy_utils::source::snippet;
 use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind};
 use rustc_errors::Applicability;
@@ -35,13 +35,13 @@ declare_clippy_lint! {
 }
 
 pub struct RedundantStaticLifetimes {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl RedundantStaticLifetimes {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -115,5 +115,5 @@ impl EarlyLintPass for RedundantStaticLifetimes {
         }
     }
 
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
 }
diff --git a/clippy_lints/src/repeat_vec_with_capacity.rs b/clippy_lints/src/repeat_vec_with_capacity.rs
index 40263afd28f..8805687efcc 100644
--- a/clippy_lints/src/repeat_vec_with_capacity.rs
+++ b/clippy_lints/src/repeat_vec_with_capacity.rs
@@ -18,9 +18,7 @@ pub struct RepeatVecWithCapacity {
 
 impl RepeatVecWithCapacity {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -101,13 +99,14 @@ fn check_vec_macro(cx: &LateContext<'_>, expr: &Expr<'_>) {
 }
 
 /// Checks `iter::repeat(Vec::with_capacity(x))`
-fn check_repeat_fn(cx: &LateContext<'_>, expr: &Expr<'_>) {
+fn check_repeat_fn(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
     if !expr.span.from_expansion()
         && fn_def_id(cx, expr).is_some_and(|did| cx.tcx.is_diagnostic_item(sym::iter_repeat, did))
         && let ExprKind::Call(_, [repeat_expr]) = expr.kind
         && fn_def_id(cx, repeat_expr).is_some_and(|did| cx.tcx.is_diagnostic_item(sym::vec_with_capacity, did))
         && !repeat_expr.span.from_expansion()
         && let Some(exec_context) = std_or_core(cx)
+        && msrv.meets(cx, msrvs::REPEAT_WITH)
     {
         emit_lint(
             cx,
@@ -126,10 +125,6 @@ fn check_repeat_fn(cx: &LateContext<'_>, expr: &Expr<'_>) {
 impl LateLintPass<'_> for RepeatVecWithCapacity {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         check_vec_macro(cx, expr);
-        if self.msrv.meets(msrvs::REPEAT_WITH) {
-            check_repeat_fn(cx, expr);
-        }
+        check_repeat_fn(cx, expr, self.msrv);
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs
index 59c13a1e2c5..d68ac8bab12 100644
--- a/clippy_lints/src/std_instead_of_core.rs
+++ b/clippy_lints/src/std_instead_of_core.rs
@@ -99,7 +99,7 @@ impl StdReexports {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             prev_span: Span::default(),
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -110,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
     fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
         if let Res::Def(_, def_id) = path.res
             && let Some(first_segment) = get_first_segment(path)
-            && is_stable(cx, def_id, &self.msrv)
+            && is_stable(cx, def_id, self.msrv)
             && !path.span.in_external_macro(cx.sess().source_map())
             && !is_from_proc_macro(cx, &first_segment.ident)
         {
@@ -153,8 +153,6 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 /// Returns the first named segment of a [`Path`].
@@ -174,7 +172,7 @@ fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>>
 /// or now stable moves that were once unstable.
 ///
 /// Does not catch individually moved items
-fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: &Msrv) -> bool {
+fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: Msrv) -> bool {
     loop {
         if let Some(stability) = cx.tcx.lookup_stability(def_id)
             && let StabilityLevel::Stable {
@@ -183,8 +181,8 @@ fn is_stable(cx: &LateContext<'_>, mut def_id: DefId, msrv: &Msrv) -> bool {
             } = stability.level
         {
             let stable = match since {
-                StableSince::Version(v) => msrv.meets(v),
-                StableSince::Current => msrv.current().is_none(),
+                StableSince::Version(v) => msrv.meets(cx, v),
+                StableSince::Current => msrv.current(cx).is_none(),
                 StableSince::Err => false,
             };
 
diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs
index 694ad4f6347..5c95dfe8347 100644
--- a/clippy_lints/src/string_patterns.rs
+++ b/clippy_lints/src/string_patterns.rs
@@ -77,9 +77,7 @@ pub struct StringPatterns {
 
 impl StringPatterns {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -136,7 +134,7 @@ fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio
     }
 }
 
-fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>, msrv: &Msrv) {
+fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>, msrv: Msrv) {
     if let ExprKind::Closure(closure) = method_arg.kind
         && let body = cx.tcx.hir_body(closure.body)
         && let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind)
@@ -192,7 +190,7 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<
         {
             return;
         }
-        if set_char_spans.len() > 1 && !msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) {
+        if set_char_spans.len() > 1 && !msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY) {
             return;
         }
         span_lint_and_then(
@@ -238,9 +236,7 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns {
         {
             check_single_char_pattern_lint(cx, arg);
 
-            check_manual_pattern_char_comparison(cx, arg, &self.msrv);
+            check_manual_pattern_char_comparison(cx, arg, self.msrv);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs
index cbf7b126632..f961e1c4d1a 100644
--- a/clippy_lints/src/trait_bounds.rs
+++ b/clippy_lints/src/trait_bounds.rs
@@ -95,7 +95,7 @@ impl TraitBounds {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             max_trait_bounds: conf.max_trait_bounds,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
         }
     }
 }
@@ -223,17 +223,15 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 impl TraitBounds {
     /// Is the given bound a `?Sized` bound, and is combining it (i.e. `T: X + ?Sized`) an error on
     /// this MSRV? See <https://github.com/rust-lang/rust-clippy/issues/8772> for details.
     fn cannot_combine_maybe_bound(&self, cx: &LateContext<'_>, bound: &GenericBound<'_>) -> bool {
-        if !self.msrv.meets(msrvs::MAYBE_BOUND_IN_WHERE)
-            && let GenericBound::Trait(tr) = bound
+        if let GenericBound::Trait(tr) = bound
             && let BoundPolarity::Maybe(_) = tr.modifiers.polarity
+            && !self.msrv.meets(cx, msrvs::MAYBE_BOUND_IN_WHERE)
         {
             cx.tcx.lang_items().get(LangItem::Sized) == tr.trait_ref.path.res.opt_def_id()
         } else {
diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs
index 7c83a218799..f2da8d35cb4 100644
--- a/clippy_lints/src/transmute/mod.rs
+++ b/clippy_lints/src/transmute/mod.rs
@@ -598,9 +598,7 @@ impl_lint_pass!(Transmute => [
 ]);
 impl Transmute {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 impl<'tcx> LateLintPass<'tcx> for Transmute {
@@ -632,16 +630,16 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
                 | crosspointer_transmute::check(cx, e, from_ty, to_ty)
                 | transmuting_null::check(cx, e, arg, to_ty)
                 | transmute_null_to_fn::check(cx, e, arg, to_ty)
-                | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, &self.msrv)
+                | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
                 | missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id)
                 | transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
                 | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
-                | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, &self.msrv)
+                | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
                 | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
-                | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context, &self.msrv)
+                | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
                 | transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg)
-                | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context, &self.msrv)
-                | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context, &self.msrv)
+                | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
+                | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
                 | (unsound_collection_transmute::check(cx, e, from_ty, to_ty)
                     || transmute_undefined_repr::check(cx, e, from_ty, to_ty))
                 | (eager_transmute::check(cx, e, arg, from_ty, to_ty));
@@ -651,6 +649,4 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/transmute/transmute_float_to_int.rs b/clippy_lints/src/transmute/transmute_float_to_int.rs
index f0b8abf9af6..f2c757952af 100644
--- a/clippy_lints/src/transmute/transmute_float_to_int.rs
+++ b/clippy_lints/src/transmute/transmute_float_to_int.rs
@@ -17,11 +17,11 @@ pub(super) fn check<'tcx>(
     to_ty: Ty<'tcx>,
     mut arg: &'tcx Expr<'_>,
     const_context: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     match (&from_ty.kind(), &to_ty.kind()) {
         (ty::Float(float_ty), ty::Int(_) | ty::Uint(_))
-            if !const_context || msrv.meets(msrvs::CONST_FLOAT_BITS_CONV) =>
+            if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) =>
         {
             span_lint_and_then(
                 cx,
diff --git a/clippy_lints/src/transmute/transmute_int_to_float.rs b/clippy_lints/src/transmute/transmute_int_to_float.rs
index e5b9aea6423..aaa95396b4b 100644
--- a/clippy_lints/src/transmute/transmute_int_to_float.rs
+++ b/clippy_lints/src/transmute/transmute_int_to_float.rs
@@ -16,10 +16,10 @@ pub(super) fn check<'tcx>(
     to_ty: Ty<'tcx>,
     arg: &'tcx Expr<'_>,
     const_context: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     match (&from_ty.kind(), &to_ty.kind()) {
-        (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context || msrv.meets(msrvs::CONST_FLOAT_BITS_CONV) => {
+        (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) => {
             span_lint_and_then(
                 cx,
                 TRANSMUTE_INT_TO_FLOAT,
diff --git a/clippy_lints/src/transmute/transmute_num_to_bytes.rs b/clippy_lints/src/transmute/transmute_num_to_bytes.rs
index 6d828bad9b3..d72be270b73 100644
--- a/clippy_lints/src/transmute/transmute_num_to_bytes.rs
+++ b/clippy_lints/src/transmute/transmute_num_to_bytes.rs
@@ -16,14 +16,15 @@ pub(super) fn check<'tcx>(
     to_ty: Ty<'tcx>,
     arg: &'tcx Expr<'_>,
     const_context: bool,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     match (&from_ty.kind(), &to_ty.kind()) {
         (ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
             if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
                 return false;
             }
-            if matches!(from_ty.kind(), ty::Float(_)) && const_context && !msrv.meets(msrvs::CONST_FLOAT_BITS_CONV) {
+            if matches!(from_ty.kind(), ty::Float(_)) && const_context && !msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV)
+            {
                 return false;
             }
 
diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs
index c4a2e20fa9d..fcc763763bd 100644
--- a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs
+++ b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs
@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
     from_ty: Ty<'tcx>,
     to_ty: Ty<'tcx>,
     arg: &'tcx Expr<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     match (from_ty.kind(), to_ty.kind()) {
         (ty::RawPtr(from_pointee_ty, from_mutbl), ty::RawPtr(to_pointee_ty, to_mutbl)) => {
@@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
                     if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
                         if from_mutbl == to_mutbl
                             && to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
-                            && msrv.meets(msrvs::POINTER_CAST)
+                            && msrv.meets(cx, msrvs::POINTER_CAST)
                         {
                             diag.span_suggestion_verbose(
                                 e.span,
@@ -43,7 +43,7 @@ pub(super) fn check<'tcx>(
                                 _ => None,
                             }
                             && !from_pointee_ty.has_erased_regions()
-                            && msrv.meets(msrvs::POINTER_CAST_CONSTNESS)
+                            && msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS)
                         {
                             diag.span_suggestion_verbose(
                                 e.span,
diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs
index ef18633d945..45ee83c78ab 100644
--- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs
+++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs
@@ -17,7 +17,7 @@ pub(super) fn check<'tcx>(
     to_ty: Ty<'tcx>,
     arg: &'tcx Expr<'_>,
     path: &'tcx Path<'_>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> bool {
     match (&from_ty.kind(), &to_ty.kind()) {
         (ty::RawPtr(from_ptr_ty, _), ty::Ref(_, to_ref_ty, mutbl)) => {
@@ -37,7 +37,7 @@ pub(super) fn check<'tcx>(
 
                     let sugg = if let Some(ty) = get_explicit_type(path) {
                         let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
-                        if msrv.meets(msrvs::POINTER_CAST) {
+                        if msrv.meets(cx, msrvs::POINTER_CAST) {
                             format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_par())
                         } else if from_ptr_ty.has_erased_regions() {
                             sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string()
@@ -46,7 +46,7 @@ pub(super) fn check<'tcx>(
                         }
                     } else if *from_ptr_ty == *to_ref_ty {
                         if from_ptr_ty.has_erased_regions() {
-                            if msrv.meets(msrvs::POINTER_CAST) {
+                            if msrv.meets(cx, msrvs::POINTER_CAST) {
                                 format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_par())
                             } else {
                                 sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}")))
diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs
index c7aefc65f70..95ce19975c7 100644
--- a/clippy_lints/src/tuple_array_conversions.rs
+++ b/clippy_lints/src/tuple_array_conversions.rs
@@ -47,15 +47,13 @@ pub struct TupleArrayConversions {
 }
 impl TupleArrayConversions {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
 impl LateLintPass<'_> for TupleArrayConversions {
     fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
-        if expr.span.in_external_macro(cx.sess().source_map()) || !self.msrv.meets(msrvs::TUPLE_ARRAY_CONVERSIONS) {
+        if expr.span.in_external_macro(cx.sess().source_map()) || !self.msrv.meets(cx, msrvs::TUPLE_ARRAY_CONVERSIONS) {
             return;
         }
 
@@ -65,8 +63,6 @@ impl LateLintPass<'_> for TupleArrayConversions {
             _ => {},
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) {
diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs
index 8923484bb58..f43715d6752 100644
--- a/clippy_lints/src/unnested_or_patterns.rs
+++ b/clippy_lints/src/unnested_or_patterns.rs
@@ -3,7 +3,7 @@
 use clippy_config::Conf;
 use clippy_utils::ast_utils::{eq_field_pat, eq_id, eq_maybe_qself, eq_pat, eq_path};
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::msrvs::{self, MsrvStack};
 use clippy_utils::over;
 use rustc_ast::PatKind::*;
 use rustc_ast::mut_visit::*;
@@ -48,13 +48,13 @@ declare_clippy_lint! {
 }
 
 pub struct UnnestedOrPatterns {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl UnnestedOrPatterns {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: MsrvStack::new(conf.msrv),
         }
     }
 }
@@ -88,7 +88,7 @@ impl EarlyLintPass for UnnestedOrPatterns {
         }
     }
 
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
 }
 
 fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) {
diff --git a/clippy_lints/src/unused_trait_names.rs b/clippy_lints/src/unused_trait_names.rs
index f8341583435..2577f1ceaa2 100644
--- a/clippy_lints/src/unused_trait_names.rs
+++ b/clippy_lints/src/unused_trait_names.rs
@@ -51,9 +51,7 @@ pub struct UnusedTraitNames {
 
 impl UnusedTraitNames {
     pub fn new(conf: &'static Conf) -> Self {
-        Self {
-            msrv: conf.msrv.clone(),
-        }
+        Self { msrv: conf.msrv }
     }
 }
 
@@ -61,8 +59,7 @@ impl_lint_pass!(UnusedTraitNames => [UNUSED_TRAIT_NAMES]);
 
 impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames {
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
-        if self.msrv.meets(msrvs::UNDERSCORE_IMPORTS)
-            && !item.span.in_external_macro(cx.sess().source_map())
+        if !item.span.in_external_macro(cx.sess().source_map())
             && let ItemKind::Use(path, UseKind::Single) = item.kind
             // Ignore imports that already use Underscore
             && item.ident.name != kw::Underscore
@@ -74,6 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames {
             && cx.tcx.visibility(item.owner_id.def_id) == Visibility::Restricted(module.to_def_id())
             && let Some(last_segment) = path.segments.last()
             && let Some(snip) = snippet_opt(cx, last_segment.ident.span)
+            && self.msrv.meets(cx, msrvs::UNDERSCORE_IMPORTS)
             && !is_from_proc_macro(cx, &last_segment.ident)
         {
             let complete_span = last_segment.ident.span.to(item.ident.span);
@@ -88,6 +86,4 @@ impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames {
             );
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs
index 569260eda34..743f54ca993 100644
--- a/clippy_lints/src/use_self.rs
+++ b/clippy_lints/src/use_self.rs
@@ -62,7 +62,7 @@ pub struct UseSelf {
 impl UseSelf {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             stack: Vec::new(),
         }
     }
@@ -198,7 +198,6 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
 
     fn check_ty(&mut self, cx: &LateContext<'tcx>, hir_ty: &Ty<'tcx, AmbigArg>) {
         if !hir_ty.span.from_expansion()
-            && self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS)
             && let Some(&StackItem::Check {
                 impl_id,
                 ref types_to_skip,
@@ -216,6 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
             // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in
             // which case we must still trigger the lint.
             && (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty))
+            && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS)
         {
             span_lint(cx, hir_ty.span);
         }
@@ -223,9 +223,9 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
 
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
         if !expr.span.from_expansion()
-            && self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS)
             && let Some(&StackItem::Check { impl_id, .. }) = self.stack.last()
             && cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity()
+            && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS)
         {
         } else {
             return;
@@ -244,19 +244,17 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
 
     fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
         if !pat.span.from_expansion()
-            && self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS)
             && let Some(&StackItem::Check { impl_id, .. }) = self.stack.last()
             // get the path from the pattern
             && let PatKind::Expr(&PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. })
                  | PatKind::TupleStruct(QPath::Resolved(_, path), _, _)
                  | PatKind::Struct(QPath::Resolved(_, path), _, _) = pat.kind
             && cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id).instantiate_identity()
+            && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS)
         {
             check_path(cx, path);
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 #[derive(Default)]
diff --git a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
index 68692246153..558acacb972 100644
--- a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
+++ b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
@@ -30,8 +30,7 @@ impl LateLintPass<'_> for MsrvAttrImpl {
                 .tcx
                 .impl_trait_ref(item.owner_id)
                 .map(EarlyBinder::instantiate_identity)
-            && let is_late_pass = match_def_path(cx, trait_ref.def_id, &paths::LATE_LINT_PASS)
-            && (is_late_pass || match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS))
+            && match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS)
             && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind()
             && self_ty_def.is_struct()
             && self_ty_def.all_fields().any(|f| {
@@ -40,20 +39,18 @@ impl LateLintPass<'_> for MsrvAttrImpl {
                     .instantiate_identity()
                     .walk()
                     .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
-                    .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV))
+                    .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV_STACK))
             })
             && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes")
         {
-            let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
-            let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
             let span = cx.sess().source_map().span_through_char(item.span, '{');
             span_lint_and_sugg(
                 cx,
                 MISSING_MSRV_ATTR_IMPL,
                 span,
-                format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
-                format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
-                format!("{}\n    extract_msrv_attr!({context});", snippet(cx, span, "..")),
+                "`extract_msrv_attr!` macro missing from `EarlyLintPass` implementation",
+                "add `extract_msrv_attr!()` to the `EarlyLintPass` implementation",
+                format!("{}\n    extract_msrv_attr!();", snippet(cx, span, "..")),
                 Applicability::MachineApplicable,
             );
         }
diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs
index 03c667846b6..3346b15dae9 100644
--- a/clippy_lints/src/vec.rs
+++ b/clippy_lints/src/vec.rs
@@ -27,7 +27,7 @@ impl UselessVec {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             too_large_for_stack: conf.too_large_for_stack,
-            msrv: conf.msrv.clone(),
+            msrv: conf.msrv,
             span_to_lint_map: BTreeMap::new(),
             allow_in_test: conf.allow_useless_vec_in_tests,
         }
@@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
             },
             // search for `for _ in vec![...]`
             Node::Expr(Expr { span, .. })
-                if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(msrvs::ARRAY_INTO_ITERATOR) =>
+                if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) =>
             {
                 let suggest_slice = suggest_type(expr);
                 self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice);
@@ -149,8 +149,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
             }
         }
     }
-
-    extract_msrv_attr!(LateContext);
 }
 
 impl UselessVec {
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 64955003bd2..0c1728d1349 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -137,24 +137,13 @@ use rustc_middle::hir::nested_filter;
 
 #[macro_export]
 macro_rules! extract_msrv_attr {
-    (LateContext) => {
-        fn check_attributes(&mut self, cx: &rustc_lint::LateContext<'_>, attrs: &[rustc_hir::Attribute]) {
+    () => {
+        fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
             let sess = rustc_lint::LintContext::sess(cx);
             self.msrv.check_attributes(sess, attrs);
         }
 
-        fn check_attributes_post(&mut self, cx: &rustc_lint::LateContext<'_>, attrs: &[rustc_hir::Attribute]) {
-            let sess = rustc_lint::LintContext::sess(cx);
-            self.msrv.check_attributes_post(sess, attrs);
-        }
-    };
-    (EarlyContext) => {
-        fn check_attributes(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) {
-            let sess = rustc_lint::LintContext::sess(cx);
-            self.msrv.check_attributes(sess, attrs);
-        }
-
-        fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) {
+        fn check_attributes_post(&mut self, cx: &rustc_lint::EarlyContext<'_>, attrs: &[rustc_ast::ast::Attribute]) {
             let sess = rustc_lint::LintContext::sess(cx);
             self.msrv.check_attributes_post(sess, attrs);
         }
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index f9cf29cbdf4..5bb2b12988a 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -1,11 +1,14 @@
+use rustc_ast::Attribute;
 use rustc_ast::attr::AttributeExt;
 
 use rustc_attr_parsing::{RustcVersion, parse_version};
+use rustc_lint::LateContext;
 use rustc_session::Session;
 use rustc_span::{Symbol, sym};
 use serde::Deserialize;
-use smallvec::{SmallVec, smallvec};
-use std::fmt;
+use smallvec::SmallVec;
+use std::iter::once;
+use std::sync::atomic::{AtomicBool, Ordering};
 
 macro_rules! msrv_aliases {
     ($($major:literal,$minor:literal,$patch:literal {
@@ -73,21 +76,15 @@ msrv_aliases! {
     1,15,0 { MAYBE_BOUND_IN_WHERE }
 }
 
-/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
-#[derive(Debug, Clone)]
-pub struct Msrv {
-    stack: SmallVec<[RustcVersion; 2]>,
-}
+/// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic
+/// optimization we can skip traversing the HIR in [`Msrv::meets`] if we never saw an MSRV attribute
+/// during the early lint passes
+static SEEN_MSRV_ATTR: AtomicBool = AtomicBool::new(false);
 
-impl fmt::Display for Msrv {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if let Some(msrv) = self.current() {
-            write!(f, "{msrv}")
-        } else {
-            f.write_str("1.0.0")
-        }
-    }
-}
+/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` in late
+/// lint passes, use [`MsrvStack`] for early passes
+#[derive(Copy, Clone, Debug, Default)]
+pub struct Msrv(Option<RustcVersion>);
 
 impl<'de> Deserialize<'de> for Msrv {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -96,14 +93,36 @@ impl<'de> Deserialize<'de> for Msrv {
     {
         let v = String::deserialize(deserializer)?;
         parse_version(Symbol::intern(&v))
-            .map(|v| Msrv { stack: smallvec![v] })
+            .map(|v| Self(Some(v)))
             .ok_or_else(|| serde::de::Error::custom("not a valid Rust version"))
     }
 }
 
 impl Msrv {
-    pub fn empty() -> Msrv {
-        Msrv { stack: SmallVec::new() }
+    /// Returns the MSRV at the current node
+    ///
+    /// If the crate being linted uses an `#[clippy::msrv]` attribute this will search the parent
+    /// nodes for that attribute, prefer to run this check after cheaper pattern matching operations
+    pub fn current(self, cx: &LateContext<'_>) -> Option<RustcVersion> {
+        if SEEN_MSRV_ATTR.load(Ordering::Relaxed) {
+            let start = cx.last_node_with_lint_attrs;
+            if let Some(msrv_attr) = once(start)
+                .chain(cx.tcx.hir_parent_id_iter(start))
+                .find_map(|id| parse_attrs(cx.tcx.sess, cx.tcx.hir().attrs(id)))
+            {
+                return Some(msrv_attr);
+            }
+        }
+
+        self.0
+    }
+
+    /// Checks if a required version from [this module](self) is met at the current node
+    ///
+    /// If the crate being linted uses an `#[clippy::msrv]` attribute this will search the parent
+    /// nodes for that attribute, prefer to run this check after cheaper pattern matching operations
+    pub fn meets(self, cx: &LateContext<'_>, required: RustcVersion) -> bool {
+        self.current(cx).is_none_or(|msrv| msrv >= required)
     }
 
     pub fn read_cargo(&mut self, sess: &Session) {
@@ -111,8 +130,8 @@ impl Msrv {
             .ok()
             .and_then(|v| parse_version(Symbol::intern(&v)));
 
-        match (self.current(), cargo_msrv) {
-            (None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv],
+        match (self.0, cargo_msrv) {
+            (None, Some(cargo_msrv)) => self.0 = Some(cargo_msrv),
             (Some(clippy_msrv), Some(cargo_msrv)) => {
                 if clippy_msrv != cargo_msrv {
                     sess.dcx().warn(format!(
@@ -123,6 +142,21 @@ impl Msrv {
             _ => {},
         }
     }
+}
+
+/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` in early
+/// lint passes, use [`Msrv`] for late passes
+#[derive(Debug, Clone)]
+pub struct MsrvStack {
+    stack: SmallVec<[RustcVersion; 2]>,
+}
+
+impl MsrvStack {
+    pub fn new(initial: Msrv) -> Self {
+        Self {
+            stack: SmallVec::from_iter(initial.0),
+        }
+    }
 
     pub fn current(&self) -> Option<RustcVersion> {
         self.stack.last().copied()
@@ -132,42 +166,43 @@ impl Msrv {
         self.current().is_none_or(|msrv| msrv >= required)
     }
 
-    fn parse_attr(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
-        let sym_msrv = Symbol::intern("msrv");
-        let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym_msrv]));
+    pub fn check_attributes(&mut self, sess: &Session, attrs: &[Attribute]) {
+        if let Some(version) = parse_attrs(sess, attrs) {
+            SEEN_MSRV_ATTR.store(true, Ordering::Relaxed);
+            self.stack.push(version);
+        }
+    }
 
-        if let Some(msrv_attr) = msrv_attrs.next() {
-            if let Some(duplicate) = msrv_attrs.next_back() {
-                sess.dcx()
-                    .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
-                    .with_span_note(msrv_attr.span(), "first definition found here")
-                    .emit();
-            }
+    pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[Attribute]) {
+        if parse_attrs(sess, attrs).is_some() {
+            self.stack.pop();
+        }
+    }
+}
 
-            if let Some(msrv) = msrv_attr.value_str() {
-                if let Some(version) = parse_version(msrv) {
-                    return Some(version);
-                }
+fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
+    let sym_msrv = Symbol::intern("msrv");
+    let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym_msrv]));
 
-                sess.dcx()
-                    .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
-            } else {
-                sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
-            }
+    if let Some(msrv_attr) = msrv_attrs.next() {
+        if let Some(duplicate) = msrv_attrs.next_back() {
+            sess.dcx()
+                .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
+                .with_span_note(msrv_attr.span(), "first definition found here")
+                .emit();
         }
 
-        None
-    }
+        if let Some(msrv) = msrv_attr.value_str() {
+            if let Some(version) = parse_version(msrv) {
+                return Some(version);
+            }
 
-    pub fn check_attributes(&mut self, sess: &Session, attrs: &[impl AttributeExt]) {
-        if let Some(version) = Self::parse_attr(sess, attrs) {
-            self.stack.push(version);
+            sess.dcx()
+                .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
+        } else {
+            sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
         }
     }
 
-    pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[impl AttributeExt]) {
-        if Self::parse_attr(sess, attrs).is_some() {
-            self.stack.pop();
-        }
-    }
+    None
 }
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 452bb4ce4c7..51d06ad9b1a 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -19,7 +19,6 @@ pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
 pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
 pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
 pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
-pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
 pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
 pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
 pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
@@ -33,7 +32,7 @@ pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
 pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];
 
 // Paths in clippy itself
-pub const MSRV: [&str; 3] = ["clippy_utils", "msrvs", "Msrv"];
+pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"];
 
 // Paths in external crates
 #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs
index c7890f33f27..8e6f4d4a317 100644
--- a/clippy_utils/src/qualify_min_const_fn.rs
+++ b/clippy_utils/src/qualify_min_const_fn.rs
@@ -11,6 +11,7 @@ use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_infer::traits::Obligation;
+use rustc_lint::LateContext;
 use rustc_middle::mir::{
     Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
     Terminator, TerminatorKind,
@@ -25,16 +26,16 @@ use std::borrow::Cow;
 
 type McfResult = Result<(), (Span, Cow<'static, str>)>;
 
-pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) -> McfResult {
+pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Msrv) -> McfResult {
     let def_id = body.source.def_id();
 
     for local in &body.local_decls {
-        check_ty(tcx, local.ty, local.source_info.span, msrv)?;
+        check_ty(cx, local.ty, local.source_info.span, msrv)?;
     }
     // impl trait is gone in MIR, so check the return type manually
     check_ty(
-        tcx,
-        tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(),
+        cx,
+        cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(),
         body.local_decls.iter().next().unwrap().source_info.span,
         msrv,
     )?;
@@ -43,16 +44,16 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
         // Cleanup blocks are ignored entirely by const eval, so we can too:
         // https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382
         if !bb.is_cleanup {
-            check_terminator(tcx, body, bb.terminator(), msrv)?;
+            check_terminator(cx, body, bb.terminator(), msrv)?;
             for stmt in &bb.statements {
-                check_statement(tcx, body, def_id, stmt, msrv)?;
+                check_statement(cx, body, def_id, stmt, msrv)?;
             }
         }
     }
     Ok(())
 }
 
-fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> McfResult {
+fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, msrv: Msrv) -> McfResult {
     for arg in ty.walk() {
         let ty = match arg.unpack() {
             GenericArgKind::Type(ty) => ty,
@@ -63,7 +64,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M
         };
 
         match ty.kind() {
-            ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(msrvs::CONST_MUT_REFS) => {
+            ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(cx, msrvs::CONST_MUT_REFS) => {
                 return Err((span, "mutable references in const fn are unstable".into()));
             },
             ty::Alias(ty::Opaque, ..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
@@ -82,7 +83,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M
                             ));
                         },
                         ty::ExistentialPredicate::Trait(trait_ref) => {
-                            if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
+                            if Some(trait_ref.def_id) != cx.tcx.lang_items().sized_trait() {
                                 return Err((
                                     span,
                                     "trait bounds other than `Sized` \
@@ -101,19 +102,19 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> M
 }
 
 fn check_rvalue<'tcx>(
-    tcx: TyCtxt<'tcx>,
+    cx: &LateContext<'tcx>,
     body: &Body<'tcx>,
     def_id: DefId,
     rvalue: &Rvalue<'tcx>,
     span: Span,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> McfResult {
     match rvalue {
         Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
         Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => {
-            check_place(tcx, *place, span, body, msrv)
+            check_place(cx, *place, span, body, msrv)
         },
-        Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body, msrv),
+        Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv),
         Rvalue::Repeat(operand, _)
         | Rvalue::Use(operand)
         | Rvalue::WrapUnsafeBinder(operand, _)
@@ -128,7 +129,7 @@ fn check_rvalue<'tcx>(
             | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _),
             operand,
             _,
-        ) => check_operand(tcx, operand, span, body, msrv),
+        ) => check_operand(cx, operand, span, body, msrv),
         Rvalue::Cast(
             CastKind::PointerCoercion(
                 PointerCoercion::UnsafeFnPointer
@@ -144,9 +145,11 @@ fn check_rvalue<'tcx>(
                 // We cannot allow this for now.
                 return Err((span, "unsizing casts are only allowed for references right now".into()));
             };
-            let unsized_ty = tcx.struct_tail_for_codegen(pointee_ty, ty::TypingEnv::post_analysis(tcx, def_id));
+            let unsized_ty = cx
+                .tcx
+                .struct_tail_for_codegen(pointee_ty, ty::TypingEnv::post_analysis(cx.tcx, def_id));
             if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
-                check_operand(tcx, op, span, body, msrv)?;
+                check_operand(cx, op, span, body, msrv)?;
                 // Casting/coercing things to slices is fine.
                 Ok(())
             } else {
@@ -167,9 +170,9 @@ fn check_rvalue<'tcx>(
         )),
         // binops are fine on integers
         Rvalue::BinaryOp(_, box (lhs, rhs)) => {
-            check_operand(tcx, lhs, span, body, msrv)?;
-            check_operand(tcx, rhs, span, body, msrv)?;
-            let ty = lhs.ty(body, tcx);
+            check_operand(cx, lhs, span, body, msrv)?;
+            check_operand(cx, rhs, span, body, msrv)?;
+            let ty = lhs.ty(body, cx.tcx);
             if ty.is_integral() || ty.is_bool() || ty.is_char() {
                 Ok(())
             } else {
@@ -185,16 +188,16 @@ fn check_rvalue<'tcx>(
         )
         | Rvalue::ShallowInitBox(_, _) => Ok(()),
         Rvalue::UnaryOp(_, operand) => {
-            let ty = operand.ty(body, tcx);
+            let ty = operand.ty(body, cx.tcx);
             if ty.is_integral() || ty.is_bool() {
-                check_operand(tcx, operand, span, body, msrv)
+                check_operand(cx, operand, span, body, msrv)
             } else {
                 Err((span, "only int and `bool` operations are stable in const fn".into()))
             }
         },
         Rvalue::Aggregate(_, operands) => {
             for operand in operands {
-                check_operand(tcx, operand, span, body, msrv)?;
+                check_operand(cx, operand, span, body, msrv)?;
             }
             Ok(())
         },
@@ -202,33 +205,33 @@ fn check_rvalue<'tcx>(
 }
 
 fn check_statement<'tcx>(
-    tcx: TyCtxt<'tcx>,
+    cx: &LateContext<'tcx>,
     body: &Body<'tcx>,
     def_id: DefId,
     statement: &Statement<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> McfResult {
     let span = statement.source_info.span;
     match &statement.kind {
         StatementKind::Assign(box (place, rval)) => {
-            check_place(tcx, *place, span, body, msrv)?;
-            check_rvalue(tcx, body, def_id, rval, span, msrv)
+            check_place(cx, *place, span, body, msrv)?;
+            check_rvalue(cx, body, def_id, rval, span, msrv)
         },
 
-        StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body, msrv),
+        StatementKind::FakeRead(box (_, place)) => check_place(cx, *place, span, body, msrv),
         // just an assignment
         StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
-            check_place(tcx, **place, span, body, msrv)
+            check_place(cx, **place, span, body, msrv)
         },
 
-        StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body, msrv),
+        StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(cx, op, span, body, msrv),
 
         StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(
             rustc_middle::mir::CopyNonOverlapping { dst, src, count },
         )) => {
-            check_operand(tcx, dst, span, body, msrv)?;
-            check_operand(tcx, src, span, body, msrv)?;
-            check_operand(tcx, count, span, body, msrv)
+            check_operand(cx, dst, span, body, msrv)?;
+            check_operand(cx, src, span, body, msrv)?;
+            check_operand(cx, count, span, body, msrv)
         },
         // These are all NOPs
         StatementKind::StorageLive(_)
@@ -244,16 +247,16 @@ fn check_statement<'tcx>(
 }
 
 fn check_operand<'tcx>(
-    tcx: TyCtxt<'tcx>,
+    cx: &LateContext<'tcx>,
     operand: &Operand<'tcx>,
     span: Span,
     body: &Body<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> McfResult {
     match operand {
         Operand::Move(place) => {
             if !place.projection.as_ref().is_empty()
-                && !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body)
+                && !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body)
             {
                 return Err((
                     span,
@@ -261,29 +264,35 @@ fn check_operand<'tcx>(
                 ));
             }
 
-            check_place(tcx, *place, span, body, msrv)
+            check_place(cx, *place, span, body, msrv)
         },
-        Operand::Copy(place) => check_place(tcx, *place, span, body, msrv),
-        Operand::Constant(c) => match c.check_static_ptr(tcx) {
+        Operand::Copy(place) => check_place(cx, *place, span, body, msrv),
+        Operand::Constant(c) => match c.check_static_ptr(cx.tcx) {
             Some(_) => Err((span, "cannot access `static` items in const fn".into())),
             None => Ok(()),
         },
     }
 }
 
-fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>, msrv: &Msrv) -> McfResult {
+fn check_place<'tcx>(
+    cx: &LateContext<'tcx>,
+    place: Place<'tcx>,
+    span: Span,
+    body: &Body<'tcx>,
+    msrv: Msrv,
+) -> McfResult {
     for (base, elem) in place.as_ref().iter_projections() {
         match elem {
             ProjectionElem::Field(..) => {
-                if base.ty(body, tcx).ty.is_union() && !msrv.meets(msrvs::CONST_FN_UNION) {
+                if base.ty(body, cx.tcx).ty.is_union() && !msrv.meets(cx, msrvs::CONST_FN_UNION) {
                     return Err((span, "accessing union fields is unstable".into()));
                 }
             },
-            ProjectionElem::Deref => match base.ty(body, tcx).ty.kind() {
+            ProjectionElem::Deref => match base.ty(body, cx.tcx).ty.kind() {
                 ty::RawPtr(_, hir::Mutability::Mut) => {
                     return Err((span, "dereferencing raw mut pointer in const fn is unstable".into()));
                 },
-                ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(msrvs::CONST_RAW_PTR_DEREF) => {
+                ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(cx, msrvs::CONST_RAW_PTR_DEREF) => {
                     return Err((span, "dereferencing raw const pointer in const fn is unstable".into()));
                 },
                 _ => (),
@@ -302,10 +311,10 @@ fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &B
 }
 
 fn check_terminator<'tcx>(
-    tcx: TyCtxt<'tcx>,
+    cx: &LateContext<'tcx>,
     body: &Body<'tcx>,
     terminator: &Terminator<'tcx>,
-    msrv: &Msrv,
+    msrv: Msrv,
 ) -> McfResult {
     let span = terminator.source_info.span;
     match &terminator.kind {
@@ -317,7 +326,7 @@ fn check_terminator<'tcx>(
         | TerminatorKind::UnwindTerminate(_)
         | TerminatorKind::Unreachable => Ok(()),
         TerminatorKind::Drop { place, .. } => {
-            if !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) {
+            if !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body) {
                 return Err((
                     span,
                     "cannot drop locals with a non constant destructor in const fn".into(),
@@ -325,7 +334,7 @@ fn check_terminator<'tcx>(
             }
             Ok(())
         },
-        TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body, msrv),
+        TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(cx, discr, span, body, msrv),
         TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } => {
             Err((span, "const fn coroutines are unstable".into()))
         },
@@ -339,9 +348,9 @@ fn check_terminator<'tcx>(
             fn_span: _,
         }
         | TerminatorKind::TailCall { func, args, fn_span: _ } => {
-            let fn_ty = func.ty(body, tcx);
+            let fn_ty = func.ty(body, cx.tcx);
             if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
-                if !is_stable_const_fn(tcx, fn_def_id, msrv) {
+                if !is_stable_const_fn(cx, fn_def_id, msrv) {
                     return Err((
                         span,
                         format!(
@@ -356,17 +365,17 @@ fn check_terminator<'tcx>(
                 // within const fns. `transmute` is allowed in all other const contexts.
                 // This won't really scale to more intrinsics or functions. Let's allow const
                 // transmutes in const fn before we add more hacks to this.
-                if tcx.is_intrinsic(fn_def_id, sym::transmute) {
+                if cx.tcx.is_intrinsic(fn_def_id, sym::transmute) {
                     return Err((
                         span,
                         "can only call `transmute` from const items, not `const fn`".into(),
                     ));
                 }
 
-                check_operand(tcx, func, span, body, msrv)?;
+                check_operand(cx, func, span, body, msrv)?;
 
                 for arg in args {
-                    check_operand(tcx, &arg.node, span, body, msrv)?;
+                    check_operand(cx, &arg.node, span, body, msrv)?;
                 }
                 Ok(())
             } else {
@@ -379,14 +388,14 @@ fn check_terminator<'tcx>(
             msg: _,
             target: _,
             unwind: _,
-        } => check_operand(tcx, cond, span, body, msrv),
+        } => check_operand(cx, cond, span, body, msrv),
         TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
     }
 }
 
-fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
-    tcx.is_const_fn(def_id)
-        && tcx.lookup_const_stability(def_id).is_none_or(|const_stab| {
+fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bool {
+    cx.tcx.is_const_fn(def_id)
+        && cx.tcx.lookup_const_stability(def_id).is_none_or(|const_stab| {
             if let rustc_attr_parsing::StabilityLevel::Stable { since, .. } = const_stab.level {
                 // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
                 // function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`.
@@ -398,10 +407,10 @@ fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
                     StableSince::Err => return false,
                 };
 
-                msrv.meets(const_stab_rust_version)
+                msrv.meets(cx, const_stab_rust_version)
             } else {
                 // Unstable const fn, check if the feature is enabled.
-                tcx.features().enabled(const_stab.feature) && msrv.current().is_none()
+                cx.tcx.features().enabled(const_stab.feature) && msrv.current(cx).is_none()
             }
         })
 }
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.fixed b/tests/ui-internal/invalid_msrv_attr_impl.fixed
index 928596d0809..7011ef518f2 100644
--- a/tests/ui-internal/invalid_msrv_attr_impl.fixed
+++ b/tests/ui-internal/invalid_msrv_attr_impl.fixed
@@ -9,7 +9,7 @@ extern crate rustc_middle;
 #[macro_use]
 extern crate rustc_session;
 use clippy_utils::extract_msrv_attr;
-use clippy_utils::msrvs::Msrv;
+use clippy_utils::msrvs::MsrvStack;
 use rustc_hir::Expr;
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 
@@ -20,18 +20,13 @@ declare_lint! {
 }
 
 struct Pass {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl_lint_pass!(Pass => [TEST_LINT]);
 
-impl LateLintPass<'_> for Pass {
-    extract_msrv_attr!(LateContext);
-    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
-}
-
 impl EarlyLintPass for Pass {
-    extract_msrv_attr!(EarlyContext);
+    extract_msrv_attr!();
     fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
 }
 
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.rs b/tests/ui-internal/invalid_msrv_attr_impl.rs
index 50b28648ccc..323061decd2 100644
--- a/tests/ui-internal/invalid_msrv_attr_impl.rs
+++ b/tests/ui-internal/invalid_msrv_attr_impl.rs
@@ -9,7 +9,7 @@ extern crate rustc_middle;
 #[macro_use]
 extern crate rustc_session;
 use clippy_utils::extract_msrv_attr;
-use clippy_utils::msrvs::Msrv;
+use clippy_utils::msrvs::MsrvStack;
 use rustc_hir::Expr;
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 
@@ -20,15 +20,11 @@ declare_lint! {
 }
 
 struct Pass {
-    msrv: Msrv,
+    msrv: MsrvStack,
 }
 
 impl_lint_pass!(Pass => [TEST_LINT]);
 
-impl LateLintPass<'_> for Pass {
-    fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
-}
-
 impl EarlyLintPass for Pass {
     fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
 }
diff --git a/tests/ui-internal/invalid_msrv_attr_impl.stderr b/tests/ui-internal/invalid_msrv_attr_impl.stderr
index 8b69af122e4..0a7636313ef 100644
--- a/tests/ui-internal/invalid_msrv_attr_impl.stderr
+++ b/tests/ui-internal/invalid_msrv_attr_impl.stderr
@@ -1,8 +1,8 @@
-error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
+error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
   --> tests/ui-internal/invalid_msrv_attr_impl.rs:28:1
    |
-LL | impl LateLintPass<'_> for Pass {
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | impl EarlyLintPass for Pass {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
 note: the lint level is defined here
   --> tests/ui-internal/invalid_msrv_attr_impl.rs:1:9
@@ -10,23 +10,11 @@ note: the lint level is defined here
 LL | #![deny(clippy::internal)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[deny(clippy::missing_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]`
-help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation
-   |
-LL ~ impl LateLintPass<'_> for Pass {
-LL +     extract_msrv_attr!(LateContext);
-   |
-
-error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
-  --> tests/ui-internal/invalid_msrv_attr_impl.rs:32:1
-   |
-LL | impl EarlyLintPass for Pass {
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation
+help: add `extract_msrv_attr!()` to the `EarlyLintPass` implementation
    |
 LL ~ impl EarlyLintPass for Pass {
-LL +     extract_msrv_attr!(EarlyContext);
+LL +     extract_msrv_attr!();
    |
 
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
diff --git a/tests/ui/msrv_attributes_without_early_lints.rs b/tests/ui/msrv_attributes_without_early_lints.rs
new file mode 100644
index 00000000000..dcef1a485fc
--- /dev/null
+++ b/tests/ui/msrv_attributes_without_early_lints.rs
@@ -0,0 +1,14 @@
+//@check-pass
+
+#![allow(clippy::all, clippy::pedantic, clippy::restriction, clippy::nursery)]
+#![forbid(clippy::ptr_as_ptr)]
+
+/// MSRV checking in late passes skips checking the parent nodes if no early pass sees a
+/// `#[clippy::msrv]` attribute
+///
+/// Here we ensure that even if all early passes are allowed (above) the attribute is still detected
+/// in late lints such as `clippy::ptr_as_ptr`
+#[clippy::msrv = "1.37"]
+fn f(p: *const i32) {
+    let _ = p as *const i64;
+}