1
- use clippy_utils:: { diagnostics:: span_lint_and_help, is_default_equivalent, path_def_id} ;
2
- use rustc_hir:: { Expr , ExprKind , QPath } ;
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
+ } ;
3
10
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
4
11
use rustc_middle:: lint:: in_external_macro;
5
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -15,12 +22,6 @@ declare_clippy_lint! {
15
22
/// Second, `Box::default()` can be faster
16
23
/// [in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box).
17
24
///
18
- /// ### Known problems
19
- /// The lint may miss some cases (e.g. Box::new(String::from(""))).
20
- /// On the other hand, it will trigger on cases where the `default`
21
- /// code comes from a macro that does something different based on
22
- /// e.g. target operating system.
23
- ///
24
25
/// ### Example
25
26
/// ```rust
26
27
/// let x: Box<String> = Box::new(Default::default());
@@ -41,21 +42,88 @@ impl LateLintPass<'_> for BoxDefault {
41
42
fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
42
43
if let ExprKind :: Call ( box_new, [ arg] ) = expr. kind
43
44
&& let ExprKind :: Path ( QPath :: TypeRelative ( ty, seg) ) = box_new. kind
44
- && let ExprKind :: Call ( ..) = arg. kind
45
+ && let ExprKind :: Call ( arg_path , ..) = arg. kind
45
46
&& !in_external_macro ( cx. sess ( ) , expr. span )
46
- && expr. span . eq_ctxt ( arg. span )
47
+ && ( expr. span . eq_ctxt ( arg. span ) || is_vec_expn ( cx , arg ) )
47
48
&& seg. ident . name == sym:: new
48
- && path_def_id ( cx, ty) == cx. tcx . lang_items ( ) . owned_box ( )
49
+ && path_def_id ( cx, ty) . map_or ( false , |id| Some ( id ) == cx. tcx . lang_items ( ) . owned_box ( ) )
49
50
&& is_default_equivalent ( cx, arg)
50
51
{
51
- span_lint_and_help (
52
+ let arg_ty = cx. typeck_results ( ) . expr_ty ( arg) ;
53
+ span_lint_and_sugg (
52
54
cx,
53
55
BOX_DEFAULT ,
54
56
expr. span ,
55
57
"`Box::new(_)` of default value" ,
56
- None ,
57
- "use `Box::default()` instead" ,
58
+ "try" ,
59
+ if is_plain_default ( arg_path) || given_type ( cx, expr) {
60
+ "Box::default()" . into ( )
61
+ } else {
62
+ format ! ( "Box::<{arg_ty}>::default()" )
63
+ } ,
64
+ Applicability :: MachineApplicable
58
65
) ;
59
66
}
60
67
}
61
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
+ }
0 commit comments