Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unwrap_or_else_default -> unwrap_or_default and improve resulting lint #10120

Merged
merged 3 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5366,6 +5366,7 @@ Released 2018-09-13
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
[`unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default
[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
crate::methods::UNNECESSARY_SORT_BY_INFO,
crate::methods::UNNECESSARY_TO_OWNED_INFO,
crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,
crate::methods::UNWRAP_OR_DEFAULT_INFO,
crate::methods::UNWRAP_USED_INFO,
crate::methods::USELESS_ASREF_INFO,
crate::methods::VEC_RESIZE_TO_ZERO_INFO,
Expand Down
37 changes: 22 additions & 15 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ mod unnecessary_lazy_eval;
mod unnecessary_literal_unwrap;
mod unnecessary_sort_by;
mod unnecessary_to_owned;
mod unwrap_or_else_default;
mod unwrap_used;
mod useless_asref;
mod utils;
Expand Down Expand Up @@ -476,29 +475,40 @@ declare_clippy_lint! {

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `_.unwrap_or_else(Default::default)` on `Option` and
/// `Result` values.
/// Checks for usages of the following functions with an argument that constructs a default value
/// (e.g., `Default::default` or `String::new`):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// (e.g., `Default::default` or `String::new`):
/// (e.g. `Default::default` or `String::new`):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are correct and used a similar amount of times in rustc (512 without the comma vs 680 with the comma). The preferred spelling differs from place to place though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a personal preference, but I feel like it's weird to have 2 punctuation signs right next to each other and it kinda worsens readability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally find it easier to read as it keeps the punctuation mark only being used at the end of a sentence. That's subjective though ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it's weird to have 2 punctuation signs right next to each other

@blyxyas Personally, I agree with you 100%.

But literally every English writing guide I have consulted says to put a ',' after "e.g.".

So I think the question comes down to: should Clippy comments follow English writing guides?

(And, to state the obvious, this is a total bike shed. 😀)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with either. It's bikeshedding and won't cause confusion anyway.

Copy link
Member

@blyxyas blyxyas Jul 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In American English, a comma is always placed after “i.e.” or “e.g.” In fact, this is a punctuation rule on which both the AP Style Book and the Chicago Manual of Style agree. However, in British English, a comma is not used after “i.e.” or “e.g.”

Mmmm... Guess it doesn't matter. It just looks weird, I don't know and honestly, it isn't that big of a problem.
We can continue designing the power plant, do what you consider the best 🤠

/// - `unwrap_or`
/// - `unwrap_or_else`
/// - `or_insert`
/// - `or_insert_with`
///
/// ### Why is this bad?
/// Readability, these can be written as `_.unwrap_or_default`, which is
/// simpler and more concise.
/// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default`
/// in place of `or_insert`/`or_insert_with`, is simpler and more concise.
///
/// ### Known problems
/// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a
/// heuristic to try to identify such cases. However, the heuristic can produce false negatives.
///
/// ### Examples
/// ```rust
/// # let x = Some(1);
/// x.unwrap_or_else(Default::default);
/// x.unwrap_or_else(u32::default);
/// # let mut map = std::collections::HashMap::<u64, String>::new();
/// x.unwrap_or(Default::default());
/// map.entry(42).or_insert_with(String::new);
/// ```
///
/// Use instead:
/// ```rust
/// # let x = Some(1);
/// # let mut map = std::collections::HashMap::<u64, String>::new();
/// x.unwrap_or_default();
/// map.entry(42).or_default();
/// ```
#[clippy::version = "1.56.0"]
pub UNWRAP_OR_ELSE_DEFAULT,
pub UNWRAP_OR_DEFAULT,
style,
"using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
"using `.unwrap_or`, etc. with an argument that constructs a default value"
}

declare_clippy_lint! {
Expand Down Expand Up @@ -3495,7 +3505,7 @@ impl_lint_pass!(Methods => [
SHOULD_IMPLEMENT_TRAIT,
WRONG_SELF_CONVENTION,
OK_EXPECT,
UNWRAP_OR_ELSE_DEFAULT,
UNWRAP_OR_DEFAULT,
MAP_UNWRAP_OR,
RESULT_MAP_OR_INTO_OPTION,
OPTION_MAP_OR_NONE,
Expand Down Expand Up @@ -3756,8 +3766,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
then {
let first_arg_span = first_arg_ty.span;
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
.self_ty();
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
wrong_self_convention::check(
cx,
item.ident.name.as_str(),
Expand All @@ -3774,8 +3783,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
if item.ident.name == sym::new;
if let TraitItemKind::Fn(_, _) = item.kind;
let ret_ty = return_ty(cx, item.owner_id);
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id())
.self_ty();
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
if !ret_ty.contains(self_ty);

then {
Expand Down Expand Up @@ -4134,7 +4142,6 @@ impl Methods {
Some(("map", recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
_ => {
unwrap_or_else_default::check(cx, expr, recv, u_arg);
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
},
}
Expand Down
93 changes: 69 additions & 24 deletions clippy_lints/src/methods/or_fun_call.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_trait_item, last_path_segment};
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 if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::symbol::{self, sym, Symbol};
use {rustc_ast as ast, rustc_hir as hir};

use super::OR_FUN_CALL;
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};

/// Checks for the `OR_FUN_CALL` lint.
#[allow(clippy::too_many_lines)]
Expand All @@ -24,44 +25,64 @@ pub(super) fn check<'tcx>(
) {
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
/// `or_insert(T::new())` or `or_insert(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)`.
#[allow(clippy::too_many_arguments)]
fn check_unwrap_or_default(
cx: &LateContext<'_>,
name: &str,
receiver: &hir::Expr<'_>,
fun: &hir::Expr<'_>,
arg: &hir::Expr<'_>,
or_has_args: bool,
call_expr: Option<&hir::Expr<'_>>,
span: Span,
method_span: Span,
) -> bool {
let is_default_default = || is_trait_item(cx, fun, sym::Default);
if !expr_type_is_certain(cx, receiver) {
return false;
}

let is_new = |fun: &hir::Expr<'_>| {
if let hir::ExprKind::Path(ref qpath) = fun.kind {
let path = last_path_segment(qpath).ident.name;
matches!(path, sym::new)
} else {
false
}
};

let implements_default = |arg, default_trait_id| {
let arg_ty = cx.typeck_results().expr_ty(arg);
implements_trait(cx, arg_ty, default_trait_id, &[])
let output_type_implements_default = |fun| {
let fun_ty = cx.typeck_results().expr_ty(fun);
if let ty::FnDef(def_id, substs) = fun_ty.kind() {
let output_ty = cx.tcx.fn_sig(def_id).subst(cx.tcx, substs).skip_binder().output();
cx.tcx
.get_diagnostic_item(sym::Default)
.map_or(false, |default_trait_id| {
implements_trait(cx, output_ty, default_trait_id, substs)
})
} else {
false
}
};

if_chain! {
if !or_has_args;
if let Some(sugg) = match name {
"unwrap_or" => Some("unwrap_or_default"),
"or_insert" => Some("or_default"),
if let Some(sugg) = match (name, call_expr.is_some()) {
("unwrap_or", true) | ("unwrap_or_else", false) => Some("unwrap_or_default"),
("or_insert", true) | ("or_insert_with", false) => Some("or_default"),
_ => None,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement could be replaced by an early return (the compiler probably optimizes this, but just to be sure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the change I made the one you had in mind?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :)

if let hir::ExprKind::Path(ref qpath) = fun.kind;
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
let path = last_path_segment(qpath).ident.name;
// needs to target Default::default in particular or be *::new and have a Default impl
// available
if (matches!(path, kw::Default) && is_default_default())
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));

if (is_new(fun) && output_type_implements_default(fun))
|| match call_expr {
Some(call_expr) => is_default_equivalent(cx, call_expr),
None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun),
};
then {
span_lint_and_sugg(
cx,
OR_FUN_CALL,
UNWRAP_OR_DEFAULT,
method_span.with_hi(span.hi()),
&format!("use of `{name}` followed by a call to `{path}`"),
&format!("use of `{name}` to construct default value"),
"try",
format!("{sugg}()"),
Applicability::MachineApplicable,
Expand Down Expand Up @@ -168,11 +189,16 @@ pub(super) fn check<'tcx>(
match inner_arg.kind {
hir::ExprKind::Call(fun, or_args) => {
let or_has_args = !or_args.is_empty();
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
if or_has_args
|| !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span)
{
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);
}
},
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => {
check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span);
},
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None);
},
Expand All @@ -189,3 +215,22 @@ pub(super) fn check<'tcx>(
}
}
}

fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
let body = cx.tcx.hir().body(body);

if body.params.is_empty()
&& let hir::Expr{ kind, .. } = &body.value
&& let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind
&& ident == &symbol::Ident::from_str("to_string")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary allocation here 🐄

Suggested change
&& ident == &symbol::Ident::from_str("to_string")
&& ident.name == sym::to_string

&& let hir::Expr{ kind, .. } = self_arg
&& let hir::ExprKind::Lit(lit) = kind
&& let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node
{
return true;
}
}

false
}
70 changes: 0 additions & 70 deletions clippy_lints/src/methods/unwrap_or_else_default.rs

This file was deleted.

1 change: 1 addition & 0 deletions clippy_lints/src/renamed_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::single_char_push_str", "clippy::single_char_add_str"),
("clippy::stutter", "clippy::module_name_repetitions"),
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
("clippy::zero_width_space", "clippy::invisible_characters"),
("clippy::cast_ref_to_mut", "cast_ref_to_mut"),
("clippy::clone_double_ref", "suspicious_double_ref_op"),
Expand Down
Loading