Skip to content

Commit a260e65

Browse files
authored
Merge pull request #1312 from devonhollowood/get-unwrap
Implement `get_unwrap` lint
2 parents c56b8df + e94a4d4 commit a260e65

File tree

5 files changed

+143
-3
lines changed

5 files changed

+143
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ All notable changes to this project will be documented in this file.
266266
[`for_kv_map`]: https://github.com/Manishearth/rust-clippy/wiki#for_kv_map
267267
[`for_loop_over_option`]: https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option
268268
[`for_loop_over_result`]: https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result
269+
[`get_unwrap`]: https://github.com/Manishearth/rust-clippy/wiki#get_unwrap
269270
[`identity_op`]: https://github.com/Manishearth/rust-clippy/wiki#identity_op
270271
[`if_let_redundant_pattern_matching`]: https://github.com/Manishearth/rust-clippy/wiki#if_let_redundant_pattern_matching
271272
[`if_let_some_result`]: https://github.com/Manishearth/rust-clippy/wiki#if_let_some_result

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ You can check out this great service at [clippy.bashy.io](https://clippy.bashy.i
182182

183183
## Lints
184184

185-
There are 176 lints included in this crate:
185+
There are 177 lints included in this crate:
186186

187187
name | default | triggers on
188188
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
@@ -238,6 +238,7 @@ name
238238
[for_kv_map](https://github.com/Manishearth/rust-clippy/wiki#for_kv_map) | warn | looping on a map using `iter` when `keys` or `values` would do
239239
[for_loop_over_option](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_option) | warn | for-looping over an `Option`, which is more clearly expressed as an `if let`
240240
[for_loop_over_result](https://github.com/Manishearth/rust-clippy/wiki#for_loop_over_result) | warn | for-looping over a `Result`, which is more clearly expressed as an `if let`
241+
[get_unwrap](https://github.com/Manishearth/rust-clippy/wiki#get_unwrap) | warn | using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead
241242
[identity_op](https://github.com/Manishearth/rust-clippy/wiki#identity_op) | warn | using identity operations, e.g. `x + 0` or `y / 1`
242243
[if_let_redundant_pattern_matching](https://github.com/Manishearth/rust-clippy/wiki#if_let_redundant_pattern_matching) | warn | use the proper utility function avoiding an `if let`
243244
[if_let_some_result](https://github.com/Manishearth/rust-clippy/wiki#if_let_some_result) | warn | usage of `ok()` in `if let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
383383
methods::CLONE_ON_COPY,
384384
methods::EXTEND_FROM_SLICE,
385385
methods::FILTER_NEXT,
386+
methods::GET_UNWRAP,
386387
methods::ITER_NTH,
387388
methods::ITER_SKIP_NEXT,
388389
methods::NEW_RET_NO_SELF,

clippy_lints/src/methods.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,32 @@ declare_lint! {
464464
"using `.skip(x).next()` on an iterator"
465465
}
466466

467+
/// **What it does:** Checks for use of `.get().unwrap()` (or
468+
/// `.get_mut().unwrap`) on a standard library type which implements `Index`
469+
///
470+
/// **Why is this bad?** Using the Index trait (`[]`) is more clear and more
471+
/// concise.
472+
///
473+
/// **Known problems:** None.
474+
///
475+
/// **Example:**
476+
/// ```rust
477+
/// let some_vec = vec![0, 1, 2, 3];
478+
/// let last = some_vec.get(3).unwrap();
479+
/// *some_vec.get_mut(0).unwrap() = 1;
480+
/// ```
481+
/// The correct use would be:
482+
/// ```rust
483+
/// let some_vec = vec![0, 1, 2, 3];
484+
/// let last = some_vec[3];
485+
/// some_vec[0] = 1;
486+
/// ```
487+
declare_lint! {
488+
pub GET_UNWRAP,
489+
Warn,
490+
"using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
491+
}
492+
467493

468494
impl LintPass for Pass {
469495
fn get_lints(&self) -> LintArray {
@@ -487,11 +513,15 @@ impl LintPass for Pass {
487513
FILTER_NEXT,
488514
FILTER_MAP,
489515
ITER_NTH,
490-
ITER_SKIP_NEXT)
516+
ITER_SKIP_NEXT,
517+
GET_UNWRAP)
491518
}
492519
}
493520

494521
impl LateLintPass for Pass {
522+
#[allow(unused_attributes)]
523+
// ^ required because `cyclomatic_complexity` attribute shows up as unused
524+
#[cyclomatic_complexity = "30"]
495525
fn check_expr(&mut self, cx: &LateContext, expr: &hir::Expr) {
496526
if in_macro(cx, expr.span) {
497527
return;
@@ -500,7 +530,12 @@ impl LateLintPass for Pass {
500530
match expr.node {
501531
hir::ExprMethodCall(name, _, ref args) => {
502532
// Chain calls
503-
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
533+
// GET_UNWRAP needs to be checked before general `UNWRAP` lints
534+
if let Some(arglists) = method_chain_args(expr, &["get", "unwrap"]) {
535+
lint_get_unwrap(cx, expr, arglists[0], false);
536+
} else if let Some(arglists) = method_chain_args(expr, &["get_mut", "unwrap"]) {
537+
lint_get_unwrap(cx, expr, arglists[0], true);
538+
} else if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
504539
lint_unwrap(cx, expr, arglists[0]);
505540
} else if let Some(arglists) = method_chain_args(expr, &["ok", "expect"]) {
506541
lint_ok_expect(cx, expr, arglists[0]);
@@ -818,6 +853,43 @@ fn lint_iter_nth(cx: &LateContext, expr: &hir::Expr, iter_args: &MethodArgs, is_
818853
);
819854
}
820855

856+
fn lint_get_unwrap(cx: &LateContext, expr: &hir::Expr, get_args: &MethodArgs, is_mut: bool) {
857+
// Note: we don't want to lint `get_mut().unwrap` for HashMap or BTreeMap,
858+
// because they do not implement `IndexMut`
859+
let expr_ty = cx.tcx.expr_ty(&get_args[0]);
860+
let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() {
861+
"slice"
862+
} else if match_type(cx, expr_ty, &paths::VEC) {
863+
"Vec"
864+
} else if match_type(cx, expr_ty, &paths::VEC_DEQUE) {
865+
"VecDeque"
866+
} else if !is_mut && match_type(cx, expr_ty, &paths::HASHMAP) {
867+
"HashMap"
868+
} else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) {
869+
"BTreeMap"
870+
} else {
871+
return; // caller is not a type that we want to lint
872+
};
873+
874+
let mut_str = if is_mut { "_mut" } else { "" };
875+
let borrow_str = if is_mut { "&mut " } else { "&" };
876+
span_lint_and_then(
877+
cx,
878+
GET_UNWRAP,
879+
expr.span,
880+
&format!("called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise",
881+
mut_str, caller_type),
882+
|db| {
883+
db.span_suggestion(
884+
expr.span,
885+
"try this",
886+
format!("{}{}[{}]", borrow_str, snippet(cx, get_args[0].span, "_"),
887+
snippet(cx, get_args[1].span, "_"))
888+
);
889+
}
890+
);
891+
}
892+
821893
fn lint_iter_skip_next(cx: &LateContext, expr: &hir::Expr){
822894
// lint if caller of skip is an Iterator
823895
if match_trait_method(cx, expr, &paths::ITERATOR) {

tests/compile-fail/methods.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::collections::HashMap;
1010
use std::collections::HashSet;
1111
use std::collections::VecDeque;
1212
use std::ops::Mul;
13+
use std::iter::FromIterator;
1314

1415
struct T;
1516

@@ -388,6 +389,70 @@ fn iter_skip_next() {
388389
let _ = foo.filter().skip(42).next();
389390
}
390391

392+
struct GetFalsePositive {
393+
arr: [u32; 3],
394+
}
395+
396+
impl GetFalsePositive {
397+
fn get(&self, pos: usize) -> Option<&u32> { self.arr.get(pos) }
398+
fn get_mut(&mut self, pos: usize) -> Option<&mut u32> { self.arr.get_mut(pos) }
399+
}
400+
401+
/// Checks implementation of `GET_UNWRAP` lint
402+
fn get_unwrap() {
403+
let mut some_slice = &mut [0, 1, 2, 3];
404+
let mut some_vec = vec![0, 1, 2, 3];
405+
let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect();
406+
let mut some_hashmap: HashMap<u8, char> = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]);
407+
let mut some_btreemap: BTreeMap<u8, char> = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]);
408+
let mut false_positive = GetFalsePositive { arr: [0, 1, 2] };
409+
410+
{ // Test `get().unwrap()`
411+
let _ = some_slice.get(0).unwrap();
412+
//~^ERROR called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise
413+
//~|HELP try this
414+
//~|SUGGESTION some_slice[0]
415+
let _ = some_vec.get(0).unwrap();
416+
//~^ERROR called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise
417+
//~|HELP try this
418+
//~|SUGGESTION some_vec[0]
419+
let _ = some_vecdeque.get(0).unwrap();
420+
//~^ERROR called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
421+
//~|HELP try this
422+
//~|SUGGESTION some_vecdeque[0]
423+
let _ = some_hashmap.get(&1).unwrap();
424+
//~^ERROR called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise
425+
//~|HELP try this
426+
//~|SUGGESTION some_hashmap[&1]
427+
let _ = some_btreemap.get(&1).unwrap();
428+
//~^ERROR called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise
429+
//~|HELP try this
430+
//~|SUGGESTION some_btreemap[&1]
431+
432+
let _ = false_positive.get(0).unwrap();
433+
}
434+
435+
{ // Test `get_mut().unwrap()`
436+
*some_slice.get_mut(0).unwrap() = 1;
437+
//~^ERROR called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise
438+
//~|HELP try this
439+
//~|SUGGESTION &mut some_slice[0]
440+
*some_vec.get_mut(0).unwrap() = 1;
441+
//~^ERROR called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise
442+
//~|HELP try this
443+
//~|SUGGESTION &mut some_vec[0]
444+
*some_vecdeque.get_mut(0).unwrap() = 1;
445+
//~^ERROR called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise
446+
//~|HELP try this
447+
//~|SUGGESTION &mut some_vecdeque[0]
448+
449+
// Check false positives
450+
*some_hashmap.get_mut(&1).unwrap() = 'b';
451+
*some_btreemap.get_mut(&1).unwrap() = 'b';
452+
*false_positive.get_mut(0).unwrap() = 1;
453+
}
454+
}
455+
391456

392457
#[allow(similar_names)]
393458
fn main() {

0 commit comments

Comments
 (0)