Skip to content

Commit 5c96c86

Browse files
committed
Report uninhabited call return types on MIR.
1 parent fe55364 commit 5c96c86

File tree

14 files changed

+150
-113
lines changed

14 files changed

+150
-113
lines changed

compiler/rustc_mir_build/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,11 @@ mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
383383
mir_build_union_pattern = cannot use unions in constant patterns
384384
.label = can't use a `union` here
385385
386+
mir_build_unreachable_due_to_uninhabited = unreachable {$descr}
387+
.label = unreachable {$descr}
388+
.label_orig = any code following this expression is unreachable
389+
.note = this expression has type `{$ty}`, which is uninhabited
390+
386391
mir_build_unreachable_making_this_unreachable = collectively making this unreachable
387392
388393
mir_build_unreachable_making_this_unreachable_n_more = ...and {$covered_by_many_n_more_count} other patterns collectively make this unreachable

compiler/rustc_mir_build/src/builder/expr/into.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -388,18 +388,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
388388
args,
389389
unwind: UnwindAction::Continue,
390390
destination,
391-
// The presence or absence of a return edge affects control-flow sensitive
392-
// MIR checks and ultimately whether code is accepted or not. We can only
393-
// omit the return edge if a return type is visibly uninhabited to a module
394-
// that makes the call.
395-
target: expr
396-
.ty
397-
.is_inhabited_from(
398-
this.tcx,
399-
this.parent_module,
400-
this.infcx.typing_env(this.param_env),
401-
)
402-
.then_some(success),
391+
target: Some(success),
403392
call_source: if from_hir_call {
404393
CallSource::Normal
405394
} else {

compiler/rustc_mir_build/src/builder/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ use rustc_middle::mir::*;
2424
use rustc_middle::thir::{self, ExprId, LintLevel, LocalVarId, Param, ParamId, PatKind, Thir};
2525
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode};
2626
use rustc_middle::{bug, span_bug};
27+
use rustc_session::lint;
2728
use rustc_span::{Span, Symbol, sym};
2829

2930
use crate::builder::expr::as_place::PlaceBuilder;
3031
use crate::builder::scope::DropKind;
32+
use crate::errors;
3133

3234
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
3335
tcx: TyCtxt<'tcx>,
@@ -535,6 +537,7 @@ fn construct_fn<'tcx>(
535537
return_block.unit()
536538
});
537539

540+
builder.lint_and_remove_uninhabited();
538541
let mut body = builder.finish();
539542

540543
body.spread_arg = if abi == ExternAbi::RustCall {
@@ -592,6 +595,7 @@ fn construct_const<'a, 'tcx>(
592595

593596
builder.build_drop_trees();
594597

598+
builder.lint_and_remove_uninhabited();
595599
builder.finish()
596600
}
597601

@@ -812,6 +816,78 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
812816
.unwrap();
813817
}
814818

819+
fn lint_and_remove_uninhabited(&mut self) {
820+
let mut lints = vec![];
821+
822+
for bbdata in self.cfg.basic_blocks.iter_mut() {
823+
let term = bbdata.terminator_mut();
824+
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
825+
continue;
826+
};
827+
let Some(target_bb) = *target else { continue };
828+
829+
let ty = destination.ty(&self.local_decls, self.tcx).ty;
830+
let ty_is_inhabited = ty.is_inhabited_from(
831+
self.tcx,
832+
self.parent_module,
833+
self.infcx.typing_env(self.param_env),
834+
);
835+
836+
if !ty_is_inhabited {
837+
// Unreachable code warnings are already emitted during type checking.
838+
// However, during type checking, full type information is being
839+
// calculated but not yet available, so the check for diverging
840+
// expressions due to uninhabited result types is pretty crude and
841+
// only checks whether ty.is_never(). Here, we have full type
842+
// information available and can issue warnings for less obviously
843+
// uninhabited types (e.g. empty enums). The check above is used so
844+
// that we do not emit the same warning twice if the uninhabited type
845+
// is indeed `!`.
846+
if !ty.is_never() {
847+
lints.push((target_bb, ty, term.source_info.span));
848+
}
849+
850+
// The presence or absence of a return edge affects control-flow sensitive
851+
// MIR checks and ultimately whether code is accepted or not. We can only
852+
// omit the return edge if a return type is visibly uninhabited to a module
853+
// that makes the call.
854+
*target = None;
855+
}
856+
}
857+
858+
for (target_bb, orig_ty, orig_span) in lints {
859+
if orig_span.in_external_macro(self.tcx.sess.source_map()) {
860+
continue;
861+
}
862+
let target_bb = &self.cfg.basic_blocks[target_bb];
863+
let (target_loc, descr) = target_bb
864+
.statements
865+
.iter()
866+
.find_map(|stmt| match stmt.kind {
867+
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None,
868+
StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")),
869+
_ => Some((stmt.source_info, "expression")),
870+
})
871+
.unwrap_or_else(|| (target_bb.terminator().source_info, "expression"));
872+
let lint_root = self.source_scopes[target_loc.scope]
873+
.local_data
874+
.as_ref()
875+
.unwrap_crate_local()
876+
.lint_root;
877+
self.tcx.emit_node_span_lint(
878+
lint::builtin::UNREACHABLE_CODE,
879+
lint_root,
880+
target_loc.span,
881+
errors::UnreachableDueToUninhabited {
882+
expr: target_loc.span,
883+
orig: orig_span,
884+
descr,
885+
ty: orig_ty,
886+
},
887+
);
888+
}
889+
}
890+
815891
fn finish(self) -> Body<'tcx> {
816892
let mut body = Body::new(
817893
MirSource::item(self.def_id.to_def_id()),

compiler/rustc_mir_build/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,18 @@ pub(crate) struct WantedConstant {
721721
pub(crate) const_path: String,
722722
}
723723

724+
#[derive(LintDiagnostic)]
725+
#[diag(mir_build_unreachable_due_to_uninhabited)]
726+
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
727+
pub descr: &'desc str,
728+
#[label]
729+
pub expr: Span,
730+
#[label(mir_build_label_orig)]
731+
#[note]
732+
pub orig: Span,
733+
pub ty: Ty<'tcx>,
734+
}
735+
724736
#[derive(Diagnostic)]
725737
#[diag(mir_build_const_pattern_depends_on_generic_parameter, code = E0158)]
726738
pub(crate) struct ConstPatternDependsOnGenericParameter {

compiler/rustc_passes/messages.ftl

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
622622
623623
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
624624
625-
passes_unreachable_due_to_uninhabited = unreachable {$descr}
626-
.label = unreachable {$descr}
627-
.label_orig = any code following this expression is unreachable
628-
.note = this expression has type `{$ty}`, which is uninhabited
629-
630625
passes_unrecognized_argument =
631626
unrecognized argument
632627

compiler/rustc_passes/src/errors.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,18 +1318,6 @@ pub(crate) struct ProcMacroBadSig {
13181318
pub kind: ProcMacroKind,
13191319
}
13201320

1321-
#[derive(LintDiagnostic)]
1322-
#[diag(passes_unreachable_due_to_uninhabited)]
1323-
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
1324-
pub descr: &'desc str,
1325-
#[label]
1326-
pub expr: Span,
1327-
#[label(passes_label_orig)]
1328-
#[note]
1329-
pub orig: Span,
1330-
pub ty: Ty<'tcx>,
1331-
}
1332-
13331321
#[derive(LintDiagnostic)]
13341322
#[diag(passes_unused_var_maybe_capture_ref)]
13351323
#[help]

compiler/rustc_passes/src/liveness.rs

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet, find_attr};
9595
use rustc_index::IndexVec;
9696
use rustc_middle::query::Providers;
9797
use rustc_middle::span_bug;
98-
use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
98+
use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt};
9999
use rustc_session::lint;
100100
use rustc_span::{BytePos, Span, Symbol};
101101
use tracing::{debug, instrument};
@@ -1315,52 +1315,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
13151315
fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
13161316
let ty = self.typeck_results.expr_ty(expr);
13171317
let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
1318-
if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) {
1319-
return succ;
1320-
}
1321-
match self.ir.lnks[succ] {
1322-
LiveNodeKind::ExprNode(succ_span, succ_id) => {
1323-
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
1324-
}
1325-
LiveNodeKind::VarDefNode(succ_span, succ_id) => {
1326-
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
1327-
}
1328-
_ => {}
1329-
};
1330-
self.exit_ln
1331-
}
1332-
1333-
fn warn_about_unreachable<'desc>(
1334-
&mut self,
1335-
orig_span: Span,
1336-
orig_ty: Ty<'tcx>,
1337-
expr_span: Span,
1338-
expr_id: HirId,
1339-
descr: &'desc str,
1340-
) {
1341-
if !orig_ty.is_never() {
1342-
// Unreachable code warnings are already emitted during type checking.
1343-
// However, during type checking, full type information is being
1344-
// calculated but not yet available, so the check for diverging
1345-
// expressions due to uninhabited result types is pretty crude and
1346-
// only checks whether ty.is_never(). Here, we have full type
1347-
// information available and can issue warnings for less obviously
1348-
// uninhabited types (e.g. empty enums). The check above is used so
1349-
// that we do not emit the same warning twice if the uninhabited type
1350-
// is indeed `!`.
1351-
1352-
self.ir.tcx.emit_node_span_lint(
1353-
lint::builtin::UNREACHABLE_CODE,
1354-
expr_id,
1355-
expr_span,
1356-
errors::UnreachableDueToUninhabited {
1357-
expr: expr_span,
1358-
orig: orig_span,
1359-
descr,
1360-
ty: orig_ty,
1361-
},
1362-
);
1363-
}
1318+
if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) { succ } else { self.exit_ln }
13641319
}
13651320
}
13661321

tests/ui/enum-discriminant/issue-46519.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#[should_panic(expected = "creating inhabited type")]
88
fn test() {
99
FontLanguageOverride::system_font(SystemFont::new());
10+
//~^ WARNING unreachable expression
1011
}
1112

1213
pub enum FontLanguageOverride {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
warning: unreachable expression
2+
--> $DIR/issue-46519.rs:9:5
3+
|
4+
LL | FontLanguageOverride::system_font(SystemFont::new());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------^
6+
| | |
7+
| | any code following this expression is unreachable
8+
| unreachable expression
9+
|
10+
note: this expression has type `SystemFont`, which is uninhabited
11+
--> $DIR/issue-46519.rs:9:39
12+
|
13+
LL | FontLanguageOverride::system_font(SystemFont::new());
14+
| ^^^^^^^^^^^^^^^^^
15+
= note: `#[warn(unreachable_code)]` (part of `#[warn(unused)]`) on by default
16+
17+
warning: 1 warning emitted
18+

tests/ui/intrinsics/panic-uninitialized-zeroed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//@ needs-subprocess
77
//@ ignore-backends: gcc
88

9-
#![allow(deprecated, invalid_value)]
9+
#![allow(deprecated, invalid_value, unreachable_code)]
1010
#![feature(never_type)]
1111

1212
use std::{

0 commit comments

Comments
 (0)