diff options
| author | bors <bors@rust-lang.org> | 2023-11-22 14:03:16 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-11-22 14:03:16 +0000 |
| commit | 73bc12199ea8c7651ed98b069c0dd6b0bb5fabcf (patch) | |
| tree | d9ce950e043ddf53ca3d5081f9c0d45812f6911e | |
| parent | a6b8ae582a89321e24ea942d9c3eb73229809487 (diff) | |
| parent | 8da09aed94e0c407f3c82450fa8268e6bff0f71b (diff) | |
| download | rust-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.ftl | 3 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lints.rs | 7 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/unit_bindings.rs | 72 | ||||
| -rw-r--r-- | tests/ui/closures/issue-868.rs | 1 | ||||
| -rw-r--r-- | tests/ui/never_type/diverging-fallback-unconstrained-return.rs | 4 |
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() {} |
