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+ } ;
310use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
411use rustc_middle:: lint:: in_external_macro;
512use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
@@ -15,12 +22,6 @@ declare_clippy_lint! {
1522 /// Second, `Box::default()` can be faster
1623 /// [in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box).
1724 ///
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- ///
2425 /// ### Example
2526 /// ```rust
2627 /// let x: Box<String> = Box::new(Default::default());
@@ -41,21 +42,88 @@ impl LateLintPass<'_> for BoxDefault {
4142 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
4243 if let ExprKind :: Call ( box_new, [ arg] ) = expr. kind
4344 && let ExprKind :: Path ( QPath :: TypeRelative ( ty, seg) ) = box_new. kind
44- && let ExprKind :: Call ( ..) = arg. kind
45+ && let ExprKind :: Call ( arg_path , ..) = arg. kind
4546 && !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 ) )
4748 && seg. ident . name == sym:: new
4849 && path_def_id ( cx, ty) == cx. tcx . lang_items ( ) . owned_box ( )
4950 && is_default_equivalent ( cx, arg)
5051 {
51- span_lint_and_help (
52+ let arg_ty = cx. typeck_results ( ) . expr_ty ( arg) ;
53+ span_lint_and_sugg (
5254 cx,
5355 BOX_DEFAULT ,
5456 expr. span ,
5557 "`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
5865 ) ;
5966 }
6067 }
6168}
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 ) ;
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