Skip to content

Commit f2e348c

Browse files
committed
Report uninhabited call return types on MIR.
1 parent e703dff commit f2e348c

File tree

14 files changed

+147
-113
lines changed

14 files changed

+147
-113
lines changed

compiler/rustc_mir_build/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
346346
mir_build_union_pattern = cannot use unions in constant patterns
347347
.label = can't use a `union` here
348348
349+
mir_build_unreachable_due_to_uninhabited = unreachable {$descr}
350+
.label = unreachable {$descr}
351+
.label_orig = any code following this expression is unreachable
352+
.note = this expression has type `{$ty}`, which is uninhabited
353+
349354
mir_build_unreachable_making_this_unreachable = collectively making this unreachable
350355
351356
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
@@ -269,18 +269,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
269269
args,
270270
unwind: UnwindAction::Continue,
271271
destination,
272-
// The presence or absence of a return edge affects control-flow sensitive
273-
// MIR checks and ultimately whether code is accepted or not. We can only
274-
// omit the return edge if a return type is visibly uninhabited to a module
275-
// that makes the call.
276-
target: expr
277-
.ty
278-
.is_inhabited_from(
279-
this.tcx,
280-
this.parent_module,
281-
this.infcx.typing_env(this.param_env),
282-
)
283-
.then_some(success),
272+
target: Some(success),
284273
call_source: if from_hir_call {
285274
CallSource::Normal
286275
} else {

compiler/rustc_mir_build/src/builder/mod.rs

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

2829
use crate::builder::expr::as_place::PlaceBuilder;
2930
use crate::builder::scope::DropKind;
31+
use crate::errors;
3032

3133
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
3234
tcx: TyCtxt<'tcx>,
@@ -534,6 +536,7 @@ fn construct_fn<'tcx>(
534536
return_block.unit()
535537
});
536538

539+
builder.lint_and_remove_uninhabited();
537540
let mut body = builder.finish();
538541

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

592595
builder.build_drop_trees();
593596

597+
builder.lint_and_remove_uninhabited();
594598
builder.finish()
595599
}
596600

@@ -789,6 +793,75 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
789793
builder
790794
}
791795

796+
fn lint_and_remove_uninhabited(&mut self) {
797+
let mut lints = vec![];
798+
799+
for bbdata in self.cfg.basic_blocks.iter_mut() {
800+
let term = bbdata.terminator_mut();
801+
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
802+
continue;
803+
};
804+
let Some(target_bb) = *target else { continue };
805+
806+
let ty = destination.ty(&self.local_decls, self.tcx).ty;
807+
let ty_is_inhabited = ty.is_inhabited_from(
808+
self.tcx,
809+
self.parent_module,
810+
self.infcx.typing_env(self.param_env),
811+
);
812+
813+
if !ty_is_inhabited {
814+
// Unreachable code warnings are already emitted during type checking.
815+
// However, during type checking, full type information is being
816+
// calculated but not yet available, so the check for diverging
817+
// expressions due to uninhabited result types is pretty crude and
818+
// only checks whether ty.is_never(). Here, we have full type
819+
// information available and can issue warnings for less obviously
820+
// uninhabited types (e.g. empty enums). The check above is used so
821+
// that we do not emit the same warning twice if the uninhabited type
822+
// is indeed `!`.
823+
if !ty.is_never() {
824+
lints.push((target_bb, ty, term.source_info.span));
825+
}
826+
827+
// The presence or absence of a return edge affects control-flow sensitive
828+
// MIR checks and ultimately whether code is accepted or not. We can only
829+
// omit the return edge if a return type is visibly uninhabited to a module
830+
// that makes the call.
831+
*target = None;
832+
}
833+
}
834+
835+
for (target_bb, orig_ty, orig_span) in lints {
836+
let target_bb = &self.cfg.basic_blocks[target_bb];
837+
let (target_loc, descr) = target_bb
838+
.statements
839+
.iter()
840+
.find_map(|stmt| match stmt.kind {
841+
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None,
842+
StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")),
843+
_ => Some((stmt.source_info, "expression")),
844+
})
845+
.unwrap_or_else(|| (target_bb.terminator().source_info, "expression"));
846+
let lint_root = self.source_scopes[target_loc.scope]
847+
.local_data
848+
.as_ref()
849+
.unwrap_crate_local()
850+
.lint_root;
851+
self.tcx.emit_node_span_lint(
852+
lint::builtin::UNREACHABLE_CODE,
853+
lint_root,
854+
target_loc.span,
855+
errors::UnreachableDueToUninhabited {
856+
expr: target_loc.span,
857+
orig: orig_span,
858+
descr,
859+
ty: orig_ty,
860+
},
861+
);
862+
}
863+
}
864+
792865
fn finish(self) -> Body<'tcx> {
793866
let mut body = Body::new(
794867
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
@@ -726,11 +726,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
726726
727727
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
728728
729-
passes_unreachable_due_to_uninhabited = unreachable {$descr}
730-
.label = unreachable {$descr}
731-
.label_orig = any code following this expression is unreachable
732-
.note = this expression has type `{$ty}`, which is uninhabited
733-
734729
passes_unrecognized_argument =
735730
unrecognized argument
736731

compiler/rustc_passes/src/errors.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,18 +1539,6 @@ pub(crate) struct ProcMacroBadSig {
15391539
pub kind: ProcMacroKind,
15401540
}
15411541

1542-
#[derive(LintDiagnostic)]
1543-
#[diag(passes_unreachable_due_to_uninhabited)]
1544-
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
1545-
pub descr: &'desc str,
1546-
#[label]
1547-
pub expr: Span,
1548-
#[label(passes_label_orig)]
1549-
#[note]
1550-
pub orig: Span,
1551-
pub ty: Ty<'tcx>,
1552-
}
1553-
15541542
#[derive(LintDiagnostic)]
15551543
#[diag(passes_unused_var_maybe_capture_ref)]
15561544
#[help]

compiler/rustc_passes/src/liveness.rs

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

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)]` 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
@@ -5,7 +5,7 @@
55
//@ [strict]compile-flags: -Zstrict-init-checks
66
//@ needs-subprocess
77

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

1111
use std::{

tests/ui/lint/dead-code/issue-85071-2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ fn main() {
1717
let s = S;
1818
let x = s.f();
1919
//~^ WARNING: unused variable: `x`
20+
//~| WARNING: unreachable definition
2021
let _y = x;
21-
//~^ WARNING: unreachable definition
2222
}
Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
warning: unreachable definition
2-
--> $DIR/issue-85071-2.rs:20:9
1+
warning: unused variable: `x`
2+
--> $DIR/issue-85071-2.rs:18:9
33
|
44
LL | let x = s.f();
5-
| ----- any code following this expression is unreachable
6-
LL |
7-
LL | let _y = x;
8-
| ^^ unreachable definition
9-
|
10-
note: this expression has type `Foo`, which is uninhabited
11-
--> $DIR/issue-85071-2.rs:18:13
5+
| ^ help: if this is intentional, prefix it with an underscore: `_x`
126
|
13-
LL | let x = s.f();
14-
| ^^^^^
157
note: the lint level is defined here
16-
--> $DIR/issue-85071-2.rs:7:26
8+
--> $DIR/issue-85071-2.rs:7:9
179
|
1810
LL | #![warn(unused_variables,unreachable_code)]
19-
| ^^^^^^^^^^^^^^^^
11+
| ^^^^^^^^^^^^^^^^
2012

21-
warning: unused variable: `x`
13+
warning: unreachable definition
2214
--> $DIR/issue-85071-2.rs:18:9
2315
|
2416
LL | let x = s.f();
25-
| ^ help: if this is intentional, prefix it with an underscore: `_x`
17+
| ^ ----- any code following this expression is unreachable
18+
| |
19+
| unreachable definition
20+
|
21+
note: this expression has type `Foo`, which is uninhabited
22+
--> $DIR/issue-85071-2.rs:18:13
2623
|
24+
LL | let x = s.f();
25+
| ^^^^^
2726
note: the lint level is defined here
28-
--> $DIR/issue-85071-2.rs:7:9
27+
--> $DIR/issue-85071-2.rs:7:26
2928
|
3029
LL | #![warn(unused_variables,unreachable_code)]
31-
| ^^^^^^^^^^^^^^^^
30+
| ^^^^^^^^^^^^^^^^
3231

3332
warning: 2 warnings emitted
3433

tests/ui/lint/dead-code/issue-85071.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ fn f() -> Foo {todo!()}
1414
fn main() {
1515
let x = f();
1616
//~^ WARNING: unused variable: `x`
17+
//~| WARNING: unreachable definition
1718
let _ = x;
18-
//~^ WARNING: unreachable expression
1919
}

0 commit comments

Comments
 (0)