Auto merge of #13085 - J-ZhengLi:issue12973, r=llogiq

make [`or_fun_call`] recursive.

fixes: #12973

---

changelog: make [`or_fun_call`] recursive.
This commit is contained in:
bors 2024-07-13 15:38:23 +00:00
commit 1aac7783ef
4 changed files with 183 additions and 47 deletions

View file

@ -1,8 +1,13 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval; use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment}; use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment, peel_blocks,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
@ -13,7 +18,7 @@ use {rustc_ast as ast, rustc_hir as hir};
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
/// Checks for the `OR_FUN_CALL` lint. /// Checks for the `OR_FUN_CALL` lint.
#[allow(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub(super) fn check<'tcx>( pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
@ -26,7 +31,6 @@ pub(super) fn check<'tcx>(
/// `or_insert(T::new())` or `or_insert(T::default())`. /// `or_insert(T::new())` or `or_insert(T::default())`.
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`. /// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
#[allow(clippy::too_many_arguments)]
fn check_unwrap_or_default( fn check_unwrap_or_default(
cx: &LateContext<'_>, cx: &LateContext<'_>,
name: &str, name: &str,
@ -123,8 +127,8 @@ pub(super) fn check<'tcx>(
} }
/// Checks for `*or(foo())`. /// Checks for `*or(foo())`.
#[allow(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
fn check_general_case<'tcx>( fn check_or_fn_call<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
name: &str, name: &str,
method_span: Span, method_span: Span,
@ -135,7 +139,7 @@ pub(super) fn check<'tcx>(
span: Span, span: Span,
// None if lambda is required // None if lambda is required
fun_span: Option<Span>, fun_span: Option<Span>,
) { ) -> bool {
// (path, fn_has_argument, methods, suffix) // (path, fn_has_argument, methods, suffix)
const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [ const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
(sym::BTreeEntry, false, &["or_insert"], "with"), (sym::BTreeEntry, false, &["or_insert"], "with"),
@ -185,54 +189,68 @@ pub(super) fn check<'tcx>(
format!("{name}_{suffix}({sugg})"), format!("{name}_{suffix}({sugg})"),
app, app,
); );
true
} else {
false
} }
} }
let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| {
if let hir::ExprKind::Block(
hir::Block {
stmts: [],
expr: Some(expr),
..
},
_,
) = arg.kind
{
expr
} else {
arg
}
};
if let [arg] = args { if let [arg] = args {
let inner_arg = extract_inner_arg(arg); let inner_arg = peel_blocks(arg);
match inner_arg.kind { for_each_expr(cx, inner_arg, |ex| {
hir::ExprKind::Call(fun, or_args) => { // `or_fun_call` lint needs to take nested expr into account,
let or_has_args = !or_args.is_empty(); // but `unwrap_or_default` lint doesn't, we don't want something like:
if or_has_args // `opt.unwrap_or(Foo { inner: String::default(), other: 1 })` to get replaced by
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span) // `opt.unwrap_or_default()`.
{ let is_nested_expr = ex.hir_id != inner_arg.hir_id;
let fun_span = if or_has_args { None } else { Some(fun.span) };
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span); let is_triggered = match ex.kind {
} hir::ExprKind::Call(fun, fun_args) => {
}, let inner_fun_has_args = !fun_args.is_empty();
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => { let fun_span = if inner_fun_has_args || is_nested_expr {
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span); None
}, } else {
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { Some(fun.span)
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None); };
}, (!inner_fun_has_args
_ => (), && !is_nested_expr
} && check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span))
|| check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, fun_span)
},
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) if !is_nested_expr => {
check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span)
},
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, None)
},
_ => false,
};
if is_triggered {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
});
} }
// `map_or` takes two arguments // `map_or` takes two arguments
if let [arg, lambda] = args { if let [arg, lambda] = args {
let inner_arg = extract_inner_arg(arg); let inner_arg = peel_blocks(arg);
if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind { for_each_expr(cx, inner_arg, |ex| {
let fun_span = if or_args.is_empty() { Some(fun.span) } else { None }; let is_top_most_expr = ex.hir_id == inner_arg.hir_id;
check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span); if let hir::ExprKind::Call(fun, fun_args) = ex.kind {
} let fun_span = if fun_args.is_empty() && is_top_most_expr {
Some(fun.span)
} else {
None
};
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) {
return ControlFlow::Break(());
}
}
ControlFlow::Continue(())
});
} }
} }

View file

@ -330,4 +330,39 @@ mod issue_10228 {
} }
} }
// issue #12973
fn fn_call_in_nested_expr() {
struct Foo {
val: String,
}
fn f() -> i32 {
1
}
let opt: Option<i32> = Some(1);
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or_else(f); // suggest `.unwrap_or_else(f)`
//
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or_else(|| f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
//
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or_else(|| {
let x = f();
x + 1
});
//~v ERROR: use of `map_or` followed by a function call
let _ = opt.map_or_else(|| f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
//
//~v ERROR: use of `unwrap_or` to construct default value
let _ = opt.unwrap_or_default();
let opt_foo = Some(Foo {
val: String::from("123"),
});
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt_foo.unwrap_or_else(|| Foo { val: String::default() });
}
fn main() {} fn main() {}

View file

@ -330,4 +330,39 @@ mod issue_10228 {
} }
} }
// issue #12973
fn fn_call_in_nested_expr() {
struct Foo {
val: String,
}
fn f() -> i32 {
1
}
let opt: Option<i32> = Some(1);
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)`
//
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
//
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt.unwrap_or({
let x = f();
x + 1
});
//~v ERROR: use of `map_or` followed by a function call
let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
//
//~v ERROR: use of `unwrap_or` to construct default value
let _ = opt.unwrap_or({ i32::default() });
let opt_foo = Some(Foo {
val: String::from("123"),
});
//~v ERROR: use of `unwrap_or` followed by a function call
let _ = opt_foo.unwrap_or(Foo { val: String::default() });
}
fn main() {} fn main() {}

View file

@ -196,5 +196,53 @@ error: use of `unwrap_or_else` to construct default value
LL | let _ = stringy.unwrap_or_else(String::new); LL | let _ = stringy.unwrap_or_else(String::new);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()`
error: aborting due to 32 previous errors error: use of `unwrap_or` followed by a function call
--> tests/ui/or_fun_call.rs:345:17
|
LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)`
| ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)`
error: use of `unwrap_or` followed by a function call
--> tests/ui/or_fun_call.rs:348:17
|
LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)`
| ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)`
error: use of `unwrap_or` followed by a function call
--> tests/ui/or_fun_call.rs:351:17
|
LL | let _ = opt.unwrap_or({
| _________________^
LL | | let x = f();
LL | | x + 1
LL | | });
| |______^
|
help: try
|
LL ~ let _ = opt.unwrap_or_else(|| {
LL + let x = f();
LL + x + 1
LL ~ });
|
error: use of `map_or` followed by a function call
--> tests/ui/or_fun_call.rs:356:17
|
LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)`
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)`
error: use of `unwrap_or` to construct default value
--> tests/ui/or_fun_call.rs:359:17
|
LL | let _ = opt.unwrap_or({ i32::default() });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()`
error: use of `unwrap_or` followed by a function call
--> tests/ui/or_fun_call.rs:365:21
|
LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })`
error: aborting due to 38 previous errors