about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/lib.register_all.rs1
-rw-r--r--clippy_lints/src/lib.register_lints.rs1
-rw-r--r--clippy_lints/src/lib.register_suspicious.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/multi_assignments.rs65
-rw-r--r--tests/ui/multi_assignments.rs9
-rw-r--r--tests/ui/multi_assignments.stderr40
8 files changed, 120 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3abdc61e63..964f339b26c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3897,6 +3897,7 @@ Released 2018-09-13
 [`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
 [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
 [`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
+[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
 [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
 [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
 [`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs
index 3e46ba12c44..61bb5d1337c 100644
--- a/clippy_lints/src/lib.register_all.rs
+++ b/clippy_lints/src/lib.register_all.rs
@@ -232,6 +232,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
     LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
     LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
     LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION),
+    LintId::of(multi_assignments::MULTI_ASSIGNMENTS),
     LintId::of(mut_key::MUTABLE_KEY_TYPE),
     LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
     LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index e99b4ffdb3a..0ec2884cb6b 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -398,6 +398,7 @@ store.register_lints(&[
     mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
     module_style::MOD_MODULE_FILES,
     module_style::SELF_NAMED_MODULE_FILES,
+    multi_assignments::MULTI_ASSIGNMENTS,
     mut_key::MUTABLE_KEY_TYPE,
     mut_mut::MUT_MUT,
     mut_reference::UNNECESSARY_MUT_PASSED,
diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs
index 369d4b4eed6..6f480c52cc9 100644
--- a/clippy_lints/src/lib.register_suspicious.rs
+++ b/clippy_lints/src/lib.register_suspicious.rs
@@ -24,6 +24,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
     LintId::of(loops::MUT_RANGE_BOUND),
     LintId::of(methods::NO_EFFECT_REPLACE),
     LintId::of(methods::SUSPICIOUS_MAP),
+    LintId::of(multi_assignments::MULTI_ASSIGNMENTS),
     LintId::of(mut_key::MUTABLE_KEY_TYPE),
     LintId::of(octal_escapes::OCTAL_ESCAPES),
     LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index be91630b8fa..05d4dbd6859 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -289,6 +289,7 @@ mod missing_enforced_import_rename;
 mod missing_inline;
 mod mixed_read_write_in_expression;
 mod module_style;
+mod multi_assignments;
 mod mut_key;
 mod mut_mut;
 mod mut_reference;
@@ -896,6 +897,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| Box::new(partialeq_to_none::PartialeqToNone));
     store.register_late_pass(|| Box::new(manual_string_new::ManualStringNew));
     store.register_late_pass(|| Box::new(unused_peekable::UnusedPeekable));
+    store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/multi_assignments.rs b/clippy_lints/src/multi_assignments.rs
new file mode 100644
index 00000000000..81eb1a085ae
--- /dev/null
+++ b/clippy_lints/src/multi_assignments.rs
@@ -0,0 +1,65 @@
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for nested assignments.
+    ///
+    /// ### Why is this bad?
+    /// While this is in most cases already a type mismatch,
+    /// the result of an assignment being `()` can throw off people coming from languages like python or C,
+    /// where such assignments return a copy of the assigned value.
+    ///
+    /// ### Example
+    /// ```rust
+    ///# let (a, b);
+    /// a = b = 42;
+    /// ```
+    /// Use instead:
+    /// ```rust
+    ///# let (a, b);
+    /// b = 42;
+    /// a = b;
+    /// ```
+    #[clippy::version = "1.65.0"]
+    pub MULTI_ASSIGNMENTS,
+    suspicious,
+    "instead of using `a = b = c;` use `a = c; b = c;`"
+}
+
+declare_lint_pass!(MultiAssignments => [MULTI_ASSIGNMENTS]);
+
+fn strip_paren_blocks(expr: &Expr) -> &Expr {
+    match &expr.kind {
+        ExprKind::Paren(e) => strip_paren_blocks(e),
+        ExprKind::Block(b, _) => {
+            if let [
+                Stmt {
+                    kind: StmtKind::Expr(e),
+                    ..
+                },
+            ] = &b.stmts[..]
+            {
+                strip_paren_blocks(e)
+            } else {
+                expr
+            }
+        },
+        _ => expr,
+    }
+}
+
+impl EarlyLintPass for MultiAssignments {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+        if let ExprKind::Assign(target, source, _) = &expr.kind {
+            if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(target).kind {
+                span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively");
+            };
+            if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(source).kind {
+                span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively");
+            }
+        };
+    }
+}
diff --git a/tests/ui/multi_assignments.rs b/tests/ui/multi_assignments.rs
new file mode 100644
index 00000000000..b186bf8bbdb
--- /dev/null
+++ b/tests/ui/multi_assignments.rs
@@ -0,0 +1,9 @@
+#![warn(clippy::multi_assignments)]
+fn main() {
+    let (mut a, mut b, mut c, mut d) = ((), (), (), ());
+    a = b = c;
+    a = b = c = d;
+    a = b = { c };
+    a = { b = c };
+    a = (b = c);
+}
diff --git a/tests/ui/multi_assignments.stderr b/tests/ui/multi_assignments.stderr
new file mode 100644
index 00000000000..d6c42bb698c
--- /dev/null
+++ b/tests/ui/multi_assignments.stderr
@@ -0,0 +1,40 @@
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:4:5
+   |
+LL |     a = b = c;
+   |     ^^^^^^^^^
+   |
+   = note: `-D clippy::multi-assignments` implied by `-D warnings`
+
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:5:5
+   |
+LL |     a = b = c = d;
+   |     ^^^^^^^^^^^^^
+
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:5:9
+   |
+LL |     a = b = c = d;
+   |         ^^^^^^^^^
+
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:6:5
+   |
+LL |     a = b = { c };
+   |     ^^^^^^^^^^^^^
+
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:7:5
+   |
+LL |     a = { b = c };
+   |     ^^^^^^^^^^^^^
+
+error: assignments don't nest intuitively
+  --> $DIR/multi_assignments.rs:8:5
+   |
+LL |     a = (b = c);
+   |     ^^^^^^^^^^^
+
+error: aborting due to 6 previous errors
+