Skip to content

Commit e62a6ca

Browse files
committed
Auto merge of #7516 - lf-:unwrap-or-default, r=xFrednet
Add `unwrap_or_else_default` lint --- *Please write a short comment explaining your change (or "none" for internal only changes)* changelog: Add a new [`unwrap_or_else_default`] style lint. This will catch `unwrap_or_else(Default::default)` on Result and Option and suggest `unwrap_or_default()` instead.
2 parents dd9fe5c + 295df88 commit e62a6ca

18 files changed

+361
-39
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2999,6 +2999,7 @@ Released 2018-09-13
29992999
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
30003000
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
30013001
[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
3002+
[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
30023003
[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
30033004
[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
30043005
[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
797797
methods::UNNECESSARY_FILTER_MAP,
798798
methods::UNNECESSARY_FOLD,
799799
methods::UNNECESSARY_LAZY_EVALUATIONS,
800+
methods::UNWRAP_OR_ELSE_DEFAULT,
800801
methods::UNWRAP_USED,
801802
methods::USELESS_ASREF,
802803
methods::WRONG_SELF_CONVENTION,
@@ -1341,6 +1342,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13411342
LintId::of(methods::UNNECESSARY_FILTER_MAP),
13421343
LintId::of(methods::UNNECESSARY_FOLD),
13431344
LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
1345+
LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
13441346
LintId::of(methods::USELESS_ASREF),
13451347
LintId::of(methods::WRONG_SELF_CONVENTION),
13461348
LintId::of(methods::ZST_OFFSET),
@@ -1535,6 +1537,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15351537
LintId::of(methods::STRING_EXTEND_CHARS),
15361538
LintId::of(methods::UNNECESSARY_FOLD),
15371539
LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
1540+
LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
15381541
LintId::of(methods::WRONG_SELF_CONVENTION),
15391542
LintId::of(misc::TOPLEVEL_REF_ARG),
15401543
LintId::of(misc::ZERO_PTR),

clippy_lints/src/methods/mod.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ mod uninit_assumed_init;
5656
mod unnecessary_filter_map;
5757
mod unnecessary_fold;
5858
mod unnecessary_lazy_eval;
59+
mod unwrap_or_else_default;
5960
mod unwrap_used;
6061
mod useless_asref;
6162
mod utils;
@@ -310,6 +311,31 @@ declare_clippy_lint! {
310311
"using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
311312
}
312313

314+
declare_clippy_lint! {
315+
/// ### What it does
316+
/// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
317+
/// `Result` values.
318+
///
319+
/// ### Why is this bad?
320+
/// Readability, these can be written as `_.unwrap_or_default`, which is
321+
/// simpler and more concise.
322+
///
323+
/// ### Examples
324+
/// ```rust
325+
/// # let x = Some(1);
326+
///
327+
/// // Bad
328+
/// x.unwrap_or_else(Default::default);
329+
/// x.unwrap_or_else(u32::default);
330+
///
331+
/// // Good
332+
/// x.unwrap_or_default();
333+
/// ```
334+
pub UNWRAP_OR_ELSE_DEFAULT,
335+
style,
336+
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
337+
}
338+
313339
declare_clippy_lint! {
314340
/// ### What it does
315341
/// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
@@ -1766,6 +1792,7 @@ impl_lint_pass!(Methods => [
17661792
SHOULD_IMPLEMENT_TRAIT,
17671793
WRONG_SELF_CONVENTION,
17681794
OK_EXPECT,
1795+
UNWRAP_OR_ELSE_DEFAULT,
17691796
MAP_UNWRAP_OR,
17701797
RESULT_MAP_OR_INTO_OPTION,
17711798
OPTION_MAP_OR_NONE,
@@ -2172,7 +2199,10 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
21722199
},
21732200
("unwrap_or_else", [u_arg]) => match method_call!(recv) {
21742201
Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {},
2175-
_ => unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"),
2202+
_ => {
2203+
unwrap_or_else_default::check(cx, expr, recv, u_arg);
2204+
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
2205+
},
21762206
},
21772207
_ => {},
21782208
}

clippy_lints/src/methods/or_fun_call.rs

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::eager_or_lazy::is_lazyness_candidate;
3+
use clippy_utils::is_trait_item;
34
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
4-
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type};
5+
use clippy_utils::ty::implements_trait;
6+
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
57
use clippy_utils::{contains_return, last_path_segment, paths};
68
use if_chain::if_chain;
79
use rustc_errors::Applicability;
@@ -34,15 +36,23 @@ pub(super) fn check<'tcx>(
3436
or_has_args: bool,
3537
span: Span,
3638
) -> bool {
39+
let is_default_default = || is_trait_item(cx, fun, sym::Default);
40+
41+
let implements_default = |arg, default_trait_id| {
42+
let arg_ty = cx.typeck_results().expr_ty(arg);
43+
implements_trait(cx, arg_ty, default_trait_id, &[])
44+
};
45+
3746
if_chain! {
3847
if !or_has_args;
3948
if name == "unwrap_or";
4049
if let hir::ExprKind::Path(ref qpath) = fun.kind;
41-
let path = last_path_segment(qpath).ident.name;
42-
if matches!(path, kw::Default | sym::new);
43-
let arg_ty = cx.typeck_results().expr_ty(arg);
4450
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
45-
if implements_trait(cx, arg_ty, default_trait_id, &[]);
51+
let path = last_path_segment(qpath).ident.name;
52+
// needs to target Default::default in particular or be *::new and have a Default impl
53+
// available
54+
if (matches!(path, kw::Default) && is_default_default())
55+
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
4656

4757
then {
4858
let mut applicability = Applicability::MachineApplicable;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! Lint for `some_result_or_option.unwrap_or_else(Default::default)`
2+
3+
use super::UNWRAP_OR_ELSE_DEFAULT;
4+
use clippy_utils::{
5+
diagnostics::span_lint_and_sugg, is_trait_item, source::snippet_with_applicability, ty::is_type_diagnostic_item,
6+
};
7+
use rustc_errors::Applicability;
8+
use rustc_hir as hir;
9+
use rustc_lint::LateContext;
10+
use rustc_span::sym;
11+
12+
pub(super) fn check<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
expr: &'tcx hir::Expr<'_>,
15+
recv: &'tcx hir::Expr<'_>,
16+
u_arg: &'tcx hir::Expr<'_>,
17+
) {
18+
// something.unwrap_or_else(Default::default)
19+
// ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg
20+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr
21+
let recv_ty = cx.typeck_results().expr_ty(recv);
22+
let is_option = is_type_diagnostic_item(cx, recv_ty, sym::option_type);
23+
let is_result = is_type_diagnostic_item(cx, recv_ty, sym::result_type);
24+
25+
if_chain! {
26+
if is_option || is_result;
27+
if is_trait_item(cx, u_arg, sym::Default);
28+
then {
29+
let mut applicability = Applicability::MachineApplicable;
30+
31+
span_lint_and_sugg(
32+
cx,
33+
UNWRAP_OR_ELSE_DEFAULT,
34+
expr.span,
35+
"use of `.unwrap_or_else(..)` to construct default value",
36+
"try",
37+
format!(
38+
"{}.unwrap_or_default()",
39+
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
40+
),
41+
applicability,
42+
);
43+
}
44+
}
45+
}

clippy_lints/src/needless_continue.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) {
422422
///
423423
/// is transformed to
424424
///
425-
/// ```ignore
425+
/// ```text
426426
/// {
427427
/// let x = 5;
428428
/// ```

clippy_lints/src/needless_for_each.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl LateLintPass<'_> for NeedlessForEach {
122122
/// 2. Detect use of `return` in `Loop` in the closure body.
123123
///
124124
/// NOTE: The functionality of this type is similar to
125-
/// [`crate::utilts::visitors::find_all_ret_expressions`], but we can't use
125+
/// [`clippy_utils::visitors::find_all_ret_expressions`], but we can't use
126126
/// `find_all_ret_expressions` instead of this type. The reasons are:
127127
/// 1. `find_all_ret_expressions` passes the argument of `ExprKind::Ret` to a callback, but what we
128128
/// need here is `ExprKind::Ret` itself.

clippy_utils/src/diagnostics.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
6565
///
6666
/// # Example
6767
///
68-
/// ```ignore
68+
/// ```text
6969
/// error: constant division of 0.0 with 0.0 will always result in NaN
7070
/// --> $DIR/zero_div_zero.rs:6:25
7171
/// |
@@ -103,7 +103,7 @@ pub fn span_lint_and_help<'a, T: LintContext>(
103103
///
104104
/// # Example
105105
///
106-
/// ```ignore
106+
/// ```text
107107
/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
108108
/// --> $DIR/drop_forget_ref.rs:10:5
109109
/// |
@@ -189,7 +189,7 @@ pub fn span_lint_hir_and_then(
189189
///
190190
/// # Example
191191
///
192-
/// ```ignore
192+
/// ```text
193193
/// error: This `.fold` can be more succinctly expressed as `.any`
194194
/// --> $DIR/methods.rs:390:13
195195
/// |

clippy_utils/src/higher.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<Ve
195195
/// - `assert!`, `assert_eq!` and `assert_ne!`
196196
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
197197
/// For example:
198-
/// `assert!(expr)` will return Some([expr])
199-
/// `debug_assert_eq!(a, b)` will return Some([a, b])
198+
/// `assert!(expr)` will return `Some([expr])`
199+
/// `debug_assert_eq!(a, b)` will return `Some([a, b])`
200200
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
201201
/// Try to match the AST for a pattern that contains a match, for example when two args are
202202
/// compared
@@ -283,7 +283,7 @@ pub struct FormatArgsExpn<'tcx> {
283283

284284
/// String literal expressions which represent the format string split by "{}"
285285
pub format_string_parts: &'tcx [Expr<'tcx>],
286-
/// Symbols corresponding to [`format_string_parts`]
286+
/// Symbols corresponding to [`Self::format_string_parts`]
287287
pub format_string_symbols: Vec<Symbol>,
288288
/// Expressions like `ArgumentV1::new(arg0, Debug::fmt)`
289289
pub args: &'tcx [Expr<'tcx>],

clippy_utils/src/lib.rs

+19
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,25 @@ pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol)
326326
.map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
327327
}
328328

329+
/// Checks if the given expression is a path referring an item on the trait
330+
/// that is marked with the given diagnostic item.
331+
///
332+
/// For checking method call expressions instead of path expressions, use
333+
/// [`is_trait_method`].
334+
///
335+
/// For example, this can be used to find if an expression like `u64::default`
336+
/// refers to an item of the trait `Default`, which is associated with the
337+
/// `diag_item` of `sym::Default`.
338+
pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
339+
if let hir::ExprKind::Path(ref qpath) = expr.kind {
340+
cx.qpath_res(qpath, expr.hir_id)
341+
.opt_def_id()
342+
.map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item))
343+
} else {
344+
false
345+
}
346+
}
347+
329348
pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
330349
match *path {
331350
QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),

clippy_utils/src/source.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<
168168
snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
169169
}
170170

171-
/// Same as `snippet`, but it adapts the applicability level by following rules:
171+
/// Same as [`snippet`], but it adapts the applicability level by following rules:
172172
///
173173
/// - Applicability level `Unspecified` will never be changed.
174174
/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.

clippy_utils/src/ty.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
114114

115115
/// Checks whether a type implements a trait.
116116
/// The function returns false in case the type contains an inference variable.
117-
/// See also `get_trait_def_id`.
117+
/// See also [`get_trait_def_id`](super::get_trait_def_id).
118118
pub fn implements_trait<'tcx>(
119119
cx: &LateContext<'tcx>,
120120
ty: Ty<'tcx>,

tests/ui/or_fun_call.fixed

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ fn or_fun_call() {
1818
}
1919
}
2020

21+
struct FakeDefault;
22+
impl FakeDefault {
23+
fn default() -> Self {
24+
FakeDefault
25+
}
26+
}
27+
28+
impl Default for FakeDefault {
29+
fn default() -> Self {
30+
FakeDefault
31+
}
32+
}
33+
2134
enum Enum {
2235
A(i32),
2336
}
@@ -53,6 +66,12 @@ fn or_fun_call() {
5366
let with_default_type = Some(1);
5467
with_default_type.unwrap_or_default();
5568

69+
let self_default = None::<FakeDefault>;
70+
self_default.unwrap_or_else(<FakeDefault>::default);
71+
72+
let real_default = None::<FakeDefault>;
73+
real_default.unwrap_or_default();
74+
5675
let with_vec = Some(vec![1]);
5776
with_vec.unwrap_or_default();
5877

tests/ui/or_fun_call.rs

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ fn or_fun_call() {
1818
}
1919
}
2020

21+
struct FakeDefault;
22+
impl FakeDefault {
23+
fn default() -> Self {
24+
FakeDefault
25+
}
26+
}
27+
28+
impl Default for FakeDefault {
29+
fn default() -> Self {
30+
FakeDefault
31+
}
32+
}
33+
2134
enum Enum {
2235
A(i32),
2336
}
@@ -53,6 +66,12 @@ fn or_fun_call() {
5366
let with_default_type = Some(1);
5467
with_default_type.unwrap_or(u64::default());
5568

69+
let self_default = None::<FakeDefault>;
70+
self_default.unwrap_or(<FakeDefault>::default());
71+
72+
let real_default = None::<FakeDefault>;
73+
real_default.unwrap_or(<FakeDefault as Default>::default());
74+
5675
let with_vec = Some(vec![1]);
5776
with_vec.unwrap_or(vec![]);
5877

0 commit comments

Comments
 (0)