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

Issue:
`unnecessary_safety_comment` false positive in `_ = unsafe { foo_unsafe() }` expression, conflicts with `undocumented_unsafe_blocks` correct analysis
### Summary

The `unnecessary_safety_comment` lint considers a safety comment as being unnecessary if the code is of the shape:

```
_ = unsafe { foo_unsafe() }
```

The following alternative shape is correctly identified as requiring a safety comment:

```
let _ = unsafe { foo_unsafe() }
```

The `undocumented_unsafe_blocks` lint expects a safety comment in both cases, so the first case cannot pass Clippy check if both lints are enabled.

### Lint Name

unnecessary_safety_comment

### Reproducer

```rust
unsafe fn foo() -> usize {
    1234
}

#[deny(clippy::unnecessary_safety_comment)]
#[deny(clippy::undocumented_unsafe_blocks)] // For comparison
fn main() {
    // SAFETY: unnecessary_safety_comment incorrectly triggers here
    _ = unsafe { foo() };
}
```

### Version

```text
rustc 1.79.0 (c6bcfe6a60 2024-06-18) (1.79.0-ms-20240619.1+c6bcfe6a60)
binary: rustc
commit-hash: c6bcfe6a6082b23fa084d9690fee241c23ee9d29
commit-date: 2024-06-18
host: x86_64-pc-windows-msvc
release: 1.79.0
LLVM version: 18.1.7
```


### Additional Labels

_No response_


Discussion:

Patch:
diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs
index b824eb88abf9..816e400ed840 100644
--- a/clippy_lints/src/undocumented_unsafe_blocks.rs
+++ b/clippy_lints/src/undocumented_unsafe_blocks.rs
@@ -312,6 +312,25 @@ fn expr_has_unnecessary_safety_comment<'tcx>(
             },
             _,
         ) => ControlFlow::Break(()),
+        // `_ = foo()` is desugared to `{ let _ = foo(); }`
+        hir::ExprKind::Block(
+            Block {
+                rules: BlockCheckMode::DefaultBlock,
+                stmts:
+                    [
+                        hir::Stmt {
+                            kind:
+                                hir::StmtKind::Let(hir::LetStmt {
+                                    source: hir::LocalSource::AssignDesugar(_),
+                                    ..
+                                }),
+                            ..
+                        },
+                    ],
+                ..
+            },
+            _,
+        ) => ControlFlow::Continue(Descend::Yes),
         // statements will be handled by check_stmt itself again
         hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
         _ => ControlFlow::Continue(Descend::Yes),


Test patch:
diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr
index 4357c455e50e..8a2f201009a9 100644
--- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr
+++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr
@@ -342,5 +342,17 @@ LL |         const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 };
    |
    = help: consider adding a safety comment on the preceding line
 
-error: aborting due to 39 previous errors
+error: statement has unnecessary safety comment
+  --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:719:5
+   |
+LL |     _ = bar();
+   |     ^^^^^^^^^^
+   |
+help: consider removing the safety comment
+  --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:718:5
+   |
+LL |     // SAFETY: unnecessary_safety_comment triggers here
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 40 previous errors
 
diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr
index eb61e1e6da08..e9c5e5f9f114 100644
--- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr
+++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr
@@ -438,5 +438,17 @@ LL |         unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian
    |
    = help: consider adding a safety comment on the preceding line
 
-error: aborting due to 51 previous errors
+error: statement has unnecessary safety comment
+  --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:719:5
+   |
+LL |     _ = bar();
+   |     ^^^^^^^^^^
+   |
+help: consider removing the safety comment
+  --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:718:5
+   |
+LL |     // SAFETY: unnecessary_safety_comment triggers here
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 52 previous errors
 
diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs
index 2492a3c2f562..91a02bc3d7c6 100644
--- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs
+++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs
@@ -703,4 +703,24 @@ mod issue_11709_regression {
     //~[disabled]^ undocumented_unsafe_blocks
 }
 
+fn issue_13039() {
+    unsafe fn foo() -> usize {
+        1234
+    }
+
+    fn bar() -> usize {
+        1234
+    }
+
+    // SAFETY: unnecessary_safety_comment should not trigger here
+    _ = unsafe { foo() };
+
+    // SAFETY: unnecessary_safety_comment triggers here
+    _ = bar();
+    //~^ unnecessary_safety_comment
+
+    // SAFETY: unnecessary_safety_comment should not trigger here
+    _ = unsafe { foo() }
+}
+
 fn main() {}