Skip to content

Commit b777188

Browse files
camsteffencjgillot
authored andcommitted
Lint uninhabited types in typeck
1 parent f6e7f7c commit b777188

24 files changed

+197
-167
lines changed

compiler/rustc_hir_typeck/src/_match.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
249249
/// warn the user about the match arms being unreachable.
250250
fn warn_arms_when_scrutinee_diverges(&self, arms: &'tcx [hir::Arm<'tcx>]) {
251251
for arm in arms {
252-
self.warn_if_unreachable(arm.body.hir_id, arm.body.span, "arm");
252+
if !arm.pat.is_never_pattern() {
253+
self.warn_if_unreachable(arm.body.hir_id, arm.body.span, "arm");
254+
}
253255
}
254256
}
255257

compiler/rustc_hir_typeck/src/diverges.rs

+15-3
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
@@ -13,6 +14,14 @@ pub(crate) enum Diverges {
1314
/// others require a CFG to determine them.
1415
Maybe,
1516

17+
/// This expression is uninhabited, we want to
18+
/// emit a diagnostic but not pollute type checking.
19+
UninhabitedExpr(HirId, Span),
20+
21+
/// Same as `UninhabitedExpr` but with reachability
22+
/// warning already emitted.
23+
Warned,
24+
1625
/// Definitely known to diverge and therefore
1726
/// not reach the next sibling or its parent.
1827
Always(DivergeReason, Span),
@@ -54,16 +63,18 @@ impl ops::BitOrAssign for Diverges {
5463
impl Diverges {
5564
pub(super) fn is_always(self) -> bool {
5665
match self {
57-
Self::Maybe => false,
66+
Self::Maybe | Diverges::UninhabitedExpr(..) | Diverges::Warned => false,
5867
Self::Always(..) | Self::WarnedAlways => true,
5968
}
6069
}
6170

6271
fn ordinal(&self) -> u8 {
6372
match self {
6473
Self::Maybe => 0,
65-
Self::Always { .. } => 1,
66-
Self::WarnedAlways => 2,
74+
Self::UninhabitedExpr(..) => 1,
75+
Self::Warned => 2,
76+
Self::Always { .. } => 3,
77+
Self::WarnedAlways => 4,
6778
}
6879
}
6980
}
@@ -72,5 +83,6 @@ impl Diverges {
7283
pub(crate) enum DivergeReason {
7384
AllArmsDiverge,
7485
NeverPattern,
86+
UninhabitedExpr(HirId),
7587
Other,
7688
}

compiler/rustc_hir_typeck/src/expr.rs

+53-5
Original file line numberDiff line numberDiff line change
@@ -234,20 +234,41 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
234234
// diverging expression (e.g. it arose from desugaring of `try { return }`),
235235
// we skip issuing a warning because it is autogenerated code.
236236
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
237-
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
237+
ExprKind::Call(callee, _) => {
238+
// Do not emit a warning for a call to a constructor.
239+
let emit_warning = if let ExprKind::Path(ref qpath) = callee.kind {
240+
let res = self.typeck_results.borrow().qpath_res(qpath, callee.hir_id);
241+
!matches!(res, Res::Def(DefKind::Ctor(..), _))
242+
} else {
243+
true
244+
};
245+
if emit_warning {
246+
self.warn_if_unreachable(expr.hir_id, callee.span, "call")
247+
}
248+
}
238249
ExprKind::MethodCall(segment, ..) => {
239250
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
240251
}
252+
// Allow field access when the struct is uninhabited.
253+
ExprKind::Field(..)
254+
if matches!(self.diverges.get(), Diverges::UninhabitedExpr(_, _)) => {}
241255
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
242256
}
243257

244258
// Any expression that produces a value of type `!` must have diverged,
245259
// unless it's a place expression that isn't being read from, in which case
246260
// diverging would be unsound since we may never actually read the `!`.
247261
// e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
248-
if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) {
249-
self.diverges
250-
.set(self.diverges.get() | Diverges::Always(DivergeReason::Other, expr.span));
262+
let cur_diverges = self.diverges.get();
263+
if !cur_diverges.is_always() && self.expr_guaranteed_to_constitute_read_for_never(expr) {
264+
if ty.is_never() {
265+
// Any expression that produces a value of type `!` must have diverged.
266+
self.diverges.set(cur_diverges | Diverges::Always(DivergeReason::Other, expr.span));
267+
} else if self.ty_is_uninhabited(ty) {
268+
// This expression produces a value of uninhabited type.
269+
// This means it has diverged somehow.
270+
self.diverges.set(cur_diverges | Diverges::UninhabitedExpr(expr.hir_id, expr.span));
271+
}
251272
}
252273

253274
// Record the type, which applies it effects.
@@ -353,6 +374,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
353374
self.pat_guaranteed_to_constitute_read_for_never(*pat)
354375
}
355376

377+
hir::Node::Pat(parent_pat) => match parent_pat.kind {
378+
hir::PatKind::Lit(..) | hir::PatKind::Range(..) => false,
379+
380+
// These nodes do not have direct sub-exprs.
381+
hir::PatKind::Wild
382+
| hir::PatKind::Binding(..)
383+
| hir::PatKind::Struct(..)
384+
| hir::PatKind::TupleStruct(..)
385+
| hir::PatKind::Or(..)
386+
| hir::PatKind::Never
387+
| hir::PatKind::Path(..)
388+
| hir::PatKind::Tuple(..)
389+
| hir::PatKind::Box(..)
390+
| hir::PatKind::Deref(..)
391+
| hir::PatKind::Ref(..)
392+
| hir::PatKind::Slice(..)
393+
| hir::PatKind::Err(..) => {
394+
unreachable!("no sub-expr expected for {parent_pat:?}")
395+
}
396+
},
397+
356398
// These nodes (if they have a sub-expr) do constitute a read.
357399
hir::Node::Block(_)
358400
| hir::Node::Arm(_)
@@ -382,7 +424,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
382424
| hir::Node::Ty(_)
383425
| hir::Node::AssocItemConstraint(_)
384426
| hir::Node::TraitRef(_)
385-
| hir::Node::Pat(_)
386427
| hir::Node::PatField(_)
387428
| hir::Node::LetStmt(_)
388429
| hir::Node::Synthetic
@@ -443,6 +484,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
443484
}
444485
}
445486

487+
fn ty_is_uninhabited(&self, ty: Ty<'tcx>) -> bool {
488+
let ty = self.resolve_vars_if_possible(ty);
489+
// Freshen the type as `is_inhabited_from` may call a query on `ty`.
490+
let ty = self.freshen(ty);
491+
!ty.is_inhabited_from(self.tcx, self.parent_module, self.param_env)
492+
}
493+
446494
#[instrument(skip(self, expr), level = "debug")]
447495
fn check_expr_kind(
448496
&self,

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+34-7
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

@@ -31,7 +32,7 @@ use rustc_session::lint;
3132
use rustc_span::Span;
3233
use rustc_span::def_id::LocalDefId;
3334
use rustc_span::hygiene::DesugaringKind;
34-
use rustc_span::symbol::kw;
35+
use rustc_span::symbol::{kw, sym};
3536
use rustc_target::abi::FieldIdx;
3637
use rustc_trait_selection::error_reporting::infer::need_type_info::TypeAnnotationNeeded;
3738
use rustc_trait_selection::traits::{
@@ -50,8 +51,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5051
/// Produces warning on the given node, if the current point in the
5152
/// function is unreachable, and there hasn't been another warning.
5253
pub(crate) fn warn_if_unreachable(&self, id: HirId, span: Span, kind: &str) {
53-
let Diverges::Always(reason, orig_span) = self.diverges.get() else {
54-
return;
54+
let (reason, orig_span) = match self.diverges.get() {
55+
Diverges::UninhabitedExpr(hir_id, orig_span) => {
56+
(DivergeReason::UninhabitedExpr(hir_id), orig_span)
57+
}
58+
Diverges::Always(reason, orig_span) => (reason, orig_span),
59+
Diverges::Maybe | Diverges::Warned | Diverges::WarnedAlways => return,
5560
};
5661

5762
match span.desugaring_kind() {
@@ -73,20 +78,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
7378
_ => {}
7479
}
7580

81+
if matches!(reason, DivergeReason::UninhabitedExpr(_)) {
82+
if let Some(impl_of) = self.tcx.impl_of_method(self.body_id.to_def_id()) {
83+
if self.tcx.has_attr(impl_of, sym::automatically_derived) {
84+
// Built-in derives are generated before typeck,
85+
// so they may contain unreachable code if there are uninhabited types
86+
return;
87+
}
88+
}
89+
}
90+
7691
// Don't warn twice.
77-
self.diverges.set(Diverges::WarnedAlways);
92+
self.diverges.set(match self.diverges.get() {
93+
Diverges::UninhabitedExpr(..) => Diverges::Warned,
94+
Diverges::Always(..) => Diverges::WarnedAlways,
95+
Diverges::Maybe | Diverges::Warned | Diverges::WarnedAlways => bug!(),
96+
});
7897

7998
debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
8099

81100
let msg = format!("unreachable {kind}");
82101
self.tcx().node_span_lint(lint::builtin::UNREACHABLE_CODE, id, span, |lint| {
83102
lint.primary_message(msg.clone());
84-
let custom_note = match reason {
103+
let custom_note: Cow<'_, _> = match reason {
85104
DivergeReason::AllArmsDiverge => {
86105
"any code following this `match` expression is unreachable, as all arms diverge"
106+
.into()
107+
}
108+
DivergeReason::NeverPattern => {
109+
"any code following a never pattern is unreachable".into()
87110
}
88-
DivergeReason::NeverPattern => "any code following a never pattern is unreachable",
89-
DivergeReason::Other => "any code following this expression is unreachable",
111+
DivergeReason::UninhabitedExpr(hir_id) => format!(
112+
"this expression has type `{}`, which is uninhabited",
113+
self.typeck_results.borrow().node_type(hir_id)
114+
)
115+
.into(),
116+
DivergeReason::Other => "any code following this expression is unreachable".into(),
90117
};
91118
lint.span_label(span, msg).span_label(orig_span, custom_note);
92119
})

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ pub(crate) struct FnCtxt<'a, 'tcx> {
4949
/// eventually).
5050
pub(super) param_env: ty::ParamEnv<'tcx>,
5151

52+
/// The module in which the current function is defined. This
53+
/// is used to compute type inhabitedness, which accounts for
54+
/// visibility information.
55+
pub(super) parent_module: DefId,
56+
5257
/// If `Some`, this stores coercion information for returned
5358
/// expressions. If `None`, this is in a context where return is
5459
/// inappropriate, such as a const expression.
@@ -127,6 +132,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
127132
FnCtxt {
128133
body_id,
129134
param_env,
135+
parent_module: root_ctxt.tcx.parent_module_from_def_id(body_id).to_def_id(),
130136
ret_coercion: None,
131137
ret_coercion_span: Cell::new(None),
132138
coroutine_types: None,

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

+1-21
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/messages.ftl

-5
Original file line numberDiff line numberDiff line change
@@ -752,11 +752,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
752752
753753
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
754754
755-
passes_unreachable_due_to_uninhabited = unreachable {$descr}
756-
.label = unreachable {$descr}
757-
.label_orig = any code following this expression is unreachable
758-
.note = this expression has type `{$ty}`, which is uninhabited
759-
760755
passes_unrecognized_field =
761756
unrecognized field name `{$name}`
762757

compiler/rustc_passes/src/errors.rs

-12
Original file line numberDiff line numberDiff line change
@@ -1679,18 +1679,6 @@ pub(crate) struct SkippingConstChecks {
16791679
pub span: Span,
16801680
}
16811681

1682-
#[derive(LintDiagnostic)]
1683-
#[diag(passes_unreachable_due_to_uninhabited)]
1684-
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
1685-
pub descr: &'desc str,
1686-
#[label]
1687-
pub expr: Span,
1688-
#[label(passes_label_orig)]
1689-
#[note]
1690-
pub orig: Span,
1691-
pub ty: Ty<'tcx>,
1692-
}
1693-
16941682
#[derive(LintDiagnostic)]
16951683
#[diag(passes_unused_var_maybe_capture_ref)]
16961684
#[help]

0 commit comments

Comments
 (0)