Skip to content

Commit 5864200

Browse files
committed
Merge unwrap_or_else_default.rs into or_fun_call.rs
1 parent be15e60 commit 5864200

15 files changed

+923
-190
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
388388
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
389389
crate::methods::UNNECESSARY_SORT_BY_INFO,
390390
crate::methods::UNNECESSARY_TO_OWNED_INFO,
391-
crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,
391+
crate::methods::UNWRAP_OR_DEFAULT_INFO,
392392
crate::methods::UNWRAP_USED_INFO,
393393
crate::methods::USELESS_ASREF_INFO,
394394
crate::methods::VEC_RESIZE_TO_ZERO_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ mod unnecessary_join;
9292
mod unnecessary_lazy_eval;
9393
mod unnecessary_sort_by;
9494
mod unnecessary_to_owned;
95-
mod unwrap_or_else_default;
9695
mod unwrap_used;
9796
mod useless_asref;
9897
mod utils;
@@ -438,29 +437,40 @@ declare_clippy_lint! {
438437

439438
declare_clippy_lint! {
440439
/// ### What it does
441-
/// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
442-
/// `Result` values.
440+
/// Checks for usages of the following functions with an argument that constructs a default value
441+
/// (e.g., `Default::default` or `String::new`):
442+
/// - `unwrap_or`
443+
/// - `unwrap_or_else`
444+
/// - `or_insert`
445+
/// - `or_insert_with`
443446
///
444447
/// ### Why is this bad?
445-
/// Readability, these can be written as `_.unwrap_or_default`, which is
446-
/// simpler and more concise.
448+
/// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default`
449+
/// in place of `or_insert`/`or_insert_with`, is simpler and more concise.
450+
///
451+
/// ### Known problems
452+
/// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a
453+
/// heuristic to try to identify such cases. However, the heuristic can produce false negatives.
447454
///
448455
/// ### Examples
449456
/// ```rust
450457
/// # let x = Some(1);
451-
/// x.unwrap_or_else(Default::default);
452-
/// x.unwrap_or_else(u32::default);
458+
/// # let mut map = std::collections::HashMap::<u64, String>::new();
459+
/// x.unwrap_or(Default::default());
460+
/// map.entry(42).or_insert_with(String::new);
453461
/// ```
454462
///
455463
/// Use instead:
456464
/// ```rust
457465
/// # let x = Some(1);
466+
/// # let mut map = std::collections::HashMap::<u64, String>::new();
458467
/// x.unwrap_or_default();
468+
/// map.entry(42).or_default();
459469
/// ```
460470
#[clippy::version = "1.56.0"]
461-
pub UNWRAP_OR_ELSE_DEFAULT,
471+
pub UNWRAP_OR_DEFAULT,
462472
style,
463-
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
473+
"using `.unwrap_or`, etc. with an argument that constructs a default value"
464474
}
465475

466476
declare_clippy_lint! {
@@ -3191,7 +3201,7 @@ impl_lint_pass!(Methods => [
31913201
SHOULD_IMPLEMENT_TRAIT,
31923202
WRONG_SELF_CONVENTION,
31933203
OK_EXPECT,
3194-
UNWRAP_OR_ELSE_DEFAULT,
3204+
UNWRAP_OR_DEFAULT,
31953205
MAP_UNWRAP_OR,
31963206
RESULT_MAP_OR_INTO_OPTION,
31973207
OPTION_MAP_OR_NONE,
@@ -3765,7 +3775,6 @@ impl Methods {
37653775
Some(("map", recv, [map_arg], _, _))
37663776
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
37673777
_ => {
3768-
unwrap_or_else_default::check(cx, expr, recv, u_arg);
37693778
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
37703779
},
37713780
},

clippy_lints/src/methods/or_fun_call.rs

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
33
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
4-
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
5-
use clippy_utils::{contains_return, is_trait_item, last_path_segment};
4+
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};
66
use if_chain::if_chain;
7+
use rustc_ast as ast;
78
use rustc_errors::Applicability;
89
use rustc_hir as hir;
910
use rustc_lint::LateContext;
11+
use rustc_middle::ty::{self, EarlyBinder};
1012
use rustc_span::source_map::Span;
11-
use rustc_span::symbol::{kw, sym, Symbol};
13+
use rustc_span::symbol::{self, sym, Symbol};
1214
use std::borrow::Cow;
1315

14-
use super::OR_FUN_CALL;
16+
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
1517

1618
/// Checks for the `OR_FUN_CALL` lint.
1719
#[allow(clippy::too_many_lines)]
@@ -25,45 +27,67 @@ pub(super) fn check<'tcx>(
2527
) {
2628
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
2729
/// `or_insert(T::new())` or `or_insert(T::default())`.
30+
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
31+
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
2832
#[allow(clippy::too_many_arguments)]
2933
fn check_unwrap_or_default(
3034
cx: &LateContext<'_>,
3135
name: &str,
36+
receiver: &hir::Expr<'_>,
3237
fun: &hir::Expr<'_>,
33-
arg: &hir::Expr<'_>,
34-
or_has_args: bool,
38+
call_expr: Option<&hir::Expr<'_>>,
3539
span: Span,
3640
method_span: Span,
3741
) -> bool {
38-
let is_default_default = || is_trait_item(cx, fun, sym::Default);
42+
if !expr_type_is_certain(cx, receiver) {
43+
return false;
44+
}
45+
46+
let is_new = |fun: &hir::Expr<'_>| {
47+
if let hir::ExprKind::Path(ref qpath) = fun.kind {
48+
let path = last_path_segment(qpath).ident.name;
49+
matches!(path, sym::new)
50+
} else {
51+
false
52+
}
53+
};
3954

40-
let implements_default = |arg, default_trait_id| {
41-
let arg_ty = cx.typeck_results().expr_ty(arg);
42-
implements_trait(cx, arg_ty, default_trait_id, &[])
55+
let output_type_implements_default = |fun| {
56+
let fun_ty = cx.typeck_results().expr_ty(fun);
57+
if let ty::FnDef(def_id, substs) = fun_ty.kind() {
58+
let output_ty = EarlyBinder(cx.tcx.fn_sig(def_id).output())
59+
.subst(cx.tcx, substs)
60+
.skip_binder();
61+
cx.tcx
62+
.get_diagnostic_item(sym::Default)
63+
.map_or(false, |default_trait_id| {
64+
implements_trait(cx, output_ty, default_trait_id, substs)
65+
})
66+
} else {
67+
false
68+
}
4369
};
4470

4571
if_chain! {
46-
if !or_has_args;
47-
if let Some(sugg) = match name {
48-
"unwrap_or" => Some("unwrap_or_default"),
49-
"or_insert" => Some("or_default"),
72+
if let Some(sugg) = match (name, call_expr.is_some()) {
73+
("unwrap_or", true) | ("unwrap_or_else", false) => Some("unwrap_or_default"),
74+
("or_insert", true) | ("or_insert_with", false) => Some("or_default"),
5075
_ => None,
5176
};
52-
if let hir::ExprKind::Path(ref qpath) = fun.kind;
53-
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
54-
let path = last_path_segment(qpath).ident.name;
5577
// needs to target Default::default in particular or be *::new and have a Default impl
5678
// available
57-
if (matches!(path, kw::Default) && is_default_default())
58-
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
59-
79+
if (is_new(fun) && output_type_implements_default(fun))
80+
|| match call_expr {
81+
Some(call_expr) => is_default_equivalent(cx, call_expr),
82+
None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun),
83+
};
6084
then {
6185
span_lint_and_sugg(
6286
cx,
63-
OR_FUN_CALL,
87+
UNWRAP_OR_DEFAULT,
6488
method_span.with_hi(span.hi()),
65-
&format!("use of `{name}` followed by a call to `{path}`"),
66-
"try this",
89+
&format!("use of `{name}` to construct default value"),
90+
"try",
6791
format!("{sugg}()"),
6892
Applicability::MachineApplicable,
6993
);
@@ -153,7 +177,7 @@ pub(super) fn check<'tcx>(
153177
OR_FUN_CALL,
154178
span_replace_word,
155179
&format!("use of `{name}` followed by a function call"),
156-
"try this",
180+
"try",
157181
format!("{name}_{suffix}({sugg})"),
158182
Applicability::HasPlaceholders,
159183
);
@@ -182,11 +206,16 @@ pub(super) fn check<'tcx>(
182206
match inner_arg.kind {
183207
hir::ExprKind::Call(fun, or_args) => {
184208
let or_has_args = !or_args.is_empty();
185-
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
209+
if or_has_args
210+
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span)
211+
{
186212
let fun_span = if or_has_args { None } else { Some(fun.span) };
187213
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
188214
}
189215
},
216+
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
217+
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
218+
},
190219
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
191220
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
192221
},
@@ -203,3 +232,22 @@ pub(super) fn check<'tcx>(
203232
}
204233
}
205234
}
235+
236+
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
237+
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
238+
let body = cx.tcx.hir().body(body);
239+
240+
if body.params.is_empty()
241+
&& let hir::Expr{ kind, .. } = &body.value
242+
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
243+
&& ident == &symbol::Ident::from_str("to_string")
244+
&& let hir::Expr{ kind, .. } = self_arg
245+
&& let hir::ExprKind::Lit(lit) = kind
246+
&& let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node
247+
{
248+
return true;
249+
}
250+
}
251+
252+
false
253+
}

clippy_lints/src/methods/unwrap_or_else_default.rs

Lines changed: 0 additions & 66 deletions
This file was deleted.

clippy_lints/src/renamed_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
2828
("clippy::single_char_push_str", "clippy::single_char_add_str"),
2929
("clippy::stutter", "clippy::module_name_repetitions"),
3030
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
31+
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
3132
("clippy::zero_width_space", "clippy::invisible_characters"),
3233
("clippy::drop_bounds", "drop_bounds"),
3334
("clippy::for_loop_over_option", "for_loops_over_fallibles"),

clippy_utils/src/ty.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ use std::iter;
3030

3131
use crate::{match_def_path, path_res, paths};
3232

33+
mod type_certainty;
34+
pub use type_certainty::expr_type_is_certain;
35+
3336
/// Checks if the given type implements copy.
3437
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
3538
ty.is_copy_modulo_regions(cx.tcx, cx.param_env)

0 commit comments

Comments
 (0)