Skip to content

Commit 84c4112

Browse files
committed
Merge unwrap_or_else_default.rs into or_fun_call.rs
1 parent 0b63e95 commit 84c4112

9 files changed

+343
-191
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 20 additions & 13 deletions
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"]
499509
pub UNWRAP_OR_ELSE_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! {
@@ -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

Lines changed: 69 additions & 24 deletions
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_ELSE_DEFAULT};
1415

1516
/// Checks for the `OR_FUN_CALL` lint.
1617
#[allow(clippy::too_many_lines)]
@@ -24,44 +25,64 @@ 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+
}
43+
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+
}
51+
};
3852

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, &[])
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+
})
62+
} else {
63+
false
64+
}
4265
};
4366

4467
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"),
68+
if let Some(sugg) = match (name, call_expr.is_some()) {
69+
("unwrap_or", true) | ("unwrap_or_else", false) => Some("unwrap_or_default"),
70+
("or_insert", true) | ("or_insert_with", false) => Some("or_default"),
4971
_ => None,
5072
};
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;
5473
// needs to target Default::default in particular or be *::new and have a Default impl
5574
// available
56-
if (matches!(path, kw::Default) && is_default_default())
57-
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
58-
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+
};
5980
then {
6081
span_lint_and_sugg(
6182
cx,
62-
OR_FUN_CALL,
83+
UNWRAP_OR_ELSE_DEFAULT,
6384
method_span.with_hi(span.hi()),
64-
&format!("use of `{name}` followed by a call to `{path}`"),
85+
&format!("use of `{name}` to construct default value"),
6586
"try",
6687
format!("{sugg}()"),
6788
Applicability::MachineApplicable,
@@ -168,11 +189,16 @@ pub(super) fn check<'tcx>(
168189
match inner_arg.kind {
169190
hir::ExprKind::Call(fun, or_args) => {
170191
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) {
192+
if or_has_args
193+
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span)
194+
{
172195
let fun_span = if or_has_args { None } else { Some(fun.span) };
173196
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span);
174197
}
175198
},
199+
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
200+
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
201+
},
176202
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
177203
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
178204
},
@@ -189,3 +215,22 @@ pub(super) fn check<'tcx>(
189215
}
190216
}
191217
}
218+
219+
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
220+
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
221+
let body = cx.tcx.hir().body(body);
222+
223+
if body.params.is_empty()
224+
&& let hir::Expr{ kind, .. } = &body.value
225+
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
226+
&& ident == &symbol::Ident::from_str("to_string")
227+
&& let hir::Expr{ kind, .. } = self_arg
228+
&& let hir::ExprKind::Lit(lit) = kind
229+
&& let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node
230+
{
231+
return true;
232+
}
233+
}
234+
235+
false
236+
}

clippy_lints/src/methods/unwrap_or_else_default.rs

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

tests/ui/or_fun_call.fixed

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ mod issue8239 {
190190
acc.push_str(&f);
191191
acc
192192
})
193-
.unwrap_or_default();
193+
.unwrap_or(String::new());
194194
}
195195

196196
fn more_to_max_suggestion_highest_lines_1() {
@@ -203,7 +203,7 @@ mod issue8239 {
203203
acc.push_str(&f);
204204
acc
205205
})
206-
.unwrap_or_default();
206+
.unwrap_or(String::new());
207207
}
208208

209209
fn equal_to_max_suggestion_highest_lines() {
@@ -215,7 +215,7 @@ mod issue8239 {
215215
acc.push_str(&f);
216216
acc
217217
})
218-
.unwrap_or_default();
218+
.unwrap_or(String::new());
219219
}
220220

221221
fn less_than_max_suggestion_highest_lines() {
@@ -226,7 +226,7 @@ mod issue8239 {
226226
acc.push_str(&f);
227227
acc
228228
})
229-
.unwrap_or_default();
229+
.unwrap_or(String::new());
230230
}
231231
}
232232

@@ -257,4 +257,59 @@ mod issue8993 {
257257
}
258258
}
259259

260+
mod lazy {
261+
use super::*;
262+
263+
fn foo() {
264+
struct Foo;
265+
266+
impl Foo {
267+
fn new() -> Foo {
268+
Foo
269+
}
270+
}
271+
272+
struct FakeDefault;
273+
impl FakeDefault {
274+
fn default() -> Self {
275+
FakeDefault
276+
}
277+
}
278+
279+
impl Default for FakeDefault {
280+
fn default() -> Self {
281+
FakeDefault
282+
}
283+
}
284+
285+
let with_new = Some(vec![1]);
286+
with_new.unwrap_or_default();
287+
288+
let with_default_trait = Some(1);
289+
with_default_trait.unwrap_or_default();
290+
291+
let with_default_type = Some(1);
292+
with_default_type.unwrap_or_default();
293+
294+
let real_default = None::<FakeDefault>;
295+
real_default.unwrap_or_default();
296+
297+
let mut map = HashMap::<u64, String>::new();
298+
map.entry(42).or_default();
299+
300+
let mut btree = BTreeMap::<u64, String>::new();
301+
btree.entry(42).or_default();
302+
303+
let stringy = Some(String::new());
304+
let _ = stringy.unwrap_or_default();
305+
306+
// negative tests
307+
let self_default = None::<FakeDefault>;
308+
self_default.unwrap_or_else(<FakeDefault>::default);
309+
310+
let without_default = Some(Foo);
311+
without_default.unwrap_or_else(Foo::new);
312+
}
313+
}
314+
260315
fn main() {}

0 commit comments

Comments
 (0)