Skip to content

Commit d2393bb

Browse files
committed
Report uninhabited call return types on MIR.
1 parent 99b9a88 commit d2393bb

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
@@ -390,18 +390,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
390390
args,
391391
unwind: UnwindAction::Continue,
392392
destination,
393-
// The presence or absence of a return edge affects control-flow sensitive
394-
// MIR checks and ultimately whether code is accepted or not. We can only
395-
// omit the return edge if a return type is visibly uninhabited to a module
396-
// that makes the call.
397-
target: expr
398-
.ty
399-
.is_inhabited_from(
400-
this.tcx,
401-
this.parent_module,
402-
this.infcx.typing_env(this.param_env),
403-
)
404-
.then_some(success),
393+
target: Some(success),
405394
call_source: if from_hir_call {
406395
CallSource::Normal
407396
} 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>,
@@ -531,6 +533,7 @@ fn construct_fn<'tcx>(
531533
return_block.unit()
532534
});
533535

536+
builder.lint_and_remove_uninhabited();
534537
let mut body = builder.finish();
535538

536539
body.spread_arg = if abi == ExternAbi::RustCall {
@@ -588,6 +591,7 @@ fn construct_const<'a, 'tcx>(
588591

589592
builder.build_drop_trees();
590593

594+
builder.lint_and_remove_uninhabited();
591595
builder.finish()
592596
}
593597

@@ -806,6 +810,78 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
806810
writer.write_mir_fn(&body, &mut std::io::stdout()).unwrap();
807811
}
808812

813+
fn lint_and_remove_uninhabited(&mut self) {
814+
let mut lints = vec![];
815+
816+
for bbdata in self.cfg.basic_blocks.iter_mut() {
817+
let term = bbdata.terminator_mut();
818+
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
819+
continue;
820+
};
821+
let Some(target_bb) = *target else { continue };
822+
823+
let ty = destination.ty(&self.local_decls, self.tcx).ty;
824+
let ty_is_inhabited = ty.is_inhabited_from(
825+
self.tcx,
826+
self.parent_module,
827+
self.infcx.typing_env(self.param_env),
828+
);
829+
830+
if !ty_is_inhabited {
831+
// Unreachable code warnings are already emitted during type checking.
832+
// However, during type checking, full type information is being
833+
// calculated but not yet available, so the check for diverging
834+
// expressions due to uninhabited result types is pretty crude and
835+
// only checks whether ty.is_never(). Here, we have full type
836+
// information available and can issue warnings for less obviously
837+
// uninhabited types (e.g. empty enums). The check above is used so
838+
// that we do not emit the same warning twice if the uninhabited type
839+
// is indeed `!`.
840+
if !ty.is_never() {
841+
lints.push((target_bb, ty, term.source_info.span));
842+
}
843+
844+
// The presence or absence of a return edge affects control-flow sensitive
845+
// MIR checks and ultimately whether code is accepted or not. We can only
846+
// omit the return edge if a return type is visibly uninhabited to a module
847+
// that makes the call.
848+
*target = None;
849+
}
850+
}
851+
852+
for (target_bb, orig_ty, orig_span) in lints {
853+
if orig_span.in_external_macro(self.tcx.sess.source_map()) {
854+
continue;
855+
}
856+
let target_bb = &self.cfg.basic_blocks[target_bb];
857+
let (target_loc, descr) = target_bb
858+
.statements
859+
.iter()
860+
.find_map(|stmt| match stmt.kind {
861+
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None,
862+
StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")),
863+
_ => Some((stmt.source_info, "expression")),
864+
})
865+
.unwrap_or_else(|| (target_bb.terminator().source_info, "expression"));
866+
let lint_root = self.source_scopes[target_loc.scope]
867+
.local_data
868+
.as_ref()
869+
.unwrap_crate_local()
870+
.lint_root;
871+
self.tcx.emit_node_span_lint(
872+
lint::builtin::UNREACHABLE_CODE,
873+
lint_root,
874+
target_loc.span,
875+
errors::UnreachableDueToUninhabited {
876+
expr: target_loc.span,
877+
orig: orig_span,
878+
descr,
879+
ty: orig_ty,
880+
},
881+
);
882+
}
883+
}
884+
809885
fn finish(self) -> Body<'tcx> {
810886
let mut body = Body::new(
811887
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
@@ -619,11 +619,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
619619
620620
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
621621
622-
passes_unreachable_due_to_uninhabited = unreachable {$descr}
623-
.label = unreachable {$descr}
624-
.label_orig = any code following this expression is unreachable
625-
.note = this expression has type `{$ty}`, which is uninhabited
626-
627622
passes_unrecognized_argument =
628623
unrecognized argument
629624

compiler/rustc_passes/src/errors.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,18 +1308,6 @@ pub(crate) struct ProcMacroBadSig {
13081308
pub kind: ProcMacroKind,
13091309
}
13101310

1311-
#[derive(LintDiagnostic)]
1312-
#[diag(passes_unreachable_due_to_uninhabited)]
1313-
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
1314-
pub descr: &'desc str,
1315-
#[label]
1316-
pub expr: Span,
1317-
#[label(passes_label_orig)]
1318-
#[note]
1319-
pub orig: Span,
1320-
pub ty: Ty<'tcx>,
1321-
}
1322-
13231311
#[derive(LintDiagnostic)]
13241312
#[diag(passes_unused_var_maybe_capture_ref)]
13251313
#[help]

compiler/rustc_passes/src/liveness.rs

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

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
@@ -7,7 +7,7 @@
77
//@ ignore-backends: gcc
88
//@ edition:2024
99

10-
#![allow(deprecated, invalid_value)]
10+
#![allow(deprecated, invalid_value, unreachable_code)]
1111
#![feature(never_type, rustc_private)]
1212

1313
use std::{

0 commit comments

Comments
 (0)