Skip to content

Commit 1aac778

Browse files
committed
Auto merge of #13085 - J-ZhengLi:issue12973, r=llogiq
make [`or_fun_call`] recursive. fixes: #12973 --- changelog: make [`or_fun_call`] recursive.
2 parents 0cbbee1 + cc1bb8f commit 1aac778

File tree

4 files changed

+183
-47
lines changed

4 files changed

+183
-47
lines changed

clippy_lints/src/methods/or_fun_call.rs

+64-46
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
24
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
35
use clippy_utils::source::snippet_with_context;
46
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item};
5-
use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment};
7+
use clippy_utils::visitors::for_each_expr;
8+
use clippy_utils::{
9+
contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment, peel_blocks,
10+
};
611
use rustc_errors::Applicability;
712
use rustc_lint::LateContext;
813
use rustc_middle::ty;
@@ -13,7 +18,7 @@ use {rustc_ast as ast, rustc_hir as hir};
1318
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
1419

1520
/// Checks for the `OR_FUN_CALL` lint.
16-
#[allow(clippy::too_many_lines)]
21+
#[expect(clippy::too_many_lines)]
1722
pub(super) fn check<'tcx>(
1823
cx: &LateContext<'tcx>,
1924
expr: &hir::Expr<'_>,
@@ -26,7 +31,6 @@ pub(super) fn check<'tcx>(
2631
/// `or_insert(T::new())` or `or_insert(T::default())`.
2732
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
2833
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
29-
#[allow(clippy::too_many_arguments)]
3034
fn check_unwrap_or_default(
3135
cx: &LateContext<'_>,
3236
name: &str,
@@ -123,8 +127,8 @@ pub(super) fn check<'tcx>(
123127
}
124128

125129
/// Checks for `*or(foo())`.
126-
#[allow(clippy::too_many_arguments)]
127-
fn check_general_case<'tcx>(
130+
#[expect(clippy::too_many_arguments)]
131+
fn check_or_fn_call<'tcx>(
128132
cx: &LateContext<'tcx>,
129133
name: &str,
130134
method_span: Span,
@@ -135,7 +139,7 @@ pub(super) fn check<'tcx>(
135139
span: Span,
136140
// None if lambda is required
137141
fun_span: Option<Span>,
138-
) {
142+
) -> bool {
139143
// (path, fn_has_argument, methods, suffix)
140144
const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
141145
(sym::BTreeEntry, false, &["or_insert"], "with"),
@@ -185,54 +189,68 @@ pub(super) fn check<'tcx>(
185189
format!("{name}_{suffix}({sugg})"),
186190
app,
187191
);
188-
}
189-
}
190-
191-
let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| {
192-
if let hir::ExprKind::Block(
193-
hir::Block {
194-
stmts: [],
195-
expr: Some(expr),
196-
..
197-
},
198-
_,
199-
) = arg.kind
200-
{
201-
expr
192+
true
202193
} else {
203-
arg
194+
false
204195
}
205-
};
196+
}
206197

207198
if let [arg] = args {
208-
let inner_arg = extract_inner_arg(arg);
209-
match inner_arg.kind {
210-
hir::ExprKind::Call(fun, or_args) => {
211-
let or_has_args = !or_args.is_empty();
212-
if or_has_args
213-
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span)
214-
{
215-
let fun_span = if or_has_args { None } else { Some(fun.span) };
216-
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
217-
}
218-
},
219-
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
220-
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
221-
},
222-
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
223-
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
224-
},
225-
_ => (),
226-
}
199+
let inner_arg = peel_blocks(arg);
200+
for_each_expr(cx, inner_arg, |ex| {
201+
// `or_fun_call` lint needs to take nested expr into account,
202+
// but `unwrap_or_default` lint doesn't, we don't want something like:
203+
// `opt.unwrap_or(Foo { inner: String::default(), other: 1 })` to get replaced by
204+
// `opt.unwrap_or_default()`.
205+
let is_nested_expr = ex.hir_id != inner_arg.hir_id;
206+
207+
let is_triggered = match ex.kind {
208+
hir::ExprKind::Call(fun, fun_args) => {
209+
let inner_fun_has_args = !fun_args.is_empty();
210+
let fun_span = if inner_fun_has_args || is_nested_expr {
211+
None
212+
} else {
213+
Some(fun.span)
214+
};
215+
(!inner_fun_has_args
216+
&& !is_nested_expr
217+
&& check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span))
218+
|| check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, fun_span)
219+
},
220+
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) if !is_nested_expr => {
221+
check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span)
222+
},
223+
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
224+
check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, None)
225+
},
226+
_ => false,
227+
};
228+
229+
if is_triggered {
230+
ControlFlow::Break(())
231+
} else {
232+
ControlFlow::Continue(())
233+
}
234+
});
227235
}
228236

229237
// `map_or` takes two arguments
230238
if let [arg, lambda] = args {
231-
let inner_arg = extract_inner_arg(arg);
232-
if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind {
233-
let fun_span = if or_args.is_empty() { Some(fun.span) } else { None };
234-
check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span);
235-
}
239+
let inner_arg = peel_blocks(arg);
240+
for_each_expr(cx, inner_arg, |ex| {
241+
let is_top_most_expr = ex.hir_id == inner_arg.hir_id;
242+
if let hir::ExprKind::Call(fun, fun_args) = ex.kind {
243+
let fun_span = if fun_args.is_empty() && is_top_most_expr {
244+
Some(fun.span)
245+
} else {
246+
None
247+
};
248+
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) {
249+
return ControlFlow::Break(());
250+
}
251+
}
252+
ControlFlow::Continue(())
253+
});
236254
}
237255
}
238256

tests/ui/or_fun_call.fixed

+35
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,39 @@ mod issue_10228 {
330330
}
331331
}
332332

333+
// issue #12973
334+
fn fn_call_in_nested_expr() {
335+
struct Foo {
336+
val: String,
337+
}
338+
339+
fn f() -> i32 {
340+
1
341+
}
342+
let opt: Option<i32> = Some(1);
343+
344+
//~v ERROR: use of `unwrap_or` followed by a function call
345+
let _ = opt.unwrap_or_else(f); // suggest `.unwrap_or_else(f)`
346+
//
347+
//~v ERROR: use of `unwrap_or` followed by a function call
348+
let _ = opt.unwrap_or_else(|| f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
349+
//
350+
//~v ERROR: use of `unwrap_or` followed by a function call
351+
let _ = opt.unwrap_or_else(|| {
352+
let x = f();
353+
x + 1
354+
});
355+
//~v ERROR: use of `map_or` followed by a function call
356+
let _ = opt.map_or_else(|| f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
357+
//
358+
//~v ERROR: use of `unwrap_or` to construct default value
359+
let _ = opt.unwrap_or_default();
360+
361+
let opt_foo = Some(Foo {
362+
val: String::from("123"),
363+
});
364+
//~v ERROR: use of `unwrap_or` followed by a function call
365+
let _ = opt_foo.unwrap_or_else(|| Foo { val: String::default() });
366+
}
367+
333368
fn main() {}

tests/ui/or_fun_call.rs

+35
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,39 @@ mod issue_10228 {
330330
}
331331
}
332332

333+
// issue #12973
334+
fn fn_call_in_nested_expr() {
335+
struct Foo {
336+
val: String,
337+
}
338+
339+
fn f() -> i32 {
340+
1
341+
}
342+
let opt: Option<i32> = Some(1);
343+
344+
//~v ERROR: use of `unwrap_or` followed by a function call
345+
let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)`
346+
//
347+
//~v ERROR: use of `unwrap_or` followed by a function call
348+
let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
349+
//
350+
//~v ERROR: use of `unwrap_or` followed by a function call
351+
let _ = opt.unwrap_or({
352+
let x = f();
353+
x + 1
354+
});
355+
//~v ERROR: use of `map_or` followed by a function call
356+
let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
357+
//
358+
//~v ERROR: use of `unwrap_or` to construct default value
359+
let _ = opt.unwrap_or({ i32::default() });
360+
361+
let opt_foo = Some(Foo {
362+
val: String::from("123"),
363+
});
364+
//~v ERROR: use of `unwrap_or` followed by a function call
365+
let _ = opt_foo.unwrap_or(Foo { val: String::default() });
366+
}
367+
333368
fn main() {}

tests/ui/or_fun_call.stderr

+49-1
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,53 @@ error: use of `unwrap_or_else` to construct default value
196196
LL | let _ = stringy.unwrap_or_else(String::new);
197197
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()`
198198

199-
error: aborting due to 32 previous errors
199+
error: use of `unwrap_or` followed by a function call
200+
--> tests/ui/or_fun_call.rs:345:17
201+
|
202+
LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)`
203+
| ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)`
204+
205+
error: use of `unwrap_or` followed by a function call
206+
--> tests/ui/or_fun_call.rs:348:17
207+
|
208+
LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
209+
| ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)`
210+
211+
error: use of `unwrap_or` followed by a function call
212+
--> tests/ui/or_fun_call.rs:351:17
213+
|
214+
LL | let _ = opt.unwrap_or({
215+
| _________________^
216+
LL | | let x = f();
217+
LL | | x + 1
218+
LL | | });
219+
| |______^
220+
|
221+
help: try
222+
|
223+
LL ~ let _ = opt.unwrap_or_else(|| {
224+
LL + let x = f();
225+
LL + x + 1
226+
LL ~ });
227+
|
228+
229+
error: use of `map_or` followed by a function call
230+
--> tests/ui/or_fun_call.rs:356:17
231+
|
232+
LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
233+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)`
234+
235+
error: use of `unwrap_or` to construct default value
236+
--> tests/ui/or_fun_call.rs:359:17
237+
|
238+
LL | let _ = opt.unwrap_or({ i32::default() });
239+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()`
240+
241+
error: use of `unwrap_or` followed by a function call
242+
--> tests/ui/or_fun_call.rs:365:21
243+
|
244+
LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() });
245+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })`
246+
247+
error: aborting due to 38 previous errors
200248

0 commit comments

Comments
 (0)