Skip to content

Commit 827be96

Browse files
camsteffencjgillot
authored andcommitted
Check for uninhabited types in typeck
1 parent 5ba9e4b commit 827be96

Some content is hidden

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

45 files changed

+606
-315
lines changed

compiler/rustc_hir_typeck/src/diverges.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use rustc_hir::HirId;
12
use rustc_span::Span;
23

34
use std::{cmp, ops};
@@ -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

+74-5
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
244244
// diverging expression (e.g. it arose from desugaring of `try { return }`),
245245
// we skip issuing a warning because it is autogenerated code.
246246
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
247-
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
247+
ExprKind::Call(callee, _) => {
248+
let emit_warning = if let ExprKind::Path(ref qpath) = callee.kind {
249+
// Do not emit a warning for a call to a constructor.
250+
let res = self.typeck_results.borrow().qpath_res(qpath, callee.hir_id);
251+
!matches!(res, Res::Def(DefKind::Ctor(..), _))
252+
} else {
253+
true
254+
};
255+
if emit_warning {
256+
self.warn_if_unreachable(expr.hir_id, callee.span, "call")
257+
}
258+
}
248259
ExprKind::MethodCall(segment, ..) => {
249260
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
250261
}
262+
// allow field access when the struct and the field are both uninhabited
263+
ExprKind::Field(..)
264+
if matches!(
265+
self.diverges.get(),
266+
Diverges::Always(DivergeReason::UninhabitedExpr(_), _)
267+
) && self.ty_is_uninhabited(ty) => {}
251268
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
252269
}
253270

254-
// Any expression that produces a value of type `!` must have diverged
255-
if ty.is_never() {
256-
self.diverges
257-
.set(self.diverges.get() | Diverges::Always(DivergeReason::Other, expr.span));
271+
if !self.diverges.get().is_always() {
272+
if ty.is_never() {
273+
// Any expression that produces a value of type `!` must have diverged.
274+
self.diverges.set(Diverges::Always(DivergeReason::Other, expr.span));
275+
} else if expr_may_be_uninhabited(expr) && self.ty_is_uninhabited(ty) {
276+
// This expression produces a value of uninhabited type.
277+
// This means it has diverged somehow.
278+
self.diverges
279+
.set(Diverges::Always(DivergeReason::UninhabitedExpr(expr.hir_id), expr.span));
280+
}
258281
}
259282

260283
// Record the type, which applies it effects.
@@ -271,6 +294,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
271294
ty
272295
}
273296

297+
fn ty_is_uninhabited(&self, ty: Ty<'tcx>) -> bool {
298+
let ty = self.resolve_vars_if_possible(ty);
299+
// Freshen the type as `is_inhabited_from` may call a query on `ty`.
300+
let ty = self.freshen(ty);
301+
!ty.is_inhabited_from(self.tcx, self.parent_module, self.param_env)
302+
}
303+
274304
#[instrument(skip(self, expr), level = "debug")]
275305
fn check_expr_kind(
276306
&self,
@@ -3451,3 +3481,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
34513481
self.tcx.types.usize
34523482
}
34533483
}
3484+
3485+
fn expr_may_be_uninhabited(expr: &hir::Expr<'_>) -> bool {
3486+
match expr.kind {
3487+
ExprKind::Call(..)
3488+
| ExprKind::MethodCall(..)
3489+
| ExprKind::Cast(..)
3490+
| ExprKind::Unary(hir::UnOp::Deref, _)
3491+
| ExprKind::Field(..)
3492+
| ExprKind::Path(..)
3493+
| ExprKind::Struct(..) => true,
3494+
ExprKind::ConstBlock(..)
3495+
| ExprKind::Array(..)
3496+
| ExprKind::Tup(..)
3497+
| ExprKind::Binary(..)
3498+
| ExprKind::Unary(hir::UnOp::Neg | hir::UnOp::Not, _)
3499+
| ExprKind::Lit(..)
3500+
| ExprKind::Type(..)
3501+
| ExprKind::DropTemps(..)
3502+
| ExprKind::OffsetOf(..)
3503+
| ExprKind::Let(..)
3504+
| ExprKind::If(..)
3505+
| ExprKind::Loop(..)
3506+
| ExprKind::Match(..)
3507+
| ExprKind::Closure(..)
3508+
| ExprKind::Block(..)
3509+
| ExprKind::Assign(..)
3510+
| ExprKind::AssignOp(..)
3511+
| ExprKind::Index(..)
3512+
| ExprKind::AddrOf(..)
3513+
| ExprKind::Break(..)
3514+
| ExprKind::Continue(..)
3515+
| ExprKind::Ret(..)
3516+
| ExprKind::Become(..)
3517+
| ExprKind::InlineAsm(..)
3518+
| ExprKind::Repeat(..)
3519+
| ExprKind::Yield(..)
3520+
| ExprKind::Err(_) => false,
3521+
}
3522+
}

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use rustc_trait_selection::traits::{
4141
self, NormalizeExt, ObligationCauseCode, ObligationCtxt, StructurallyNormalizeExt,
4242
};
4343

44+
use std::borrow::Cow;
4445
use std::collections::hash_map::Entry;
4546
use std::slice;
4647

@@ -63,16 +64,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
6364

6465
self.diverges.set(Diverges::WarnedAlways);
6566

67+
if matches!(reason, DivergeReason::UninhabitedExpr(_)) {
68+
if let Some(impl_of) = self.tcx.impl_of_method(self.body_id.to_def_id()) {
69+
if self.tcx.has_attr(impl_of, sym::automatically_derived) {
70+
// Built-in derives are generated before typeck,
71+
// so they may contain unreachable code if there are uninhabited types
72+
return;
73+
}
74+
}
75+
}
76+
6677
debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
6778

6879
let msg = format!("unreachable {kind}");
6980
self.tcx().node_span_lint(lint::builtin::UNREACHABLE_CODE, id, span, msg.clone(), |lint| {
70-
let label = match reason {
81+
let label: Cow<'_, _> = match reason {
7182
DivergeReason::AllArmsDiverge => {
7283
"any code following this `match` expression is unreachable, as all arms diverge"
84+
.into()
85+
}
86+
DivergeReason::NeverPattern => {
87+
"any code following a never pattern is unreachable".into()
7388
}
74-
DivergeReason::NeverPattern => "any code following a never pattern is unreachable",
75-
DivergeReason::Other => "any code following this expression is unreachable",
89+
DivergeReason::UninhabitedExpr(hir_id) => format!(
90+
"this expression has type `{}`, which is uninhabited",
91+
self.typeck_results.borrow().node_type(hir_id)
92+
)
93+
.into(),
94+
DivergeReason::Other => "any code following this expression is unreachable".into(),
7695
};
7796
lint.span_label(span, msg).span_label(orig_span, label);
7897
})

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub 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

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

compiler/rustc_passes/src/liveness.rs

+13-58
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ use rustc_hir::intravisit::{self, Visitor};
9494
use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet};
9595
use rustc_index::IndexVec;
9696
use rustc_middle::query::Providers;
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};
@@ -118,8 +118,8 @@ rustc_index::newtype_index! {
118118
#[derive(Copy, Clone, PartialEq, Debug)]
119119
enum LiveNodeKind {
120120
UpvarNode(Span),
121-
ExprNode(Span, HirId),
122-
VarDefNode(Span, HirId),
121+
ExprNode(Span),
122+
VarDefNode(Span),
123123
ClosureNode,
124124
ExitNode,
125125
ErrNode,
@@ -129,8 +129,8 @@ fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
129129
let sm = tcx.sess.source_map();
130130
match lnk {
131131
UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)),
132-
ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
133-
VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
132+
ExprNode(s) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
133+
VarDefNode(s) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
134134
ClosureNode => "Closure node".to_owned(),
135135
ExitNode => "Exit node".to_owned(),
136136
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)