Skip to content

Unhelpful unreachable_code warning on a tuple constructor with fields of uninhabited types #139627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
snejugal opened this issue Apr 10, 2025 · 4 comments · Fixed by #139782
Closed
Assignees
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. L-unreachable_code Lint: unreachable_code T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@snejugal
Copy link

snejugal commented Apr 10, 2025

Code

pub enum Void {}

pub struct S<T>(T);

pub fn foo(void: Void) {
    let s = S(void);
    drop(s)
}

Current output

warning: unreachable expression
 --> src/lib.rs:7:10
  |
6 |     let s = S(void);
  |             ------- any code following this expression is unreachable
7 |     drop(s)
  |          ^ unreachable expression
  |
note: this expression has type `S<Void>`, which is uninhabited
 --> src/lib.rs:6:13
  |
6 |     let s = S(void);
  |             ^^^^^^^
  = note: `#[warn(unreachable_code)]` on by default

warning: unused variable: `s`
 --> src/lib.rs:6:9
  |
6 |     let s = S(void);
  |         ^ help: if this is intentional, prefix it with an underscore: `_s`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: `playground` (lib) generated 2 warnings

Desired output

Preferably no warning at all.

Rationale and extra context

I can technically agree that the drop(s) expression is unreachable, but this is not because S(void) is of an uninhabited type — it's because this function is impossible to call. However, since a body of a function is not marked as unreachable just because of the function's parameters, and since constructors do not divert on their own (even if using an impossible value for one of the fields), there should be no warning about unreachable code here. Moreover, in this example, the warning caused another unhelpful warning about an unused variable that is actually used.

Other cases

Changing `let s = S(void);` to `let s = (void,);` removes both warnings. Changing it to `let s = S { 0: void };` also removes both warnings.

Rust Version

$ rustc --version --verbose
rustc 1.86.0 (05f9846f8 2025-03-31)
binary: rustc
commit-hash: 05f9846f893b09a1be1fc8560e33fc3c815cfecb
commit-date: 2025-03-31
host: x86_64-unknown-linux-gnu
release: 1.86.0
LLVM version: 19.1.7

Anything else?

No response

@snejugal snejugal added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 10, 2025
@jieyouxu jieyouxu added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. L-unreachable_code Lint: unreachable_code labels Apr 10, 2025
@xizheyin
Copy link
Contributor

@rustbot claim

@xizheyin
Copy link
Contributor

Here's a question. When changing let s = S(void); to let s = (void,); (note: it is S(void,) instead of (void,)), there's still a warning. However, there's no more warning when changing it to let s = S { 0: void };.

I think either adding a warning for struct{0:void} or marking the whole foo unreachable would be more appropriate.

  • For the first approach, it is assumed that the active variable analysis does not take into account the formal parameters of the function, but only the expressions inside the body. Adding warnings to struct{0:void} ensures consistency.
  • For the second approach, it is assumed that the liveness analysis considers the formal parameter as an uninhabitable type, marking the whole function as unreachable.

Does anyone have a better suggestion?

@snejugal
Copy link
Author

note: it is S(void,) instead of (void,)

I did mean switching to a regular 1-tuple constructor. It would be strange if a trailing comma affected unreachability analysis.

I feel like the second approach would add a lot of noise when implementing traits for uninhabited types (or just any function with the purpose of transforming an uninhabited type into some other expected type). The first approach gives the impression that code before the constructor may actually be reachable and it is the constructor that diverges.

Note that in my particular case my enum does have variants, but each of them is behind a feature gate (this is basically an enum for different backends that may be enabled in my application). The enum being empty is an expected edge case, and I wouldn't want to pollute the code with even more #[cfg]s and #[expect]s that are not really necessary.

@xizheyin
Copy link
Contributor

xizheyin commented Apr 13, 2025

You mean that in the case of call, the actual arguments of the function should not affect the return value, right? This seems to make sense.

@bors bors closed this as completed in 0757d24 Apr 17, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Apr 17, 2025
Rollup merge of rust-lang#139782 - xizheyin:issue-139627, r=wesleywiser

Consistent with treating Ctor Call as Struct in liveness analysis

Fixes rust-lang#139627

When `ExprKind::Call` is a `Ctor`, skips the checking of `expr` and only checks the arguments, thus being consistent with `ExprKind::Struct`.

r? compiler
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. L-unreachable_code Lint: unreachable_code T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants