Skip to content

Commit 446a159

Browse files
camsteffencjgillot
authored andcommitted
Check for uninhabited types in typeck
1 parent 90698b6 commit 446a159

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+523
-315
lines changed

compiler/rustc_hir_typeck/src/diverges.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{cmp, ops};
22

3+
use rustc_hir::HirId;
34
use rustc_span::Span;
45

56
/// Tracks whether executing a node may exit normally (versus
@@ -72,5 +73,6 @@ impl Diverges {
7273
pub enum DivergeReason {
7374
AllArmsDiverge,
7475
NeverPattern,
76+
UninhabitedExpr(HirId),
7577
Other,
7678
}

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,17 +230,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
230230
// diverging expression (e.g. it arose from desugaring of `try { return }`),
231231
// we skip issuing a warning because it is autogenerated code.
232232
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
233-
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
233+
ExprKind::Call(callee, _) => {
234+
let emit_warning = if let ExprKind::Path(ref qpath) = callee.kind {
235+
// Do not emit a warning for a call to a constructor.
236+
let res = self.typeck_results.borrow().qpath_res(qpath, callee.hir_id);
237+
!matches!(res, Res::Def(DefKind::Ctor(..), _))
238+
} else {
239+
true
240+
};
241+
if emit_warning {
242+
self.warn_if_unreachable(expr.hir_id, callee.span, "call")
243+
}
244+
}
234245
ExprKind::MethodCall(segment, ..) => {
235246
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
236247
}
248+
// allow field access when the struct and the field are both uninhabited
249+
ExprKind::Field(..)
250+
if matches!(
251+
self.diverges.get(),
252+
Diverges::Always(DivergeReason::UninhabitedExpr(_), _)
253+
) && self.ty_is_uninhabited(ty) => {}
237254
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
238255
}
239256

240-
// Any expression that produces a value of type `!` must have diverged
241-
if ty.is_never() {
242-
self.diverges
243-
.set(self.diverges.get() | Diverges::Always(DivergeReason::Other, expr.span));
257+
if !self.diverges.get().is_always() {
258+
if ty.is_never() {
259+
// Any expression that produces a value of type `!` must have diverged.
260+
self.diverges.set(Diverges::Always(DivergeReason::Other, expr.span));
261+
} else if expr_may_be_uninhabited(expr) && self.ty_is_uninhabited(ty) {
262+
// This expression produces a value of uninhabited type.
263+
// This means it has diverged somehow.
264+
self.diverges
265+
.set(Diverges::Always(DivergeReason::UninhabitedExpr(expr.hir_id), expr.span));
266+
}
244267
}
245268

246269
// Record the type, which applies it effects.
@@ -257,6 +280,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
257280
ty
258281
}
259282

283+
fn ty_is_uninhabited(&self, ty: Ty<'tcx>) -> bool {
284+
let ty = self.resolve_vars_if_possible(ty);
285+
// Freshen the type as `is_inhabited_from` may call a query on `ty`.
286+
let ty = self.freshen(ty);
287+
!ty.is_inhabited_from(self.tcx, self.parent_module, self.param_env)
288+
}
289+
260290
#[instrument(skip(self, expr), level = "debug")]
261291
fn check_expr_kind(
262292
&self,
@@ -3524,3 +3554,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
35243554
self.tcx.types.usize
35253555
}
35263556
}
3557+
3558+
fn expr_may_be_uninhabited(expr: &hir::Expr<'_>) -> bool {
3559+
match expr.kind {
3560+
ExprKind::Call(..)
3561+
| ExprKind::MethodCall(..)
3562+
| ExprKind::Cast(..)
3563+
| ExprKind::Unary(hir::UnOp::Deref, _)
3564+
| ExprKind::Field(..)
3565+
| ExprKind::Path(..)
3566+
| ExprKind::Struct(..) => true,
3567+
ExprKind::ConstBlock(..)
3568+
| ExprKind::Array(..)
3569+
| ExprKind::Tup(..)
3570+
| ExprKind::Binary(..)
3571+
| ExprKind::Unary(hir::UnOp::Neg | hir::UnOp::Not, _)
3572+
| ExprKind::Lit(..)
3573+
| ExprKind::Type(..)
3574+
| ExprKind::DropTemps(..)
3575+
| ExprKind::OffsetOf(..)
3576+
| ExprKind::Let(..)
3577+
| ExprKind::If(..)
3578+
| ExprKind::Loop(..)
3579+
| ExprKind::Match(..)
3580+
| ExprKind::Closure(..)
3581+
| ExprKind::Block(..)
3582+
| ExprKind::Assign(..)
3583+
| ExprKind::AssignOp(..)
3584+
| ExprKind::Index(..)
3585+
| ExprKind::AddrOf(..)
3586+
| ExprKind::Break(..)
3587+
| ExprKind::Continue(..)
3588+
| ExprKind::Ret(..)
3589+
| ExprKind::Become(..)
3590+
| ExprKind::InlineAsm(..)
3591+
| ExprKind::Repeat(..)
3592+
| ExprKind::Yield(..)
3593+
| ExprKind::Err(_) => false,
3594+
}
3595+
}

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::collections::hash_map::Entry;
23
use std::slice;
34

@@ -76,17 +77,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
7677
// Don't warn twice.
7778
self.diverges.set(Diverges::WarnedAlways);
7879

80+
if matches!(reason, DivergeReason::UninhabitedExpr(_)) {
81+
if let Some(impl_of) = self.tcx.impl_of_method(self.body_id.to_def_id()) {
82+
if self.tcx.has_attr(impl_of, sym::automatically_derived) {
83+
// Built-in derives are generated before typeck,
84+
// so they may contain unreachable code if there are uninhabited types
85+
return;
86+
}
87+
}
88+
}
89+
7990
debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
8091

8192
let msg = format!("unreachable {kind}");
8293
self.tcx().node_span_lint(lint::builtin::UNREACHABLE_CODE, id, span, |lint| {
8394
lint.primary_message(msg.clone());
84-
let custom_note = match reason {
95+
let custom_note: Cow<'_, _> = match reason {
8596
DivergeReason::AllArmsDiverge => {
8697
"any code following this `match` expression is unreachable, as all arms diverge"
98+
.into()
99+
}
100+
DivergeReason::NeverPattern => {
101+
"any code following a never pattern is unreachable".into()
87102
}
88-
DivergeReason::NeverPattern => "any code following a never pattern is unreachable",
89-
DivergeReason::Other => "any code following this expression is unreachable",
103+
DivergeReason::UninhabitedExpr(hir_id) => format!(
104+
"this expression has type `{}`, which is uninhabited",
105+
self.typeck_results.borrow().node_type(hir_id)
106+
)
107+
.into(),
108+
DivergeReason::Other => "any code following this expression is unreachable".into(),
90109
};
91110
lint.span_label(span, msg).span_label(orig_span, custom_note);
92111
})

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

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

52+
pub(super) parent_module: DefId,
53+
5254
/// If `Some`, this stores coercion information for returned
5355
/// expressions. If `None`, this is in a context where return is
5456
/// inappropriate, such as a const expression.
@@ -127,6 +129,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
127129
FnCtxt {
128130
body_id,
129131
param_env,
132+
parent_module: root_ctxt.tcx.parent_module_from_def_id(body_id).to_def_id(),
130133
ret_coercion: None,
131134
ret_coercion_span: Cell::new(None),
132135
coroutine_types: None,

compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,7 @@ impl<'tcx> InhabitedPredicate<'tcx> {
9292
Self::NotInModule(id) => in_module(id).map(|in_mod| !in_mod),
9393
// `t` may be a projection, for which `inhabited_predicate` returns a `GenericType`. As
9494
// we have a param_env available, we can do better.
95-
Self::GenericType(t) => {
96-
let normalized_pred = tcx
97-
.try_normalize_erasing_regions(param_env, t)
98-
.map_or(self, |t| t.inhabited_predicate(tcx));
99-
match normalized_pred {
100-
// We don't have more information than we started with, so consider inhabited.
101-
Self::GenericType(_) => Ok(true),
102-
pred => {
103-
// A type which is cyclic when monomorphized can happen here since the
104-
// layout error would only trigger later. See e.g. `tests/ui/sized/recursive-type-2.rs`.
105-
if eval_stack.contains(&t) {
106-
return Ok(true); // Recover; this will error later.
107-
}
108-
eval_stack.push(t);
109-
let ret =
110-
pred.apply_inner(tcx, param_env, eval_stack, in_module, reveal_opaque);
111-
eval_stack.pop();
112-
ret
113-
}
114-
}
115-
}
95+
Self::GenericType(_) => Ok(true),
11696
Self::OpaqueType(key) => match reveal_opaque(key) {
11797
// Unknown opaque is assumed inhabited.
11898
None => Ok(true),

compiler/rustc_passes/src/liveness.rs

Lines changed: 13 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet};
9494
use rustc_index::IndexVec;
9595
use rustc_middle::query::Providers;
9696
use rustc_middle::span_bug;
97-
use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
97+
use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt};
9898
use rustc_session::lint;
9999
use rustc_span::symbol::{kw, sym, Symbol};
100100
use rustc_span::{BytePos, Span};
@@ -119,8 +119,8 @@ rustc_index::newtype_index! {
119119
#[derive(Copy, Clone, PartialEq, Debug)]
120120
enum LiveNodeKind {
121121
UpvarNode(Span),
122-
ExprNode(Span, HirId),
123-
VarDefNode(Span, HirId),
122+
ExprNode(Span),
123+
VarDefNode(Span),
124124
ClosureNode,
125125
ExitNode,
126126
ErrNode,
@@ -130,8 +130,8 @@ fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
130130
let sm = tcx.sess.source_map();
131131
match lnk {
132132
UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)),
133-
ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
134-
VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
133+
ExprNode(s) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
134+
VarDefNode(s) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
135135
ClosureNode => "Closure node".to_owned(),
136136
ExitNode => "Exit node".to_owned(),
137137
ErrNode => "Error node".to_owned(),
@@ -331,7 +331,7 @@ impl<'tcx> IrMaps<'tcx> {
331331
let shorthand_field_ids = self.collect_shorthand_field_ids(pat);
332332

333333
pat.each_binding(|_, hir_id, _, ident| {
334-
self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id));
334+
self.add_live_node_for_node(hir_id, VarDefNode(ident.span));
335335
self.add_variable(Local(LocalInfo {
336336
id: hir_id,
337337
name: ident.name,
@@ -345,7 +345,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
345345
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
346346
self.add_from_pat(local.pat);
347347
if local.els.is_some() {
348-
self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id));
348+
self.add_live_node_for_node(local.hir_id, ExprNode(local.span));
349349
}
350350
intravisit::walk_local(self, local);
351351
}
@@ -377,13 +377,13 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
377377
hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
378378
debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res);
379379
if let Res::Local(_var_hir_id) = path.res {
380-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
380+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
381381
}
382382
}
383383
hir::ExprKind::Closure(closure) => {
384384
// Interesting control flow (for loops can contain labeled
385385
// breaks or continues)
386-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
386+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
387387

388388
// Make a live_node for each mentioned variable, with the span
389389
// being the location that the variable is used. This results
@@ -409,15 +409,15 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
409409
| hir::ExprKind::Match(..)
410410
| hir::ExprKind::Loop(..)
411411
| hir::ExprKind::Yield(..) => {
412-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
412+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
413413
}
414414
hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => {
415-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
415+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
416416
}
417417

418418
// Inline assembly may contain labels.
419419
hir::ExprKind::InlineAsm(asm) if asm.contains_label() => {
420-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
420+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
421421
intravisit::walk_expr(self, expr);
422422
}
423423

@@ -1297,52 +1297,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
12971297
fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
12981298
let ty = self.typeck_results.expr_ty(expr);
12991299
let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
1300-
if ty.is_inhabited_from(self.ir.tcx, m, self.param_env) {
1301-
return succ;
1302-
}
1303-
match self.ir.lnks[succ] {
1304-
LiveNodeKind::ExprNode(succ_span, succ_id) => {
1305-
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
1306-
}
1307-
LiveNodeKind::VarDefNode(succ_span, succ_id) => {
1308-
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
1309-
}
1310-
_ => {}
1311-
};
1312-
self.exit_ln
1313-
}
1314-
1315-
fn warn_about_unreachable<'desc>(
1316-
&mut self,
1317-
orig_span: Span,
1318-
orig_ty: Ty<'tcx>,
1319-
expr_span: Span,
1320-
expr_id: HirId,
1321-
descr: &'desc str,
1322-
) {
1323-
if !orig_ty.is_never() {
1324-
// Unreachable code warnings are already emitted during type checking.
1325-
// However, during type checking, full type information is being
1326-
// calculated but not yet available, so the check for diverging
1327-
// expressions due to uninhabited result types is pretty crude and
1328-
// only checks whether ty.is_never(). Here, we have full type
1329-
// information available and can issue warnings for less obviously
1330-
// uninhabited types (e.g. empty enums). The check above is used so
1331-
// that we do not emit the same warning twice if the uninhabited type
1332-
// is indeed `!`.
1333-
1334-
self.ir.tcx.emit_node_span_lint(
1335-
lint::builtin::UNREACHABLE_CODE,
1336-
expr_id,
1337-
expr_span,
1338-
errors::UnreachableDueToUninhabited {
1339-
expr: expr_span,
1340-
orig: orig_span,
1341-
descr,
1342-
ty: orig_ty,
1343-
},
1344-
);
1345-
}
1300+
if ty.is_inhabited_from(self.ir.tcx, m, self.param_env) { succ } else { self.exit_ln }
13461301
}
13471302
}
13481303

0 commit comments

Comments
 (0)