Unify manual_unwrap_or and manual_unwrap_or_default code (#14332)
Both lints share a lot of characteristics but were implemented in unrelated ways. This unifies them, saving around 100 SLOC in the process, and making one more test trigger the lint. Also, this removes useless blocks in suggestions. Close #12956 changelog: none
This commit is contained in:
commit
1893e1e5e7
13 changed files with 328 additions and 355 deletions
|
|
@ -335,7 +335,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
|
||||
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
|
||||
crate::manual_strip::MANUAL_STRIP_INFO,
|
||||
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
|
||||
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
|
||||
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
|
||||
crate::match_result_ok::MATCH_RESULT_OK_INFO,
|
||||
|
|
@ -345,6 +344,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::matches::MANUAL_MAP_INFO,
|
||||
crate::matches::MANUAL_OK_ERR_INFO,
|
||||
crate::matches::MANUAL_UNWRAP_OR_INFO,
|
||||
crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO,
|
||||
crate::matches::MATCH_AS_REF_INFO,
|
||||
crate::matches::MATCH_BOOL_INFO,
|
||||
crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO,
|
||||
|
|
|
|||
|
|
@ -226,7 +226,6 @@ mod manual_rotate;
|
|||
mod manual_slice_size_calculation;
|
||||
mod manual_string_new;
|
||||
mod manual_strip;
|
||||
mod manual_unwrap_or_default;
|
||||
mod map_unit_fn;
|
||||
mod match_result_ok;
|
||||
mod matches;
|
||||
|
|
@ -960,7 +959,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
|
||||
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
|
||||
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
|
||||
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
|
||||
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher::IfLetOrMatch;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait};
|
||||
use clippy_utils::{is_default_equivalent, is_in_const_context, path_res, peel_blocks, span_contains_comment};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if a `match` or `if let` expression can be simplified using
|
||||
/// `.unwrap_or_default()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It can be done in one call with `.unwrap_or_default()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: Option<String> = Some(String::new());
|
||||
/// let y: String = match x {
|
||||
/// Some(v) => v,
|
||||
/// None => String::new(),
|
||||
/// };
|
||||
///
|
||||
/// let x: Option<Vec<String>> = Some(Vec::new());
|
||||
/// let y: Vec<String> = if let Some(v) = x {
|
||||
/// v
|
||||
/// } else {
|
||||
/// Vec::new()
|
||||
/// };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let x: Option<String> = Some(String::new());
|
||||
/// let y: String = x.unwrap_or_default();
|
||||
///
|
||||
/// let x: Option<Vec<String>> = Some(Vec::new());
|
||||
/// let y: Vec<String> = x.unwrap_or_default();
|
||||
/// ```
|
||||
#[clippy::version = "1.79.0"]
|
||||
pub MANUAL_UNWRAP_OR_DEFAULT,
|
||||
suspicious,
|
||||
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]);
|
||||
|
||||
fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
|
||||
&& let PatKind::Binding(_, pat_id, _, _) = pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
|
||||
|| cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id))
|
||||
{
|
||||
Some(pat_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
|
||||
{
|
||||
Some(arm.body)
|
||||
} else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id)
|
||||
{
|
||||
Some(arm.body)
|
||||
} else if let PatKind::Wild = arm.pat.kind {
|
||||
// We consider that the `Some` check will filter it out if it's not right.
|
||||
Some(arm.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_some_and_none_bodies<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
arm1: &'tcx Arm<'tcx>,
|
||||
arm2: &'tcx Arm<'tcx>,
|
||||
) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> {
|
||||
if let Some(binding_id) = get_some(cx, arm1.pat)
|
||||
&& let Some(body_none) = get_none(cx, arm2)
|
||||
{
|
||||
Some(((arm1.body, binding_id), body_none))
|
||||
} else if let Some(binding_id) = get_some(cx, arm2.pat)
|
||||
&& let Some(body_none) = get_none(cx, arm1)
|
||||
{
|
||||
Some(((arm2.body, binding_id), body_none))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
// Get expr_name ("if let" or "match" depending on kind of expression), the condition, the body for
|
||||
// the some arm, the body for the none arm and the binding id of the some arm
|
||||
let (expr_name, condition, body_some, body_none, binding_id) = match if_let_or_match {
|
||||
IfLetOrMatch::Match(condition, [arm1, arm2], MatchSource::Normal | MatchSource::ForLoopDesugar)
|
||||
// Make sure there are no guards to keep things simple
|
||||
if arm1.guard.is_none()
|
||||
&& arm2.guard.is_none()
|
||||
// Get the some and none bodies and the binding id of the some arm
|
||||
&& let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2) =>
|
||||
{
|
||||
("match", condition, body_some, body_none, binding_id)
|
||||
},
|
||||
IfLetOrMatch::IfLet(condition, pat, if_expr, Some(else_expr), _)
|
||||
if let Some(binding_id) = get_some(cx, pat) =>
|
||||
{
|
||||
("if let", condition, if_expr, else_expr, binding_id)
|
||||
},
|
||||
_ => {
|
||||
// All other cases (match with number of arms != 2, if let without else, etc.)
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// We check if the return type of the expression implements Default.
|
||||
let expr_type = cx.typeck_results().expr_ty(expr);
|
||||
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
|
||||
&& implements_trait(cx, expr_type, default_trait_id, &[])
|
||||
// We check if the initial condition implements Default.
|
||||
&& let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1)
|
||||
&& let GenericArgKind::Type(condition_ty) = condition_ty.unpack()
|
||||
&& implements_trait(cx, condition_ty, default_trait_id, &[])
|
||||
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = peel_blocks(body_some).kind
|
||||
&& let Res::Local(local_id) = path.res
|
||||
&& local_id == binding_id
|
||||
// We now check the `None` arm is calling a method equivalent to `Default::default`.
|
||||
&& let body_none = peel_blocks(body_none)
|
||||
&& is_default_equivalent(cx, body_none)
|
||||
&& let Some(receiver) = Sugg::hir_opt(cx, condition).map(Sugg::maybe_paren)
|
||||
{
|
||||
// Machine applicable only if there are no comments present
|
||||
let applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
|
||||
Applicability::MaybeIncorrect
|
||||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
|
||||
// We now check if the condition is a None variant, in which case we need to specify the type
|
||||
if path_res(cx, condition)
|
||||
.opt_def_id()
|
||||
.is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant())
|
||||
{
|
||||
return span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
"replace it with",
|
||||
format!("{receiver}::<{expr_type}>.unwrap_or_default()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
// We check if the expression type is still uncertain, in which case we ask the user to specify it
|
||||
if !expr_type_is_certain(cx, condition) {
|
||||
return span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
format!("ascribe the type {expr_type} and replace your expression with"),
|
||||
format!("{receiver}.unwrap_or_default()"),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
"replace it with",
|
||||
format!("{receiver}.unwrap_or_default()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr)
|
||||
&& !expr.span.from_expansion()
|
||||
&& !is_in_const_context(cx)
|
||||
{
|
||||
handle(cx, if_let_or_match, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +1,219 @@
|
|||
use clippy_utils::consts::ConstEvalCtxt;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::usage::contains_return_break_continue_macro;
|
||||
use clippy_utils::{is_res_lang_ctor, path_to_local_id, peel_blocks, sugg};
|
||||
use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, ResultErr};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, Expr, Pat, PatExpr, PatExprKind, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, QPath};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::{GenericArgKind, Ty};
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::MANUAL_UNWRAP_OR;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{expr_type_is_certain, get_type_diagnostic_name, implements_trait};
|
||||
use clippy_utils::{is_default_equivalent, is_lint_allowed, path_res, peel_blocks, span_contains_comment};
|
||||
|
||||
pub(super) fn check_match<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
scrutinee: &'tcx Expr<'_>,
|
||||
arms: &'tcx [Arm<'_>],
|
||||
) {
|
||||
let ty = cx.typeck_results().expr_ty(scrutinee);
|
||||
if let Some((or_arm, unwrap_arm)) = applicable_or_arm(cx, arms) {
|
||||
check_and_lint(cx, expr, unwrap_arm.pat, scrutinee, unwrap_arm.body, or_arm.body, ty);
|
||||
use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
|
||||
|
||||
fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<HirId> {
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
|
||||
&& let PatKind::Binding(_, pat_id, _, _) = pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& let Some(lang_item) = cx.tcx.lang_items().from_def_id(def_id)
|
||||
&& matches!(lang_item, LangItem::OptionSome | LangItem::ResultOk)
|
||||
{
|
||||
Some(pat_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_if_let<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
let_pat: &'tcx Pat<'_>,
|
||||
let_expr: &'tcx Expr<'_>,
|
||||
then_expr: &'tcx Expr<'_>,
|
||||
else_expr: &'tcx Expr<'_>,
|
||||
) {
|
||||
let ty = cx.typeck_results().expr_ty(let_expr);
|
||||
let then_ty = cx.typeck_results().expr_ty(then_expr);
|
||||
// The signature is `fn unwrap_or<T>(self: Option<T>, default: T) -> T`.
|
||||
// When `expr_adjustments(then_expr).is_empty()`, `T` should equate to `default`'s type.
|
||||
// Otherwise, type error will occur.
|
||||
if cx.typeck_results().expr_adjustments(then_expr).is_empty()
|
||||
&& let rustc_middle::ty::Adt(_did, args) = ty.kind()
|
||||
&& let Some(some_ty) = args.first().and_then(|arg| arg.as_type())
|
||||
&& some_ty != then_ty
|
||||
fn get_none<'tcx>(cx: &LateContext<'_>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
|
||||
{
|
||||
Some(arm.body)
|
||||
} else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
// Since it comes from a pattern binding, we need to get the parent to actually match
|
||||
// against it.
|
||||
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
|
||||
&& cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id)
|
||||
{
|
||||
Some(arm.body)
|
||||
} else if let PatKind::Wild = arm.pat.kind {
|
||||
// We consider that the `Some` check will filter it out if it's not right.
|
||||
Some(arm.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_some_and_none_bodies<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
arm1: &'tcx Arm<'tcx>,
|
||||
arm2: &'tcx Arm<'tcx>,
|
||||
) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> {
|
||||
if let Some(binding_id) = get_some(cx, arm1.pat)
|
||||
&& let Some(body_none) = get_none(cx, arm2)
|
||||
{
|
||||
Some(((arm1.body, binding_id), body_none))
|
||||
} else if let Some(binding_id) = get_some(cx, arm2.pat)
|
||||
&& let Some(body_none) = get_none(cx, arm1)
|
||||
{
|
||||
Some(((arm2.body, binding_id), body_none))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
expr_name: &'static str,
|
||||
condition: &Expr<'_>,
|
||||
body_some: &Expr<'_>,
|
||||
body_none: &Expr<'_>,
|
||||
binding_id: HirId,
|
||||
) {
|
||||
// Only deal with situations where both alternatives return the same non-adjusted type.
|
||||
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) {
|
||||
return;
|
||||
}
|
||||
check_and_lint(cx, expr, let_pat, let_expr, then_expr, peel_blocks(else_expr), ty);
|
||||
}
|
||||
|
||||
fn check_and_lint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
let_pat: &'tcx Pat<'_>,
|
||||
let_expr: &'tcx Expr<'_>,
|
||||
then_expr: &'tcx Expr<'_>,
|
||||
else_expr: &'tcx Expr<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
) {
|
||||
if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = let_pat.kind
|
||||
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
|
||||
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
|
||||
&& (cx.tcx.lang_items().option_some_variant() == Some(variant_id)
|
||||
|| cx.tcx.lang_items().result_ok_variant() == Some(variant_id))
|
||||
&& let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind
|
||||
&& path_to_local_id(peel_blocks(then_expr), binding_hir_id)
|
||||
&& cx.typeck_results().expr_adjustments(then_expr).is_empty()
|
||||
&& let Some(ty_name) = find_type_name(cx, ty)
|
||||
&& let Some(or_body_snippet) = else_expr.span.get_source_text(cx)
|
||||
&& let Some(indent) = indent_of(cx, expr.span)
|
||||
&& ConstEvalCtxt::new(cx).eval_simple(else_expr).is_some()
|
||||
let expr_type = cx.typeck_results().expr_ty(expr);
|
||||
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = peel_blocks(body_some).kind
|
||||
&& let Res::Local(local_id) = path.res
|
||||
&& local_id == binding_id
|
||||
{
|
||||
lint(cx, expr, let_expr, ty_name, &or_body_snippet, indent);
|
||||
// Machine applicable only if there are no comments present
|
||||
let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
|
||||
Applicability::MaybeIncorrect
|
||||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
let receiver = Sugg::hir_with_applicability(cx, condition, "_", &mut applicability).maybe_paren();
|
||||
|
||||
// We now check the `None` arm is calling a method equivalent to `Default::default`.
|
||||
if !is_lint_allowed(cx, MANUAL_UNWRAP_OR_DEFAULT, expr.hir_id)
|
||||
// We check if the return type of the expression implements Default.
|
||||
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
|
||||
&& implements_trait(cx, expr_type, default_trait_id, &[])
|
||||
// We check if the initial condition implements Default.
|
||||
&& let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1)
|
||||
&& let GenericArgKind::Type(condition_ty) = condition_ty.unpack()
|
||||
&& implements_trait(cx, condition_ty, default_trait_id, &[])
|
||||
&& is_default_equivalent(cx, peel_blocks(body_none))
|
||||
{
|
||||
// We now check if the condition is a None variant, in which case we need to specify the type
|
||||
if path_res(cx, condition)
|
||||
.opt_def_id()
|
||||
.is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant())
|
||||
{
|
||||
return span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
"replace it with",
|
||||
format!("{receiver}::<{expr_type}>.unwrap_or_default()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
// We check if the expression type is still uncertain, in which case we ask the user to specify it
|
||||
if !expr_type_is_certain(cx, condition) {
|
||||
return span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
format!("ascribe the type {expr_type} and replace your expression with"),
|
||||
format!("{receiver}.unwrap_or_default()"),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
expr.span,
|
||||
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
|
||||
"replace it with",
|
||||
format!("{receiver}.unwrap_or_default()"),
|
||||
applicability,
|
||||
);
|
||||
} else if let Some(ty_name) = find_type_name(cx, cx.typeck_results().expr_ty(condition))
|
||||
&& cx.typeck_results().expr_adjustments(body_some).is_empty()
|
||||
&& let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx)
|
||||
&& let Some(indent) = indent_of(cx, expr.span)
|
||||
&& ConstEvalCtxt::new(cx).eval_simple(body_none).is_some()
|
||||
{
|
||||
let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent));
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let suggestion = Sugg::hir_with_context(cx, condition, expr.span.ctxt(), "..", &mut app).maybe_paren();
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR,
|
||||
expr.span,
|
||||
format!("this pattern reimplements `{ty_name}::unwrap_or`"),
|
||||
"replace with",
|
||||
format!("{suggestion}.unwrap_or({reindented_or_body})",),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static str> {
|
||||
if is_type_diagnostic_item(cx, ty, sym::Option) {
|
||||
Some("Option")
|
||||
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
|
||||
Some("Result")
|
||||
} else {
|
||||
None
|
||||
match get_type_diagnostic_name(cx, ty)? {
|
||||
sym::Option => Some("Option"),
|
||||
sym::Result => Some("Result"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<(&'a Arm<'a>, &'a Arm<'a>)> {
|
||||
if arms.len() == 2
|
||||
&& arms.iter().all(|arm| arm.guard.is_none())
|
||||
&& let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind {
|
||||
PatKind::Expr(PatExpr {
|
||||
hir_id,
|
||||
kind: PatExprKind::Path(qpath),
|
||||
..
|
||||
}) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone),
|
||||
PatKind::TupleStruct(ref qpath, [pat], _) => {
|
||||
matches!(pat.kind, PatKind::Wild)
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr)
|
||||
},
|
||||
_ => false,
|
||||
})
|
||||
&& let unwrap_arm = &arms[1 - idx]
|
||||
&& !contains_return_break_continue_macro(or_arm.body)
|
||||
{
|
||||
Some((or_arm, unwrap_arm))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn lint<'tcx>(
|
||||
pub fn check_match<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'tcx>,
|
||||
scrutinee: &'tcx Expr<'_>,
|
||||
ty_name: &str,
|
||||
or_body_snippet: &str,
|
||||
indent: usize,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
scrutinee: &'tcx Expr<'tcx>,
|
||||
arms: &'tcx [Arm<'tcx>],
|
||||
) {
|
||||
let reindented_or_body = reindent_multiline(or_body_snippet, true, Some(indent));
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_paren();
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_UNWRAP_OR,
|
||||
expr.span,
|
||||
format!("this pattern reimplements `{ty_name}::unwrap_or`"),
|
||||
"replace with",
|
||||
format!("{suggestion}.unwrap_or({reindented_or_body})",),
|
||||
app,
|
||||
);
|
||||
if let [arm1, arm2] = arms
|
||||
// Make sure there are no guards to keep things simple
|
||||
&& arm1.guard.is_none()
|
||||
&& arm2.guard.is_none()
|
||||
// Get the some and none bodies and the binding id of the some arm
|
||||
&& let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2)
|
||||
{
|
||||
handle(cx, expr, "match", scrutinee, body_some, body_none, binding_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_if_let<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
pat: &'tcx Pat<'tcx>,
|
||||
scrutinee: &'tcx Expr<'tcx>,
|
||||
then_expr: &'tcx Expr<'tcx>,
|
||||
else_expr: &'tcx Expr<'tcx>,
|
||||
) {
|
||||
if let Some(binding_id) = get_some(cx, pat) {
|
||||
handle(
|
||||
cx,
|
||||
expr,
|
||||
"if let",
|
||||
scrutinee,
|
||||
peel_blocks(then_expr),
|
||||
peel_blocks(else_expr),
|
||||
binding_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -722,6 +722,43 @@ declare_clippy_lint! {
|
|||
"finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if a `match` or `if let` expression can be simplified using
|
||||
/// `.unwrap_or_default()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It can be done in one call with `.unwrap_or_default()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: Option<String> = Some(String::new());
|
||||
/// let y: String = match x {
|
||||
/// Some(v) => v,
|
||||
/// None => String::new(),
|
||||
/// };
|
||||
///
|
||||
/// let x: Option<Vec<String>> = Some(Vec::new());
|
||||
/// let y: Vec<String> = if let Some(v) = x {
|
||||
/// v
|
||||
/// } else {
|
||||
/// Vec::new()
|
||||
/// };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let x: Option<String> = Some(String::new());
|
||||
/// let y: String = x.unwrap_or_default();
|
||||
///
|
||||
/// let x: Option<Vec<String>> = Some(Vec::new());
|
||||
/// let y: Vec<String> = x.unwrap_or_default();
|
||||
/// ```
|
||||
#[clippy::version = "1.79.0"]
|
||||
pub MANUAL_UNWRAP_OR_DEFAULT,
|
||||
suspicious,
|
||||
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `match vec[idx]` or `match vec[n..m]`.
|
||||
|
|
@ -1040,6 +1077,7 @@ impl_lint_pass!(Matches => [
|
|||
NEEDLESS_MATCH,
|
||||
COLLAPSIBLE_MATCH,
|
||||
MANUAL_UNWRAP_OR,
|
||||
MANUAL_UNWRAP_OR_DEFAULT,
|
||||
MATCH_ON_VEC_ITEMS,
|
||||
MATCH_STR_CASE_MISMATCH,
|
||||
SIGNIFICANT_DROP_IN_SCRUTINEE,
|
||||
|
|
|
|||
|
|
@ -176,6 +176,12 @@ impl<'hir> IfLetOrMatch<'hir> {
|
|||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scrutinee(&self) -> &'hir Expr<'hir> {
|
||||
match self {
|
||||
Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An `if` or `if let` expression
|
||||
|
|
|
|||
|
|
@ -1027,6 +1027,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
|||
ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
|
||||
ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone),
|
||||
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
|
||||
ExprKind::Block(Block { stmts: [], expr, .. }, _) => expr.is_some_and(|e| is_default_equivalent(cx, e)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@ fn option_unwrap_or() {
|
|||
|
||||
// multiline case
|
||||
#[rustfmt::skip]
|
||||
Some(1).unwrap_or({
|
||||
42 + 42
|
||||
+ 42 + 42 + 42
|
||||
+ 42 + 42 + 42
|
||||
});
|
||||
Some(1).unwrap_or(42 + 42
|
||||
+ 42 + 42 + 42
|
||||
+ 42 + 42 + 42);
|
||||
|
||||
// string case
|
||||
Some("Bob").unwrap_or("Alice");
|
||||
|
|
@ -125,11 +123,9 @@ fn result_unwrap_or() {
|
|||
|
||||
// multiline case
|
||||
#[rustfmt::skip]
|
||||
Ok::<i32, &str>(1).unwrap_or({
|
||||
42 + 42
|
||||
+ 42 + 42 + 42
|
||||
+ 42 + 42 + 42
|
||||
});
|
||||
Ok::<i32, &str>(1).unwrap_or(42 + 42
|
||||
+ 42 + 42 + 42
|
||||
+ 42 + 42 + 42);
|
||||
|
||||
// string case
|
||||
Ok::<&str, &str>("Bob").unwrap_or("Alice");
|
||||
|
|
@ -159,11 +155,7 @@ fn result_unwrap_or() {
|
|||
Ok(s) => s,
|
||||
Err(s) => s,
|
||||
};
|
||||
// could lint, but unused_variables takes care of it
|
||||
match Ok::<&str, &str>("Alice") {
|
||||
Ok(s) => s,
|
||||
Err(s) => "Bob",
|
||||
};
|
||||
Ok::<&str, &str>("Alice").unwrap_or("Bob");
|
||||
|
||||
Ok::<i32, i32>(1).unwrap_or(42);
|
||||
|
||||
|
|
@ -250,4 +242,12 @@ mod issue_13018 {
|
|||
}
|
||||
}
|
||||
|
||||
fn implicit_deref(v: Vec<String>) {
|
||||
let _ = if let Some(s) = v.first() { s } else { "" };
|
||||
}
|
||||
|
||||
fn allowed_manual_unwrap_or_zero() -> u32 {
|
||||
Some(42).unwrap_or(0)
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -216,8 +216,8 @@ fn result_unwrap_or() {
|
|||
Ok(s) => s,
|
||||
Err(s) => s,
|
||||
};
|
||||
// could lint, but unused_variables takes care of it
|
||||
match Ok::<&str, &str>("Alice") {
|
||||
//~^ manual_unwrap_or
|
||||
Ok(s) => s,
|
||||
Err(s) => "Bob",
|
||||
};
|
||||
|
|
@ -316,4 +316,17 @@ mod issue_13018 {
|
|||
}
|
||||
}
|
||||
|
||||
fn implicit_deref(v: Vec<String>) {
|
||||
let _ = if let Some(s) = v.first() { s } else { "" };
|
||||
}
|
||||
|
||||
fn allowed_manual_unwrap_or_zero() -> u32 {
|
||||
if let Some(x) = Some(42) {
|
||||
//~^ manual_unwrap_or
|
||||
x
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,9 @@ LL | | };
|
|||
|
|
||||
help: replace with
|
||||
|
|
||||
LL ~ Some(1).unwrap_or({
|
||||
LL + 42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL ~ });
|
||||
LL ~ Some(1).unwrap_or(42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL ~ + 42 + 42 + 42);
|
||||
|
|
||||
|
||||
error: this pattern reimplements `Option::unwrap_or`
|
||||
|
|
@ -145,11 +143,9 @@ LL | | };
|
|||
|
|
||||
help: replace with
|
||||
|
|
||||
LL ~ Ok::<i32, &str>(1).unwrap_or({
|
||||
LL + 42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL ~ });
|
||||
LL ~ Ok::<i32, &str>(1).unwrap_or(42 + 42
|
||||
LL + + 42 + 42 + 42
|
||||
LL ~ + 42 + 42 + 42);
|
||||
|
|
||||
|
||||
error: this pattern reimplements `Result::unwrap_or`
|
||||
|
|
@ -162,6 +158,16 @@ LL | | Err(_) => "Alice",
|
|||
LL | | };
|
||||
| |_____^ help: replace with: `Ok::<&str, &str>("Bob").unwrap_or("Alice")`
|
||||
|
||||
error: this pattern reimplements `Result::unwrap_or`
|
||||
--> tests/ui/manual_unwrap_or.rs:219:5
|
||||
|
|
||||
LL | / match Ok::<&str, &str>("Alice") {
|
||||
LL | |
|
||||
LL | | Ok(s) => s,
|
||||
LL | | Err(s) => "Bob",
|
||||
LL | | };
|
||||
| |_____^ help: replace with: `Ok::<&str, &str>("Alice").unwrap_or("Bob")`
|
||||
|
||||
error: this pattern reimplements `Result::unwrap_or`
|
||||
--> tests/ui/manual_unwrap_or.rs:225:5
|
||||
|
|
||||
|
|
@ -184,5 +190,16 @@ LL | | None => 0,
|
|||
LL | | };
|
||||
| |_________^ help: replace with: `some_macro!().unwrap_or(0)`
|
||||
|
||||
error: aborting due to 16 previous errors
|
||||
error: this pattern reimplements `Option::unwrap_or`
|
||||
--> tests/ui/manual_unwrap_or.rs:324:5
|
||||
|
|
||||
LL | / if let Some(x) = Some(42) {
|
||||
LL | |
|
||||
LL | | x
|
||||
LL | | } else {
|
||||
LL | | 0
|
||||
LL | | }
|
||||
| |_____^ help: replace with: `Some(42).unwrap_or(0)`
|
||||
|
||||
error: aborting due to 18 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::manual_unwrap_or_default)]
|
||||
#![allow(clippy::unnecessary_literal_unwrap, clippy::manual_unwrap_or)]
|
||||
#![allow(clippy::unnecessary_literal_unwrap)]
|
||||
|
||||
fn main() {
|
||||
let x: Option<Vec<String>> = None;
|
||||
|
|
@ -99,3 +99,8 @@ fn issue_12928() {
|
|||
let y = if let Some(Y(a, _)) = x { a } else { 0 };
|
||||
let y = if let Some(Y(a, ..)) = x { a } else { 0 };
|
||||
}
|
||||
|
||||
// For symetry with `manual_unwrap_or` test
|
||||
fn allowed_manual_unwrap_or_zero() -> u32 {
|
||||
Some(42).unwrap_or_default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::manual_unwrap_or_default)]
|
||||
#![allow(clippy::unnecessary_literal_unwrap, clippy::manual_unwrap_or)]
|
||||
#![allow(clippy::unnecessary_literal_unwrap)]
|
||||
|
||||
fn main() {
|
||||
let x: Option<Vec<String>> = None;
|
||||
|
|
@ -135,3 +135,13 @@ fn issue_12928() {
|
|||
let y = if let Some(Y(a, _)) = x { a } else { 0 };
|
||||
let y = if let Some(Y(a, ..)) = x { a } else { 0 };
|
||||
}
|
||||
|
||||
// For symetry with `manual_unwrap_or` test
|
||||
fn allowed_manual_unwrap_or_zero() -> u32 {
|
||||
if let Some(x) = Some(42) {
|
||||
//~^ manual_unwrap_or_default
|
||||
x
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,5 +86,16 @@ LL | | _ => 0,
|
|||
LL | | },
|
||||
| |_________^ help: replace it with: `(*b).unwrap_or_default()`
|
||||
|
||||
error: aborting due to 8 previous errors
|
||||
error: if let can be simplified with `.unwrap_or_default()`
|
||||
--> tests/ui/manual_unwrap_or_default.rs:141:5
|
||||
|
|
||||
LL | / if let Some(x) = Some(42) {
|
||||
LL | |
|
||||
LL | | x
|
||||
LL | | } else {
|
||||
LL | | 0
|
||||
LL | | }
|
||||
| |_____^ help: replace it with: `Some(42).unwrap_or_default()`
|
||||
|
||||
error: aborting due to 9 previous errors
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue