fix: map_unwrap_or fail to cover Result::unwrap_or
This commit is contained in:
parent
34fab5cd37
commit
f2283e2ef5
9 changed files with 325 additions and 227 deletions
|
|
@ -1,71 +1,199 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::res::MaybeDef;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{Visitor, walk_path};
|
||||
use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::MAP_UNWRAP_OR;
|
||||
|
||||
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
|
||||
///
|
||||
/// Returns true if the lint was emitted
|
||||
/// lint use of `map().unwrap_or()` for `Option`s and `Result`s
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
recv: &'tcx hir::Expr<'_>,
|
||||
map_arg: &'tcx hir::Expr<'_>,
|
||||
unwrap_arg: &'tcx hir::Expr<'_>,
|
||||
expr: &rustc_hir::Expr<'_>,
|
||||
recv: &rustc_hir::Expr<'_>,
|
||||
map_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
unwrap_recv: &rustc_hir::Expr<'_>,
|
||||
unwrap_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
map_span: Span,
|
||||
msrv: Msrv,
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option` or a `Result`.
|
||||
let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option);
|
||||
let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result);
|
||||
|
||||
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) {
|
||||
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
if is_option || is_result {
|
||||
// Don't make a suggestion that may fail to compile due to mutably borrowing
|
||||
// the same variable twice.
|
||||
let map_mutated_vars = mutated_variables(recv, cx);
|
||||
let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
|
||||
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
|
||||
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
|
||||
if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
|
||||
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
|
||||
// borrowck errors, see #10579 for one such instance.
|
||||
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
|
||||
// ```
|
||||
// let x = vec![1, 2];
|
||||
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
|
||||
// ```
|
||||
// This compiles, but changing it to `map_or` will produce a compile error:
|
||||
// ```
|
||||
// let x = vec![1, 2];
|
||||
// x.get(0..1).map_or(x, |s| s.to_vec())
|
||||
// ^ moving `x` here
|
||||
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
|
||||
// ```
|
||||
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
|
||||
// before the call to `unwrap_or`.
|
||||
|
||||
let mut unwrap_visitor = UnwrapVisitor {
|
||||
cx,
|
||||
identifiers: FxHashSet::default(),
|
||||
};
|
||||
unwrap_visitor.visit_expr(unwrap_arg);
|
||||
|
||||
let mut reference_visitor = ReferenceVisitor {
|
||||
cx,
|
||||
identifiers: unwrap_visitor.identifiers,
|
||||
unwrap_or_span: unwrap_arg.span,
|
||||
};
|
||||
|
||||
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
|
||||
|
||||
// Visit the body, and return if we've found a reference
|
||||
if reference_visitor.visit_body(body).is_break() {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if !unwrap_arg.span.eq_ctxt(map_span) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
|
||||
let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
|
||||
if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
|
||||
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
// get snippet for unwrap_or()
|
||||
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
|
||||
// lint message
|
||||
let msg = if is_option {
|
||||
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
|
||||
// comparing the snippet from source to raw text ("None") below is safe
|
||||
// because we already have checked the type.
|
||||
let unwrap_snippet_none = is_option && unwrap_snippet == "None";
|
||||
let arg = if unwrap_snippet_none {
|
||||
"None"
|
||||
} else if suggest_is_some_and {
|
||||
"false"
|
||||
} else {
|
||||
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
|
||||
"<a>"
|
||||
};
|
||||
// get snippets for args to map() and unwrap_or_else()
|
||||
let map_snippet = snippet(cx, map_arg.span, "..");
|
||||
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
|
||||
// lint, with note if both map() and unwrap_or_else() have the same span
|
||||
if map_arg.span.eq_ctxt(unwrap_arg.span) {
|
||||
let var_snippet = snippet(cx, recv.span, "..");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_UNWRAP_OR,
|
||||
expr.span,
|
||||
msg,
|
||||
"try",
|
||||
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
let suggest = if unwrap_snippet_none {
|
||||
"and_then(<f>)"
|
||||
} else if suggest_is_some_and {
|
||||
if is_result {
|
||||
"is_ok_and(<f>)"
|
||||
} else {
|
||||
"is_some_and(<f>)"
|
||||
}
|
||||
} else {
|
||||
"map_or(<a>, <f>)"
|
||||
};
|
||||
let msg = format!(
|
||||
"called `map(<f>).unwrap_or({arg})` on an `{}` value",
|
||||
if is_option { "Option" } else { "Result" }
|
||||
);
|
||||
|
||||
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
|
||||
let map_arg_span = map_arg.span;
|
||||
|
||||
let mut suggestion = vec![
|
||||
(
|
||||
map_span,
|
||||
String::from(if unwrap_snippet_none {
|
||||
"and_then"
|
||||
} else if suggest_is_some_and {
|
||||
if is_result { "is_ok_and" } else { "is_some_and" }
|
||||
} else {
|
||||
"map_or"
|
||||
}),
|
||||
),
|
||||
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
|
||||
];
|
||||
|
||||
if !unwrap_snippet_none && !suggest_is_some_and {
|
||||
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
|
||||
}
|
||||
|
||||
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
struct UnwrapVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
identifiers: FxHashSet<HirId>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::All;
|
||||
|
||||
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
|
||||
if let Res::Local(local_id) = path.res
|
||||
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
|
||||
&& let PatKind::Binding(_, local_id, ..) = pat.kind
|
||||
{
|
||||
self.identifiers.insert(local_id);
|
||||
}
|
||||
walk_path(self, path);
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferenceVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
identifiers: FxHashSet<HirId>,
|
||||
unwrap_or_span: Span,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::All;
|
||||
type Result = ControlFlow<()>;
|
||||
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> {
|
||||
// If we haven't found a reference yet, check if this references
|
||||
// one of the locals that was moved in the `unwrap_or` argument.
|
||||
// We are only interested in exprs that appear before the `unwrap_or` call.
|
||||
if expr.span < self.unwrap_or_span
|
||||
&& let ExprKind::Path(ref path) = expr.kind
|
||||
&& let QPath::Resolved(_, path) = path
|
||||
&& let Res::Local(local_id) = path.res
|
||||
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
|
||||
&& let PatKind::Binding(_, local_id, ..) = pat.kind
|
||||
&& self.identifiers.contains(&local_id)
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
rustc_hir::intravisit::walk_expr(self, expr)
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
clippy_lints/src/methods/map_unwrap_or_else.rs
Normal file
71
clippy_lints/src/methods/map_unwrap_or_else.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::res::MaybeDef;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::MAP_UNWRAP_OR;
|
||||
|
||||
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
|
||||
///
|
||||
/// Returns true if the lint was emitted
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
recv: &'tcx hir::Expr<'_>,
|
||||
map_arg: &'tcx hir::Expr<'_>,
|
||||
unwrap_arg: &'tcx hir::Expr<'_>,
|
||||
msrv: Msrv,
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option` or a `Result`.
|
||||
let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option);
|
||||
let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result);
|
||||
|
||||
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if is_option || is_result {
|
||||
// Don't make a suggestion that may fail to compile due to mutably borrowing
|
||||
// the same variable twice.
|
||||
let map_mutated_vars = mutated_variables(recv, cx);
|
||||
let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
|
||||
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
|
||||
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// lint message
|
||||
let msg = if is_option {
|
||||
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
|
||||
} else {
|
||||
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
|
||||
};
|
||||
// get snippets for args to map() and unwrap_or_else()
|
||||
let map_snippet = snippet(cx, map_arg.span, "..");
|
||||
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
|
||||
// lint, with note if both map() and unwrap_or_else() have the same span
|
||||
if map_arg.span.eq_ctxt(unwrap_arg.span) {
|
||||
let var_snippet = snippet(cx, recv.span, "..");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_UNWRAP_OR,
|
||||
expr.span,
|
||||
msg,
|
||||
"try",
|
||||
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
@ -74,6 +74,7 @@ mod map_err_ignore;
|
|||
mod map_flatten;
|
||||
mod map_identity;
|
||||
mod map_unwrap_or;
|
||||
mod map_unwrap_or_else;
|
||||
mod map_with_unused_argument_over_ranges;
|
||||
mod mut_mutex_lock;
|
||||
mod needless_as_bytes;
|
||||
|
|
@ -89,7 +90,6 @@ mod open_options;
|
|||
mod option_as_ref_cloned;
|
||||
mod option_as_ref_deref;
|
||||
mod option_map_or_none;
|
||||
mod option_map_unwrap_or;
|
||||
mod or_fun_call;
|
||||
mod or_then_unwrap;
|
||||
mod path_buf_push_overwrite;
|
||||
|
|
@ -5607,7 +5607,7 @@ impl Methods {
|
|||
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
|
||||
},
|
||||
Some((sym::map, m_recv, [m_arg], span, _)) => {
|
||||
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
|
||||
map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
|
||||
},
|
||||
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
|
||||
obfuscated_if_else::check(
|
||||
|
|
@ -5648,7 +5648,7 @@ impl Methods {
|
|||
(sym::unwrap_or_else, [u_arg]) => {
|
||||
match method_call(recv) {
|
||||
Some((sym::map, recv, [map_arg], _, _))
|
||||
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
|
||||
if map_unwrap_or_else::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
|
||||
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
|
||||
obfuscated_if_else::check(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::res::MaybeDef;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{Visitor, walk_path};
|
||||
use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::MAP_UNWRAP_OR;
|
||||
|
||||
/// lint use of `map().unwrap_or()` for `Option`s
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &rustc_hir::Expr<'_>,
|
||||
recv: &rustc_hir::Expr<'_>,
|
||||
map_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
unwrap_recv: &rustc_hir::Expr<'_>,
|
||||
unwrap_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
map_span: Span,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) {
|
||||
if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
|
||||
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
|
||||
// borrowck errors, see #10579 for one such instance.
|
||||
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
|
||||
// ```
|
||||
// let x = vec![1, 2];
|
||||
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
|
||||
// ```
|
||||
// This compiles, but changing it to `map_or` will produce a compile error:
|
||||
// ```
|
||||
// let x = vec![1, 2];
|
||||
// x.get(0..1).map_or(x, |s| s.to_vec())
|
||||
// ^ moving `x` here
|
||||
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
|
||||
// ```
|
||||
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
|
||||
// before the call to `unwrap_or`.
|
||||
|
||||
let mut unwrap_visitor = UnwrapVisitor {
|
||||
cx,
|
||||
identifiers: FxHashSet::default(),
|
||||
};
|
||||
unwrap_visitor.visit_expr(unwrap_arg);
|
||||
|
||||
let mut reference_visitor = ReferenceVisitor {
|
||||
cx,
|
||||
identifiers: unwrap_visitor.identifiers,
|
||||
unwrap_or_span: unwrap_arg.span,
|
||||
};
|
||||
|
||||
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
|
||||
|
||||
// Visit the body, and return if we've found a reference
|
||||
if reference_visitor.visit_body(body).is_break() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if !unwrap_arg.span.eq_ctxt(map_span) {
|
||||
return;
|
||||
}
|
||||
|
||||
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
|
||||
let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
|
||||
if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
|
||||
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
// get snippet for unwrap_or()
|
||||
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
|
||||
// lint message
|
||||
// comparing the snippet from source to raw text ("None") below is safe
|
||||
// because we already have checked the type.
|
||||
let arg = if unwrap_snippet == "None" {
|
||||
"None"
|
||||
} else if suggest_is_some_and {
|
||||
"false"
|
||||
} else {
|
||||
"<a>"
|
||||
};
|
||||
let unwrap_snippet_none = unwrap_snippet == "None";
|
||||
let suggest = if unwrap_snippet_none {
|
||||
"and_then(<f>)"
|
||||
} else if suggest_is_some_and {
|
||||
"is_some_and(<f>)"
|
||||
} else {
|
||||
"map_or(<a>, <f>)"
|
||||
};
|
||||
let msg = format!("called `map(<f>).unwrap_or({arg})` on an `Option` value");
|
||||
|
||||
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
|
||||
let map_arg_span = map_arg.span;
|
||||
|
||||
let mut suggestion = vec![
|
||||
(
|
||||
map_span,
|
||||
String::from(if unwrap_snippet_none {
|
||||
"and_then"
|
||||
} else if suggest_is_some_and {
|
||||
"is_some_and"
|
||||
} else {
|
||||
"map_or"
|
||||
}),
|
||||
),
|
||||
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
|
||||
];
|
||||
|
||||
if !unwrap_snippet_none && !suggest_is_some_and {
|
||||
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
|
||||
}
|
||||
|
||||
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct UnwrapVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
identifiers: FxHashSet<HirId>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::All;
|
||||
|
||||
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
|
||||
if let Res::Local(local_id) = path.res
|
||||
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
|
||||
&& let PatKind::Binding(_, local_id, ..) = pat.kind
|
||||
{
|
||||
self.identifiers.insert(local_id);
|
||||
}
|
||||
walk_path(self, path);
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferenceVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
identifiers: FxHashSet<HirId>,
|
||||
unwrap_or_span: Span,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> {
|
||||
type NestedFilter = nested_filter::All;
|
||||
type Result = ControlFlow<()>;
|
||||
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> {
|
||||
// If we haven't found a reference yet, check if this references
|
||||
// one of the locals that was moved in the `unwrap_or` argument.
|
||||
// We are only interested in exprs that appear before the `unwrap_or` call.
|
||||
if expr.span < self.unwrap_or_span
|
||||
&& let ExprKind::Path(ref path) = expr.kind
|
||||
&& let QPath::Resolved(_, path) = path
|
||||
&& let Res::Local(local_id) = path.res
|
||||
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
|
||||
&& let PatKind::Binding(_, local_id, ..) = pat.kind
|
||||
&& self.identifiers.contains(&local_id)
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
rustc_hir::intravisit::walk_expr(self, expr)
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ msrv_aliases! {
|
|||
1,45,0 { STR_STRIP_PREFIX }
|
||||
1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
|
||||
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
|
||||
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
|
||||
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR, RESULT_MAP_OR_ELSE }
|
||||
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
|
||||
1,38,0 { POINTER_CAST, REM_EUCLID }
|
||||
1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
|
||||
|
|
|
|||
|
|
@ -281,8 +281,7 @@ impl CrateWithSource {
|
|||
CrateSource::Path { path } => {
|
||||
fn is_cache_dir(entry: &DirEntry) -> bool {
|
||||
fs::read(entry.path().join("CACHEDIR.TAG"))
|
||||
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
|
||||
.unwrap_or(false)
|
||||
.is_ok_and(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
|
||||
}
|
||||
|
||||
// copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
|
||||
|
|
|
|||
|
|
@ -51,3 +51,19 @@ fn main() {
|
|||
option_methods();
|
||||
result_methods();
|
||||
}
|
||||
|
||||
fn issue15714() {
|
||||
let o: Option<i32> = Some(3);
|
||||
let r: Result<i32, ()> = Ok(3);
|
||||
println!("{}", o.map_or(3, |y| y + 1));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", o.map_or_else(|| 3, |y| y + 1));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", r.map_or(3, |y| y + 1));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", r.map_or_else(|()| 3, |y| y + 1));
|
||||
//~^ map_unwrap_or
|
||||
|
||||
println!("{}", r.is_ok_and(|y| y == 1));
|
||||
//~^ map_unwrap_or
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,3 +57,19 @@ fn main() {
|
|||
option_methods();
|
||||
result_methods();
|
||||
}
|
||||
|
||||
fn issue15714() {
|
||||
let o: Option<i32> = Some(3);
|
||||
let r: Result<i32, ()> = Ok(3);
|
||||
println!("{}", o.map(|y| y + 1).unwrap_or(3));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", r.map(|y| y + 1).unwrap_or(3));
|
||||
//~^ map_unwrap_or
|
||||
println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3));
|
||||
//~^ map_unwrap_or
|
||||
|
||||
println!("{}", r.map(|y| y == 1).unwrap_or(false));
|
||||
//~^ map_unwrap_or
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,5 +19,53 @@ LL | let _ = res.map(|x| x + 1)
|
|||
LL | | .unwrap_or_else(|_e| 0);
|
||||
| |_______________________________^ help: try: `res.map_or_else(|_e| 0, |x| x + 1)`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error: called `map(<f>).unwrap_or(<a>)` on an `Option` value
|
||||
--> tests/ui/map_unwrap_or_fixable.rs:64:20
|
||||
|
|
||||
LL | println!("{}", o.map(|y| y + 1).unwrap_or(3));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: use `map_or(<a>, <f>)` instead
|
||||
|
|
||||
LL - println!("{}", o.map(|y| y + 1).unwrap_or(3));
|
||||
LL + println!("{}", o.map_or(3, |y| y + 1));
|
||||
|
|
||||
|
||||
error: called `map(<f>).unwrap_or_else(<g>)` on an `Option` value
|
||||
--> tests/ui/map_unwrap_or_fixable.rs:66:20
|
||||
|
|
||||
LL | println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `o.map_or_else(|| 3, |y| y + 1)`
|
||||
|
||||
error: called `map(<f>).unwrap_or(<a>)` on an `Result` value
|
||||
--> tests/ui/map_unwrap_or_fixable.rs:68:20
|
||||
|
|
||||
LL | println!("{}", r.map(|y| y + 1).unwrap_or(3));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: use `map_or(<a>, <f>)` instead
|
||||
|
|
||||
LL - println!("{}", r.map(|y| y + 1).unwrap_or(3));
|
||||
LL + println!("{}", r.map_or(3, |y| y + 1));
|
||||
|
|
||||
|
||||
error: called `map(<f>).unwrap_or_else(<g>)` on a `Result` value
|
||||
--> tests/ui/map_unwrap_or_fixable.rs:70:20
|
||||
|
|
||||
LL | println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r.map_or_else(|()| 3, |y| y + 1)`
|
||||
|
||||
error: called `map(<f>).unwrap_or(false)` on an `Result` value
|
||||
--> tests/ui/map_unwrap_or_fixable.rs:73:20
|
||||
|
|
||||
LL | println!("{}", r.map(|y| y == 1).unwrap_or(false));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: use `is_ok_and(<f>)` instead
|
||||
|
|
||||
LL - println!("{}", r.map(|y| y == 1).unwrap_or(false));
|
||||
LL + println!("{}", r.is_ok_and(|y| y == 1));
|
||||
|
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue