Skip to content

Commit 9a409af

Browse files
Michael WrightGrzegorz
Michael Wright
authored and
Grzegorz
committed
Fix expect_fun_call lint suggestions
This commit corrects some bad suggestions produced by the `expect_fun_call` lint and enables `rust-fix` checking on the tests. Addresses rust-lang#3630
1 parent e3022dd commit 9a409af

File tree

4 files changed

+238
-116
lines changed

4 files changed

+238
-116
lines changed

clippy_lints/src/methods/mod.rs

+93-93
Original file line numberDiff line numberDiff line change
@@ -1148,28 +1148,6 @@ fn lint_or_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span: Spa
11481148

11491149
/// Checks for the `EXPECT_FUN_CALL` lint.
11501150
fn lint_expect_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span: Span, name: &str, args: &[hir::Expr]) {
1151-
fn extract_format_args(arg: &hir::Expr) -> Option<(&hir::Expr, &hir::Expr)> {
1152-
let arg = match &arg.node {
1153-
hir::ExprKind::AddrOf(_, expr) => expr,
1154-
hir::ExprKind::MethodCall(method_name, _, args)
1155-
if method_name.ident.name == "as_str" || method_name.ident.name == "as_ref" =>
1156-
{
1157-
&args[0]
1158-
},
1159-
_ => arg,
1160-
};
1161-
1162-
if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg.node {
1163-
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1 {
1164-
if let hir::ExprKind::Call(_, format_args) = &inner_args[0].node {
1165-
return Some((&format_args[0], &format_args[1]));
1166-
}
1167-
}
1168-
}
1169-
1170-
None
1171-
}
1172-
11731151
fn generate_format_arg_snippet(
11741152
cx: &LateContext<'_, '_>,
11751153
a: &hir::Expr,
@@ -1189,93 +1167,115 @@ fn lint_expect_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span:
11891167
unreachable!()
11901168
}
11911169

1192-
fn check_general_case(
1193-
cx: &LateContext<'_, '_>,
1194-
name: &str,
1195-
method_span: Span,
1196-
self_expr: &hir::Expr,
1197-
arg: &hir::Expr,
1198-
span: Span,
1199-
) {
1200-
fn is_call(node: &hir::ExprKind) -> bool {
1201-
match node {
1202-
hir::ExprKind::AddrOf(_, expr) => {
1203-
is_call(&expr.node)
1204-
},
1205-
hir::ExprKind::Call(..)
1206-
| hir::ExprKind::MethodCall(..)
1207-
// These variants are debatable or require further examination
1208-
| hir::ExprKind::If(..)
1209-
| hir::ExprKind::Match(..)
1210-
| hir::ExprKind::Block{ .. } => true,
1211-
_ => false,
1212-
}
1213-
}
1214-
1215-
if name != "expect" {
1216-
return;
1170+
fn is_call(node: &hir::ExprKind) -> bool {
1171+
match node {
1172+
hir::ExprKind::AddrOf(_, expr) => {
1173+
is_call(&expr.node)
1174+
},
1175+
hir::ExprKind::Call(..)
1176+
| hir::ExprKind::MethodCall(..)
1177+
// These variants are debatable or require further examination
1178+
| hir::ExprKind::If(..)
1179+
| hir::ExprKind::Match(..)
1180+
| hir::ExprKind::Block{ .. } => true,
1181+
_ => false,
12171182
}
1183+
}
12181184

1219-
let self_type = cx.tables.expr_ty(self_expr);
1220-
let known_types = &[&paths::OPTION, &paths::RESULT];
1221-
1222-
// if not a known type, return early
1223-
if known_types.iter().all(|&k| !match_type(cx, self_type, k)) {
1224-
return;
1225-
}
1185+
if args.len() != 2 || name != "expect" || !is_call(&args[1].node) {
1186+
return;
1187+
}
12261188

1227-
if !is_call(&arg.node) {
1228-
return;
1229-
}
1189+
let receiver_type = cx.tables.expr_ty(&args[0]);
1190+
let closure_args = if match_type(cx, receiver_type, &paths::OPTION) {
1191+
"||"
1192+
} else if match_type(cx, receiver_type, &paths::RESULT) {
1193+
"|_|"
1194+
} else {
1195+
return;
1196+
};
12301197

1231-
let closure = if match_type(cx, self_type, &paths::OPTION) {
1232-
"||"
1233-
} else {
1234-
"|_|"
1198+
// Strip off `&`, `as_ref()` and `as_str()` until we're left with either a `String` or `&str`
1199+
// which we call `arg_root`.
1200+
let mut arg_root = &args[1];
1201+
loop {
1202+
arg_root = match &arg_root.node {
1203+
hir::ExprKind::AddrOf(_, expr) => expr,
1204+
hir::ExprKind::MethodCall(method_name, _, call_args) => {
1205+
if call_args.len() == 1
1206+
&& (method_name.ident.name == "as_str" || method_name.ident.name == "as_ref")
1207+
&& {
1208+
let arg_type = cx.tables.expr_ty(&call_args[0]);
1209+
let base_type = walk_ptrs_ty(arg_type);
1210+
base_type.sty == ty::Str || match_type(cx, base_type, &paths::STRING)
1211+
}
1212+
{
1213+
&call_args[0]
1214+
} else {
1215+
break;
1216+
}
1217+
},
1218+
_ => break,
12351219
};
1236-
let span_replace_word = method_span.with_hi(span.hi());
1220+
}
12371221

1238-
if let Some((fmt_spec, fmt_args)) = extract_format_args(arg) {
1239-
let mut applicability = Applicability::MachineApplicable;
1240-
let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
1222+
let span_replace_word = method_span.with_hi(expr.span.hi());
12411223

1242-
args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
1224+
let mut applicability = Applicability::MachineApplicable;
12431225

1244-
let sugg = args.join(", ");
1226+
//Special handling for `format!` as arg_root
1227+
if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.node {
1228+
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1 {
1229+
if let hir::ExprKind::Call(_, format_args) = &inner_args[0].node {
1230+
let fmt_spec = &format_args[0];
1231+
let fmt_args = &format_args[1];
12451232

1246-
span_lint_and_sugg(
1247-
cx,
1248-
EXPECT_FUN_CALL,
1249-
span_replace_word,
1250-
&format!("use of `{}` followed by a function call", name),
1251-
"try this",
1252-
format!("unwrap_or_else({} panic!({}))", closure, sugg),
1253-
applicability,
1254-
);
1233+
let mut applicability = Applicability::MachineApplicable;
1234+
let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()];
12551235

1256-
return;
1257-
}
1236+
args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability));
12581237

1259-
let mut applicability = Applicability::MachineApplicable;
1260-
let sugg: Cow<'_, _> = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
1238+
let sugg = args.join(", ");
12611239

1262-
span_lint_and_sugg(
1263-
cx,
1264-
EXPECT_FUN_CALL,
1265-
span_replace_word,
1266-
&format!("use of `{}` followed by a function call", name),
1267-
"try this",
1268-
format!("unwrap_or_else({} {{ let msg = {}; panic!(msg) }}))", closure, sugg),
1269-
applicability,
1270-
);
1240+
span_lint_and_sugg(
1241+
cx,
1242+
EXPECT_FUN_CALL,
1243+
span_replace_word,
1244+
&format!("use of `{}` followed by a function call", name),
1245+
"try this",
1246+
format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
1247+
applicability,
1248+
);
1249+
1250+
return;
1251+
}
1252+
}
12711253
}
12721254

1273-
if args.len() == 2 {
1274-
match args[1].node {
1275-
hir::ExprKind::Lit(_) => {},
1276-
_ => check_general_case(cx, name, method_span, &args[0], &args[1], expr.span),
1255+
// If root_arg is `&'static str` or `String` we can use it directly in the `panic!` call otherwise
1256+
// we must use `to_string` to convert it.
1257+
let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
1258+
let arg_root_ty = cx.tables.expr_ty(arg_root);
1259+
let mut requires_conv = !match_type(cx, arg_root_ty, &paths::STRING);
1260+
if let ty::Ref(ty::ReStatic, ty, ..) = arg_root_ty.sty {
1261+
if ty.sty == ty::Str {
1262+
requires_conv = false;
12771263
}
1264+
};
1265+
1266+
if requires_conv {
1267+
arg_root_snippet.to_mut().push_str(".to_string()");
12781268
}
1269+
1270+
span_lint_and_sugg(
1271+
cx,
1272+
EXPECT_FUN_CALL,
1273+
span_replace_word,
1274+
&format!("use of `{}` followed by a function call", name),
1275+
"try this",
1276+
format!("unwrap_or_else({} {{ panic!({}) }})", closure_args, arg_root_snippet),
1277+
applicability,
1278+
);
12791279
}
12801280

12811281
/// Checks for the `CLONE_ON_COPY` lint.

tests/ui/expect_fun_call.fixed

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::expect_fun_call)]
4+
5+
/// Checks implementation of the `EXPECT_FUN_CALL` lint
6+
7+
fn main() {
8+
struct Foo;
9+
10+
impl Foo {
11+
fn new() -> Self {
12+
Foo
13+
}
14+
15+
fn expect(&self, msg: &str) {
16+
panic!("{}", msg)
17+
}
18+
}
19+
20+
let with_some = Some("value");
21+
with_some.expect("error");
22+
23+
let with_none: Option<i32> = None;
24+
with_none.expect("error");
25+
26+
let error_code = 123_i32;
27+
let with_none_and_format: Option<i32> = None;
28+
with_none_and_format.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
29+
30+
let with_none_and_as_str: Option<i32> = None;
31+
with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
32+
33+
let with_ok: Result<(), ()> = Ok(());
34+
with_ok.expect("error");
35+
36+
let with_err: Result<(), ()> = Err(());
37+
with_err.expect("error");
38+
39+
let error_code = 123_i32;
40+
let with_err_and_format: Result<(), ()> = Err(());
41+
with_err_and_format.unwrap_or_else(|_| panic!("Error {}: fake error", error_code));
42+
43+
let with_err_and_as_str: Result<(), ()> = Err(());
44+
with_err_and_as_str.unwrap_or_else(|_| panic!("Error {}: fake error", error_code));
45+
46+
let with_dummy_type = Foo::new();
47+
with_dummy_type.expect("another test string");
48+
49+
let with_dummy_type_and_format = Foo::new();
50+
with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code));
51+
52+
let with_dummy_type_and_as_str = Foo::new();
53+
with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
54+
55+
//Issue #2937
56+
Some("foo").unwrap_or_else(|| panic!("{} {}", 1, 2));
57+
58+
//Issue #2979 - this should not lint
59+
{
60+
let msg = "bar";
61+
Some("foo").expect(msg);
62+
}
63+
64+
{
65+
fn get_string() -> String {
66+
"foo".to_string()
67+
}
68+
69+
fn get_static_str() -> &'static str {
70+
"foo"
71+
}
72+
73+
fn get_non_static_str(_: &u32) -> &str {
74+
"foo"
75+
}
76+
77+
Some("foo").unwrap_or_else(|| { panic!(get_string()) });
78+
Some("foo").unwrap_or_else(|| { panic!(get_string()) });
79+
Some("foo").unwrap_or_else(|| { panic!(get_string()) });
80+
81+
Some("foo").unwrap_or_else(|| { panic!(get_static_str()) });
82+
Some("foo").unwrap_or_else(|| { panic!(get_non_static_str(&0).to_string()) });
83+
}
84+
}

tests/ui/expect_fun_call.rs

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
// run-rustfix
2+
13
#![warn(clippy::expect_fun_call)]
2-
#![allow(clippy::useless_format)]
34

45
/// Checks implementation of the `EXPECT_FUN_CALL` lint
56
6-
fn expect_fun_call() {
7+
fn main() {
78
struct Foo;
89

910
impl Foo {
@@ -51,14 +52,33 @@ fn expect_fun_call() {
5152
let with_dummy_type_and_as_str = Foo::new();
5253
with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
5354

55+
//Issue #2937
56+
Some("foo").expect(format!("{} {}", 1, 2).as_ref());
57+
5458
//Issue #2979 - this should not lint
55-
let msg = "bar";
56-
Some("foo").expect(msg);
59+
{
60+
let msg = "bar";
61+
Some("foo").expect(msg);
62+
}
5763

58-
Some("foo").expect({ &format!("error") });
59-
Some("foo").expect(format!("error").as_ref());
64+
{
65+
fn get_string() -> String {
66+
"foo".to_string()
67+
}
6068

61-
Some("foo").expect(format!("{} {}", 1, 2).as_ref());
62-
}
69+
fn get_static_str() -> &'static str {
70+
"foo"
71+
}
72+
73+
fn get_non_static_str(_: &u32) -> &str {
74+
"foo"
75+
}
6376

64-
fn main() {}
77+
Some("foo").expect(&get_string());
78+
Some("foo").expect(get_string().as_ref());
79+
Some("foo").expect(get_string().as_str());
80+
81+
Some("foo").expect(get_static_str());
82+
Some("foo").expect(get_non_static_str(&0));
83+
}
84+
}

0 commit comments

Comments
 (0)