Skip to content

Commit d9b5b2a

Browse files
committed
Merge pull request #730 from mcarton/unused-labels
Lint unused labels and types with `fn new() -> Self` and no `Default` impl
2 parents 878041b + 052f598 commit d9b5b2a

11 files changed

+302
-49
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
88
[Jump to usage instructions](#usage)
99

1010
##Lints
11-
There are 131 lints included in this crate:
11+
There are 133 lints included in this crate:
1212

1313
name | default | meaning
1414
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -83,6 +83,7 @@ name
8383
[needless_return](https://github.com/Manishearth/rust-clippy/wiki#needless_return) | warn | using a return statement like `return expr;` where an expression would suffice
8484
[needless_update](https://github.com/Manishearth/rust-clippy/wiki#needless_update) | warn | using `{ ..base }` when there are no missing fields
8585
[new_ret_no_self](https://github.com/Manishearth/rust-clippy/wiki#new_ret_no_self) | warn | not returning `Self` in a `new` method
86+
[new_without_default](https://github.com/Manishearth/rust-clippy/wiki#new_without_default) | warn | `fn new() -> Self` method without `Default` implementation
8687
[no_effect](https://github.com/Manishearth/rust-clippy/wiki#no_effect) | warn | statements with no effect
8788
[non_ascii_literal](https://github.com/Manishearth/rust-clippy/wiki#non_ascii_literal) | allow | using any literal non-ASCII chars in a string literal; suggests using the \\u escape instead
8889
[nonsensical_open_options](https://github.com/Manishearth/rust-clippy/wiki#nonsensical_open_options) | warn | nonsensical combination of options for opening a file
@@ -131,6 +132,7 @@ name
131132
[unstable_as_mut_slice](https://github.com/Manishearth/rust-clippy/wiki#unstable_as_mut_slice) | warn | as_mut_slice is not stable and can be replaced by &mut v[..]see https://github.com/rust-lang/rust/issues/27729
132133
[unstable_as_slice](https://github.com/Manishearth/rust-clippy/wiki#unstable_as_slice) | warn | as_slice is not stable and can be replaced by & v[..]see https://github.com/rust-lang/rust/issues/27729
133134
[unused_collect](https://github.com/Manishearth/rust-clippy/wiki#unused_collect) | warn | `collect()`ing an iterator without using the result; this is usually better written as a for loop
135+
[unused_label](https://github.com/Manishearth/rust-clippy/wiki#unused_label) | warn | unused label
134136
[unused_lifetimes](https://github.com/Manishearth/rust-clippy/wiki#unused_lifetimes) | warn | unused lifetimes in function definitions
135137
[use_debug](https://github.com/Manishearth/rust-clippy/wiki#use_debug) | allow | use `Debug`-based formatting
136138
[used_underscore_binding](https://github.com/Manishearth/rust-clippy/wiki#used_underscore_binding) | warn | using a binding which is prefixed with an underscore

src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub mod mutex_atomic;
7777
pub mod needless_bool;
7878
pub mod needless_features;
7979
pub mod needless_update;
80+
pub mod new_without_default;
8081
pub mod no_effect;
8182
pub mod open_options;
8283
pub mod overflow_check_conditional;
@@ -94,6 +95,7 @@ pub mod temporary_assignment;
9495
pub mod transmute;
9596
pub mod types;
9697
pub mod unicode;
98+
pub mod unused_label;
9799
pub mod vec;
98100
pub mod zero_div_zero;
99101
// end lints modules, do not remove this comment, it’s used in `update_lints`
@@ -175,6 +177,8 @@ pub fn plugin_registrar(reg: &mut Registry) {
175177
reg.register_late_lint_pass(box swap::Swap);
176178
reg.register_early_lint_pass(box if_not_else::IfNotElse);
177179
reg.register_late_lint_pass(box overflow_check_conditional::OverflowCheckConditional);
180+
reg.register_late_lint_pass(box unused_label::UnusedLabel);
181+
reg.register_late_lint_pass(box new_without_default::NewWithoutDefault);
178182

179183
reg.register_lint_group("clippy_pedantic", vec![
180184
enum_glob_use::ENUM_GLOB_USE,
@@ -283,6 +287,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
283287
needless_features::UNSTABLE_AS_MUT_SLICE,
284288
needless_features::UNSTABLE_AS_SLICE,
285289
needless_update::NEEDLESS_UPDATE,
290+
new_without_default::NEW_WITHOUT_DEFAULT,
286291
no_effect::NO_EFFECT,
287292
open_options::NONSENSICAL_OPEN_OPTIONS,
288293
overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
@@ -309,6 +314,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
309314
types::TYPE_COMPLEXITY,
310315
types::UNIT_CMP,
311316
unicode::ZERO_WIDTH_SPACE,
317+
unused_label::UNUSED_LABEL,
312318
vec::USELESS_VEC,
313319
zero_div_zero::ZERO_DIVIDED_BY_ZERO,
314320
]);

src/methods.rs

+10-24
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use std::{fmt, iter};
1010
use syntax::codemap::Span;
1111
use syntax::ptr::P;
1212
use utils::{get_trait_def_id, implements_trait, in_external_macro, in_macro, match_path, match_trait_method,
13-
match_type, method_chain_args, snippet, snippet_opt, span_lint, span_lint_and_then, span_note_and_lint,
14-
walk_ptrs_ty, walk_ptrs_ty_depth};
13+
match_type, method_chain_args, return_ty, same_tys, snippet, snippet_opt, span_lint,
14+
span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth};
1515
use utils::{BTREEMAP_ENTRY_PATH, DEFAULT_TRAIT_PATH, HASHMAP_ENTRY_PATH, OPTION_PATH, RESULT_PATH, STRING_PATH,
1616
VEC_PATH};
1717
use utils::MethodArgs;
@@ -431,26 +431,12 @@ impl LateLintPass for MethodsPass {
431431
}
432432
}
433433

434-
if &name.as_str() == &"new" {
435-
let returns_self = if let FunctionRetTy::Return(ref ret_ty) = sig.decl.output {
436-
let ast_ty_to_ty_cache = cx.tcx.ast_ty_to_ty_cache.borrow();
437-
let ret_ty = ast_ty_to_ty_cache.get(&ret_ty.id);
438-
439-
if let Some(&ret_ty) = ret_ty {
440-
ret_ty.walk().any(|t| t == ty)
441-
} else {
442-
false
443-
}
444-
} else {
445-
false
446-
};
447-
448-
if !returns_self {
449-
span_lint(cx,
450-
NEW_RET_NO_SELF,
451-
sig.explicit_self.span,
452-
"methods called `new` usually return `Self`");
453-
}
434+
let ret_ty = return_ty(cx.tcx.node_id_to_type(implitem.id));
435+
if &name.as_str() == &"new" && !ret_ty.map_or(false, |ret_ty| ret_ty.walk().any(|t| same_tys(cx, t, ty))) {
436+
span_lint(cx,
437+
NEW_RET_NO_SELF,
438+
sig.explicit_self.span,
439+
"methods called `new` usually return `Self`");
454440
}
455441
}
456442
}
@@ -485,7 +471,7 @@ fn lint_or_fun_call(cx: &LateContext, expr: &Expr, name: &str, args: &[P<Expr>])
485471
return false;
486472
};
487473

488-
if implements_trait(cx, arg_ty, default_trait_id, None) {
474+
if implements_trait(cx, arg_ty, default_trait_id, Vec::new()) {
489475
span_lint(cx,
490476
OR_FUN_CALL,
491477
span,
@@ -869,7 +855,7 @@ fn get_error_type<'a>(cx: &LateContext, ty: ty::Ty<'a>) -> Option<ty::Ty<'a>> {
869855
/// This checks whether a given type is known to implement Debug.
870856
fn has_debug_impl<'a, 'b>(ty: ty::Ty<'a>, cx: &LateContext<'b, 'a>) -> bool {
871857
match cx.tcx.lang_items.debug_trait() {
872-
Some(debug) => implements_trait(cx, ty, debug, Some(vec![])),
858+
Some(debug) => implements_trait(cx, ty, debug, Vec::new()),
873859
None => false,
874860
}
875861
}

src/misc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ fn check_to_owned(cx: &LateContext, expr: &Expr, other: &Expr, left: bool, op: S
253253
None => return,
254254
};
255255

256-
if !implements_trait(cx, arg_ty, partial_eq_trait_id, Some(vec![other_ty])) {
256+
if !implements_trait(cx, arg_ty, partial_eq_trait_id, vec![other_ty]) {
257257
return;
258258
}
259259

src/new_without_default.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir;
3+
use rustc_front::intravisit::FnKind;
4+
use syntax::ast;
5+
use syntax::codemap::Span;
6+
use utils::{get_trait_def_id, implements_trait, in_external_macro, return_ty, same_tys, span_lint,
7+
DEFAULT_TRAIT_PATH};
8+
9+
/// **What it does:** This lints about type with a `fn new() -> Self` method and no `Default`
10+
/// implementation.
11+
///
12+
/// **Why is this bad?** User might expect to be able to use `Default` is the type can be
13+
/// constructed without arguments.
14+
///
15+
/// **Known problems:** Hopefully none.
16+
///
17+
/// **Example:**
18+
///
19+
/// ```rust,ignore
20+
/// struct Foo;
21+
///
22+
/// impl Foo {
23+
/// fn new() -> Self {
24+
/// Foo
25+
/// }
26+
/// }
27+
/// ```
28+
declare_lint! {
29+
pub NEW_WITHOUT_DEFAULT,
30+
Warn,
31+
"`fn new() -> Self` method without `Default` implementation"
32+
}
33+
34+
#[derive(Copy,Clone)]
35+
pub struct NewWithoutDefault;
36+
37+
impl LintPass for NewWithoutDefault {
38+
fn get_lints(&self) -> LintArray {
39+
lint_array!(NEW_WITHOUT_DEFAULT)
40+
}
41+
}
42+
43+
impl LateLintPass for NewWithoutDefault {
44+
fn check_fn(&mut self, cx: &LateContext, kind: FnKind, decl: &hir::FnDecl, _: &hir::Block, span: Span, id: ast::NodeId) {
45+
if in_external_macro(cx, span) {
46+
return;
47+
}
48+
49+
if let FnKind::Method(name, _, _) = kind {
50+
if decl.inputs.is_empty() && name.as_str() == "new" {
51+
let self_ty = cx.tcx.lookup_item_type(cx.tcx.map.local_def_id(cx.tcx.map.get_parent(id))).ty;
52+
53+
if_let_chain!{[
54+
let Some(ret_ty) = return_ty(cx.tcx.node_id_to_type(id)),
55+
same_tys(cx, self_ty, ret_ty),
56+
let Some(default_trait_id) = get_trait_def_id(cx, &DEFAULT_TRAIT_PATH),
57+
!implements_trait(cx, self_ty, default_trait_id, Vec::new())
58+
], {
59+
span_lint(cx, NEW_WITHOUT_DEFAULT, span,
60+
&format!("you should consider adding a `Default` implementation for `{}`", self_ty));
61+
}}
62+
}
63+
}
64+
}
65+
}

src/ptr_arg.rs

+21-20
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc::front::map::NodeItem;
66
use rustc::lint::*;
77
use rustc::middle::ty;
88
use rustc_front::hir::*;
9+
use syntax::ast::NodeId;
910
use utils::{STRING_PATH, VEC_PATH};
1011
use utils::{span_lint, match_type};
1112

@@ -35,7 +36,7 @@ impl LintPass for PtrArg {
3536
impl LateLintPass for PtrArg {
3637
fn check_item(&mut self, cx: &LateContext, item: &Item) {
3738
if let ItemFn(ref decl, _, _, _, _, _) = item.node {
38-
check_fn(cx, decl);
39+
check_fn(cx, decl, item.id);
3940
}
4041
}
4142

@@ -46,34 +47,34 @@ impl LateLintPass for PtrArg {
4647
return; // ignore trait impls
4748
}
4849
}
49-
check_fn(cx, &sig.decl);
50+
check_fn(cx, &sig.decl, item.id);
5051
}
5152
}
5253

5354
fn check_trait_item(&mut self, cx: &LateContext, item: &TraitItem) {
5455
if let MethodTraitItem(ref sig, _) = item.node {
55-
check_fn(cx, &sig.decl);
56+
check_fn(cx, &sig.decl, item.id);
5657
}
5758
}
5859
}
5960

60-
fn check_fn(cx: &LateContext, decl: &FnDecl) {
61-
for arg in &decl.inputs {
62-
if let Some(ty) = cx.tcx.ast_ty_to_ty_cache.borrow().get(&arg.ty.id) {
63-
if let ty::TyRef(_, ty::TypeAndMut { ty, mutbl: MutImmutable }) = ty.sty {
64-
if match_type(cx, ty, &VEC_PATH) {
65-
span_lint(cx,
66-
PTR_ARG,
67-
arg.ty.span,
68-
"writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \
69-
with non-Vec-based slices. Consider changing the type to `&[...]`");
70-
} else if match_type(cx, ty, &STRING_PATH) {
71-
span_lint(cx,
72-
PTR_ARG,
73-
arg.ty.span,
74-
"writing `&String` instead of `&str` involves a new object where a slice will do. \
75-
Consider changing the type to `&str`");
76-
}
61+
fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId) {
62+
let fn_ty = cx.tcx.node_id_to_type(fn_id).fn_sig().skip_binder();
63+
64+
for (arg, ty) in decl.inputs.iter().zip(&fn_ty.inputs) {
65+
if let ty::TyRef(_, ty::TypeAndMut { ty, mutbl: MutImmutable }) = ty.sty {
66+
if match_type(cx, ty, &VEC_PATH) {
67+
span_lint(cx,
68+
PTR_ARG,
69+
arg.ty.span,
70+
"writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \
71+
with non-Vec-based slices. Consider changing the type to `&[...]`");
72+
} else if match_type(cx, ty, &STRING_PATH) {
73+
span_lint(cx,
74+
PTR_ARG,
75+
arg.ty.span,
76+
"writing `&String` instead of `&str` involves a new object where a slice will do. \
77+
Consider changing the type to `&str`");
7778
}
7879
}
7980
}

src/unused_label.rs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir;
3+
use rustc_front::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
4+
use std::collections::HashMap;
5+
use syntax::ast;
6+
use syntax::codemap::Span;
7+
use syntax::parse::token::InternedString;
8+
use utils::{in_macro, span_lint};
9+
10+
/// **What it does:** This lint checks for unused labels.
11+
///
12+
/// **Why is this bad?** Maybe the label should be used in which case there is an error in the
13+
/// code or it should be removed.
14+
///
15+
/// **Known problems:** Hopefully none.
16+
///
17+
/// **Example:**
18+
/// ```rust,ignore
19+
/// fn unused_label() {
20+
/// 'label: for i in 1..2 {
21+
/// if i > 4 { continue }
22+
/// }
23+
/// ```
24+
declare_lint! {
25+
pub UNUSED_LABEL,
26+
Warn,
27+
"unused label"
28+
}
29+
30+
pub struct UnusedLabel;
31+
32+
#[derive(Default)]
33+
struct UnusedLabelVisitor {
34+
labels: HashMap<InternedString, Span>,
35+
}
36+
37+
impl UnusedLabelVisitor {
38+
pub fn new() -> UnusedLabelVisitor {
39+
::std::default::Default::default()
40+
}
41+
}
42+
43+
impl LintPass for UnusedLabel {
44+
fn get_lints(&self) -> LintArray {
45+
lint_array!(UNUSED_LABEL)
46+
}
47+
}
48+
49+
impl LateLintPass for UnusedLabel {
50+
fn check_fn(&mut self, cx: &LateContext, kind: FnKind, decl: &hir::FnDecl, body: &hir::Block, span: Span, _: ast::NodeId) {
51+
if in_macro(cx, span) {
52+
return;
53+
}
54+
55+
let mut v = UnusedLabelVisitor::new();
56+
walk_fn(&mut v, kind, decl, body, span);
57+
58+
for (label, span) in v.labels {
59+
span_lint(cx, UNUSED_LABEL, span, &format!("unused label `{}`", label));
60+
}
61+
}
62+
}
63+
64+
impl<'v> Visitor<'v> for UnusedLabelVisitor {
65+
fn visit_expr(&mut self, expr: &hir::Expr) {
66+
match expr.node {
67+
hir::ExprBreak(Some(label)) | hir::ExprAgain(Some(label)) => {
68+
self.labels.remove(&label.node.name.as_str());
69+
}
70+
hir::ExprLoop(_, Some(label)) | hir::ExprWhile(_, _, Some(label)) => {
71+
self.labels.insert(label.name.as_str(), expr.span);
72+
}
73+
_ => (),
74+
}
75+
76+
walk_expr(self, expr);
77+
}
78+
}

src/utils/mod.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ pub fn get_trait_def_id(cx: &LateContext, path: &[&str]) -> Option<DefId> {
264264
/// Check whether a type implements a trait.
265265
/// See also `get_trait_def_id`.
266266
pub fn implements_trait<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: ty::Ty<'tcx>, trait_id: DefId,
267-
ty_params: Option<Vec<ty::Ty<'tcx>>>)
267+
ty_params: Vec<ty::Ty<'tcx>>)
268268
-> bool {
269269
cx.tcx.populate_implementations_for_trait_if_necessary(trait_id);
270270

@@ -274,7 +274,7 @@ pub fn implements_trait<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: ty::Ty<'tcx>,
274274
trait_id,
275275
0,
276276
ty,
277-
ty_params.unwrap_or_default());
277+
ty_params);
278278

279279
traits::SelectionContext::new(&infcx).evaluate_obligation_conservatively(&obligation)
280280
}
@@ -731,3 +731,20 @@ pub fn unsugar_range(expr: &Expr) -> Option<UnsugaredRange> {
731731
None
732732
}
733733
}
734+
735+
/// Convenience function to get the return type of a function or `None` if the function diverges.
736+
pub fn return_ty(fun: ty::Ty) -> Option<ty::Ty> {
737+
if let ty::FnConverging(ret_ty) = fun.fn_sig().skip_binder().output {
738+
Some(ret_ty)
739+
} else {
740+
None
741+
}
742+
}
743+
744+
/// Check if two types are the same.
745+
// FIXME: this works correctly for lifetimes bounds (`for <'a> Foo<'a>` == `for <'b> Foo<'b>` but
746+
// not for type parameters.
747+
pub fn same_tys<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, a: ty::Ty<'tcx>, b: ty::Ty<'tcx>) -> bool {
748+
let infcx = infer::new_infer_ctxt(cx.tcx, &cx.tcx.tables, None);
749+
infcx.can_equate(&cx.tcx.erase_regions(&a), &cx.tcx.erase_regions(&b)).is_ok()
750+
}

0 commit comments

Comments
 (0)