]>
Commit | Line | Data |
---|---|---|
2b03887a FG |
1 | use clippy_utils::{ |
2 | diagnostics::span_lint_and_sugg, get_parent_node, is_default_equivalent, macros::macro_backtrace, match_path, | |
3 | path_def_id, paths, ty::expr_sig, | |
4 | }; | |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir::{ | |
7 | intravisit::{walk_ty, Visitor}, | |
8 | Block, Expr, ExprKind, Local, Node, QPath, TyKind, | |
9 | }; | |
10 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
9c376795 | 11 | use rustc_middle::{lint::in_external_macro, ty::print::with_forced_trimmed_paths}; |
2b03887a FG |
12 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
13 | use rustc_span::sym; | |
14 | ||
15 | declare_clippy_lint! { | |
16 | /// ### What it does | |
17 | /// checks for `Box::new(T::default())`, which is better written as | |
18 | /// `Box::<T>::default()`. | |
19 | /// | |
20 | /// ### Why is this bad? | |
21 | /// First, it's more complex, involving two calls instead of one. | |
22 | /// Second, `Box::default()` can be faster | |
23 | /// [in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box). | |
24 | /// | |
25 | /// ### Example | |
26 | /// ```rust | |
27 | /// let x: Box<String> = Box::new(Default::default()); | |
28 | /// ``` | |
29 | /// Use instead: | |
30 | /// ```rust | |
31 | /// let x: Box<String> = Box::default(); | |
32 | /// ``` | |
9c376795 | 33 | #[clippy::version = "1.66.0"] |
2b03887a FG |
34 | pub BOX_DEFAULT, |
35 | perf, | |
36 | "Using Box::new(T::default()) instead of Box::default()" | |
37 | } | |
38 | ||
39 | declare_lint_pass!(BoxDefault => [BOX_DEFAULT]); | |
40 | ||
41 | impl LateLintPass<'_> for BoxDefault { | |
42 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { | |
43 | if let ExprKind::Call(box_new, [arg]) = expr.kind | |
44 | && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind | |
45 | && let ExprKind::Call(arg_path, ..) = arg.kind | |
46 | && !in_external_macro(cx.sess(), expr.span) | |
47 | && (expr.span.eq_ctxt(arg.span) || is_vec_expn(cx, arg)) | |
48 | && seg.ident.name == sym::new | |
49 | && path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box()) | |
50 | && is_default_equivalent(cx, arg) | |
51 | { | |
52 | let arg_ty = cx.typeck_results().expr_ty(arg); | |
53 | span_lint_and_sugg( | |
54 | cx, | |
55 | BOX_DEFAULT, | |
56 | expr.span, | |
57 | "`Box::new(_)` of default value", | |
58 | "try", | |
59 | if is_plain_default(arg_path) || given_type(cx, expr) { | |
60 | "Box::default()".into() | |
61 | } else { | |
9c376795 | 62 | with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()")) |
2b03887a FG |
63 | }, |
64 | Applicability::MachineApplicable | |
65 | ); | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
70 | fn is_plain_default(arg_path: &Expr<'_>) -> bool { | |
71 | // we need to match the actual path so we don't match e.g. "u8::default" | |
72 | if let ExprKind::Path(QPath::Resolved(None, path)) = &arg_path.kind { | |
73 | // avoid generic parameters | |
74 | match_path(path, &paths::DEFAULT_TRAIT_METHOD) && path.segments.iter().all(|seg| seg.args.is_none()) | |
75 | } else { | |
76 | false | |
77 | } | |
78 | } | |
79 | ||
80 | fn is_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
81 | macro_backtrace(expr.span) | |
82 | .next() | |
83 | .map_or(false, |call| cx.tcx.is_diagnostic_item(sym::vec_macro, call.def_id)) | |
84 | } | |
85 | ||
86 | #[derive(Default)] | |
87 | struct InferVisitor(bool); | |
88 | ||
89 | impl<'tcx> Visitor<'tcx> for InferVisitor { | |
90 | fn visit_ty(&mut self, t: &rustc_hir::Ty<'_>) { | |
91 | self.0 |= matches!(t.kind, TyKind::Infer | TyKind::OpaqueDef(..) | TyKind::TraitObject(..)); | |
92 | if !self.0 { | |
93 | walk_ty(self, t); | |
94 | } | |
95 | } | |
96 | } | |
97 | ||
98 | fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
99 | match get_parent_node(cx.tcx, expr.hir_id) { | |
100 | Some(Node::Local(Local { ty: Some(ty), .. })) => { | |
101 | let mut v = InferVisitor::default(); | |
102 | v.visit_ty(ty); | |
103 | !v.0 | |
104 | }, | |
105 | Some( | |
106 | Node::Expr(Expr { | |
107 | kind: ExprKind::Call(path, args), | |
108 | .. | |
109 | }) | Node::Block(Block { | |
110 | expr: | |
111 | Some(Expr { | |
112 | kind: ExprKind::Call(path, args), | |
113 | .. | |
114 | }), | |
115 | .. | |
116 | }), | |
117 | ) => { | |
118 | if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) && | |
119 | let Some(sig) = expr_sig(cx, path) && | |
120 | let Some(input) = sig.input(index) | |
121 | { | |
122 | input.no_bound_vars().is_some() | |
123 | } else { | |
124 | false | |
125 | } | |
126 | }, | |
127 | _ => false, | |
128 | } | |
129 | } |