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
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use rustc::hir::intravisit::FnKind;
use rustc::hir::def_id::DefId;
use rustc::hir;
use rustc::lint::*;
use rustc::ty;
use syntax::ast;
use syntax::codemap::Span;
use utils::paths;
use utils::{get_trait_def_id, implements_trait, in_external_macro, return_ty, same_tys, span_lint_and_then};
use utils::sugg::DiagnosticBuilderExt;

/// **What it does:** Checks for types with a `fn new() -> Self` method and no
/// implementation of
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
///
/// **Why is this bad?** The user might expect to be able to use
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
/// type can be constructed without arguments.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
///
/// ```rust,ignore
/// struct Foo(Bar);
///
/// impl Foo {
///     fn new() -> Self {
///         Foo(Bar::new())
///     }
/// }
/// ```
///
/// Instead, use:
///
/// ```rust
/// struct Foo(Bar);
///
/// impl Default for Foo {
///     fn default() -> Self {
///         Foo(Bar::new())
///     }
/// }
/// ```
///
/// You can also have `new()` call `Default::default()`.
declare_lint! {
    pub NEW_WITHOUT_DEFAULT,
    Warn,
    "`fn new() -> Self` method without `Default` implementation"
}

/// **What it does:** Checks for types with a `fn new() -> Self` method
/// and no implementation of
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html),
/// where the `Default` can be derived by `#[derive(Default)]`.
///
/// **Why is this bad?** The user might expect to be able to use
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
/// type can be constructed without arguments.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
///
/// ```rust,ignore
/// struct Foo;
///
/// impl Foo {
///     fn new() -> Self {
///         Foo
///     }
/// }
/// ```
///
/// Just prepend `#[derive(Default)]` before the `struct` definition.
declare_lint! {
    pub NEW_WITHOUT_DEFAULT_DERIVE,
    Warn,
    "`fn new() -> Self` without `#[derive]`able `Default` implementation"
}

#[derive(Copy,Clone)]
pub struct NewWithoutDefault;

impl LintPass for NewWithoutDefault {
    fn get_lints(&self) -> LintArray {
        lint_array!(NEW_WITHOUT_DEFAULT, NEW_WITHOUT_DEFAULT_DERIVE)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        kind: FnKind<'tcx>,
        decl: &'tcx hir::FnDecl,
        _: &'tcx hir::Body,
        span: Span,
        id: ast::NodeId
    ) {
        if in_external_macro(cx, span) {
            return;
        }

        if let FnKind::Method(name, sig, _, _) = kind {
            if sig.constness == hir::Constness::Const {
                // can't be implemented by default
                return;
            }
            if decl.inputs.is_empty() && &*name.as_str() == "new" && cx.access_levels.is_reachable(id) {
                let self_ty = cx.tcx
                    .item_type(cx.tcx.map.local_def_id(cx.tcx.map.get_parent(id)));
                if_let_chain!{[
                    self_ty.walk_shallow().next().is_none(), // implements_trait does not work with generics
                    same_tys(cx, self_ty, return_ty(cx, id), id),
                    let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT),
                    !implements_trait(cx, self_ty, default_trait_id, Vec::new())
                ], {
                    if let Some(sp) = can_derive_default(self_ty, cx, default_trait_id) {
                        span_lint_and_then(cx,
                                           NEW_WITHOUT_DEFAULT_DERIVE, span,
                                           &format!("you should consider deriving a \
                                                     `Default` implementation for `{}`",
                                                    self_ty),
                                           |db| {
                            db.suggest_item_with_attr(cx, sp, "try this", "#[derive(Default)]");
                        });
                    } else {
                        span_lint_and_then(cx,
                                           NEW_WITHOUT_DEFAULT, span,
                                           &format!("you should consider adding a \
                                                    `Default` implementation for `{}`",
                                                    self_ty),
                                           |db| {
                        db.suggest_prepend_item(cx,
                                                  span,
                                                  "try this",
                                                  &format!(
"impl Default for {} {{
    fn default() -> Self {{
        Self::new()
    }}
}}",
                                                           self_ty));
                        });
                    }
                }}
            }
        }
    }
}

fn can_derive_default<'t, 'c>(ty: ty::Ty<'t>, cx: &LateContext<'c, 't>, default_trait_id: DefId) -> Option<Span> {
    match ty.sty {
        ty::TyAdt(adt_def, substs) if adt_def.is_struct() => {
            for field in adt_def.all_fields() {
                let f_ty = field.ty(cx.tcx, substs);
                if !implements_trait(cx, f_ty, default_trait_id, Vec::new()) {
                    return None;
                }
            }
            cx.tcx.map.span_if_local(adt_def.did)
        },
        _ => None,
    }
}