|
| 1 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use rustc_errors::Applicability; |
| 3 | +use rustc_hir::{BinOpKind, Expr, ExprKind}; |
| 4 | +use rustc_lint::{LateContext, LateLintPass}; |
| 5 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| 6 | +use rustc_span::source_map::Spanned; |
| 7 | + |
| 8 | +declare_clippy_lint! { |
| 9 | + /// ### What it does |
| 10 | + /// Lints subtraction between `Instant::now()` and another `Instant`. |
| 11 | + /// |
| 12 | + /// ### Why is this bad? |
| 13 | + /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns |
| 14 | + /// as `Instant` subtraction saturates. |
| 15 | + /// |
| 16 | + /// `prev_instant.elapsed()` also more clearly signals intention. |
| 17 | + /// |
| 18 | + /// ### Example |
| 19 | + /// ```rust |
| 20 | + /// use std::time::Instant; |
| 21 | + /// let prev_instant = Instant::now(); |
| 22 | + /// let duration = Instant::now() - prev_instant; |
| 23 | + /// ``` |
| 24 | + /// Use instead: |
| 25 | + /// ```rust |
| 26 | + /// use std::time::Instant; |
| 27 | + /// let prev_instant = Instant::now(); |
| 28 | + /// let duration = prev_instant.elapsed(); |
| 29 | + /// ``` |
| 30 | + #[clippy::version = "1.64.0"] |
| 31 | + pub MANUAL_INSTANT_ELAPSED, |
| 32 | + pedantic, |
| 33 | + "subtraction between `Instant::now()` and previous `Instant`" |
| 34 | +} |
| 35 | + |
| 36 | +declare_lint_pass!(ManualInstantElapsed => [MANUAL_INSTANT_ELAPSED]); |
| 37 | + |
| 38 | +impl LateLintPass<'_> for ManualInstantElapsed { |
| 39 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { |
| 40 | + if let ExprKind::Binary(Spanned {node: BinOpKind::Sub, ..}, lhs, rhs) = expr.kind |
| 41 | + && check_instant_now_call(cx, lhs) |
| 42 | + && let ty_resolved = cx.typeck_results().expr_ty(rhs) |
| 43 | + && let rustc_middle::ty::Adt(def, _) = ty_resolved.kind() |
| 44 | + && clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT) |
| 45 | + && let Some(sugg) = clippy_utils::sugg::Sugg::hir_opt(cx, rhs) |
| 46 | + { |
| 47 | + span_lint_and_sugg( |
| 48 | + cx, |
| 49 | + MANUAL_INSTANT_ELAPSED, |
| 50 | + expr.span, |
| 51 | + "manual implementation of `Instant::elapsed`", |
| 52 | + "try", |
| 53 | + format!("{}.elapsed()", sugg.maybe_par()), |
| 54 | + Applicability::MachineApplicable, |
| 55 | + ); |
| 56 | + } |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +fn check_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { |
| 61 | + if let ExprKind::Call(fn_expr, []) = expr_block.kind |
| 62 | + && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr) |
| 63 | + && clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW) |
| 64 | + { |
| 65 | + true |
| 66 | + } else { |
| 67 | + false |
| 68 | + } |
| 69 | +} |
0 commit comments