Skip to content

Commit 398be8c

Browse files
committed
Auto merge of #13443 - SpriteOvO:simplify-option-neg-methods, r=xFrednet
Simplify negative `Option::{is_some_and,is_none_or}` Closes #13436. Improved based on the existing lint `nonminimal_bool`, since there is already handling of similar methods `Option::{is_some,is_none}` and `Result::{is_ok,is_err}`, and there is a lot of reusable code. When `is_some_and` or `is_none_or` have a negation, we invert it into another method by removing the Not sign and inverting the expression in the closure. For the case where the closure block has statements, currently no simplification is implemented. (Should we do it?) ```rust // Currently will not simplify this _ = !opt.is_some_and(|x| { let complex_block = 100; x == complex_block }); ``` changelog: [`nonminimal_bool`]: Simplify negative `Option::{is_some_and,is_none_or}`
2 parents d578f6a + 762a91b commit 398be8c

8 files changed

+321
-20
lines changed

Diff for: clippy_config/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ macro_rules! msrv_aliases {
1919
msrv_aliases! {
2020
1,83,0 { CONST_EXTERN_FN }
2121
1,83,0 { CONST_FLOAT_BITS_CONV }
22+
1,82,0 { IS_NONE_OR }
2223
1,81,0 { LINT_REASONS_STABILIZATION }
2324
1,80,0 { BOX_INTO_ITER}
2425
1,77,0 { C_STR_LITERALS }

Diff for: clippy_lints/src/booleans.rs

+60-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use clippy_config::Conf;
2+
use clippy_config::msrvs::{self, Msrv};
13
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
24
use clippy_utils::eq_expr_value;
35
use clippy_utils::source::SpanRangeExt;
@@ -7,7 +9,7 @@ use rustc_errors::Applicability;
79
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
810
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
911
use rustc_lint::{LateContext, LateLintPass, Level};
10-
use rustc_session::declare_lint_pass;
12+
use rustc_session::{RustcVersion, impl_lint_pass};
1113
use rustc_span::def_id::LocalDefId;
1214
use rustc_span::{Span, sym};
1315

@@ -69,9 +71,25 @@ declare_clippy_lint! {
6971
}
7072

7173
// For each pairs, both orders are considered.
72-
const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")];
74+
const METHODS_WITH_NEGATION: [(Option<RustcVersion>, &str, &str); 3] = [
75+
(None, "is_some", "is_none"),
76+
(None, "is_err", "is_ok"),
77+
(Some(msrvs::IS_NONE_OR), "is_some_and", "is_none_or"),
78+
];
79+
80+
pub struct NonminimalBool {
81+
msrv: Msrv,
82+
}
83+
84+
impl NonminimalBool {
85+
pub fn new(conf: &'static Conf) -> Self {
86+
Self {
87+
msrv: conf.msrv.clone(),
88+
}
89+
}
90+
}
7391

74-
declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]);
92+
impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]);
7593

7694
impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
7795
fn check_fn(
@@ -83,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
83101
_: Span,
84102
_: LocalDefId,
85103
) {
86-
NonminimalBoolVisitor { cx }.visit_body(body);
104+
NonminimalBoolVisitor { cx, msrv: &self.msrv }.visit_body(body);
87105
}
88106

89107
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
@@ -100,6 +118,8 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
100118
_ => {},
101119
}
102120
}
121+
122+
extract_msrv_attr!(LateContext);
103123
}
104124

105125
fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
@@ -176,11 +196,11 @@ fn check_inverted_bool_in_condition(
176196
);
177197
}
178198

179-
fn check_simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) {
199+
fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) {
180200
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
181201
&& !expr.span.from_expansion()
182202
&& !inner.span.from_expansion()
183-
&& let Some(suggestion) = simplify_not(cx, inner)
203+
&& let Some(suggestion) = simplify_not(cx, msrv, inner)
184204
&& cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
185205
{
186206
span_lint_and_sugg(
@@ -197,6 +217,7 @@ fn check_simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) {
197217

198218
struct NonminimalBoolVisitor<'a, 'tcx> {
199219
cx: &'a LateContext<'tcx>,
220+
msrv: &'a Msrv,
200221
}
201222

202223
use quine_mc_cluskey::Bool;
@@ -289,6 +310,7 @@ impl<'v> Hir2Qmm<'_, '_, 'v> {
289310
struct SuggestContext<'a, 'tcx, 'v> {
290311
terminals: &'v [&'v Expr<'v>],
291312
cx: &'a LateContext<'tcx>,
313+
msrv: &'a Msrv,
292314
output: String,
293315
}
294316

@@ -311,7 +333,7 @@ impl SuggestContext<'_, '_, '_> {
311333
},
312334
Term(n) => {
313335
let terminal = self.terminals[n as usize];
314-
if let Some(str) = simplify_not(self.cx, terminal) {
336+
if let Some(str) = simplify_not(self.cx, self.msrv, terminal) {
315337
self.output.push_str(&str);
316338
} else {
317339
self.output.push('!');
@@ -358,7 +380,7 @@ impl SuggestContext<'_, '_, '_> {
358380
}
359381
}
360382

361-
fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
383+
fn simplify_not(cx: &LateContext<'_>, curr_msrv: &Msrv, expr: &Expr<'_>) -> Option<String> {
362384
match &expr.kind {
363385
ExprKind::Binary(binop, lhs, rhs) => {
364386
if !implements_ord(cx, lhs) {
@@ -389,7 +411,7 @@ fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
389411
Some(format!("{lhs_snippet}{op}{rhs_snippet}"))
390412
})
391413
},
392-
ExprKind::MethodCall(path, receiver, [], _) => {
414+
ExprKind::MethodCall(path, receiver, args, _) => {
393415
let type_of_receiver = cx.typeck_results().expr_ty(receiver);
394416
if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option)
395417
&& !is_type_diagnostic_item(cx, type_of_receiver, sym::Result)
@@ -399,21 +421,41 @@ fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
399421
METHODS_WITH_NEGATION
400422
.iter()
401423
.copied()
402-
.flat_map(|(a, b)| vec![(a, b), (b, a)])
403-
.find(|&(a, _)| {
404-
let path: &str = path.ident.name.as_str();
405-
a == path
424+
.flat_map(|(msrv, a, b)| vec![(msrv, a, b), (msrv, b, a)])
425+
.find(|&(msrv, a, _)| msrv.is_none_or(|msrv| curr_msrv.meets(msrv)) && a == path.ident.name.as_str())
426+
.and_then(|(_, _, neg_method)| {
427+
let negated_args = args
428+
.iter()
429+
.map(|arg| simplify_not(cx, curr_msrv, arg))
430+
.collect::<Option<Vec<_>>>()?
431+
.join(", ");
432+
Some(format!(
433+
"{}.{neg_method}({negated_args})",
434+
receiver.span.get_source_text(cx)?
435+
))
406436
})
407-
.and_then(|(_, neg_method)| Some(format!("{}.{neg_method}()", receiver.span.get_source_text(cx)?)))
408437
},
438+
ExprKind::Closure(closure) => {
439+
let body = cx.tcx.hir().body(closure.body);
440+
let params = body
441+
.params
442+
.iter()
443+
.map(|param| param.span.get_source_text(cx).map(|t| t.to_string()))
444+
.collect::<Option<Vec<_>>>()?
445+
.join(", ");
446+
let negated = simplify_not(cx, curr_msrv, body.value)?;
447+
Some(format!("|{params}| {negated}"))
448+
},
449+
ExprKind::Unary(UnOp::Not, expr) => expr.span.get_source_text(cx).map(|t| t.to_string()),
409450
_ => None,
410451
}
411452
}
412453

413-
fn suggest(cx: &LateContext<'_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
454+
fn suggest(cx: &LateContext<'_>, msrv: &Msrv, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
414455
let mut suggest_context = SuggestContext {
415456
terminals,
416457
cx,
458+
msrv,
417459
output: String::new(),
418460
};
419461
suggest_context.recurse(suggestion);
@@ -526,7 +568,7 @@ impl<'tcx> NonminimalBoolVisitor<'_, 'tcx> {
526568
diag.span_suggestion(
527569
e.span,
528570
"it would look like the following",
529-
suggest(self.cx, suggestion, &h2q.terminals),
571+
suggest(self.cx, self.msrv, suggestion, &h2q.terminals),
530572
// nonminimal_bool can produce minimal but
531573
// not human readable expressions (#3141)
532574
Applicability::Unspecified,
@@ -569,12 +611,12 @@ impl<'tcx> NonminimalBoolVisitor<'_, 'tcx> {
569611
}
570612
};
571613
if improvements.is_empty() {
572-
check_simplify_not(self.cx, e);
614+
check_simplify_not(self.cx, self.msrv, e);
573615
} else {
574616
nonminimal_bool_lint(
575617
improvements
576618
.into_iter()
577-
.map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
619+
.map(|suggestion| suggest(self.cx, self.msrv, suggestion, &h2q.terminals))
578620
.collect(),
579621
);
580622
}

Diff for: clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
609609
store.register_late_pass(move |tcx| Box::new(await_holding_invalid::AwaitHolding::new(tcx, conf)));
610610
store.register_late_pass(|_| Box::new(serde_api::SerdeApi));
611611
store.register_late_pass(move |_| Box::new(types::Types::new(conf)));
612-
store.register_late_pass(|_| Box::new(booleans::NonminimalBool));
612+
store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf)));
613613
store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant));
614614
store.register_late_pass(|_| Box::new(float_literal::FloatLiteral));
615615
store.register_late_pass(|_| Box::new(ptr::Ptr));

Diff for: tests/ui/nonminimal_bool_methods.fixed

+62
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,66 @@ fn issue_12625() {
115115
if a as u64 > b {} //~ ERROR: this boolean expression can be simplified
116116
}
117117

118+
fn issue_13436() {
119+
fn not_zero(x: i32) -> bool {
120+
x != 0
121+
}
122+
123+
let opt = Some(500);
124+
_ = opt.is_some_and(|x| x < 1000);
125+
_ = opt.is_some_and(|x| x <= 1000);
126+
_ = opt.is_some_and(|x| x > 1000);
127+
_ = opt.is_some_and(|x| x >= 1000);
128+
_ = opt.is_some_and(|x| x == 1000);
129+
_ = opt.is_some_and(|x| x != 1000);
130+
_ = opt.is_some_and(not_zero);
131+
_ = opt.is_none_or(|x| x >= 1000); //~ ERROR: this boolean expression can be simplified
132+
_ = opt.is_none_or(|x| x > 1000); //~ ERROR: this boolean expression can be simplified
133+
_ = opt.is_none_or(|x| x <= 1000); //~ ERROR: this boolean expression can be simplified
134+
_ = opt.is_none_or(|x| x < 1000); //~ ERROR: this boolean expression can be simplified
135+
_ = opt.is_none_or(|x| x != 1000); //~ ERROR: this boolean expression can be simplified
136+
_ = opt.is_none_or(|x| x == 1000); //~ ERROR: this boolean expression can be simplified
137+
_ = !opt.is_some_and(not_zero);
138+
_ = opt.is_none_or(|x| x < 1000);
139+
_ = opt.is_none_or(|x| x <= 1000);
140+
_ = opt.is_none_or(|x| x > 1000);
141+
_ = opt.is_none_or(|x| x >= 1000);
142+
_ = opt.is_none_or(|x| x == 1000);
143+
_ = opt.is_none_or(|x| x != 1000);
144+
_ = opt.is_none_or(not_zero);
145+
_ = opt.is_some_and(|x| x >= 1000); //~ ERROR: this boolean expression can be simplified
146+
_ = opt.is_some_and(|x| x > 1000); //~ ERROR: this boolean expression can be simplified
147+
_ = opt.is_some_and(|x| x <= 1000); //~ ERROR: this boolean expression can be simplified
148+
_ = opt.is_some_and(|x| x < 1000); //~ ERROR: this boolean expression can be simplified
149+
_ = opt.is_some_and(|x| x != 1000); //~ ERROR: this boolean expression can be simplified
150+
_ = opt.is_some_and(|x| x == 1000); //~ ERROR: this boolean expression can be simplified
151+
_ = !opt.is_none_or(not_zero);
152+
153+
let opt = Some(true);
154+
_ = opt.is_some_and(|x| x);
155+
_ = opt.is_some_and(|x| !x);
156+
_ = !opt.is_some_and(|x| x);
157+
_ = opt.is_none_or(|x| x); //~ ERROR: this boolean expression can be simplified
158+
_ = opt.is_none_or(|x| x);
159+
_ = opt.is_none_or(|x| !x);
160+
_ = !opt.is_none_or(|x| x);
161+
_ = opt.is_some_and(|x| x); //~ ERROR: this boolean expression can be simplified
162+
163+
let opt: Option<Result<i32, i32>> = Some(Ok(123));
164+
_ = opt.is_some_and(|x| x.is_ok());
165+
_ = opt.is_some_and(|x| x.is_err());
166+
_ = opt.is_none_or(|x| x.is_ok());
167+
_ = opt.is_none_or(|x| x.is_err());
168+
_ = opt.is_none_or(|x| x.is_err()); //~ ERROR: this boolean expression can be simplified
169+
_ = opt.is_none_or(|x| x.is_ok()); //~ ERROR: this boolean expression can be simplified
170+
_ = opt.is_some_and(|x| x.is_err()); //~ ERROR: this boolean expression can be simplified
171+
_ = opt.is_some_and(|x| x.is_ok()); //~ ERROR: this boolean expression can be simplified
172+
173+
#[clippy::msrv = "1.81"]
174+
fn before_stabilization() {
175+
let opt = Some(500);
176+
_ = !opt.is_some_and(|x| x < 1000);
177+
}
178+
}
179+
118180
fn main() {}

Diff for: tests/ui/nonminimal_bool_methods.rs

+62
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,66 @@ fn issue_12625() {
115115
if !(a as u64 <= b) {} //~ ERROR: this boolean expression can be simplified
116116
}
117117

118+
fn issue_13436() {
119+
fn not_zero(x: i32) -> bool {
120+
x != 0
121+
}
122+
123+
let opt = Some(500);
124+
_ = opt.is_some_and(|x| x < 1000);
125+
_ = opt.is_some_and(|x| x <= 1000);
126+
_ = opt.is_some_and(|x| x > 1000);
127+
_ = opt.is_some_and(|x| x >= 1000);
128+
_ = opt.is_some_and(|x| x == 1000);
129+
_ = opt.is_some_and(|x| x != 1000);
130+
_ = opt.is_some_and(not_zero);
131+
_ = !opt.is_some_and(|x| x < 1000); //~ ERROR: this boolean expression can be simplified
132+
_ = !opt.is_some_and(|x| x <= 1000); //~ ERROR: this boolean expression can be simplified
133+
_ = !opt.is_some_and(|x| x > 1000); //~ ERROR: this boolean expression can be simplified
134+
_ = !opt.is_some_and(|x| x >= 1000); //~ ERROR: this boolean expression can be simplified
135+
_ = !opt.is_some_and(|x| x == 1000); //~ ERROR: this boolean expression can be simplified
136+
_ = !opt.is_some_and(|x| x != 1000); //~ ERROR: this boolean expression can be simplified
137+
_ = !opt.is_some_and(not_zero);
138+
_ = opt.is_none_or(|x| x < 1000);
139+
_ = opt.is_none_or(|x| x <= 1000);
140+
_ = opt.is_none_or(|x| x > 1000);
141+
_ = opt.is_none_or(|x| x >= 1000);
142+
_ = opt.is_none_or(|x| x == 1000);
143+
_ = opt.is_none_or(|x| x != 1000);
144+
_ = opt.is_none_or(not_zero);
145+
_ = !opt.is_none_or(|x| x < 1000); //~ ERROR: this boolean expression can be simplified
146+
_ = !opt.is_none_or(|x| x <= 1000); //~ ERROR: this boolean expression can be simplified
147+
_ = !opt.is_none_or(|x| x > 1000); //~ ERROR: this boolean expression can be simplified
148+
_ = !opt.is_none_or(|x| x >= 1000); //~ ERROR: this boolean expression can be simplified
149+
_ = !opt.is_none_or(|x| x == 1000); //~ ERROR: this boolean expression can be simplified
150+
_ = !opt.is_none_or(|x| x != 1000); //~ ERROR: this boolean expression can be simplified
151+
_ = !opt.is_none_or(not_zero);
152+
153+
let opt = Some(true);
154+
_ = opt.is_some_and(|x| x);
155+
_ = opt.is_some_and(|x| !x);
156+
_ = !opt.is_some_and(|x| x);
157+
_ = !opt.is_some_and(|x| !x); //~ ERROR: this boolean expression can be simplified
158+
_ = opt.is_none_or(|x| x);
159+
_ = opt.is_none_or(|x| !x);
160+
_ = !opt.is_none_or(|x| x);
161+
_ = !opt.is_none_or(|x| !x); //~ ERROR: this boolean expression can be simplified
162+
163+
let opt: Option<Result<i32, i32>> = Some(Ok(123));
164+
_ = opt.is_some_and(|x| x.is_ok());
165+
_ = opt.is_some_and(|x| x.is_err());
166+
_ = opt.is_none_or(|x| x.is_ok());
167+
_ = opt.is_none_or(|x| x.is_err());
168+
_ = !opt.is_some_and(|x| x.is_ok()); //~ ERROR: this boolean expression can be simplified
169+
_ = !opt.is_some_and(|x| x.is_err()); //~ ERROR: this boolean expression can be simplified
170+
_ = !opt.is_none_or(|x| x.is_ok()); //~ ERROR: this boolean expression can be simplified
171+
_ = !opt.is_none_or(|x| x.is_err()); //~ ERROR: this boolean expression can be simplified
172+
173+
#[clippy::msrv = "1.81"]
174+
fn before_stabilization() {
175+
let opt = Some(500);
176+
_ = !opt.is_some_and(|x| x < 1000);
177+
}
178+
}
179+
118180
fn main() {}

0 commit comments

Comments
 (0)