Skip to content

Commit e4923c2

Browse files
committed
Auto merge of rust-lang#10120 - smoelius:or_insert_with, r=blyxyas
`unwrap_or_else_default` -> `unwrap_or_default` and improve resulting lint Resolves rust-lang#10080 (though it doesn't implement exactly what's described there) This PR does the following: 1. Merges `unwrap_or_else_default.rs`'s code into `or_fun_call.rs` 2. Extracts the code to handle `unwrap_or(/* default value */)` and similar, and moves it into `unwrap_or_else_default` 3. Implements the missing functionality from rust-lang#9342, e.g.,, to handle `or_insert_with(Default::default)` 4. Renames `unwrap_or_else_default` to `unwrap_or_default` (since the "new" lint handles both `unwrap_or` and `unwrap_or_else`, it seemed sensible to use the shortened name) This PR is currently two commits. The first implements 1-3, the second implements 4. A word about 2: the `or_fun_call` lint currently produces warnings like the following: ``` error: use of `unwrap_or` followed by a call to `new` --> $DIR/or_fun_call.rs:56:14 | LL | with_new.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` ``` To me, such warnings look like they should come from `unwrap_or_else_default`, not `or_fun_call`, especially since `or_fun_call` is [in the nursery](rust-lang/rust-clippy#9829). --- changelog: Move: Renamed `unwrap_or_else_default` to [`unwrap_or_default`] [rust-lang#10120](rust-lang/rust-clippy#10120) changelog: Enhancement: [`unwrap_or_default`]: Now handles more functions, like `or_insert_with` [rust-lang#10120](rust-lang/rust-clippy#10120) <!-- changelog_checked-->
2 parents 5877504 + 99202a0 commit e4923c2

15 files changed

+429
-266
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5370,6 +5370,7 @@ Released 2018-09-13
53705370
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
53715371
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
53725372
[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
5373+
[`unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default
53735374
[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
53745375
[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
53755376
[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms

clippy_lints/src/declared_lints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
425425
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
426426
crate::methods::UNNECESSARY_SORT_BY_INFO,
427427
crate::methods::UNNECESSARY_TO_OWNED_INFO,
428-
crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,
428+
crate::methods::UNWRAP_OR_DEFAULT_INFO,
429429
crate::methods::UNWRAP_USED_INFO,
430430
crate::methods::USELESS_ASREF_INFO,
431431
crate::methods::VEC_RESIZE_TO_ZERO_INFO,

clippy_lints/src/methods/mod.rs

+22-15
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ mod unnecessary_lazy_eval;
103103
mod unnecessary_literal_unwrap;
104104
mod unnecessary_sort_by;
105105
mod unnecessary_to_owned;
106-
mod unwrap_or_else_default;
107106
mod unwrap_used;
108107
mod useless_asref;
109108
mod utils;
@@ -476,29 +475,40 @@ declare_clippy_lint! {
476475

477476
declare_clippy_lint! {
478477
/// ### What it does
479-
/// Checks for usage of `_.unwrap_or_else(Default::default)` on `Option` and
480-
/// `Result` values.
478+
/// Checks for usages of the following functions with an argument that constructs a default value
479+
/// (e.g., `Default::default` or `String::new`):
480+
/// - `unwrap_or`
481+
/// - `unwrap_or_else`
482+
/// - `or_insert`
483+
/// - `or_insert_with`
481484
///
482485
/// ### Why is this bad?
483-
/// Readability, these can be written as `_.unwrap_or_default`, which is
484-
/// simpler and more concise.
486+
/// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default`
487+
/// in place of `or_insert`/`or_insert_with`, is simpler and more concise.
488+
///
489+
/// ### Known problems
490+
/// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a
491+
/// heuristic to try to identify such cases. However, the heuristic can produce false negatives.
485492
///
486493
/// ### Examples
487494
/// ```rust
488495
/// # let x = Some(1);
489-
/// x.unwrap_or_else(Default::default);
490-
/// x.unwrap_or_else(u32::default);
496+
/// # let mut map = std::collections::HashMap::<u64, String>::new();
497+
/// x.unwrap_or(Default::default());
498+
/// map.entry(42).or_insert_with(String::new);
491499
/// ```
492500
///
493501
/// Use instead:
494502
/// ```rust
495503
/// # let x = Some(1);
504+
/// # let mut map = std::collections::HashMap::<u64, String>::new();
496505
/// x.unwrap_or_default();
506+
/// map.entry(42).or_default();
497507
/// ```
498508
#[clippy::version = "1.56.0"]
499-
pub UNWRAP_OR_ELSE_DEFAULT,
509+
pub UNWRAP_OR_DEFAULT,
500510
style,
501-
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
511+
"using `.unwrap_or`, etc. with an argument that constructs a default value"
502512
}
503513

504514
declare_clippy_lint! {
@@ -3495,7 +3505,7 @@ impl_lint_pass!(Methods => [
34953505
SHOULD_IMPLEMENT_TRAIT,
34963506
WRONG_SELF_CONVENTION,
34973507
OK_EXPECT,
3498-
UNWRAP_OR_ELSE_DEFAULT,
3508+
UNWRAP_OR_DEFAULT,
34993509
MAP_UNWRAP_OR,
35003510
RESULT_MAP_OR_INTO_OPTION,
35013511
OPTION_MAP_OR_NONE,
@@ -3756,8 +3766,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
37563766
then {
37573767
let first_arg_span = first_arg_ty.span;
37583768
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
3759-
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
3760-
.self_ty();
3769+
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
37613770
wrong_self_convention::check(
37623771
cx,
37633772
item.ident.name.as_str(),
@@ -3774,8 +3783,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
37743783
if item.ident.name == sym::new;
37753784
if let TraitItemKind::Fn(_, _) = item.kind;
37763785
let ret_ty = return_ty(cx, item.owner_id);
3777-
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
3778-
.self_ty();
3786+
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
37793787
if !ret_ty.contains(self_ty);
37803788

37813789
then {
@@ -4134,7 +4142,6 @@ impl Methods {
41344142
Some(("map", recv, [map_arg], _, _))
41354143
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
41364144
_ => {
4137-
unwrap_or_else_default::check(cx, expr, recv, u_arg);
41384145
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
41394146
},
41404147
}

clippy_lints/src/methods/or_fun_call.rs

+83-39
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
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_with_context;
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;
77
use rustc_errors::Applicability;
8-
use rustc_hir as hir;
98
use rustc_lint::LateContext;
9+
use rustc_middle::ty;
1010
use rustc_span::source_map::Span;
11-
use rustc_span::symbol::{kw, sym, Symbol};
11+
use rustc_span::symbol::{self, sym, Symbol};
12+
use {rustc_ast as ast, rustc_hir as hir};
1213

13-
use super::OR_FUN_CALL;
14+
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
1415

1516
/// Checks for the `OR_FUN_CALL` lint.
1617
#[allow(clippy::too_many_lines)]
@@ -24,53 +25,72 @@ pub(super) fn check<'tcx>(
2425
) {
2526
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
2627
/// `or_insert(T::new())` or `or_insert(T::default())`.
28+
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
29+
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
2730
#[allow(clippy::too_many_arguments)]
2831
fn check_unwrap_or_default(
2932
cx: &LateContext<'_>,
3033
name: &str,
34+
receiver: &hir::Expr<'_>,
3135
fun: &hir::Expr<'_>,
32-
arg: &hir::Expr<'_>,
33-
or_has_args: bool,
36+
call_expr: Option<&hir::Expr<'_>>,
3437
span: Span,
3538
method_span: Span,
3639
) -> bool {
37-
let is_default_default = || is_trait_item(cx, fun, sym::Default);
40+
if !expr_type_is_certain(cx, receiver) {
41+
return false;
42+
}
3843

39-
let implements_default = |arg, default_trait_id| {
40-
let arg_ty = cx.typeck_results().expr_ty(arg);
41-
implements_trait(cx, arg_ty, default_trait_id, &[])
44+
let is_new = |fun: &hir::Expr<'_>| {
45+
if let hir::ExprKind::Path(ref qpath) = fun.kind {
46+
let path = last_path_segment(qpath).ident.name;
47+
matches!(path, sym::new)
48+
} else {
49+
false
50+
}
4251
};
4352

44-
if_chain! {
45-
if !or_has_args;
46-
if let Some(sugg) = match name {
47-
"unwrap_or" => Some("unwrap_or_default"),
48-
"or_insert" => Some("or_default"),
49-
_ => None,
50-
};
51-
if let hir::ExprKind::Path(ref qpath) = fun.kind;
52-
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
53-
let path = last_path_segment(qpath).ident.name;
54-
// needs to target Default::default in particular or be *::new and have a Default impl
55-
// available
56-
if (matches!(path, kw::Default) && is_default_default())
57-
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
58-
59-
then {
60-
span_lint_and_sugg(
61-
cx,
62-
OR_FUN_CALL,
63-
method_span.with_hi(span.hi()),
64-
&format!("use of `{name}` followed by a call to `{path}`"),
65-
"try",
66-
format!("{sugg}()"),
67-
Applicability::MachineApplicable,
68-
);
69-
70-
true
53+
let output_type_implements_default = |fun| {
54+
let fun_ty = cx.typeck_results().expr_ty(fun);
55+
if let ty::FnDef(def_id, substs) = fun_ty.kind() {
56+
let output_ty = cx.tcx.fn_sig(def_id).subst(cx.tcx, substs).skip_binder().output();
57+
cx.tcx
58+
.get_diagnostic_item(sym::Default)
59+
.map_or(false, |default_trait_id| {
60+
implements_trait(cx, output_ty, default_trait_id, substs)
61+
})
7162
} else {
7263
false
7364
}
65+
};
66+
67+
let sugg = match (name, call_expr.is_some()) {
68+
("unwrap_or", true) | ("unwrap_or_else", false) => "unwrap_or_default",
69+
("or_insert", true) | ("or_insert_with", false) => "or_default",
70+
_ => return false,
71+
};
72+
73+
// needs to target Default::default in particular or be *::new and have a Default impl
74+
// available
75+
if (is_new(fun) && output_type_implements_default(fun))
76+
|| match call_expr {
77+
Some(call_expr) => is_default_equivalent(cx, call_expr),
78+
None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun),
79+
}
80+
{
81+
span_lint_and_sugg(
82+
cx,
83+
UNWRAP_OR_DEFAULT,
84+
method_span.with_hi(span.hi()),
85+
&format!("use of `{name}` to construct default value"),
86+
"try",
87+
format!("{sugg}()"),
88+
Applicability::MachineApplicable,
89+
);
90+
91+
true
92+
} else {
93+
false
7494
}
7595
}
7696

@@ -168,11 +188,16 @@ pub(super) fn check<'tcx>(
168188
match inner_arg.kind {
169189
hir::ExprKind::Call(fun, or_args) => {
170190
let or_has_args = !or_args.is_empty();
171-
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
191+
if or_has_args
192+
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span)
193+
{
172194
let fun_span = if or_has_args { None } else { Some(fun.span) };
173195
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
174196
}
175197
},
198+
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
199+
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
200+
},
176201
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
177202
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
178203
},
@@ -189,3 +214,22 @@ pub(super) fn check<'tcx>(
189214
}
190215
}
191216
}
217+
218+
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
219+
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
220+
let body = cx.tcx.hir().body(body);
221+
222+
if body.params.is_empty()
223+
&& let hir::Expr{ kind, .. } = &body.value
224+
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
225+
&& ident.name == sym::to_string
226+
&& let hir::Expr{ kind, .. } = self_arg
227+
&& let hir::ExprKind::Lit(lit) = kind
228+
&& let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node
229+
{
230+
return true;
231+
}
232+
}
233+
234+
false
235+
}

clippy_lints/src/methods/unwrap_or_else_default.rs

-70
This file was deleted.

clippy_lints/src/renamed_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
3030
("clippy::single_char_push_str", "clippy::single_char_add_str"),
3131
("clippy::stutter", "clippy::module_name_repetitions"),
3232
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
33+
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
3334
("clippy::zero_width_space", "clippy::invisible_characters"),
3435
("clippy::cast_ref_to_mut", "cast_ref_to_mut"),
3536
("clippy::clone_double_ref", "suspicious_double_ref_op"),

0 commit comments

Comments
 (0)