about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-11-22 14:03:16 +0000
committerbors <bors@rust-lang.org>2023-11-22 14:03:16 +0000
commit73bc12199ea8c7651ed98b069c0dd6b0bb5fabcf (patch)
treed9ce950e043ddf53ca3d5081f9c0d45812f6911e
parenta6b8ae582a89321e24ea942d9c3eb73229809487 (diff)
parent8da09aed94e0c407f3c82450fa8268e6bff0f71b (diff)
downloadrust-73bc12199ea8c7651ed98b069c0dd6b0bb5fabcf.tar.gz
rust-73bc12199ea8c7651ed98b069c0dd6b0bb5fabcf.zip
Auto merge of #112380 - jieyouxu:useless-bindings-lint, r=WaffleLapkin
Add allow-by-default lint for unit bindings

### Example

```rust
#![warn(unit_bindings)]

macro_rules! owo {
    () => {
        let whats_this = ();
    }
}

fn main() {
    // No warning if user explicitly wrote `()` on either side.
    let expr = ();
    let () = expr;
    let _ = ();

    let _ = expr; //~ WARN binding has unit type
    let pat = expr; //~ WARN binding has unit type
    let _pat = expr; //~ WARN binding has unit type

    // No warning for let bindings with unit type in macro expansions.
    owo!();

    // No warning if user explicitly annotates the unit type on the binding.
    let pat: () = expr;
}
```

outputs

```
warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:17:5
   |
LL |     let _ = expr;
   |     ^^^^-^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`
   |
note: the lint level is defined here
  --> $DIR/unit-bindings.rs:3:9
   |
LL | #![warn(unit_bindings)]
   |         ^^^^^^^^^^^^^

warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:18:5
   |
LL |     let pat = expr;
   |     ^^^^---^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`

warning: binding has unit type `()`
  --> $DIR/unit-bindings.rs:19:5
   |
LL |     let _pat = expr;
   |     ^^^^----^^^^^^^^
   |         |
   |         this pattern is inferred to be the unit type `()`

warning: 3 warnings emitted
```

This lint is not triggered if any of the following conditions are met:

- The user explicitly annotates the binding with the `()` type.
- The binding is from a macro expansion.
- The user explicitly wrote `let () = init;`
- The user explicitly wrote `let pat = ();`. This is allowed for local lifetimes.

### Known Issue

It is known that this lint can trigger on some proc-macro generated code whose span returns false for `Span::from_expansion` because e.g. the proc-macro simply forwards user code spans, and otherwise don't have distinguishing syntax context compared to non-macro-generated code. For those kind of proc-macros, I believe the correct way to fix them is to instead emit identifers with span like `Span::mixed_site().located_at(user_span)`.

Closes #71432.
-rw-r--r--compiler/rustc_lint/messages.ftl3
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs7
-rw-r--r--compiler/rustc_lint/src/unit_bindings.rs72
-rw-r--r--tests/ui/closures/issue-868.rs1
-rw-r--r--tests/ui/never_type/diverging-fallback-unconstrained-return.rs4
6 files changed, 88 insertions, 2 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index cb2e373c6f9..5ea283062f1 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -517,6 +517,9 @@ lint_undropped_manually_drops = calls to `std::mem::drop` with `std::mem::Manual
 lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op
      .label = this function will not propagate the caller location
 
+lint_unit_bindings = binding has unit type `()`
+    .label = this pattern is inferred to be the unit type `()`
+
 lint_unknown_gated_lint =
     unknown lint: `{$name}`
     .note = the `{$name}` lint is unstable
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 0db30cd8a3d..a48a8bd71b9 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -85,6 +85,7 @@ mod redundant_semicolon;
 mod reference_casting;
 mod traits;
 mod types;
+mod unit_bindings;
 mod unused;
 
 pub use array_into_iter::ARRAY_INTO_ITER;
@@ -123,6 +124,7 @@ use redundant_semicolon::*;
 use reference_casting::*;
 use traits::*;
 use types::*;
+use unit_bindings::*;
 use unused::*;
 
 /// Useful for other parts of the compiler / Clippy.
@@ -202,6 +204,7 @@ late_lint_methods!(
             InvalidReferenceCasting: InvalidReferenceCasting,
             // Depends on referenced function signatures in expressions
             UnusedResults: UnusedResults,
+            UnitBindings: UnitBindings,
             NonUpperCaseGlobals: NonUpperCaseGlobals,
             NonShorthandFieldPatterns: NonShorthandFieldPatterns,
             UnusedAllocation: UnusedAllocation,
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index c4d54c95e22..c82ba70908d 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -1830,3 +1830,10 @@ impl<'a> DecorateLint<'a, ()> for AsyncFnInTraitDiag {
         fluent::lint_async_fn_in_trait
     }
 }
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unit_bindings)]
+pub struct UnitBindingsDiag {
+    #[label]
+    pub label: Span,
+}
diff --git a/compiler/rustc_lint/src/unit_bindings.rs b/compiler/rustc_lint/src/unit_bindings.rs
new file mode 100644
index 00000000000..c80889f3ae7
--- /dev/null
+++ b/compiler/rustc_lint/src/unit_bindings.rs
@@ -0,0 +1,72 @@
+use crate::lints::UnitBindingsDiag;
+use crate::{LateLintPass, LintContext};
+use rustc_hir as hir;
+use rustc_middle::ty::Ty;
+
+declare_lint! {
+    /// The `unit_bindings` lint detects cases where bindings are useless because they have
+    /// the unit type `()` as their inferred type. The lint is suppressed if the user explicitly
+    /// annotates the let binding with the unit type `()`, or if the let binding uses an underscore
+    /// wildcard pattern, i.e. `let _ = expr`, or if the binding is produced from macro expansions.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,compile_fail
+    /// #![deny(unit_bindings)]
+    ///
+    /// fn foo() {
+    ///     println!("do work");
+    /// }
+    ///
+    /// pub fn main() {
+    ///     let x = foo(); // useless binding
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Creating a local binding with the unit type `()` does not do much and can be a sign of a
+    /// user error, such as in this example:
+    ///
+    /// ```rust,no_run
+    /// fn main() {
+    ///     let mut x = [1, 2, 3];
+    ///     x[0] = 5;
+    ///     let y = x.sort(); // useless binding as `sort` returns `()` and not the sorted array.
+    ///     println!("{:?}", y); // prints "()"
+    /// }
+    /// ```
+    pub UNIT_BINDINGS,
+    Allow,
+    "binding is useless because it has the unit `()` type"
+}
+
+declare_lint_pass!(UnitBindings => [UNIT_BINDINGS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnitBindings {
+    fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::Local<'tcx>) {
+        // Suppress warning if user:
+        // - explicitly ascribes a type to the pattern
+        // - explicitly wrote `let pat = ();`
+        // - explicitly wrote `let () = init;`.
+        if !local.span.from_expansion()
+            && let Some(tyck_results) = cx.maybe_typeck_results()
+            && let Some(init) = local.init
+            && let init_ty = tyck_results.expr_ty(init)
+            && let local_ty = tyck_results.node_type(local.hir_id)
+            && init_ty == Ty::new_unit(cx.tcx)
+            && local_ty == Ty::new_unit(cx.tcx)
+            && local.ty.is_none()
+            && !matches!(init.kind, hir::ExprKind::Tup([]))
+            && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..))
+        {
+            cx.emit_spanned_lint(
+                UNIT_BINDINGS,
+                local.span,
+                UnitBindingsDiag { label: local.pat.span },
+            );
+        }
+    }
+}
diff --git a/tests/ui/closures/issue-868.rs b/tests/ui/closures/issue-868.rs
index ce0a3c7ca52..df03b191a99 100644
--- a/tests/ui/closures/issue-868.rs
+++ b/tests/ui/closures/issue-868.rs
@@ -1,5 +1,6 @@
 // run-pass
 #![allow(unused_parens)]
+#![allow(unit_bindings)]
 // pretty-expanded FIXME #23616
 
 fn f<T, F>(g: F) -> T where F: FnOnce() -> T { g() }
diff --git a/tests/ui/never_type/diverging-fallback-unconstrained-return.rs b/tests/ui/never_type/diverging-fallback-unconstrained-return.rs
index 7ea97126f89..26c8175be63 100644
--- a/tests/ui/never_type/diverging-fallback-unconstrained-return.rs
+++ b/tests/ui/never_type/diverging-fallback-unconstrained-return.rs
@@ -1,4 +1,4 @@
-// Variant of diverging-falllback-control-flow that tests
+// Variant of diverging-fallback-control-flow that tests
 // the specific case of a free function with an unconstrained
 // return type. This captures the pattern we saw in the wild
 // in the objc crate, where changing the fallback from `!` to `()`
@@ -9,7 +9,7 @@
 // revisions: nofallback fallback
 
 #![cfg_attr(fallback, feature(never_type, never_type_fallback))]
-
+#![allow(unit_bindings)]
 
 fn make_unit() {}