Skip to content

Commit 3c54672

Browse files
authored
Optimize some usages of !! and -- in suggestions (#15366)
When an expression is made of a `!` or `-` unary operator which does not change the type of the expression, use a new variant in `Sugg` to denote it. This allows replacing an extra application of the same operator by the removal of the original operator instead. Some suggestions will now be shown as `x` instead of `!!x`. Right now, no suggestion seems to produce `--x`. changelog: none
2 parents 445d419 + 642bec7 commit 3c54672

File tree

4 files changed

+101
-40
lines changed

4 files changed

+101
-40
lines changed

clippy_lints/src/floating_point_arithmetic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
148148
.into();
149149

150150
suggestion = match suggestion {
151-
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
151+
Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op),
152152
_ => Sugg::NonParen(op),
153153
};
154154
}

clippy_utils/src/sugg.rs

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
55
use crate::ty::expr_sig;
66
use crate::{get_parent_expr_for_hir, higher};
7-
use rustc_ast::ast;
87
use rustc_ast::util::parser::AssocOp;
8+
use rustc_ast::{UnOp, ast};
99
use rustc_data_structures::fx::FxHashSet;
1010
use rustc_errors::Applicability;
1111
use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind};
@@ -29,6 +29,11 @@ pub enum Sugg<'a> {
2929
/// A binary operator expression, including `as`-casts and explicit type
3030
/// coercion.
3131
BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
32+
/// A unary operator expression. This is used to sometimes represent `!`
33+
/// or `-`, but only if the type with and without the operator is kept identical.
34+
/// It means that doubling the operator can be used to remove it instead, in
35+
/// order to provide better suggestions.
36+
UnOp(UnOp, Box<Sugg<'a>>),
3237
}
3338

3439
/// Literal constant `0`, for convenience.
@@ -40,9 +45,10 @@ pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
4045

4146
impl Display for Sugg<'_> {
4247
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
43-
match *self {
44-
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
45-
Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
48+
match self {
49+
Sugg::NonParen(s) | Sugg::MaybeParen(s) => s.fmt(f),
50+
Sugg::BinOp(op, lhs, rhs) => binop_to_string(*op, lhs, rhs).fmt(f),
51+
Sugg::UnOp(op, inner) => write!(f, "{}{}", op.as_str(), inner.clone().maybe_inner_paren()),
4652
}
4753
}
4854
}
@@ -100,9 +106,19 @@ impl<'a> Sugg<'a> {
100106
applicability: &mut Applicability,
101107
) -> Self {
102108
if expr.span.ctxt() == ctxt {
103-
Self::hir_from_snippet(expr, |span| {
104-
snippet_with_context(cx, span, ctxt, default, applicability).0
105-
})
109+
if let ExprKind::Unary(op, inner) = expr.kind
110+
&& matches!(op, UnOp::Neg | UnOp::Not)
111+
&& cx.typeck_results().expr_ty(expr) == cx.typeck_results().expr_ty(inner)
112+
{
113+
Sugg::UnOp(
114+
op,
115+
Box::new(Self::hir_with_context(cx, inner, ctxt, default, applicability)),
116+
)
117+
} else {
118+
Self::hir_from_snippet(expr, |span| {
119+
snippet_with_context(cx, span, ctxt, default, applicability).0
120+
})
121+
}
106122
} else {
107123
let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
108124
Sugg::NonParen(snip)
@@ -341,13 +357,34 @@ impl<'a> Sugg<'a> {
341357
let sugg = binop_to_string(op, &lhs, &rhs);
342358
Sugg::NonParen(format!("({sugg})").into())
343359
},
360+
Sugg::UnOp(op, inner) => Sugg::NonParen(format!("({}{})", op.as_str(), inner.maybe_inner_paren()).into()),
344361
}
345362
}
346363

347364
pub fn into_string(self) -> String {
348365
match self {
349366
Sugg::NonParen(p) | Sugg::MaybeParen(p) => p.into_owned(),
350367
Sugg::BinOp(b, l, r) => binop_to_string(b, &l, &r),
368+
Sugg::UnOp(op, inner) => format!("{}{}", op.as_str(), inner.maybe_inner_paren()),
369+
}
370+
}
371+
372+
/// Checks if `self` starts with a unary operator.
373+
fn starts_with_unary_op(&self) -> bool {
374+
match self {
375+
Sugg::UnOp(..) => true,
376+
Sugg::BinOp(..) => false,
377+
Sugg::MaybeParen(s) | Sugg::NonParen(s) => s.starts_with(['*', '!', '-', '&']),
378+
}
379+
}
380+
381+
/// Call `maybe_paren` on `self` if it doesn't start with a unary operator,
382+
/// don't touch it otherwise.
383+
fn maybe_inner_paren(self) -> Self {
384+
if self.starts_with_unary_op() {
385+
self
386+
} else {
387+
self.maybe_paren()
351388
}
352389
}
353390
}
@@ -430,10 +467,11 @@ impl Sub for &Sugg<'_> {
430467
forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
431468
forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
432469

433-
impl Neg for Sugg<'_> {
434-
type Output = Sugg<'static>;
435-
fn neg(self) -> Sugg<'static> {
436-
match &self {
470+
impl<'a> Neg for Sugg<'a> {
471+
type Output = Sugg<'a>;
472+
fn neg(self) -> Self::Output {
473+
match self {
474+
Self::UnOp(UnOp::Neg, sugg) => *sugg,
437475
Self::BinOp(AssocOp::Cast, ..) => Sugg::MaybeParen(format!("-({self})").into()),
438476
_ => make_unop("-", self),
439477
}
@@ -446,19 +484,21 @@ impl<'a> Not for Sugg<'a> {
446484
use AssocOp::Binary;
447485
use ast::BinOpKind::{Eq, Ge, Gt, Le, Lt, Ne};
448486

449-
if let Sugg::BinOp(op, lhs, rhs) = self {
450-
let to_op = match op {
451-
Binary(Eq) => Binary(Ne),
452-
Binary(Ne) => Binary(Eq),
453-
Binary(Lt) => Binary(Ge),
454-
Binary(Ge) => Binary(Lt),
455-
Binary(Gt) => Binary(Le),
456-
Binary(Le) => Binary(Gt),
457-
_ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
458-
};
459-
Sugg::BinOp(to_op, lhs, rhs)
460-
} else {
461-
make_unop("!", self)
487+
match self {
488+
Sugg::BinOp(op, lhs, rhs) => {
489+
let to_op = match op {
490+
Binary(Eq) => Binary(Ne),
491+
Binary(Ne) => Binary(Eq),
492+
Binary(Lt) => Binary(Ge),
493+
Binary(Ge) => Binary(Lt),
494+
Binary(Gt) => Binary(Le),
495+
Binary(Le) => Binary(Gt),
496+
_ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
497+
};
498+
Sugg::BinOp(to_op, lhs, rhs)
499+
},
500+
Sugg::UnOp(UnOp::Not, expr) => *expr,
501+
_ => make_unop("!", self),
462502
}
463503
}
464504
}
@@ -491,20 +531,11 @@ impl<T: Display> Display for ParenHelper<T> {
491531
/// Builds the string for `<op><expr>` adding parenthesis when necessary.
492532
///
493533
/// For convenience, the operator is taken as a string because all unary
494-
/// operators have the same
495-
/// precedence.
534+
/// operators have the same precedence.
496535
pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
497-
// If the `expr` starts with `op` already, do not add wrap it in
536+
// If the `expr` starts with a unary operator already, do not wrap it in
498537
// parentheses.
499-
let expr = if let Sugg::MaybeParen(ref sugg) = expr
500-
&& !has_enclosing_paren(sugg)
501-
&& sugg.starts_with(op)
502-
{
503-
expr
504-
} else {
505-
expr.maybe_paren()
506-
};
507-
Sugg::MaybeParen(format!("{op}{expr}").into())
538+
Sugg::MaybeParen(format!("{op}{}", expr.maybe_inner_paren()).into())
508539
}
509540

510541
/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.

tests/ui/nonminimal_bool.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,21 @@ mod issue14404 {
236236
}
237237
}
238238
}
239+
240+
fn dont_simplify_double_not_if_types_differ() {
241+
struct S;
242+
243+
impl std::ops::Not for S {
244+
type Output = bool;
245+
fn not(self) -> bool {
246+
true
247+
}
248+
}
249+
250+
// The lint must propose `if !!S`, not `if S`.
251+
// FIXME: `bool_comparison` will propose to use `S == true`
252+
// which is invalid.
253+
if !S != true {}
254+
//~^ nonminimal_bool
255+
//~| bool_comparison
256+
}

tests/ui/nonminimal_bool.stderr

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ error: inequality checks against true can be replaced by a negation
179179
--> tests/ui/nonminimal_bool.rs:186:8
180180
|
181181
LL | if !b != true {}
182-
| ^^^^^^^^^^ help: try simplifying it as shown: `!!b`
182+
| ^^^^^^^^^^ help: try simplifying it as shown: `b`
183183

184184
error: this boolean expression can be simplified
185185
--> tests/ui/nonminimal_bool.rs:189:8
@@ -209,7 +209,7 @@ error: inequality checks against true can be replaced by a negation
209209
--> tests/ui/nonminimal_bool.rs:193:8
210210
|
211211
LL | if true != !b {}
212-
| ^^^^^^^^^^ help: try simplifying it as shown: `!!b`
212+
| ^^^^^^^^^^ help: try simplifying it as shown: `b`
213213

214214
error: this boolean expression can be simplified
215215
--> tests/ui/nonminimal_bool.rs:196:8
@@ -235,5 +235,17 @@ error: this boolean expression can be simplified
235235
LL | if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) {
236236
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr)`
237237

238-
error: aborting due to 31 previous errors
238+
error: this boolean expression can be simplified
239+
--> tests/ui/nonminimal_bool.rs:253:8
240+
|
241+
LL | if !S != true {}
242+
| ^^^^^^^^^^ help: try: `S == true`
243+
244+
error: inequality checks against true can be replaced by a negation
245+
--> tests/ui/nonminimal_bool.rs:253:8
246+
|
247+
LL | if !S != true {}
248+
| ^^^^^^^^^^ help: try simplifying it as shown: `!!S`
249+
250+
error: aborting due to 33 previous errors
239251

0 commit comments

Comments
 (0)