Skip to content

Commit 07e2d96

Browse files
committed
Fix expect_fun_call producing invalid suggestions
1 parent 76118ec commit 07e2d96

File tree

4 files changed

+102
-136
lines changed

4 files changed

+102
-136
lines changed
Lines changed: 49 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
23
use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node};
34
use clippy_utils::source::snippet_with_applicability;
45
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
6+
use clippy_utils::{contains_return, peel_blocks};
57
use rustc_errors::Applicability;
68
use rustc_hir as hir;
79
use rustc_lint::LateContext;
8-
use rustc_middle::ty;
910
use rustc_span::symbol::sym;
1011
use rustc_span::{Span, Symbol};
1112
use std::borrow::Cow;
@@ -23,10 +24,10 @@ pub(super) fn check<'tcx>(
2324
receiver: &'tcx hir::Expr<'tcx>,
2425
args: &'tcx [hir::Expr<'tcx>],
2526
) {
26-
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
27+
// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
2728
// `&str`
2829
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
29-
let mut arg_root = arg;
30+
let mut arg_root = peel_blocks(arg);
3031
loop {
3132
arg_root = match &arg_root.kind {
3233
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
@@ -47,124 +48,55 @@ pub(super) fn check<'tcx>(
4748
arg_root
4849
}
4950

50-
// Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
51-
// converted to string.
52-
fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
53-
let arg_ty = cx.typeck_results().expr_ty(arg);
54-
if is_type_lang_item(cx, arg_ty, hir::LangItem::String) {
55-
return false;
56-
}
57-
if let ty::Ref(_, ty, ..) = arg_ty.kind()
58-
&& ty.is_str()
59-
&& can_be_static_str(cx, arg)
60-
{
61-
return false;
62-
}
63-
true
64-
}
51+
if name == sym::expect
52+
&& let [arg] = args
53+
&& let arg_root = get_arg_root(cx, arg)
54+
&& switch_to_lazy_eval(cx, arg_root)
55+
&& !contains_return(arg_root)
56+
{
57+
let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
58+
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
59+
"||"
60+
} else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
61+
"|_|"
62+
} else {
63+
return;
64+
};
6565

66-
// Check if an expression could have type `&'static str`, knowing that it
67-
// has type `&str` for some lifetime.
68-
fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
69-
match arg.kind {
70-
hir::ExprKind::Lit(_) => true,
71-
hir::ExprKind::Call(fun, _) => {
72-
if let hir::ExprKind::Path(ref p) = fun.kind {
73-
match cx.qpath_res(p, fun.hir_id) {
74-
hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
75-
cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder().kind(),
76-
ty::Ref(re, ..) if re.is_static(),
77-
),
78-
_ => false,
79-
}
80-
} else {
81-
false
82-
}
83-
},
84-
hir::ExprKind::MethodCall(..) => {
85-
cx.typeck_results()
86-
.type_dependent_def_id(arg.hir_id)
87-
.is_some_and(|method_id| {
88-
matches!(
89-
cx.tcx.fn_sig(method_id).instantiate_identity().output().skip_binder().kind(),
90-
ty::Ref(re, ..) if re.is_static()
91-
)
92-
})
93-
},
94-
hir::ExprKind::Path(ref p) => matches!(
95-
cx.qpath_res(p, arg.hir_id),
96-
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _)
97-
),
98-
_ => false,
99-
}
100-
}
66+
let span_replace_word = method_span.with_hi(expr.span.hi());
10167

102-
fn is_call(node: &hir::ExprKind<'_>) -> bool {
103-
match node {
104-
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
105-
is_call(&expr.kind)
106-
},
107-
hir::ExprKind::Call(..)
108-
| hir::ExprKind::MethodCall(..)
109-
// These variants are debatable or require further examination
110-
| hir::ExprKind::If(..)
111-
| hir::ExprKind::Match(..)
112-
| hir::ExprKind::Block{ .. } => true,
113-
_ => false,
114-
}
115-
}
68+
let mut applicability = Applicability::MachineApplicable;
11669

117-
if args.len() != 1 || name != sym::expect || !is_call(&args[0].kind) {
118-
return;
119-
}
120-
121-
let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
122-
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
123-
"||"
124-
} else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
125-
"|_|"
126-
} else {
127-
return;
128-
};
129-
130-
let arg_root = get_arg_root(cx, &args[0]);
131-
132-
let span_replace_word = method_span.with_hi(expr.span.hi());
133-
134-
let mut applicability = Applicability::MachineApplicable;
135-
136-
// Special handling for `format!` as arg_root
137-
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
138-
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
139-
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
140-
{
141-
let span = format_args_inputs_span(format_args);
142-
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
143-
span_lint_and_sugg(
144-
cx,
145-
EXPECT_FUN_CALL,
146-
span_replace_word,
147-
format!("function call inside of `{name}`"),
148-
"try",
149-
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
150-
applicability,
151-
);
70+
// Special handling for `format!` as arg_root
71+
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
72+
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
73+
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
74+
{
75+
let span = format_args_inputs_span(format_args);
76+
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
77+
span_lint_and_sugg(
78+
cx,
79+
EXPECT_FUN_CALL,
80+
span_replace_word,
81+
format!("function call inside of `{name}`"),
82+
"try",
83+
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
84+
applicability,
85+
);
86+
}
87+
return;
15288
}
153-
return;
154-
}
15589

156-
let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
157-
if requires_to_string(cx, arg_root) {
158-
arg_root_snippet.to_mut().push_str(".to_string()");
159-
}
90+
let arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
16091

161-
span_lint_and_sugg(
162-
cx,
163-
EXPECT_FUN_CALL,
164-
span_replace_word,
165-
format!("function call inside of `{name}`"),
166-
"try",
167-
format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"),
168-
applicability,
169-
);
92+
span_lint_and_sugg(
93+
cx,
94+
EXPECT_FUN_CALL,
95+
span_replace_word,
96+
format!("function call inside of `{name}`"),
97+
"try",
98+
format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"),
99+
applicability,
100+
);
101+
}
170102
}

tests/ui/expect_fun_call.fixed

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,23 @@ fn main() {
9090
"foo"
9191
}
9292

93-
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
93+
const fn const_evaluable() -> &'static str {
94+
"foo"
95+
}
96+
97+
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
9498
//~^ expect_fun_call
95-
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
99+
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
96100
//~^ expect_fun_call
97-
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
101+
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
98102
//~^ expect_fun_call
99103

100-
Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) });
104+
Some("foo").unwrap_or_else(|| panic!("{}", get_static_str()));
101105
//~^ expect_fun_call
102-
Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) });
106+
Some("foo").unwrap_or_else(|| panic!("{}", get_non_static_str(&0)));
103107
//~^ expect_fun_call
108+
109+
Some("foo").expect(const_evaluable());
104110
}
105111

106112
//Issue #3839
@@ -122,4 +128,15 @@ fn main() {
122128
let format_capture_and_value: Option<i32> = None;
123129
format_capture_and_value.unwrap_or_else(|| panic!("{error_code}, {}", 1));
124130
//~^ expect_fun_call
131+
132+
// Issue #15056
133+
let a = false;
134+
Some(5).expect(if a { "a" } else { "b" });
135+
136+
let return_in_expect: Option<i32> = None;
137+
return_in_expect.expect(if true {
138+
"Error"
139+
} else {
140+
return;
141+
});
125142
}

tests/ui/expect_fun_call.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ fn main() {
9090
"foo"
9191
}
9292

93+
const fn const_evaluable() -> &'static str {
94+
"foo"
95+
}
96+
9397
Some("foo").expect(&get_string());
9498
//~^ expect_fun_call
9599
Some("foo").expect(get_string().as_ref());
@@ -101,6 +105,8 @@ fn main() {
101105
//~^ expect_fun_call
102106
Some("foo").expect(get_non_static_str(&0));
103107
//~^ expect_fun_call
108+
109+
Some("foo").expect(const_evaluable());
104110
}
105111

106112
//Issue #3839
@@ -122,4 +128,15 @@ fn main() {
122128
let format_capture_and_value: Option<i32> = None;
123129
format_capture_and_value.expect(&format!("{error_code}, {}", 1));
124130
//~^ expect_fun_call
131+
132+
// Issue #15056
133+
let a = false;
134+
Some(5).expect(if a { "a" } else { "b" });
135+
136+
let return_in_expect: Option<i32> = None;
137+
return_in_expect.expect(if true {
138+
"Error"
139+
} else {
140+
return;
141+
});
125142
}

tests/ui/expect_fun_call.stderr

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,55 +38,55 @@ LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
3838
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
3939

4040
error: function call inside of `expect`
41-
--> tests/ui/expect_fun_call.rs:93:21
41+
--> tests/ui/expect_fun_call.rs:97:21
4242
|
4343
LL | Some("foo").expect(&get_string());
44-
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
44+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
4545

4646
error: function call inside of `expect`
47-
--> tests/ui/expect_fun_call.rs:95:21
47+
--> tests/ui/expect_fun_call.rs:99:21
4848
|
4949
LL | Some("foo").expect(get_string().as_ref());
50-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
50+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
5151

5252
error: function call inside of `expect`
53-
--> tests/ui/expect_fun_call.rs:97:21
53+
--> tests/ui/expect_fun_call.rs:101:21
5454
|
5555
LL | Some("foo").expect(get_string().as_str());
56-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
56+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
5757

5858
error: function call inside of `expect`
59-
--> tests/ui/expect_fun_call.rs:100:21
59+
--> tests/ui/expect_fun_call.rs:104:21
6060
|
6161
LL | Some("foo").expect(get_static_str());
62-
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_static_str()))`
6363

6464
error: function call inside of `expect`
65-
--> tests/ui/expect_fun_call.rs:102:21
65+
--> tests/ui/expect_fun_call.rs:106:21
6666
|
6767
LL | Some("foo").expect(get_non_static_str(&0));
68-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
68+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_non_static_str(&0)))`
6969

7070
error: function call inside of `expect`
71-
--> tests/ui/expect_fun_call.rs:107:16
71+
--> tests/ui/expect_fun_call.rs:113:16
7272
|
7373
LL | Some(true).expect(&format!("key {}, {}", 1, 2));
7474
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
7575

7676
error: function call inside of `expect`
77-
--> tests/ui/expect_fun_call.rs:114:17
77+
--> tests/ui/expect_fun_call.rs:120:17
7878
|
7979
LL | opt_ref.expect(&format!("{:?}", opt_ref));
8080
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{:?}", opt_ref))`
8181

8282
error: function call inside of `expect`
83-
--> tests/ui/expect_fun_call.rs:119:20
83+
--> tests/ui/expect_fun_call.rs:125:20
8484
|
8585
LL | format_capture.expect(&format!("{error_code}"));
8686
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}"))`
8787

8888
error: function call inside of `expect`
89-
--> tests/ui/expect_fun_call.rs:123:30
89+
--> tests/ui/expect_fun_call.rs:129:30
9090
|
9191
LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1));
9292
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))`

0 commit comments

Comments
 (0)