summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/manual_instant_elapsed.rs
blob: 331cda1db8990c8927794f5828420b71e9fa55fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;

declare_clippy_lint! {
    /// ### What it does
    /// Lints subtraction between `Instant::now()` and another `Instant`.
    ///
    /// ### Why is this bad?
    /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
    /// as `Instant` subtraction saturates.
    ///
    /// `prev_instant.elapsed()` also more clearly signals intention.
    ///
    /// ### Example
    /// ```rust
    /// use std::time::Instant;
    /// let prev_instant = Instant::now();
    /// let duration = Instant::now() - prev_instant;
    /// ```
    /// Use instead:
    /// ```rust
    /// use std::time::Instant;
    /// let prev_instant = Instant::now();
    /// let duration = prev_instant.elapsed();
    /// ```
    #[clippy::version = "1.64.0"]
    pub MANUAL_INSTANT_ELAPSED,
    pedantic,
    "subtraction between `Instant::now()` and previous `Instant`"
}

declare_lint_pass!(ManualInstantElapsed => [MANUAL_INSTANT_ELAPSED]);

impl LateLintPass<'_> for ManualInstantElapsed {
    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
        if let ExprKind::Binary(Spanned {node: BinOpKind::Sub, ..}, lhs, rhs) = expr.kind
            && check_instant_now_call(cx, lhs)
            && let ty_resolved = cx.typeck_results().expr_ty(rhs)
            && let rustc_middle::ty::Adt(def, _) = ty_resolved.kind()
            && clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT)
            && let Some(sugg) = clippy_utils::sugg::Sugg::hir_opt(cx, rhs)
        {
            span_lint_and_sugg(
                cx,
                MANUAL_INSTANT_ELAPSED,
                expr.span,
                "manual implementation of `Instant::elapsed`",
                "try",
                format!("{}.elapsed()", sugg.maybe_par()),
                Applicability::MachineApplicable,
            );
        }
    }
}

fn check_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
    if let ExprKind::Call(fn_expr, []) = expr_block.kind
        && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
        && clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
    {
        true
    } else {
        false
    }
}