Skip to content

Commit 3c58b2f

Browse files
authored
Rollup merge of #126604 - kadiwa4:uplift_double_negation, r=nnethercote
Uplift `clippy::double_neg` lint as `double_negations` Warns about cases like this: ```rust fn main() { let x = 1; let _b = --x; //~ WARN use of a double negation } ``` The intent is to keep people from thinking that `--x` is a prefix decrement operator. `++x`, `x++` and `x--` are invalid expressions and already have a helpful diagnostic. I didn't add a machine-applicable suggestion to the lint because it's not entirely clear what the programmer was trying to achieve with the `--x` operation. The code that triggers the lint should always be reviewed manually. Closes #82987
2 parents 0df0662 + c1dcbeb commit 3c58b2f

20 files changed

+254
-172
lines changed

compiler/rustc_lint/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ lint_builtin_deprecated_attr_link = use of deprecated attribute `{$name}`: {$rea
7676
lint_builtin_deref_nullptr = dereferencing a null pointer
7777
.label = this code causes undefined behavior when executed
7878
79+
lint_builtin_double_negations = use of a double negation
80+
.note = the prefix `--` could be misinterpreted as a decrement operator which exists in other languages
81+
.note_decrement = use `-= 1` if you meant to decrement the value
82+
.add_parens_suggestion = add parentheses for clarity
83+
7984
lint_builtin_ellipsis_inclusive_range_patterns = `...` range patterns are deprecated
8085
.suggestion = use `..=` for an inclusive range
8186

compiler/rustc_lint/src/builtin.rs

+66-21
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ use rustc_trait_selection::traits::{self};
4949
use crate::errors::BuiltinEllipsisInclusiveRangePatterns;
5050
use crate::lints::{
5151
BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink,
52-
BuiltinDeprecatedAttrLinkSuggestion, BuiltinDerefNullptr,
53-
BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
54-
BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
55-
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
56-
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
57-
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
58-
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
59-
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
60-
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
61-
BuiltinWhileTrue, InvalidAsmLabel,
52+
BuiltinDeprecatedAttrLinkSuggestion, BuiltinDerefNullptr, BuiltinDoubleNegations,
53+
BuiltinDoubleNegationsAddParens, BuiltinEllipsisInclusiveRangePatternsLint,
54+
BuiltinExplicitOutlives, BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote,
55+
BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures,
56+
BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc,
57+
BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
58+
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
59+
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub,
60+
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
61+
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
6262
};
6363
use crate::nonstandard_style::{MethodLateContext, method_context};
6464
use crate::{
@@ -90,19 +90,11 @@ declare_lint! {
9090

9191
declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
9292

93-
/// Traverse through any amount of parenthesis and return the first non-parens expression.
94-
fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
95-
while let ast::ExprKind::Paren(sub) = &expr.kind {
96-
expr = sub;
97-
}
98-
expr
99-
}
100-
10193
impl EarlyLintPass for WhileTrue {
10294
#[inline]
10395
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
10496
if let ast::ExprKind::While(cond, _, label) = &e.kind
105-
&& let ast::ExprKind::Lit(token_lit) = pierce_parens(cond).kind
97+
&& let ast::ExprKind::Lit(token_lit) = cond.peel_parens().kind
10698
&& let token::Lit { kind: token::Bool, symbol: kw::True, .. } = token_lit
10799
&& !cond.span.from_expansion()
108100
{
@@ -1576,6 +1568,58 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
15761568
}
15771569
}
15781570

1571+
declare_lint! {
1572+
/// The `double_negations` lint detects expressions of the form `--x`.
1573+
///
1574+
/// ### Example
1575+
///
1576+
/// ```rust
1577+
/// fn main() {
1578+
/// let x = 1;
1579+
/// let _b = --x;
1580+
/// }
1581+
/// ```
1582+
///
1583+
/// {{produces}}
1584+
///
1585+
/// ### Explanation
1586+
///
1587+
/// Negating something twice is usually the same as not negating it at all.
1588+
/// However, a double negation in Rust can easily be confused with the
1589+
/// prefix decrement operator that exists in many languages derived from C.
1590+
/// Use `-(-x)` if you really wanted to negate the value twice.
1591+
///
1592+
/// To decrement a value, use `x -= 1` instead.
1593+
pub DOUBLE_NEGATIONS,
1594+
Warn,
1595+
"detects expressions of the form `--x`"
1596+
}
1597+
1598+
declare_lint_pass!(
1599+
/// Lint for expressions of the form `--x` that can be confused with C's
1600+
/// prefix decrement operator.
1601+
DoubleNegations => [DOUBLE_NEGATIONS]
1602+
);
1603+
1604+
impl EarlyLintPass for DoubleNegations {
1605+
#[inline]
1606+
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
1607+
// only lint on the innermost `--` in a chain of `-` operators,
1608+
// even if there are 3 or more negations
1609+
if let ExprKind::Unary(UnOp::Neg, ref inner) = expr.kind
1610+
&& let ExprKind::Unary(UnOp::Neg, ref inner2) = inner.kind
1611+
&& !matches!(inner2.kind, ExprKind::Unary(UnOp::Neg, _))
1612+
{
1613+
cx.emit_span_lint(DOUBLE_NEGATIONS, expr.span, BuiltinDoubleNegations {
1614+
add_parens: BuiltinDoubleNegationsAddParens {
1615+
start_span: inner.span.shrink_to_lo(),
1616+
end_span: inner.span.shrink_to_hi(),
1617+
},
1618+
});
1619+
}
1620+
}
1621+
}
1622+
15791623
declare_lint_pass!(
15801624
/// Does nothing as a lint pass, but registers some `Lint`s
15811625
/// which are used by other parts of the compiler.
@@ -1594,7 +1638,8 @@ declare_lint_pass!(
15941638
UNSTABLE_FEATURES,
15951639
UNREACHABLE_PUB,
15961640
TYPE_ALIAS_BOUNDS,
1597-
TRIVIAL_BOUNDS
1641+
TRIVIAL_BOUNDS,
1642+
DOUBLE_NEGATIONS
15981643
]
15991644
);
16001645

@@ -2651,7 +2696,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
26512696
}
26522697

26532698
declare_lint! {
2654-
/// The `deref_nullptr` lint detects when an null pointer is dereferenced,
2699+
/// The `deref_nullptr` lint detects when a null pointer is dereferenced,
26552700
/// which causes [undefined behavior].
26562701
///
26572702
/// ### Example

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ early_lint_methods!(
181181
UnusedDocComment: UnusedDocComment,
182182
Expr2024: Expr2024,
183183
Precedence: Precedence,
184+
DoubleNegations: DoubleNegations,
184185
]
185186
]
186187
);

compiler/rustc_lint/src/lints.rs

+18
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,24 @@ pub(crate) struct BuiltinTrivialBounds<'a> {
331331
pub predicate: Clause<'a>,
332332
}
333333

334+
#[derive(LintDiagnostic)]
335+
#[diag(lint_builtin_double_negations)]
336+
#[note(lint_note)]
337+
#[note(lint_note_decrement)]
338+
pub(crate) struct BuiltinDoubleNegations {
339+
#[subdiagnostic]
340+
pub add_parens: BuiltinDoubleNegationsAddParens,
341+
}
342+
343+
#[derive(Subdiagnostic)]
344+
#[multipart_suggestion(lint_add_parens_suggestion, applicability = "maybe-incorrect")]
345+
pub(crate) struct BuiltinDoubleNegationsAddParens {
346+
#[suggestion_part(code = "(")]
347+
pub start_span: Span,
348+
#[suggestion_part(code = ")")]
349+
pub end_span: Span,
350+
}
351+
334352
#[derive(LintDiagnostic)]
335353
pub(crate) enum BuiltinEllipsisInclusiveRangePatternsLint {
336354
#[diag(lint_builtin_ellipsis_inclusive_range_patterns)]

src/tools/clippy/.github/driver.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ unset CARGO_MANIFEST_DIR
4747

4848
# Run a lint and make sure it produces the expected output. It's also expected to exit with code 1
4949
# FIXME: How to match the clippy invocation in compile-test.rs?
50-
./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/double_neg.rs 2>double_neg.stderr && exit 1
51-
sed -e "/= help: for/d" double_neg.stderr > normalized.stderr
52-
diff -u normalized.stderr tests/ui/double_neg.stderr
50+
./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/box_default.rs 2>box_default.stderr && exit 1
51+
sed -e "/= help: for/d" box_default.stderr > normalized.stderr
52+
diff -u normalized.stderr tests/ui/box_default.stderr
5353

5454
# make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same
5555
SYSROOT=$(rustc --print sysroot)

src/tools/clippy/book/src/usage.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ You can configure lint levels on the command line by adding
3333
`-A/W/D clippy::lint_name` like this:
3434

3535
```bash
36-
cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf
36+
cargo clippy -- -Aclippy::style -Wclippy::box_default -Dclippy::perf
3737
```
3838

3939
For [CI] all warnings can be elevated to errors which will in turn fail
@@ -101,11 +101,10 @@ You can configure lint levels in source code the same way you can configure
101101
```rust,ignore
102102
#![allow(clippy::style)]
103103
104-
#[warn(clippy::double_neg)]
104+
#[warn(clippy::box_default)]
105105
fn main() {
106-
let x = 1;
107-
let y = --x;
108-
// ^^ warning: double negation
106+
let _ = Box::<String>::new(Default::default());
107+
// ^ warning: `Box::new(_)` of default value
109108
}
110109
```
111110

src/tools/clippy/clippy_dev/src/new_lint.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
455455
});
456456

457457
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
458-
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token == TokenKind::Ident) {
458+
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) {
459459
let mut iter = iter
460460
.by_ref()
461461
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
@@ -465,7 +465,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
465465
// matches `!{`
466466
match_tokens!(iter, Bang OpenBrace);
467467
if let Some(LintDeclSearchResult { range, .. }) =
468-
iter.find(|result| result.token == TokenKind::CloseBrace)
468+
iter.find(|result| result.token_kind == TokenKind::CloseBrace)
469469
{
470470
last_decl_curly_offset = Some(range.end);
471471
}

src/tools/clippy/clippy_lints/src/declared_lints.rs

-1
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
507507
crate::misc::USED_UNDERSCORE_BINDING_INFO,
508508
crate::misc::USED_UNDERSCORE_ITEMS_INFO,
509509
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
510-
crate::misc_early::DOUBLE_NEG_INFO,
511510
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
512511
crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO,
513512
crate::misc_early::REDUNDANT_AT_REST_PATTERN_INFO,

src/tools/clippy/clippy_lints/src/deprecated_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
129129
("clippy::clone_double_ref", "suspicious_double_ref_op"),
130130
#[clippy::version = ""]
131131
("clippy::cmp_nan", "invalid_nan_comparisons"),
132+
#[clippy::version = "1.86.0"]
133+
("clippy::double_neg", "double_negations"),
132134
#[clippy::version = ""]
133135
("clippy::drop_bounds", "drop_bounds"),
134136
#[clippy::version = ""]

src/tools/clippy/clippy_lints/src/misc_early/double_neg.rs

-18
This file was deleted.

src/tools/clippy/clippy_lints/src/misc_early/mod.rs

-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
mod builtin_type_shadow;
2-
mod double_neg;
32
mod literal_suffix;
43
mod mixed_case_hex_literals;
54
mod redundant_at_rest_pattern;
@@ -85,25 +84,6 @@ declare_clippy_lint! {
8584
"function arguments having names which only differ by an underscore"
8685
}
8786

88-
declare_clippy_lint! {
89-
/// ### What it does
90-
/// Detects expressions of the form `--x`.
91-
///
92-
/// ### Why is this bad?
93-
/// It can mislead C/C++ programmers to think `x` was
94-
/// decremented.
95-
///
96-
/// ### Example
97-
/// ```no_run
98-
/// let mut x = 3;
99-
/// --x;
100-
/// ```
101-
#[clippy::version = "pre 1.29.0"]
102-
pub DOUBLE_NEG,
103-
style,
104-
"`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
105-
}
106-
10787
declare_clippy_lint! {
10888
/// ### What it does
10989
/// Warns on hexadecimal literals with mixed-case letter
@@ -352,7 +332,6 @@ declare_clippy_lint! {
352332
declare_lint_pass!(MiscEarlyLints => [
353333
UNNEEDED_FIELD_PATTERN,
354334
DUPLICATE_UNDERSCORE_ARGUMENT,
355-
DOUBLE_NEG,
356335
MIXED_CASE_HEX_LITERALS,
357336
UNSEPARATED_LITERAL_SUFFIX,
358337
SEPARATED_LITERAL_SUFFIX,
@@ -415,7 +394,6 @@ impl EarlyLintPass for MiscEarlyLints {
415394
if let ExprKind::Lit(lit) = expr.kind {
416395
MiscEarlyLints::check_lit(cx, lit, expr.span);
417396
}
418-
double_neg::check(cx, expr);
419397
}
420398
}
421399

src/tools/clippy/tests/ui/double_neg.rs

-10
This file was deleted.

src/tools/clippy/tests/ui/double_neg.stderr

-11
This file was deleted.

0 commit comments

Comments
 (0)