about summary refs log tree commit diff
diff options
context:
space:
mode:
authorUrgau <urgau@numericable.fr>2024-02-20 21:40:25 +0100
committerUrgau <urgau@numericable.fr>2024-07-11 00:46:47 +0200
commitde560c30659281e8c91a48579f907097efe22adf (patch)
tree2e27c209275e367550e5c0dba76cfb0e5ea263b5
parent0c81f94b9a6207fb1fc080caa83584dea2d71fc6 (diff)
downloadrust-de560c30659281e8c91a48579f907097efe22adf.tar.gz
rust-de560c30659281e8c91a48579f907097efe22adf.zip
Implement `ambiguous_negative_literals` lint
-rw-r--r--compiler/rustc_lint/messages.ftl5
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs29
-rw-r--r--compiler/rustc_lint/src/precedence.rs70
-rw-r--r--tests/ui/lint/negative_literals.rs35
-rw-r--r--tests/ui/lint/negative_literals.stderr179
6 files changed, 321 insertions, 0 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index f048f6fe8ad..4f7f01e56b6 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -5,6 +5,11 @@ lint_ambiguous_glob_reexport = ambiguous glob re-exports
     .label_first_reexport = the name `{$name}` in the {$namespace} namespace is first re-exported here
     .label_duplicate_reexport = but the name `{$name}` in the {$namespace} namespace is also re-exported here
 
+lint_ambiguous_negative_literals = `-` has lower precedence than method calls, which might be unexpected
+    .example = e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+    .negative_literal = add parentheses around the `-` and the literal to call the method on a negative literal
+    .current_behavior = add parentheses around the literal and the method call to keep the current behavior
+
 lint_ambiguous_wide_pointer_comparisons = ambiguous wide pointer comparison, the comparison includes metadata which may not be expected
     .addr_metadata_suggestion = use explicit `std::ptr::eq` method to compare metadata and addresses
     .addr_suggestion = use `std::ptr::addr_eq` or untyped pointers to only compare their addresses
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 868a44a980a..0a058b675ff 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -72,6 +72,7 @@ mod noop_method_call;
 mod opaque_hidden_inferred_bound;
 mod pass_by_value;
 mod passes;
+mod precedence;
 mod ptr_nulls;
 mod redundant_semicolon;
 mod reference_casting;
@@ -109,6 +110,7 @@ use nonstandard_style::*;
 use noop_method_call::*;
 use opaque_hidden_inferred_bound::*;
 use pass_by_value::*;
+use precedence::*;
 use ptr_nulls::*;
 use redundant_semicolon::*;
 use reference_casting::*;
@@ -173,6 +175,7 @@ early_lint_methods!(
             RedundantSemicolons: RedundantSemicolons,
             UnusedDocComment: UnusedDocComment,
             Expr2024: Expr2024,
+            Precedence: Precedence,
         ]
     ]
 );
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 54c73710eca..873322c13b4 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -1489,6 +1489,35 @@ pub struct NonLocalDefinitionsCargoUpdateNote {
     pub crate_name: Symbol,
 }
 
+// precedence.rs
+#[derive(LintDiagnostic)]
+#[diag(lint_ambiguous_negative_literals)]
+#[note(lint_example)]
+pub struct AmbiguousNegativeLiteralsDiag {
+    #[subdiagnostic]
+    pub negative_literal: AmbiguousNegativeLiteralsNegativeLiteralSuggestion,
+    #[subdiagnostic]
+    pub current_behavior: AmbiguousNegativeLiteralsCurrentBehaviorSuggestion,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_negative_literal, applicability = "maybe-incorrect")]
+pub struct AmbiguousNegativeLiteralsNegativeLiteralSuggestion {
+    #[suggestion_part(code = "(")]
+    pub start_span: Span,
+    #[suggestion_part(code = ")")]
+    pub end_span: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_current_behavior, applicability = "maybe-incorrect")]
+pub struct AmbiguousNegativeLiteralsCurrentBehaviorSuggestion {
+    #[suggestion_part(code = "(")]
+    pub start_span: Span,
+    #[suggestion_part(code = ")")]
+    pub end_span: Span,
+}
+
 // pass_by_value.rs
 #[derive(LintDiagnostic)]
 #[diag(lint_pass_by_value)]
diff --git a/compiler/rustc_lint/src/precedence.rs b/compiler/rustc_lint/src/precedence.rs
new file mode 100644
index 00000000000..eb2ba397277
--- /dev/null
+++ b/compiler/rustc_lint/src/precedence.rs
@@ -0,0 +1,70 @@
+use rustc_ast::token::LitKind;
+use rustc_ast::{Expr, ExprKind, MethodCall, UnOp};
+use rustc_session::{declare_lint, declare_lint_pass};
+
+use crate::lints::{
+    AmbiguousNegativeLiteralsCurrentBehaviorSuggestion, AmbiguousNegativeLiteralsDiag,
+    AmbiguousNegativeLiteralsNegativeLiteralSuggestion,
+};
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+
+declare_lint! {
+    /// The `ambiguous_negative_literals` lint checks for cases that are
+    /// confusing between a negative literal and a negation that's not part
+    /// of the literal.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// # #![allow(unused)]
+    /// -1i32.abs(); // equals -1, while `(-1i32).abs()` equals 1
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Method calls take precedence over unary precedence. Setting the
+    /// precedence explicitly makes the code clearer and avoid potential bugs.
+    pub AMBIGUOUS_NEGATIVE_LITERALS,
+    Deny,
+    "ambiguous negative literals operations",
+    report_in_external_macro
+}
+
+declare_lint_pass!(Precedence => [AMBIGUOUS_NEGATIVE_LITERALS]);
+
+impl EarlyLintPass for Precedence {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+        let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind else {
+            return;
+        };
+
+        let mut arg = operand;
+        let mut at_least_one = false;
+        while let ExprKind::MethodCall(box MethodCall { receiver, .. }) = &arg.kind {
+            at_least_one = true;
+            arg = receiver;
+        }
+
+        if at_least_one
+            && let ExprKind::Lit(lit) = &arg.kind
+            && let LitKind::Integer | LitKind::Float = &lit.kind
+        {
+            cx.emit_span_lint(
+                AMBIGUOUS_NEGATIVE_LITERALS,
+                expr.span,
+                AmbiguousNegativeLiteralsDiag {
+                    negative_literal: AmbiguousNegativeLiteralsNegativeLiteralSuggestion {
+                        start_span: expr.span.shrink_to_lo(),
+                        end_span: arg.span.shrink_to_hi(),
+                    },
+                    current_behavior: AmbiguousNegativeLiteralsCurrentBehaviorSuggestion {
+                        start_span: operand.span.shrink_to_lo(),
+                        end_span: operand.span.shrink_to_hi(),
+                    },
+                },
+            );
+        }
+    }
+}
diff --git a/tests/ui/lint/negative_literals.rs b/tests/ui/lint/negative_literals.rs
new file mode 100644
index 00000000000..048fcd6ff57
--- /dev/null
+++ b/tests/ui/lint/negative_literals.rs
@@ -0,0 +1,35 @@
+//@ check-fail
+
+fn main() {
+    let _ = -1i32.abs();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f32.abs();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f64.asin();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f64.asinh();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f64.tan();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f64.tanh();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1.0_f64.cos().cos();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1.0_f64.cos().sin();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1.0_f64.sin().cos();
+    //~^ ERROR `-` has lower precedence than method calls
+    let _ = -1f64.sin().sin();
+    //~^ ERROR `-` has lower precedence than method calls
+
+    dbg!( -1.0_f32.cos() );
+    //~^ ERROR `-` has lower precedence than method calls
+
+    // should not warn
+    let _ = (-1i32).abs();
+    let _ = (-1f32).abs();
+    let _ = -(1i32).abs();
+    let _ = -(1f32).abs();
+    let _ = -(1i32.abs());
+    let _ = -(1f32.abs());
+}
diff --git a/tests/ui/lint/negative_literals.stderr b/tests/ui/lint/negative_literals.stderr
new file mode 100644
index 00000000000..df000a71882
--- /dev/null
+++ b/tests/ui/lint/negative_literals.stderr
@@ -0,0 +1,179 @@
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:4:13
+   |
+LL |     let _ = -1i32.abs();
+   |             ^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+   = note: `#[deny(ambiguous_negative_literals)]` on by default
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1i32).abs();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1i32.abs());
+   |              +          +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:6:13
+   |
+LL |     let _ = -1f32.abs();
+   |             ^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f32).abs();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f32.abs());
+   |              +          +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:8:13
+   |
+LL |     let _ = -1f64.asin();
+   |             ^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f64).asin();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f64.asin());
+   |              +           +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:10:13
+   |
+LL |     let _ = -1f64.asinh();
+   |             ^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f64).asinh();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f64.asinh());
+   |              +            +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:12:13
+   |
+LL |     let _ = -1f64.tan();
+   |             ^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f64).tan();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f64.tan());
+   |              +          +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:14:13
+   |
+LL |     let _ = -1f64.tanh();
+   |             ^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f64).tanh();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f64.tanh());
+   |              +           +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:16:13
+   |
+LL |     let _ = -1.0_f64.cos().cos();
+   |             ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1.0_f64).cos().cos();
+   |             +        +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1.0_f64.cos().cos());
+   |              +                   +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:18:13
+   |
+LL |     let _ = -1.0_f64.cos().sin();
+   |             ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1.0_f64).cos().sin();
+   |             +        +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1.0_f64.cos().sin());
+   |              +                   +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:20:13
+   |
+LL |     let _ = -1.0_f64.sin().cos();
+   |             ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1.0_f64).sin().cos();
+   |             +        +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1.0_f64.sin().cos());
+   |              +                   +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:22:13
+   |
+LL |     let _ = -1f64.sin().sin();
+   |             ^^^^^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     let _ = (-1f64).sin().sin();
+   |             +     +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     let _ = -(1f64.sin().sin());
+   |              +                +
+
+error: `-` has lower precedence than method calls, which might be unexpected
+  --> $DIR/negative_literals.rs:25:11
+   |
+LL |     dbg!( -1.0_f32.cos() );
+   |           ^^^^^^^^^^^^^^
+   |
+   = note: e.g. `-4.abs()` equals `-4`; while `(-4).abs()` equals `4`
+help: add parentheses around the `-` and the literal to call the method on a negative literal
+   |
+LL |     dbg!( (-1.0_f32).cos() );
+   |           +        +
+help: add parentheses around the literal and the method call to keep the current behavior
+   |
+LL |     dbg!( -(1.0_f32.cos()) );
+   |            +             +
+
+error: aborting due to 11 previous errors
+