Repository: rust-lang/rust-clippy (Rust)

Issue:
`let_unit_value` mangles field init shorthand syntax
### Summary

If the name of the local with a unit value is the same as one of the fields of a struct, and the local is used in a constructor of that struct, then the whole field expression gets replaced by `()`.

This is a minimized reproducer of https://docs.rs/bitflags/2.6.0/src/bitflags/parser.rs.html#283

### Reproducer

I tried this code:

```rust
#![allow(unused)]
#![warn(clippy::let_unit_value)]

struct Foo {
    value: (),
}

fn foo() -> Foo {
    let value = println!();

    Foo { value }
}
```

I expected to see this happen:
```
warning: this let-binding has unit value
 --> src/main.rs:9:5
  |
9 |     let value = println!();
  |     ^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
note: the lint level is defined here
 --> src/main.rs:2:9
  |
2 | #![warn(clippy::let_unit_value)]
  |         ^^^^^^^^^^^^^^^^^^^^^^
help: omit the `let` binding and replace variable usages with `()`
  |
9 ~     println!();
10|
11~     Foo { value: () }
  |
```

Instead, this happened:
```
help: omit the `let` binding and replace variable usages with `()`
  |
9 ~     println!();
10|
11~     Foo { () }
  |
```

### Version

```text
rustc 1.90.0 (1159e78c4 2025-09-14)
binary: rustc
commit-hash: 1159e78c4747b02ef996e55082b704c09b970588
commit-date: 2025-09-14
host: x86_64-unknown-linux-gnu
release: 1.90.0
LLVM version: 20.1.8
```

### Additional Labels

@rustbot I-suggestion-causes-error

<!-- TRIAGEBOT_START -->

<!-- TRIAGEBOT_ASSIGN_START -->

<!-- TRIAGEBOT_ASSIGN_END -->
<!-- TRIAGEBOT_END -->


Discussion:
@rustbot claim



Patch:
diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs
index 424aa14cd686..2645e94358e1 100644
--- a/clippy_lints/src/unit_types/let_unit_value.rs
+++ b/clippy_lints/src/unit_types/let_unit_value.rs
@@ -90,11 +90,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag
 
                         let mut has_in_format_capture = false;
                         suggestions.extend(visitor.spans.into_iter().filter_map(|span| match span {
-                            MaybeInFormatCapture::Yes => {
+                            VariableUsage::FormatCapture => {
                                 has_in_format_capture = true;
                                 None
                             },
-                            MaybeInFormatCapture::No(span) => Some((span, "()".to_string())),
+                            VariableUsage::Normal(span) => Some((span, "()".to_string())),
+                            VariableUsage::FieldShorthand(span) => Some((span.shrink_to_hi(), ": ()".to_string())),
                         }));
 
                         if has_in_format_capture {
@@ -141,19 +142,30 @@ struct UnitVariableCollector<'a, 'tcx> {
     cx: &'a LateContext<'tcx>,
     format_args: &'a FormatArgsStorage,
     id: HirId,
-    spans: Vec<MaybeInFormatCapture>,
+    spans: Vec<VariableUsage>,
     macro_call: Option<&'a FormatArgs>,
 }
 
-/// Whether the unit variable is captured in a `format!`:
-///
-/// ```ignore
-/// let unit = ();
-/// eprintln!("{unit}");
-/// ```
-enum MaybeInFormatCapture {
-    Yes,
-    No(Span),
+/// How the unit variable is used
+enum VariableUsage {
+    Normal(Span),
+    /// Captured in a `format!`:
+    ///
+    /// ```ignore
+    /// let unit = ();
+    /// eprintln!("{unit}");
+    /// ```
+    FormatCapture,
+    /// In a field shorthand init:
+    ///
+    /// ```ignore
+    /// struct Foo {
+    ///    unit: (),
+    /// }
+    /// let unit = ();
+    /// Foo { unit };
+    /// ```
+    FieldShorthand(Span),
 }
 
 impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> {
@@ -193,9 +205,17 @@ impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> {
                     matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some()
                 })
             {
-                self.spans.push(MaybeInFormatCapture::Yes);
+                self.spans.push(VariableUsage::FormatCapture);
             } else {
-                self.spans.push(MaybeInFormatCapture::No(path.span));
+                let parent = self.cx.tcx.parent_hir_node(ex.hir_id);
+                match parent {
+                    Node::ExprField(expr_field) if expr_field.is_shorthand => {
+                        self.spans.push(VariableUsage::FieldShorthand(ex.span));
+                    },
+                    _ => {
+                        self.spans.push(VariableUsage::Normal(path.span));
+                    },
+                }
             }
         }
 


Test patch:
diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed
index e8517f18e788..6d984a495d2b 100644
--- a/tests/ui/let_unit.fixed
+++ b/tests/ui/let_unit.fixed
@@ -229,3 +229,13 @@ fn issue_15784() {
     takes_unit(());
     println!("{res:?}");
 }
+
+fn issue15789() {
+    struct Foo {
+        value: (),
+    }
+    println!();
+    //~^ let_unit_value
+
+    Foo { value: () };
+}
diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs
index 3f6f0139b2fe..a0e32f0b67a0 100644
--- a/tests/ui/let_unit.rs
+++ b/tests/ui/let_unit.rs
@@ -227,3 +227,13 @@ fn issue_15784() {
     takes_unit(res);
     println!("{res:?}");
 }
+
+fn issue15789() {
+    struct Foo {
+        value: (),
+    }
+    let value = println!();
+    //~^ let_unit_value
+
+    Foo { value };
+}
diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr
index 8ced32ab828f..6e7b958df4d9 100644
--- a/tests/ui/let_unit.stderr
+++ b/tests/ui/let_unit.stderr
@@ -114,5 +114,19 @@ LL |
 LL ~     takes_unit(());
    |
 
-error: aborting due to 8 previous errors
+error: this let-binding has unit value
+  --> tests/ui/let_unit.rs:235:5
+   |
+LL |     let value = println!();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: omit the `let` binding and replace variable usages with `()`
+   |
+LL ~     println!();
+LL |
+LL |
+LL ~     Foo { value: () };
+   |
+
+error: aborting due to 9 previous errors