-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Warn about unreachable code following an expression with an uninhabited type #85556
Conversation
r? @estebank (rust-highfive has picked a reviewer for you, use r? to override) |
if let LiveNodeKind::ExprNode(succ_span, succ_id) = self.ir.lnks[succ] { | ||
self.warn_about_unreachable( | ||
expr.span, | ||
ty, | ||
succ_span, | ||
succ_id, | ||
"expression", | ||
); | ||
} else if let LiveNodeKind::VarDefNode(succ_span, succ_id) = self.ir.lnks[succ] | ||
{ | ||
self.warn_about_unreachable( | ||
expr.span, | ||
ty, | ||
succ_span, | ||
succ_id, | ||
"definition", | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if let LiveNodeKind::ExprNode(succ_span, succ_id) = self.ir.lnks[succ] { | |
self.warn_about_unreachable( | |
expr.span, | |
ty, | |
succ_span, | |
succ_id, | |
"expression", | |
); | |
} else if let LiveNodeKind::VarDefNode(succ_span, succ_id) = self.ir.lnks[succ] | |
{ | |
self.warn_about_unreachable( | |
expr.span, | |
ty, | |
succ_span, | |
succ_id, | |
"definition", | |
); | |
} | |
match self.ir.lnks[succ] { | |
LiveNodeKind::ExprNode(succ_span, succ_id) => { | |
self.warn_about_unreachable( | |
expr.span, | |
ty, | |
succ_span, | |
succ_id, | |
"expression", | |
); | |
} | |
LiveNodeKind::VarDefNode(succ_span, succ_id) => { | |
self.warn_about_unreachable( | |
expr.span, | |
ty, | |
succ_span, | |
succ_id, | |
"definition", | |
); | |
} | |
_ => {} |
) { | ||
let ty = self.typeck_results.expr_ty(expr); | ||
let succ = if self.ir.tcx.is_ty_uninhabited_from(m, ty, self.param_env) { | ||
if let LiveNodeKind::ExprNode(succ_span, succ_id) = self.ir.lnks[succ] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, and given this is the second time you're doing this, maybe adding a method to LiveNodeKind
might be warranted?
if !orig_ty.is_never() { | ||
// Unreachable code warnings are already emitted during type checking. | ||
// However, during type checking, full type information is being | ||
// calculated but not yet available, so the check for diverging | ||
// expressions due to uninhabited result types is pretty crude and | ||
// only checks whether ty.is_never(). Here, we have full type | ||
// information available and can issue warnings for less obviously | ||
// uninhabited types (e.g. empty enums). The check above is used so | ||
// that we do not emit the same warning twice if the uninhabited type | ||
// is indeed `!`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to remove the current, existing, crude check in favor of this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because type inference makes use of the crude divergence information available, e.g. here:
rust/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
Lines 654 to 660 in 5dc8789
// Subtle: if there is no explicit tail expression, | |
// that is typically equivalent to a tail expression | |
// of `()` -- except if the block diverges. In that | |
// case, there is no value supplied from the tail | |
// expression (assuming there are no other breaks, | |
// this implies that the type of the block will be | |
// `!`). |
The crude check is here:
rust/compiler/rustc_typeck/src/check/expr.rs
Lines 202 to 205 in 5dc8789
// Any expression that produces a value of type `!` must have diverged | |
if ty.is_never() { | |
self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); | |
} |
Unfortunately, we can't replace it with
tcx.is_ty_uninhabited_from()
because type information has not been committed to tcx
yet at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with the code changes, but left some questions.
Thanks for your review and suggestions! I have slightly refactored the code now to implement your suggestions. |
@estebank this seems ready for a final review |
@bors r=estebank,jackh726 |
📌 Commit 7a98fd4 has been approved by |
☀️ Test successful - checks-actions |
Add `#[allow(unreachable_code)]` to `drop(x)` in `tests/run-pass/generator.rs` The test [starts to trigger this warning](https://github.com/rust-lang/miri/runs/3408355084?check_suite_focus=true#step:8:264) (I guess it's caused by rust-lang/rust#85556). The warning seems correct, but the unreachable code in that test also seems reasonable.
This change casued a very large regression in instruction counts. It seems to only be happening in one benchmark As part of the performance triage process, I'm marking this as a performance regression. If you believe this performance regression to be justifiable or once you have an issue or PR that addresses this regression, please mark this PR with the label perf-regression-triaged. @rustbot label +perf-regression |
This regression comes from a new unreachable expression warning specifically in webrender-wrench, which triggers the standard diagnostic infra. That infrastructure is pretty expensive, in this case loading up information from disk in order to trim paths being the most expensive bit. As such, marking as triaged -- we expect performance to be worse when emitting extra diagnostics. @rustbot label +perf-regression-triaged |
This change seems to produce a warning even when no code follows the expression with the uninhabited type. e.g. type Void = std::convert::Infallible;
fn foo() -> Result<Void, u64> {
Err(5)
}
fn main() {
match foo().expect("should") {}
} in that code the This pattern is taken straight out of a blog post by @nikomatsakis. |
This pull request fixes #85071. The issue is that liveness analysis currently is "smarter" than reachability analysis when it comes to detecting uninhabited types: Unreachable code is detected during type checking, where full type information is not yet available. Therefore, the check for type inhabitedness is quite crude:
rust/compiler/rustc_typeck/src/check/expr.rs
Lines 202 to 205 in fc81ad2
i.e. it only checks for
!
, but not other, non-trivially uninhabited types, such as empty enums, structs containing an uninhabited type, etc. By contrast, liveness analysis, which runs after type checking, can benefit from the more sophisticatedtcx.is_ty_uninhabited_from()
:rust/compiler/rustc_passes/src/liveness.rs
Line 981 in fc81ad2
rust/compiler/rustc_passes/src/liveness.rs
Line 996 in fc81ad2
This can lead to confusing warnings when a variable is reported as unused, but the use of the variable is not reported as unreachable. For instance:
currently leads to
which is confusing, because
x
appears to be used in line 6. With my changes, I get:My implementation is slightly inelegant because unreachable code warnings can now be issued in two different places (during type checking and during liveness analysis), but I think it is the solution with the least amount of unnecessary code duplication, given that the new warning integrates nicely with liveness analysis, where unreachable code is already implicitly detected for the purpose of finding unused variables.