Skip to content

Commit ad1cd99

Browse files
authored
Merge pull request #1060 from Manishearth/sugg
Improve suggestions
2 parents 208e2fc + bf51322 commit ad1cd99

33 files changed

+1063
-464
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4-
## 0.0.78 - 2016-07-02
4+
## 0.0.79 — ?
5+
* Major suggestions refactoring
6+
7+
## 0.0.78 — 2016-07-02
58
* Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)*
69
* New lints: [`wrong_transmute`, `double_neg`]
710
* For compatibility, `cargo clippy` does not defines the `clippy` feature

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ name
2525
[almost_swapped](https://github.com/Manishearth/rust-clippy/wiki#almost_swapped) | warn | `foo = bar; bar = foo` sequence
2626
[approx_constant](https://github.com/Manishearth/rust-clippy/wiki#approx_constant) | warn | the approximate of a known float constant (in `std::f64::consts` or `std::f32::consts`) is found; suggests to use the constant
2727
[assign_op_pattern](https://github.com/Manishearth/rust-clippy/wiki#assign_op_pattern) | warn | assigning the result of an operation on a variable to that same variable
28-
[assign_ops](https://github.com/Manishearth/rust-clippy/wiki#assign_ops) | allow | Any assignment operation
28+
[assign_ops](https://github.com/Manishearth/rust-clippy/wiki#assign_ops) | allow | any assignment operation
2929
[bad_bit_mask](https://github.com/Manishearth/rust-clippy/wiki#bad_bit_mask) | warn | expressions of the form `_ & mask == select` that will only ever return `true` or `false` (because in the example `select` containing bits that `mask` doesn't have)
3030
[blacklisted_name](https://github.com/Manishearth/rust-clippy/wiki#blacklisted_name) | warn | usage of a blacklisted/placeholder name
3131
[block_in_if_condition_expr](https://github.com/Manishearth/rust-clippy/wiki#block_in_if_condition_expr) | warn | braces can be eliminated in conditions that are expressions, e.g `if { true } ...`

clippy_lints/src/array_indexing.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_const_eval::eval_const_expr_partial;
66
use rustc_const_math::ConstInt;
77
use rustc::hir::*;
88
use syntax::ast::RangeLimits;
9-
use utils;
9+
use utils::{self, higher};
1010

1111
/// **What it does:** Check for out of bounds array indexing with a constant index.
1212
///
@@ -77,7 +77,7 @@ impl LateLintPass for ArrayIndexing {
7777
}
7878

7979
// Index is a constant range
80-
if let Some(range) = utils::unsugar_range(index) {
80+
if let Some(range) = higher::range(index) {
8181
let start = range.start
8282
.map(|start| eval_const_expr_partial(cx.tcx, start, ExprTypeChecked, None))
8383
.map(|v| v.ok());
@@ -94,7 +94,7 @@ impl LateLintPass for ArrayIndexing {
9494
}
9595
}
9696

97-
if let Some(range) = utils::unsugar_range(index) {
97+
if let Some(range) = higher::range(index) {
9898
// Full ranges are always valid
9999
if range.start.is_none() && range.end.is_none() {
100100
return;

clippy_lints/src/assign_ops.rs

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
use rustc::hir;
22
use rustc::lint::*;
33
use utils::{span_lint_and_then, span_lint, snippet_opt, SpanlessEq, get_trait_def_id, implements_trait};
4+
use utils::{higher, sugg};
45

5-
/// **What it does:** This lint checks for `+=` operations and similar
6+
/// **What it does:** This lint checks for `+=` operations and similar.
67
///
7-
/// **Why is this bad?** Projects with many developers from languages without those operations
8-
/// may find them unreadable and not worth their weight
8+
/// **Why is this bad?** Projects with many developers from languages without those operations may
9+
/// find them unreadable and not worth their weight.
910
///
10-
/// **Known problems:** Types implementing `OpAssign` don't necessarily implement `Op`
11+
/// **Known problems:** Types implementing `OpAssign` don't necessarily implement `Op`.
1112
///
1213
/// **Example:**
1314
/// ```
1415
/// a += 1;
1516
/// ```
1617
declare_restriction_lint! {
1718
pub ASSIGN_OPS,
18-
"Any assignment operation"
19+
"any assignment operation"
1920
}
2021

21-
/// **What it does:** Check for `a = a op b` or `a = b commutative_op a` patterns
22+
/// **What it does:** Check for `a = a op b` or `a = b commutative_op a` patterns.
2223
///
23-
/// **Why is this bad?** These can be written as the shorter `a op= b`
24+
/// **Why is this bad?** These can be written as the shorter `a op= b`.
2425
///
25-
/// **Known problems:** While forbidden by the spec, `OpAssign` traits may have implementations that differ from the regular `Op` impl
26+
/// **Known problems:** While forbidden by the spec, `OpAssign` traits may have implementations that differ from the regular `Op` impl.
2627
///
2728
/// **Example:**
2829
///
@@ -50,24 +51,14 @@ impl LateLintPass for AssignOps {
5051
fn check_expr(&mut self, cx: &LateContext, expr: &hir::Expr) {
5152
match expr.node {
5253
hir::ExprAssignOp(op, ref lhs, ref rhs) => {
53-
if let (Some(l), Some(r)) = (snippet_opt(cx, lhs.span), snippet_opt(cx, rhs.span)) {
54-
span_lint_and_then(cx, ASSIGN_OPS, expr.span, "assign operation detected", |db| {
55-
match rhs.node {
56-
hir::ExprBinary(op2, _, _) if op2 != op => {
57-
db.span_suggestion(expr.span,
58-
"replace it with",
59-
format!("{} = {} {} ({})", l, l, op.node.as_str(), r));
60-
}
61-
_ => {
62-
db.span_suggestion(expr.span,
63-
"replace it with",
64-
format!("{} = {} {} {}", l, l, op.node.as_str(), r));
65-
}
66-
}
67-
});
68-
} else {
69-
span_lint(cx, ASSIGN_OPS, expr.span, "assign operation detected");
70-
}
54+
span_lint_and_then(cx, ASSIGN_OPS, expr.span, "assign operation detected", |db| {
55+
let lhs = &sugg::Sugg::hir(cx, lhs, "..");
56+
let rhs = &sugg::Sugg::hir(cx, rhs, "..");
57+
58+
db.span_suggestion(expr.span,
59+
"replace it with",
60+
format!("{} = {}", lhs, sugg::make_binop(higher::binop(op.node), lhs, rhs)));
61+
});
7162
}
7263
hir::ExprAssign(ref assignee, ref e) => {
7364
if let hir::ExprBinary(op, ref l, ref r) = e.node {

clippy_lints/src/collapsible_if.rs

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
//! This lint is **warn** by default
1414
1515
use rustc::lint::*;
16-
use std::borrow::Cow;
17-
use syntax::codemap::Spanned;
1816
use syntax::ast;
1917

20-
use utils::{in_macro, snippet, snippet_block, span_lint_and_then};
18+
use utils::{in_macro, snippet_block, span_lint_and_then};
19+
use utils::sugg::Sugg;
2120

2221
/// **What it does:** This lint checks for nested `if`-statements which can be collapsed by
2322
/// `&&`-combining their conditions and for `else { if .. }` expressions that can be collapsed to
@@ -104,31 +103,17 @@ fn check_collapsible_no_if_let(
104103
return;
105104
}
106105
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this if statement can be collapsed", |db| {
106+
let lhs = Sugg::ast(cx, check, "..");
107+
let rhs = Sugg::ast(cx, check_inner, "..");
107108
db.span_suggestion(expr.span,
108109
"try",
109-
format!("if {} && {} {}",
110-
check_to_string(cx, check),
111-
check_to_string(cx, check_inner),
110+
format!("if {} {}",
111+
lhs.and(rhs),
112112
snippet_block(cx, content.span, "..")));
113113
});
114114
}}
115115
}
116116

117-
fn requires_brackets(e: &ast::Expr) -> bool {
118-
match e.node {
119-
ast::ExprKind::Binary(Spanned { node: n, .. }, _, _) if n == ast::BinOpKind::Eq => false,
120-
_ => true,
121-
}
122-
}
123-
124-
fn check_to_string(cx: &EarlyContext, e: &ast::Expr) -> Cow<'static, str> {
125-
if requires_brackets(e) {
126-
format!("({})", snippet(cx, e.span, "..")).into()
127-
} else {
128-
snippet(cx, e.span, "..")
129-
}
130-
}
131-
132117
/// If the block contains only one expression, returns it.
133118
fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
134119
let mut it = block.stmts.iter();

clippy_lints/src/entry.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,21 @@ impl<'a, 'tcx, 'v, 'b> Visitor<'v> for InsertVisitor<'a, 'tcx, 'b> {
121121
SpanlessEq::new(self.cx).eq_expr(self.key, &params[1])
122122
], {
123123
span_lint_and_then(self.cx, MAP_ENTRY, self.span,
124-
&format!("usage of `contains_key` followed by `insert` on `{}`", self.ty), |db| {
124+
&format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |db| {
125125
if self.sole_expr {
126126
let help = format!("{}.entry({}).or_insert({})",
127127
snippet(self.cx, self.map.span, "map"),
128128
snippet(self.cx, params[1].span, ".."),
129129
snippet(self.cx, params[2].span, ".."));
130130

131-
db.span_suggestion(self.span, "Consider using", help);
131+
db.span_suggestion(self.span, "consider using", help);
132132
}
133133
else {
134-
let help = format!("Consider using `{}.entry({})`",
134+
let help = format!("{}.entry({})",
135135
snippet(self.cx, self.map.span, "map"),
136136
snippet(self.cx, params[1].span, ".."));
137137

138-
db.span_note(self.span, &help);
138+
db.span_suggestion(self.span, "consider using", help);
139139
}
140140
});
141141
}}

clippy_lints/src/formatting.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn check_consecutive_ifs(cx: &EarlyContext, first: &ast::Expr, second: &ast::Exp
148148
}
149149
}
150150

151-
/// Match `if` or `else if` expressions and return the `then` and `else` block.
151+
/// Match `if` or `if let` expressions and return the `then` and `else` block.
152152
fn unsugar_if(expr: &ast::Expr) -> Option<(&P<ast::Block>, &Option<P<ast::Expr>>)> {
153153
match expr.node {
154154
ast::ExprKind::If(_, ref then, ref else_) |

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![feature(box_syntax)]
44
#![feature(collections)]
55
#![feature(custom_attribute)]
6+
#![feature(dotdot_in_tuple_patterns)]
67
#![feature(question_mark)]
78
#![feature(rustc_private)]
89
#![feature(slice_patterns)]
@@ -36,6 +37,7 @@ extern crate quine_mc_cluskey;
3637

3738
extern crate rustc_serialize;
3839

40+
extern crate rustc_errors;
3941
extern crate rustc_plugin;
4042
extern crate rustc_const_eval;
4143
extern crate rustc_const_math;

clippy_lints/src/loops.rs

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ use rustc::middle::region::CodeExtent;
99
use rustc::ty;
1010
use rustc_const_eval::EvalHint::ExprTypeChecked;
1111
use rustc_const_eval::eval_const_expr_partial;
12-
use std::borrow::Cow;
1312
use std::collections::HashMap;
1413
use syntax::ast;
14+
use utils::sugg;
1515

16-
use utils::{snippet, span_lint, get_parent_expr, match_trait_method, match_type, in_external_macro,
17-
span_help_and_lint, is_integer_literal, get_enclosing_block, span_lint_and_then, unsugar_range,
18-
walk_ptrs_ty, recover_for_loop};
16+
use utils::{snippet, span_lint, get_parent_expr, match_trait_method, match_type, multispan_sugg, in_external_macro,
17+
span_help_and_lint, is_integer_literal, get_enclosing_block, span_lint_and_then, higher,
18+
walk_ptrs_ty};
1919
use utils::paths;
20-
use utils::UnsugaredRange;
2120

2221
/// **What it does:** This lint checks for looping over the range of `0..len` of some collection just to get the values by index.
2322
///
@@ -224,7 +223,7 @@ impl LintPass for Pass {
224223

225224
impl LateLintPass for Pass {
226225
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
227-
if let Some((pat, arg, body)) = recover_for_loop(expr) {
226+
if let Some((pat, arg, body)) = higher::for_loop(expr) {
228227
check_for_loop(cx, pat, arg, body, expr);
229228
}
230229
// check for `loop { if let {} else break }` that could be `while let`
@@ -333,7 +332,7 @@ fn check_for_loop(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &E
333332
/// Check for looping over a range and then indexing a sequence with it.
334333
/// The iteratee must be a range literal.
335334
fn check_for_loop_range(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &Expr) {
336-
if let Some(UnsugaredRange { start: Some(ref start), ref end, .. }) = unsugar_range(arg) {
335+
if let Some(higher::Range { start: Some(ref start), ref end, limits }) = higher::range(arg) {
337336
// the var must be a single name
338337
if let PatKind::Binding(_, ref ident, _) = pat.node {
339338
let mut visitor = VarVisitor {
@@ -361,49 +360,58 @@ fn check_for_loop_range(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, ex
361360

362361
let starts_at_zero = is_integer_literal(start, 0);
363362

364-
let skip: Cow<_> = if starts_at_zero {
365-
"".into()
363+
let skip = if starts_at_zero {
364+
"".to_owned()
366365
} else {
367-
format!(".skip({})", snippet(cx, start.span, "..")).into()
366+
format!(".skip({})", snippet(cx, start.span, ".."))
368367
};
369368

370-
let take: Cow<_> = if let Some(ref end) = *end {
369+
let take = if let Some(ref end) = *end {
371370
if is_len_call(end, &indexed) {
372-
"".into()
371+
"".to_owned()
373372
} else {
374-
format!(".take({})", snippet(cx, end.span, "..")).into()
373+
match limits {
374+
ast::RangeLimits::Closed => {
375+
let end = sugg::Sugg::hir(cx, end, "<count>");
376+
format!(".take({})", end + sugg::ONE)
377+
}
378+
ast::RangeLimits::HalfOpen => {
379+
format!(".take({})", snippet(cx, end.span, ".."))
380+
}
381+
}
375382
}
376383
} else {
377-
"".into()
384+
"".to_owned()
378385
};
379386

380387
if visitor.nonindex {
381-
span_lint(cx,
382-
NEEDLESS_RANGE_LOOP,
383-
expr.span,
384-
&format!("the loop variable `{}` is used to index `{}`. Consider using `for ({}, \
385-
item) in {}.iter().enumerate(){}{}` or similar iterators",
386-
ident.node,
387-
indexed,
388-
ident.node,
389-
indexed,
390-
take,
391-
skip));
388+
span_lint_and_then(cx,
389+
NEEDLESS_RANGE_LOOP,
390+
expr.span,
391+
&format!("the loop variable `{}` is used to index `{}`", ident.node, indexed),
392+
|db| {
393+
multispan_sugg(db, "consider using an iterator".to_string(), &[
394+
(pat.span, &format!("({}, <item>)", ident.node)),
395+
(arg.span, &format!("{}.iter().enumerate(){}{}", indexed, take, skip)),
396+
]);
397+
});
392398
} else {
393399
let repl = if starts_at_zero && take.is_empty() {
394400
format!("&{}", indexed)
395401
} else {
396402
format!("{}.iter(){}{}", indexed, take, skip)
397403
};
398404

399-
span_lint(cx,
400-
NEEDLESS_RANGE_LOOP,
401-
expr.span,
402-
&format!("the loop variable `{}` is only used to index `{}`. \
403-
Consider using `for item in {}` or similar iterators",
404-
ident.node,
405-
indexed,
406-
repl));
405+
span_lint_and_then(cx,
406+
NEEDLESS_RANGE_LOOP,
407+
expr.span,
408+
&format!("the loop variable `{}` is only used to index `{}`.", ident.node, indexed),
409+
|db| {
410+
multispan_sugg(db, "consider using an iterator".to_string(), &[
411+
(pat.span, "<item>"),
412+
(arg.span, &repl),
413+
]);
414+
});
407415
}
408416
}
409417
}
@@ -427,7 +435,7 @@ fn is_len_call(expr: &Expr, var: &Name) -> bool {
427435

428436
fn check_for_loop_reverse_range(cx: &LateContext, arg: &Expr, expr: &Expr) {
429437
// if this for loop is iterating over a two-sided range...
430-
if let Some(UnsugaredRange { start: Some(ref start), end: Some(ref end), limits }) = unsugar_range(arg) {
438+
if let Some(higher::Range { start: Some(ref start), end: Some(ref end), limits }) = higher::range(arg) {
431439
// ...and both sides are compile-time constant integers...
432440
if let Ok(start_idx) = eval_const_expr_partial(cx.tcx, start, ExprTypeChecked, None) {
433441
if let Ok(end_idx) = eval_const_expr_partial(cx.tcx, end, ExprTypeChecked, None) {
@@ -588,33 +596,34 @@ fn check_for_loop_explicit_counter(cx: &LateContext, arg: &Expr, body: &Expr, ex
588596

589597
/// Check for the `FOR_KV_MAP` lint.
590598
fn check_for_loop_over_map_kv(cx: &LateContext, pat: &Pat, arg: &Expr, body: &Expr, expr: &Expr) {
599+
let pat_span = pat.span;
600+
591601
if let PatKind::Tuple(ref pat, _) = pat.node {
592602
if pat.len() == 2 {
593-
let (pat_span, kind) = match (&pat[0].node, &pat[1].node) {
594-
(key, _) if pat_is_wild(key, body) => (&pat[1].span, "values"),
595-
(_, value) if pat_is_wild(value, body) => (&pat[0].span, "keys"),
603+
let (new_pat_span, kind) = match (&pat[0].node, &pat[1].node) {
604+
(key, _) if pat_is_wild(key, body) => (pat[1].span, "value"),
605+
(_, value) if pat_is_wild(value, body) => (pat[0].span, "key"),
596606
_ => return,
597607
};
598608

599-
let arg_span = match arg.node {
600-
ExprAddrOf(MutImmutable, ref expr) => expr.span,
609+
let (arg_span, arg) = match arg.node {
610+
ExprAddrOf(MutImmutable, ref expr) => (arg.span, &**expr),
601611
ExprAddrOf(MutMutable, _) => return, // for _ in &mut _, there is no {values,keys}_mut method
602-
_ => arg.span,
612+
_ => (arg.span, arg),
603613
};
604614

605615
let ty = walk_ptrs_ty(cx.tcx.expr_ty(arg));
606616
if match_type(cx, ty, &paths::HASHMAP) || match_type(cx, ty, &paths::BTREEMAP) {
607617
span_lint_and_then(cx,
608618
FOR_KV_MAP,
609619
expr.span,
610-
&format!("you seem to want to iterate on a map's {}", kind),
620+
&format!("you seem to want to iterate on a map's {}s", kind),
611621
|db| {
612-
db.span_suggestion(expr.span,
613-
"use the corresponding method",
614-
format!("for {} in {}.{}() {{ .. }}",
615-
snippet(cx, *pat_span, ".."),
616-
snippet(cx, arg_span, ".."),
617-
kind));
622+
let map = sugg::Sugg::hir(cx, arg, "map");
623+
multispan_sugg(db, "use the corresponding method".into(), &[
624+
(pat_span, &snippet(cx, new_pat_span, kind)),
625+
(arg_span, &format!("{}.{}s()", map.maybe_par(), kind)),
626+
]);
618627
});
619628
}
620629
}

0 commit comments

Comments
 (0)