|  | 
|  | 1 | +// use clippy_config::msrvs::Msrv; | 
|  | 2 | +use clippy_config::Conf; | 
|  | 3 | +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; | 
|  | 4 | +use clippy_utils::msrvs::Msrv; | 
|  | 5 | +use clippy_utils::visitors::for_each_expr; | 
|  | 6 | +use clippy_utils::{def_path_def_ids, fn_def_id, match_def_path, path_def_id}; | 
|  | 7 | +use rustc_data_structures::fx::FxIndexMap; | 
|  | 8 | +use rustc_errors::Applicability; | 
|  | 9 | +use rustc_hir::def::{DefKind, Res}; | 
|  | 10 | +use rustc_hir::def_id::DefId; | 
|  | 11 | +use rustc_hir::{self as hir, BodyId, Expr, ExprKind, Item, ItemKind}; | 
|  | 12 | +use rustc_lint::{LateContext, LateLintPass, LintContext}; | 
|  | 13 | +use rustc_middle::lint::in_external_macro; | 
|  | 14 | +use rustc_session::impl_lint_pass; | 
|  | 15 | +use rustc_span::Span; | 
|  | 16 | + | 
|  | 17 | +declare_clippy_lint! { | 
|  | 18 | +    /// ### What it does | 
|  | 19 | +    /// Lints when `once_cell::sync::Lazy` or `lazy_static!` are used to define a static variable, | 
|  | 20 | +    /// and suggests replacing such cases with `std::sync::LazyLock` instead. | 
|  | 21 | +    /// | 
|  | 22 | +    /// Note: This lint will not trigger in crate with `no_std` context, or with MSRV < 1.80.0. | 
|  | 23 | +    /// | 
|  | 24 | +    /// ### Why restrict this? | 
|  | 25 | +    /// - Reduces the need for an extra dependency | 
|  | 26 | +    /// - Enforce convention of using standard library types when possible | 
|  | 27 | +    /// | 
|  | 28 | +    /// ### Example | 
|  | 29 | +    /// ```ignore | 
|  | 30 | +    /// lazy_static! { | 
|  | 31 | +    ///     static ref FOO: String = "foo".to_uppercase(); | 
|  | 32 | +    /// } | 
|  | 33 | +    /// static BAR: once_cell::sync::Lazy<String> = once_cell::sync::Lazy::new(|| "BAR".to_lowercase()); | 
|  | 34 | +    /// ``` | 
|  | 35 | +    /// Use instead: | 
|  | 36 | +    /// ```ignore | 
|  | 37 | +    /// static FOO: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "FOO".to_lowercase()); | 
|  | 38 | +    /// static BAR: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "BAR".to_lowercase()); | 
|  | 39 | +    /// ``` | 
|  | 40 | +    #[clippy::version = "1.81.0"] | 
|  | 41 | +    pub NON_STD_LAZY_STATICS, | 
|  | 42 | +    restriction, | 
|  | 43 | +    "lazy static that could be replaced by `std::sync::LazyLock`" | 
|  | 44 | +} | 
|  | 45 | + | 
|  | 46 | +/// A list containing functions with coresponding replacements in `LazyLock`. | 
|  | 47 | +/// | 
|  | 48 | +/// Some functions could be replaced as well if we have replaced `Lazy` to `LazyLock`, | 
|  | 49 | +/// therefore after suggesting replace the type, we need to make sure the function calls can be | 
|  | 50 | +/// replaced, otherwise the suggestions cannot be applied thus the applicability should be | 
|  | 51 | +/// `Unspecified` or `MaybeIncorret`. | 
|  | 52 | +static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[ | 
|  | 53 | +    ("once_cell::sync::Lazy::force", Some("std::sync::LazyLock::force")), | 
|  | 54 | +    ("once_cell::sync::Lazy::get", None), | 
|  | 55 | +    ("once_cell::sync::Lazy::new", Some("std::sync::LazyLock::new")), | 
|  | 56 | +    // Note: `Lazy::{into_value, get_mut, force_mut}` are not in the list. | 
|  | 57 | +    // Because the lint only checks for `static`s, and using these functions with statics | 
|  | 58 | +    // will either be a hard error or triggers `static_mut_ref` that will be hard errors. | 
|  | 59 | +    // But keep in mind that if somehow we decide to expand this lint to catch non-statics, | 
|  | 60 | +    // add those functions into the list. | 
|  | 61 | +]; | 
|  | 62 | + | 
|  | 63 | +pub struct NonStdLazyStatic { | 
|  | 64 | +    msrv: Msrv, | 
|  | 65 | +    sugg_map: FxIndexMap<DefId, Option<String>>, | 
|  | 66 | +    lazy_type_defs: FxIndexMap<DefId, LazyInfo>, | 
|  | 67 | +} | 
|  | 68 | + | 
|  | 69 | +impl NonStdLazyStatic { | 
|  | 70 | +    #[must_use] | 
|  | 71 | +    pub fn new(conf: &'static Conf) -> Self { | 
|  | 72 | +        Self { | 
|  | 73 | +            msrv: conf.msrv.clone(), | 
|  | 74 | +            sugg_map: FxIndexMap::default(), | 
|  | 75 | +            lazy_type_defs: FxIndexMap::default(), | 
|  | 76 | +        } | 
|  | 77 | +    } | 
|  | 78 | +} | 
|  | 79 | + | 
|  | 80 | +impl_lint_pass!(NonStdLazyStatic => [NON_STD_LAZY_STATICS]); | 
|  | 81 | + | 
|  | 82 | +/// Return if current MSRV does not meet the requirement for `lazy_cell` feature, | 
|  | 83 | +/// or current context has `no_std` attribute. | 
|  | 84 | +macro_rules! ensure_prerequisite { | 
|  | 85 | +    ($msrv:expr, $cx:ident) => { | 
|  | 86 | +        if !$msrv.meets(clippy_utils::msrvs::LAZY_CELL) || clippy_utils::is_no_std_crate($cx) { | 
|  | 87 | +            return; | 
|  | 88 | +        } | 
|  | 89 | +    }; | 
|  | 90 | +} | 
|  | 91 | + | 
|  | 92 | +impl<'hir> LateLintPass<'hir> for NonStdLazyStatic { | 
|  | 93 | +    extract_msrv_attr!(LateContext); | 
|  | 94 | + | 
|  | 95 | +    fn check_crate(&mut self, cx: &LateContext<'hir>) { | 
|  | 96 | +        // Do not lint if current crate does not support `LazyLock`. | 
|  | 97 | +        ensure_prerequisite!(self.msrv, cx); | 
|  | 98 | + | 
|  | 99 | +        // Convert hardcoded fn replacement list into a map with def_id | 
|  | 100 | +        for (path, sugg) in FUNCTION_REPLACEMENTS { | 
|  | 101 | +            let path_vec: Vec<&str> = path.split("::").collect(); | 
|  | 102 | +            for did in def_path_def_ids(cx.tcx, &path_vec) { | 
|  | 103 | +                self.sugg_map.insert(did, sugg.map(ToOwned::to_owned)); | 
|  | 104 | +            } | 
|  | 105 | +        } | 
|  | 106 | +    } | 
|  | 107 | + | 
|  | 108 | +    fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) { | 
|  | 109 | +        ensure_prerequisite!(self.msrv, cx); | 
|  | 110 | + | 
|  | 111 | +        if let ItemKind::Static(..) = item.kind | 
|  | 112 | +            && let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span) | 
|  | 113 | +            && match_def_path(cx, macro_call.def_id, &["lazy_static", "lazy_static"]) | 
|  | 114 | +        { | 
|  | 115 | +            span_lint( | 
|  | 116 | +                cx, | 
|  | 117 | +                NON_STD_LAZY_STATICS, | 
|  | 118 | +                macro_call.span, | 
|  | 119 | +                "this macro has been superceded by `std::sync::LazyLock`", | 
|  | 120 | +            ); | 
|  | 121 | +            return; | 
|  | 122 | +        } | 
|  | 123 | + | 
|  | 124 | +        if in_external_macro(cx.sess(), item.span) { | 
|  | 125 | +            return; | 
|  | 126 | +        } | 
|  | 127 | + | 
|  | 128 | +        if let Some(lazy_info) = LazyInfo::from_item(cx, item) { | 
|  | 129 | +            self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info); | 
|  | 130 | +        } | 
|  | 131 | +    } | 
|  | 132 | + | 
|  | 133 | +    fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &Expr<'hir>) { | 
|  | 134 | +        ensure_prerequisite!(self.msrv, cx); | 
|  | 135 | + | 
|  | 136 | +        // All functions in the `FUNCTION_REPLACEMENTS` have only one args | 
|  | 137 | +        if let ExprKind::Call(callee, [arg]) = expr.kind | 
|  | 138 | +            && let Some(call_def_id) = fn_def_id(cx, expr) | 
|  | 139 | +            && self.sugg_map.contains_key(&call_def_id) | 
|  | 140 | +            && let ExprKind::Path(qpath) = arg.peel_borrows().kind | 
|  | 141 | +            && let Some(arg_def_id) = cx.typeck_results().qpath_res(&qpath, arg.hir_id).opt_def_id() | 
|  | 142 | +            && let Some(lazy_info) = self.lazy_type_defs.get_mut(&arg_def_id) | 
|  | 143 | +        { | 
|  | 144 | +            lazy_info.calls_span_and_id.insert(callee.span, call_def_id); | 
|  | 145 | +        } | 
|  | 146 | +    } | 
|  | 147 | + | 
|  | 148 | +    fn check_crate_post(&mut self, cx: &LateContext<'hir>) { | 
|  | 149 | +        ensure_prerequisite!(self.msrv, cx); | 
|  | 150 | + | 
|  | 151 | +        for (_, lazy_info) in &self.lazy_type_defs { | 
|  | 152 | +            lazy_info.lint(cx, &self.sugg_map); | 
|  | 153 | +        } | 
|  | 154 | + | 
|  | 155 | +        self.sugg_map = FxIndexMap::default(); | 
|  | 156 | +    } | 
|  | 157 | +} | 
|  | 158 | + | 
|  | 159 | +struct LazyInfo { | 
|  | 160 | +    /// Span of the [`hir::Ty`] without including args. | 
|  | 161 | +    /// i.e.: | 
|  | 162 | +    /// ```ignore | 
|  | 163 | +    /// static FOO: Lazy<String> = Lazy::new(...); | 
|  | 164 | +    /// //          ^^^^ | 
|  | 165 | +    /// ``` | 
|  | 166 | +    ty_span_no_args: Span, | 
|  | 167 | +    /// `Span` and `DefId` of calls on `Lazy` type. | 
|  | 168 | +    /// i.e.: | 
|  | 169 | +    /// ```ignore | 
|  | 170 | +    /// static FOO: Lazy<String> = Lazy::new(...); | 
|  | 171 | +    /// //                         ^^^^^^^^^ | 
|  | 172 | +    /// ``` | 
|  | 173 | +    calls_span_and_id: FxIndexMap<Span, DefId>, | 
|  | 174 | +} | 
|  | 175 | + | 
|  | 176 | +impl LazyInfo { | 
|  | 177 | +    fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> { | 
|  | 178 | +        // Check if item is a `once_cell:sync::Lazy` static. | 
|  | 179 | +        if let ItemKind::Static(ty, _, body_id) = item.kind | 
|  | 180 | +            && let Some(path_def_id) = path_def_id(cx, ty) | 
|  | 181 | +            && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind | 
|  | 182 | +            && match_def_path(cx, path_def_id, &["once_cell", "sync", "Lazy"]) | 
|  | 183 | +        { | 
|  | 184 | +            let ty_span_no_args = path_span_without_args(path); | 
|  | 185 | +            let body = cx.tcx.hir().body(body_id); | 
|  | 186 | + | 
|  | 187 | +            // visit body to collect `Lazy::new` calls | 
|  | 188 | +            let mut new_fn_calls = FxIndexMap::default(); | 
|  | 189 | +            for_each_expr::<(), ()>(cx, body, |ex| { | 
|  | 190 | +                if let Some((fn_did, call_span)) = fn_def_id_and_span_from_body(cx, ex, body_id) | 
|  | 191 | +                    && match_def_path(cx, fn_did, &["once_cell", "sync", "Lazy", "new"]) | 
|  | 192 | +                { | 
|  | 193 | +                    new_fn_calls.insert(call_span, fn_did); | 
|  | 194 | +                } | 
|  | 195 | +                std::ops::ControlFlow::Continue(()) | 
|  | 196 | +            }); | 
|  | 197 | + | 
|  | 198 | +            Some(LazyInfo { | 
|  | 199 | +                ty_span_no_args, | 
|  | 200 | +                calls_span_and_id: new_fn_calls, | 
|  | 201 | +            }) | 
|  | 202 | +        } else { | 
|  | 203 | +            None | 
|  | 204 | +        } | 
|  | 205 | +    } | 
|  | 206 | + | 
|  | 207 | +    fn lint(&self, cx: &LateContext<'_>, sugg_map: &FxIndexMap<DefId, Option<String>>) { | 
|  | 208 | +        // Applicability might get adjusted to `Unspecified` later if any calls | 
|  | 209 | +        // in `calls_span_and_id` are not replaceable judging by the `sugg_map`. | 
|  | 210 | +        let mut appl = Applicability::MachineApplicable; | 
|  | 211 | +        let mut suggs = vec![(self.ty_span_no_args, "std::sync::LazyLock".to_string())]; | 
|  | 212 | + | 
|  | 213 | +        for (span, def_id) in &self.calls_span_and_id { | 
|  | 214 | +            let maybe_sugg = sugg_map.get(def_id).cloned().flatten(); | 
|  | 215 | +            if let Some(sugg) = maybe_sugg { | 
|  | 216 | +                suggs.push((*span, sugg)); | 
|  | 217 | +            } else { | 
|  | 218 | +                // If NO suggested replacement, not machine applicable | 
|  | 219 | +                appl = Applicability::Unspecified; | 
|  | 220 | +            } | 
|  | 221 | +        } | 
|  | 222 | + | 
|  | 223 | +        span_lint_and_then( | 
|  | 224 | +            cx, | 
|  | 225 | +            NON_STD_LAZY_STATICS, | 
|  | 226 | +            self.ty_span_no_args, | 
|  | 227 | +            "this type has been superceded by `LazyLock` in the standard library", | 
|  | 228 | +            |diag| { | 
|  | 229 | +                diag.multipart_suggestion("use `std::sync::LazyLock` instead", suggs, appl); | 
|  | 230 | +            }, | 
|  | 231 | +        ); | 
|  | 232 | +    } | 
|  | 233 | +} | 
|  | 234 | + | 
|  | 235 | +/// Return the span of a given `Path` without including any of its args. | 
|  | 236 | +/// | 
|  | 237 | +/// NB: Re-write of a private function `rustc_lint::non_local_def::path_span_without_args`. | 
|  | 238 | +fn path_span_without_args(path: &hir::Path<'_>) -> Span { | 
|  | 239 | +    path.segments | 
|  | 240 | +        .last() | 
|  | 241 | +        .and_then(|seg| seg.args) | 
|  | 242 | +        .map_or(path.span, |args| path.span.until(args.span_ext)) | 
|  | 243 | +} | 
|  | 244 | + | 
|  | 245 | +/// Returns the `DefId` and `Span` of the callee if the given expression is a function call. | 
|  | 246 | +/// | 
|  | 247 | +/// NB: Modified from [`clippy_utils::fn_def_id`], to support calling in an static `Item`'s body. | 
|  | 248 | +fn fn_def_id_and_span_from_body(cx: &LateContext<'_>, expr: &Expr<'_>, body_id: BodyId) -> Option<(DefId, Span)> { | 
|  | 249 | +    // FIXME: find a way to cache the result. | 
|  | 250 | +    let typeck = cx.tcx.typeck_body(body_id); | 
|  | 251 | +    match &expr.kind { | 
|  | 252 | +        ExprKind::Call( | 
|  | 253 | +            Expr { | 
|  | 254 | +                kind: ExprKind::Path(qpath), | 
|  | 255 | +                hir_id: path_hir_id, | 
|  | 256 | +                span, | 
|  | 257 | +                .. | 
|  | 258 | +            }, | 
|  | 259 | +            .., | 
|  | 260 | +        ) => { | 
|  | 261 | +            // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or | 
|  | 262 | +            // deref to fn pointers, dyn Fn, impl Fn - #8850 | 
|  | 263 | +            if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) = | 
|  | 264 | +                typeck.qpath_res(qpath, *path_hir_id) | 
|  | 265 | +            { | 
|  | 266 | +                Some((id, *span)) | 
|  | 267 | +            } else { | 
|  | 268 | +                None | 
|  | 269 | +            } | 
|  | 270 | +        }, | 
|  | 271 | +        _ => None, | 
|  | 272 | +    } | 
|  | 273 | +} | 
0 commit comments