Skip to content

Commit 212a9ce

Browse files
committed
Check for uninhabited types in typeck
1 parent 1450957 commit 212a9ce

File tree

6 files changed

+151
-6
lines changed

6 files changed

+151
-6
lines changed

compiler/rustc_hir_typeck/src/diverges.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,6 @@ impl Diverges<'_> {
7171
#[derive(Clone, Copy, Debug)]
7272
pub enum DivergeReason {
7373
AllArmsDiverge,
74+
Uninhabited,
7475
Other,
7576
}

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
244244
ExprKind::MethodCall(segment, ..) => {
245245
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
246246
}
247+
// allow field access when the struct and the field are both uninhabited
248+
ExprKind::Field(..)
249+
if matches!(
250+
self.diverges.get(),
251+
Diverges::Always(DivergeReason::Uninhabited, _)
252+
) && self.tcx.is_ty_uninhabited_from(self.parent_module, ty, self.param_env) => {}
247253
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
248254
}
249255

250256
// Any expression that produces a value of type `!` must have diverged
251-
if ty.is_never() {
252-
self.diverges.set(self.diverges.get() | Diverges::Always(DivergeReason::Other, expr));
257+
if !self.diverges.get().is_always() {
258+
if ty.is_never() {
259+
self.diverges.set(Diverges::Always(DivergeReason::Other, expr));
260+
} else if expr_may_be_uninhabited(expr)
261+
&& self.tcx.is_ty_uninhabited_from(self.parent_module, ty, self.param_env)
262+
{
263+
self.diverges.set(Diverges::Always(DivergeReason::Uninhabited, expr));
264+
}
253265
}
254266

255267
// Record the type, which applies it effects.
@@ -2886,3 +2898,41 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28862898
}
28872899
}
28882900
}
2901+
2902+
fn expr_may_be_uninhabited(expr: &hir::Expr<'_>) -> bool {
2903+
match expr.kind {
2904+
ExprKind::Call(..)
2905+
| ExprKind::MethodCall(..)
2906+
| ExprKind::Cast(..)
2907+
| ExprKind::Unary(hir::UnOp::Deref, _)
2908+
| ExprKind::Field(..)
2909+
| ExprKind::Path(..)
2910+
| ExprKind::Struct(..) => true,
2911+
ExprKind::Box(..)
2912+
| ExprKind::ConstBlock(..)
2913+
| ExprKind::Array(..)
2914+
| ExprKind::Tup(..)
2915+
| ExprKind::Binary(..)
2916+
| ExprKind::Unary(hir::UnOp::Neg | hir::UnOp::Not, _)
2917+
| ExprKind::Lit(..)
2918+
| ExprKind::Type(..)
2919+
| ExprKind::DropTemps(..)
2920+
| ExprKind::Let(..)
2921+
| ExprKind::If(..)
2922+
| ExprKind::Loop(..)
2923+
| ExprKind::Match(..)
2924+
| ExprKind::Closure(..)
2925+
| ExprKind::Block(..)
2926+
| ExprKind::Assign(..)
2927+
| ExprKind::AssignOp(..)
2928+
| ExprKind::Index(..)
2929+
| ExprKind::AddrOf(..)
2930+
| ExprKind::Break(..)
2931+
| ExprKind::Continue(..)
2932+
| ExprKind::Ret(..)
2933+
| ExprKind::InlineAsm(..)
2934+
| ExprKind::Repeat(..)
2935+
| ExprKind::Yield(..)
2936+
| ExprKind::Err => false,
2937+
}
2938+
}

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use rustc_trait_selection::traits::{
3636
self, ObligationCause, ObligationCauseCode, TraitEngine, TraitEngineExt,
3737
};
3838

39+
use std::borrow::Cow;
3940
use std::collections::hash_map::Entry;
4041
use std::slice;
4142

@@ -58,15 +59,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5859

5960
self.diverges.set(Diverges::WarnedAlways);
6061

62+
if matches!(reason, DivergeReason::Uninhabited) {
63+
let def_id = self.tcx.hir().body_owner_def_id(hir::BodyId { hir_id: self.body_id });
64+
if let Some(impl_of) = self.tcx.impl_of_method(def_id.to_def_id()) {
65+
if self.tcx.has_attr(impl_of, sym::automatically_derived) {
66+
// Built-in derives are generated before typeck,
67+
// so they may contain unreachable code if there are uninhabited types
68+
return;
69+
}
70+
}
71+
}
72+
6173
debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
6274

6375
let msg = format!("unreachable {}", kind);
6476
self.tcx().struct_span_lint_hir(lint::builtin::UNREACHABLE_CODE, id, span, &msg, |lint| {
6577
let label = match reason {
66-
DivergeReason::AllArmsDiverge => {
67-
"any code following this `match` expression is unreachable, as all arms diverge"
68-
}
69-
DivergeReason::Other => "any code following this expression is unreachable",
78+
DivergeReason::AllArmsDiverge =>
79+
Cow::Borrowed("any code following this `match` expression is unreachable, as all arms diverge"),
80+
DivergeReason::Uninhabited => format!(
81+
"this expression has type `{}`, which is uninhabited",
82+
self.typeck_results.borrow().node_type(diverging_expr.hir_id)
83+
).into(),
84+
DivergeReason::Other => Cow::Borrowed("any code following this expression is unreachable"),
7085
};
7186
lint.span_label(span, &msg).span_label(diverging_expr.span, label)
7287
});

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub struct FnCtxt<'a, 'tcx> {
4848
/// eventually).
4949
pub(super) param_env: ty::ParamEnv<'tcx>,
5050

51+
pub(super) parent_module: DefId,
52+
5153
/// Number of errors that had been reported when we started
5254
/// checking this function. On exit, if we find that *more* errors
5355
/// have been reported, we will skip regionck and other work that
@@ -138,6 +140,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
138140
FnCtxt {
139141
body_id,
140142
param_env,
143+
parent_module: inh.tcx.parent_module(body_id).to_def_id(),
141144
err_count_on_creation: inh.tcx.sess.err_count(),
142145
ret_coercion: None,
143146
in_tail_expr: false,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![crate_type = "lib"]
2+
3+
#![warn(unused)]
4+
5+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6+
pub enum Void {}
7+
8+
pub struct UnStruct {
9+
x: u32,
10+
v: Void
11+
}
12+
13+
pub fn match_struct(x: UnStruct) {
14+
match x {} //~ non-exhaustive patterns: type `UnStruct` is non-empty
15+
}
16+
17+
pub fn match_inhabited_field(x: UnStruct) {
18+
match x.x {} //~ non-exhaustive patterns: type `u32` is non-empty
19+
//~| unreachable expression
20+
}
21+
22+
pub fn match_uninhabited_field(x: UnStruct) {
23+
match x.v {} // ok
24+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
warning: unreachable expression
2+
--> $DIR/uninhabited-struct-match.rs:18:11
3+
|
4+
LL | match x.x {}
5+
| -^^
6+
| |
7+
| unreachable expression
8+
| this expression has type `UnStruct`, which is uninhabited
9+
|
10+
note: the lint level is defined here
11+
--> $DIR/uninhabited-struct-match.rs:3:9
12+
|
13+
LL | #![warn(unused)]
14+
| ^^^^^^
15+
= note: `#[warn(unreachable_code)]` implied by `#[warn(unused)]`
16+
17+
error[E0004]: non-exhaustive patterns: type `UnStruct` is non-empty
18+
--> $DIR/uninhabited-struct-match.rs:14:11
19+
|
20+
LL | match x {}
21+
| ^
22+
|
23+
note: `UnStruct` defined here
24+
--> $DIR/uninhabited-struct-match.rs:8:12
25+
|
26+
LL | pub struct UnStruct {
27+
| ^^^^^^^^
28+
= note: the matched value is of type `UnStruct`
29+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
30+
|
31+
LL ~ match x {
32+
LL + _ => todo!(),
33+
LL ~ }
34+
|
35+
36+
error[E0004]: non-exhaustive patterns: type `u32` is non-empty
37+
--> $DIR/uninhabited-struct-match.rs:18:11
38+
|
39+
LL | match x.x {}
40+
| ^^^
41+
|
42+
= note: the matched value is of type `u32`
43+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
44+
|
45+
LL ~ match x.x {
46+
LL + _ => todo!(),
47+
LL ~ }
48+
|
49+
50+
error: aborting due to 2 previous errors; 1 warning emitted
51+
52+
For more information about this error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)