Skip to content

Commit 8f0edba

Browse files
authored
Merge pull request #2815 from darArch/master
Warn if non-trivial work is done inside .expect
2 parents 8fe90e4 + c6fb473 commit 8f0edba

File tree

4 files changed

+235
-27
lines changed

4 files changed

+235
-27
lines changed

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
592592
methods::OK_EXPECT,
593593
methods::OPTION_MAP_OR_NONE,
594594
methods::OR_FUN_CALL,
595+
methods::EXPECT_FUN_CALL,
595596
methods::SEARCH_IS_SOME,
596597
methods::SHOULD_IMPLEMENT_TRAIT,
597598
methods::SINGLE_CHAR_PATTERN,
@@ -907,6 +908,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
907908
loops::UNUSED_COLLECT,
908909
methods::ITER_NTH,
909910
methods::OR_FUN_CALL,
911+
methods::EXPECT_FUN_CALL,
910912
methods::SINGLE_CHAR_PATTERN,
911913
misc::CMP_OWNED,
912914
mutex_atomic::MUTEX_ATOMIC,

clippy_lints/src/methods.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use std::fmt;
77
use std::iter;
88
use syntax::ast;
99
use syntax::codemap::{Span, BytePos};
10-
use crate::utils::{get_arg_name, get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_self, is_self_ty,
11-
iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method,
10+
use crate::utils::{get_arg_name, get_trait_def_id, implements_trait, in_external_macro, in_macro, is_copy, is_expn_of, is_self,
11+
is_self_ty, iter_input_pats, last_path_segment, match_def_path, match_path, match_qpath, match_trait_method,
1212
match_type, method_chain_args, match_var, return_ty, remove_blocks, same_tys, single_segment_path, snippet,
1313
span_lint, span_lint_and_sugg, span_lint_and_then, span_note_and_lint, walk_ptrs_ty, walk_ptrs_ty_depth};
1414
use crate::utils::paths;
@@ -329,6 +329,36 @@ declare_clippy_lint! {
329329
"using any `*or` method with a function call, which suggests `*or_else`"
330330
}
331331

332+
/// **What it does:** Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
333+
/// etc., and suggests to use `unwrap_or_else` instead
334+
///
335+
/// **Why is this bad?** The function will always be called.
336+
///
337+
/// **Known problems:** If the function has side-effects, not calling it will
338+
/// change the semantic of the program, but you shouldn't rely on that anyway.
339+
///
340+
/// **Example:**
341+
/// ```rust
342+
/// foo.expect(&format("Err {}: {}", err_code, err_msg))
343+
/// ```
344+
/// or
345+
/// ```rust
346+
/// foo.expect(format("Err {}: {}", err_code, err_msg).as_str())
347+
/// ```
348+
/// this can instead be written:
349+
/// ```rust
350+
/// foo.unwrap_or_else(|_| panic!("Err {}: {}", err_code, err_msg))
351+
/// ```
352+
/// or
353+
/// ```rust
354+
/// foo.unwrap_or_else(|_| panic!(format("Err {}: {}", err_code, err_msg).as_str()))
355+
/// ```
356+
declare_clippy_lint! {
357+
pub EXPECT_FUN_CALL,
358+
perf,
359+
"using any `expect` method with a function call"
360+
}
361+
332362
/// **What it does:** Checks for usage of `.clone()` on a `Copy` type.
333363
///
334364
/// **Why is this bad?** The only reason `Copy` types implement `Clone` is for
@@ -657,6 +687,7 @@ impl LintPass for Pass {
657687
RESULT_MAP_UNWRAP_OR_ELSE,
658688
OPTION_MAP_OR_NONE,
659689
OR_FUN_CALL,
690+
EXPECT_FUN_CALL,
660691
CHARS_NEXT_CMP,
661692
CHARS_LAST_CMP,
662693
CLONE_ON_COPY,
@@ -741,6 +772,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
741772
}
742773

743774
lint_or_fun_call(cx, expr, *method_span, &method_call.name.as_str(), args);
775+
lint_expect_fun_call(cx, expr, *method_span, &method_call.name.as_str(), args);
744776

745777
let self_ty = cx.tables.expr_ty_adjusted(&args[0]);
746778
if args.len() == 1 && method_call.name == "clone" {
@@ -964,6 +996,106 @@ fn lint_or_fun_call(cx: &LateContext, expr: &hir::Expr, method_span: Span, name:
964996
}
965997
}
966998

999+
/// Checks for the `EXPECT_FUN_CALL` lint.
1000+
fn lint_expect_fun_call(cx: &LateContext, expr: &hir::Expr, method_span: Span, name: &str, args: &[hir::Expr]) {
1001+
fn extract_format_args(arg: &hir::Expr) -> Option<&hir::HirVec<hir::Expr>> {
1002+
if let hir::ExprAddrOf(_, ref addr_of) = arg.node {
1003+
if let hir::ExprCall(ref inner_fun, ref inner_args) = addr_of.node {
1004+
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1 {
1005+
if let hir::ExprCall(_, ref format_args) = inner_args[0].node {
1006+
return Some(format_args);
1007+
}
1008+
}
1009+
}
1010+
}
1011+
1012+
None
1013+
}
1014+
1015+
fn generate_format_arg_snippet(cx: &LateContext, a: &hir::Expr) -> String {
1016+
if let hir::ExprAddrOf(_, ref format_arg) = a.node {
1017+
if let hir::ExprMatch(ref format_arg_expr, _, _) = format_arg.node {
1018+
if let hir::ExprTup(ref format_arg_expr_tup) = format_arg_expr.node {
1019+
return snippet(cx, format_arg_expr_tup[0].span, "..").into_owned();
1020+
}
1021+
}
1022+
};
1023+
1024+
snippet(cx, a.span, "..").into_owned()
1025+
}
1026+
1027+
fn check_general_case(
1028+
cx: &LateContext,
1029+
name: &str,
1030+
method_span: Span,
1031+
self_expr: &hir::Expr,
1032+
arg: &hir::Expr,
1033+
span: Span,
1034+
) {
1035+
if name != "expect" {
1036+
return;
1037+
}
1038+
1039+
let self_type = cx.tables.expr_ty(self_expr);
1040+
let known_types = &[&paths::OPTION, &paths::RESULT];
1041+
1042+
// if not a known type, return early
1043+
if known_types.iter().all(|&k| !match_type(cx, self_type, k)) {
1044+
return;
1045+
}
1046+
1047+
// don't lint for constant values
1048+
let owner_def = cx.tcx.hir.get_parent_did(arg.id);
1049+
let promotable = cx.tcx.rvalue_promotable_map(owner_def).contains(&arg.hir_id.local_id);
1050+
if promotable {
1051+
return;
1052+
}
1053+
1054+
let closure = if match_type(cx, self_type, &paths::OPTION) { "||" } else { "|_|" };
1055+
let span_replace_word = method_span.with_hi(span.hi());
1056+
1057+
if let Some(format_args) = extract_format_args(arg) {
1058+
let args_len = format_args.len();
1059+
let args: Vec<String> = format_args
1060+
.into_iter()
1061+
.take(args_len - 1)
1062+
.map(|a| generate_format_arg_snippet(cx, a))
1063+
.collect();
1064+
1065+
let sugg = args.join(", ");
1066+
1067+
span_lint_and_sugg(
1068+
cx,
1069+
EXPECT_FUN_CALL,
1070+
span_replace_word,
1071+
&format!("use of `{}` followed by a function call", name),
1072+
"try this",
1073+
format!("unwrap_or_else({} panic!({}))", closure, sugg),
1074+
);
1075+
1076+
return;
1077+
}
1078+
1079+
let sugg: Cow<_> = snippet(cx, arg.span, "..");
1080+
1081+
span_lint_and_sugg(
1082+
cx,
1083+
EXPECT_FUN_CALL,
1084+
span_replace_word,
1085+
&format!("use of `{}` followed by a function call", name),
1086+
"try this",
1087+
format!("unwrap_or_else({} panic!({}))", closure, sugg),
1088+
);
1089+
}
1090+
1091+
if args.len() == 2 {
1092+
match args[1].node {
1093+
hir::ExprLit(_) => {},
1094+
_ => check_general_case(cx, name, method_span, &args[0], &args[1], expr.span),
1095+
}
1096+
}
1097+
}
1098+
9671099
/// Checks for the `CLONE_ON_COPY` lint.
9681100
fn lint_clone_on_copy(cx: &LateContext, expr: &hir::Expr, arg: &hir::Expr, arg_ty: Ty) {
9691101
let ty = cx.tables.expr_ty(expr);

tests/ui/methods.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,54 @@ fn or_fun_call() {
342342
let _ = stringy.unwrap_or("".to_owned());
343343
}
344344

345+
/// Checks implementation of the `EXPECT_FUN_CALL` lint
346+
fn expect_fun_call() {
347+
struct Foo;
348+
349+
impl Foo {
350+
fn new() -> Self { Foo }
351+
352+
fn expect(&self, msg: &str) {
353+
panic!("{}", msg)
354+
}
355+
}
356+
357+
let with_some = Some("value");
358+
with_some.expect("error");
359+
360+
let with_none: Option<i32> = None;
361+
with_none.expect("error");
362+
363+
let error_code = 123_i32;
364+
let with_none_and_format: Option<i32> = None;
365+
with_none_and_format.expect(&format!("Error {}: fake error", error_code));
366+
367+
let with_none_and_as_str: Option<i32> = None;
368+
with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
369+
370+
let with_ok: Result<(), ()> = Ok(());
371+
with_ok.expect("error");
372+
373+
let with_err: Result<(), ()> = Err(());
374+
with_err.expect("error");
375+
376+
let error_code = 123_i32;
377+
let with_err_and_format: Result<(), ()> = Err(());
378+
with_err_and_format.expect(&format!("Error {}: fake error", error_code));
379+
380+
let with_err_and_as_str: Result<(), ()> = Err(());
381+
with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
382+
383+
let with_dummy_type = Foo::new();
384+
with_dummy_type.expect("another test string");
385+
386+
let with_dummy_type_and_format = Foo::new();
387+
with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code));
388+
389+
let with_dummy_type_and_as_str = Foo::new();
390+
with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
391+
}
392+
345393
/// Checks implementation of `ITER_NTH` lint
346394
fn iter_nth() {
347395
let mut some_vec = vec![0, 1, 2, 3];

tests/ui/methods.stderr

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -423,83 +423,109 @@ error: use of `unwrap_or` followed by a function call
423423
342 | let _ = stringy.unwrap_or("".to_owned());
424424
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())`
425425

426+
error: use of `expect` followed by a function call
427+
--> $DIR/methods.rs:365:26
428+
|
429+
365 | with_none_and_format.expect(&format!("Error {}: fake error", error_code));
430+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
431+
|
432+
= note: `-D expect-fun-call` implied by `-D warnings`
433+
434+
error: use of `expect` followed by a function call
435+
--> $DIR/methods.rs:368:26
436+
|
437+
368 | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
438+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!(format!("Error {}: fake error", error_code).as_str()))`
439+
440+
error: use of `expect` followed by a function call
441+
--> $DIR/methods.rs:378:25
442+
|
443+
378 | with_err_and_format.expect(&format!("Error {}: fake error", error_code));
444+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
445+
446+
error: use of `expect` followed by a function call
447+
--> $DIR/methods.rs:381:25
448+
|
449+
381 | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
450+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!(format!("Error {}: fake error", error_code).as_str()))`
451+
426452
error: called `.iter().nth()` on a Vec. Calling `.get()` is both faster and more readable
427-
--> $DIR/methods.rs:353:23
453+
--> $DIR/methods.rs:401:23
428454
|
429-
353 | let bad_vec = some_vec.iter().nth(3);
455+
401 | let bad_vec = some_vec.iter().nth(3);
430456
| ^^^^^^^^^^^^^^^^^^^^^^
431457
|
432458
= note: `-D iter-nth` implied by `-D warnings`
433459

434460
error: called `.iter().nth()` on a slice. Calling `.get()` is both faster and more readable
435-
--> $DIR/methods.rs:354:26
461+
--> $DIR/methods.rs:402:26
436462
|
437-
354 | let bad_slice = &some_vec[..].iter().nth(3);
463+
402 | let bad_slice = &some_vec[..].iter().nth(3);
438464
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
439465

440466
error: called `.iter().nth()` on a slice. Calling `.get()` is both faster and more readable
441-
--> $DIR/methods.rs:355:31
467+
--> $DIR/methods.rs:403:31
442468
|
443-
355 | let bad_boxed_slice = boxed_slice.iter().nth(3);
469+
403 | let bad_boxed_slice = boxed_slice.iter().nth(3);
444470
| ^^^^^^^^^^^^^^^^^^^^^^^^^
445471

446472
error: called `.iter().nth()` on a VecDeque. Calling `.get()` is both faster and more readable
447-
--> $DIR/methods.rs:356:29
473+
--> $DIR/methods.rs:404:29
448474
|
449-
356 | let bad_vec_deque = some_vec_deque.iter().nth(3);
475+
404 | let bad_vec_deque = some_vec_deque.iter().nth(3);
450476
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
451477

452478
error: called `.iter_mut().nth()` on a Vec. Calling `.get_mut()` is both faster and more readable
453-
--> $DIR/methods.rs:361:23
479+
--> $DIR/methods.rs:409:23
454480
|
455-
361 | let bad_vec = some_vec.iter_mut().nth(3);
481+
409 | let bad_vec = some_vec.iter_mut().nth(3);
456482
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
457483

458484
error: called `.iter_mut().nth()` on a slice. Calling `.get_mut()` is both faster and more readable
459-
--> $DIR/methods.rs:364:26
485+
--> $DIR/methods.rs:412:26
460486
|
461-
364 | let bad_slice = &some_vec[..].iter_mut().nth(3);
487+
412 | let bad_slice = &some_vec[..].iter_mut().nth(3);
462488
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
463489

464490
error: called `.iter_mut().nth()` on a VecDeque. Calling `.get_mut()` is both faster and more readable
465-
--> $DIR/methods.rs:367:29
491+
--> $DIR/methods.rs:415:29
466492
|
467-
367 | let bad_vec_deque = some_vec_deque.iter_mut().nth(3);
493+
415 | let bad_vec_deque = some_vec_deque.iter_mut().nth(3);
468494
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
469495

470496
error: called `skip(x).next()` on an iterator. This is more succinctly expressed by calling `nth(x)`
471-
--> $DIR/methods.rs:379:13
497+
--> $DIR/methods.rs:427:13
472498
|
473-
379 | let _ = some_vec.iter().skip(42).next();
499+
427 | let _ = some_vec.iter().skip(42).next();
474500
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
475501
|
476502
= note: `-D iter-skip-next` implied by `-D warnings`
477503

478504
error: called `skip(x).next()` on an iterator. This is more succinctly expressed by calling `nth(x)`
479-
--> $DIR/methods.rs:380:13
505+
--> $DIR/methods.rs:428:13
480506
|
481-
380 | let _ = some_vec.iter().cycle().skip(42).next();
507+
428 | let _ = some_vec.iter().cycle().skip(42).next();
482508
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
483509

484510
error: called `skip(x).next()` on an iterator. This is more succinctly expressed by calling `nth(x)`
485-
--> $DIR/methods.rs:381:13
511+
--> $DIR/methods.rs:429:13
486512
|
487-
381 | let _ = (1..10).skip(10).next();
513+
429 | let _ = (1..10).skip(10).next();
488514
| ^^^^^^^^^^^^^^^^^^^^^^^
489515

490516
error: called `skip(x).next()` on an iterator. This is more succinctly expressed by calling `nth(x)`
491-
--> $DIR/methods.rs:382:14
517+
--> $DIR/methods.rs:430:14
492518
|
493-
382 | let _ = &some_vec[..].iter().skip(3).next();
519+
430 | let _ = &some_vec[..].iter().skip(3).next();
494520
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
495521

496522
error: used unwrap() on an Option value. If you don't want to handle the None case gracefully, consider using expect() to provide a better panic message
497-
--> $DIR/methods.rs:391:13
523+
--> $DIR/methods.rs:439:13
498524
|
499-
391 | let _ = opt.unwrap();
525+
439 | let _ = opt.unwrap();
500526
| ^^^^^^^^^^^^
501527
|
502528
= note: `-D option-unwrap-used` implied by `-D warnings`
503529

504-
error: aborting due to 66 previous errors
530+
error: aborting due to 70 previous errors
505531

0 commit comments

Comments
 (0)