Skip to content

Commit 2bcc759

Browse files
committed
Warn about unreachable code following an expression with an uninhabited type
1 parent 237b1ef commit 2bcc759

File tree

5 files changed

+200
-20
lines changed

5 files changed

+200
-20
lines changed

Diff for: compiler/rustc_passes/src/liveness.rs

+91-20
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet};
9595
use rustc_index::vec::IndexVec;
9696
use rustc_middle::hir::map::Map;
9797
use rustc_middle::ty::query::Providers;
98-
use rustc_middle::ty::{self, DefIdTree, RootVariableMinCaptureList, TyCtxt};
98+
use rustc_middle::ty::{self, DefIdTree, RootVariableMinCaptureList, Ty, TyCtxt};
9999
use rustc_session::lint;
100100
use rustc_span::symbol::{kw, sym, Symbol};
101101
use rustc_span::Span;
@@ -123,8 +123,8 @@ rustc_index::newtype_index! {
123123
#[derive(Copy, Clone, PartialEq, Debug)]
124124
enum LiveNodeKind {
125125
UpvarNode(Span),
126-
ExprNode(Span),
127-
VarDefNode(Span),
126+
ExprNode(Span, HirId),
127+
VarDefNode(Span, HirId),
128128
ClosureNode,
129129
ExitNode,
130130
}
@@ -133,8 +133,8 @@ fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
133133
let sm = tcx.sess.source_map();
134134
match lnk {
135135
UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)),
136-
ExprNode(s) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
137-
VarDefNode(s) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
136+
ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
137+
VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
138138
ClosureNode => "Closure node".to_owned(),
139139
ExitNode => "Exit node".to_owned(),
140140
}
@@ -297,7 +297,7 @@ impl IrMaps<'tcx> {
297297
}
298298

299299
pat.each_binding(|_, hir_id, _, ident| {
300-
self.add_live_node_for_node(hir_id, VarDefNode(ident.span));
300+
self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id));
301301
self.add_variable(Local(LocalInfo {
302302
id: hir_id,
303303
name: ident.name,
@@ -391,14 +391,14 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
391391
hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => {
392392
debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res);
393393
if let Res::Local(_var_hir_id) = path.res {
394-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
394+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
395395
}
396396
intravisit::walk_expr(self, expr);
397397
}
398398
hir::ExprKind::Closure(..) => {
399399
// Interesting control flow (for loops can contain labeled
400400
// breaks or continues)
401-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
401+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
402402

403403
// Make a live_node for each captured variable, with the span
404404
// being the location that the variable is used. This results
@@ -426,11 +426,11 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
426426

427427
// live nodes required for interesting control flow:
428428
hir::ExprKind::If(..) | hir::ExprKind::Match(..) | hir::ExprKind::Loop(..) => {
429-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
429+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
430430
intravisit::walk_expr(self, expr);
431431
}
432432
hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => {
433-
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span));
433+
self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
434434
intravisit::walk_expr(self, expr);
435435
}
436436

@@ -978,11 +978,26 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
978978

979979
hir::ExprKind::Call(ref f, ref args) => {
980980
let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
981-
let succ = if self.ir.tcx.is_ty_uninhabited_from(
982-
m,
983-
self.typeck_results.expr_ty(expr),
984-
self.param_env,
985-
) {
981+
let ty = self.typeck_results.expr_ty(expr);
982+
let succ = if self.ir.tcx.is_ty_uninhabited_from(m, ty, self.param_env) {
983+
if let LiveNodeKind::ExprNode(succ_span, succ_id) = self.ir.lnks[succ] {
984+
self.warn_about_unreachable(
985+
expr.span,
986+
ty,
987+
succ_span,
988+
succ_id,
989+
"expression",
990+
);
991+
} else if let LiveNodeKind::VarDefNode(succ_span, succ_id) = self.ir.lnks[succ]
992+
{
993+
self.warn_about_unreachable(
994+
expr.span,
995+
ty,
996+
succ_span,
997+
succ_id,
998+
"definition",
999+
);
1000+
}
9861001
self.exit_ln
9871002
} else {
9881003
succ
@@ -993,11 +1008,26 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
9931008

9941009
hir::ExprKind::MethodCall(.., ref args, _) => {
9951010
let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
996-
let succ = if self.ir.tcx.is_ty_uninhabited_from(
997-
m,
998-
self.typeck_results.expr_ty(expr),
999-
self.param_env,
1000-
) {
1011+
let ty = self.typeck_results.expr_ty(expr);
1012+
let succ = if self.ir.tcx.is_ty_uninhabited_from(m, ty, self.param_env) {
1013+
if let LiveNodeKind::ExprNode(succ_span, succ_id) = self.ir.lnks[succ] {
1014+
self.warn_about_unreachable(
1015+
expr.span,
1016+
ty,
1017+
succ_span,
1018+
succ_id,
1019+
"expression",
1020+
);
1021+
} else if let LiveNodeKind::VarDefNode(succ_span, succ_id) = self.ir.lnks[succ]
1022+
{
1023+
self.warn_about_unreachable(
1024+
expr.span,
1025+
ty,
1026+
succ_span,
1027+
succ_id,
1028+
"definition",
1029+
);
1030+
}
10011031
self.exit_ln
10021032
} else {
10031033
succ
@@ -1274,6 +1304,47 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
12741304

12751305
ln
12761306
}
1307+
1308+
fn warn_about_unreachable(
1309+
&mut self,
1310+
orig_span: Span,
1311+
orig_ty: Ty<'tcx>,
1312+
expr_span: Span,
1313+
expr_id: HirId,
1314+
descr: &str,
1315+
) {
1316+
if !orig_ty.is_never() {
1317+
// Unreachable code warnings are already emitted during type checking.
1318+
// However, during type checking, full type information is being
1319+
// calculated but not yet available, so the check for diverging
1320+
// expressions due to uninhabited result types is pretty crude and
1321+
// only checks whether ty.is_never(). Here, we have full type
1322+
// information available and can issue warnings for less obviously
1323+
// uninhabited types (e.g. empty enums). The check above is used so
1324+
// that we do not emit the same warning twice if the uninhabited type
1325+
// is indeed `!`.
1326+
1327+
self.ir.tcx.struct_span_lint_hir(
1328+
lint::builtin::UNREACHABLE_CODE,
1329+
expr_id,
1330+
expr_span,
1331+
|lint| {
1332+
let msg = format!("unreachable {}", descr);
1333+
lint.build(&msg)
1334+
.span_label(expr_span, &msg)
1335+
.span_label(orig_span, "any code following this expression is unreachable")
1336+
.span_note(
1337+
orig_span,
1338+
&format!(
1339+
"this expression has type `{}`, which is uninhabited",
1340+
orig_ty
1341+
),
1342+
)
1343+
.emit();
1344+
},
1345+
);
1346+
}
1347+
}
12771348
}
12781349

12791350
// _______________________________________________________________________

Diff for: src/test/ui/lint/dead-code/issue-85071-2.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// A slight variation of issue-85071.rs. Here, a method is called instead
2+
// of a function, and the warning is about an unreachable definition
3+
// instead of an unreachable expression.
4+
5+
// check-pass
6+
7+
#![warn(unused_variables,unreachable_code)]
8+
9+
enum Foo {}
10+
11+
struct S;
12+
impl S {
13+
fn f(&self) -> Foo {todo!()}
14+
}
15+
16+
fn main() {
17+
let s = S;
18+
let x = s.f();
19+
//~^ WARNING: unused variable: `x`
20+
let _y = x;
21+
//~^ WARNING: unreachable definition
22+
}

Diff for: src/test/ui/lint/dead-code/issue-85071-2.stderr

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
warning: unreachable definition
2+
--> $DIR/issue-85071-2.rs:20:9
3+
|
4+
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: the lint level is defined here
11+
--> $DIR/issue-85071-2.rs:7:26
12+
|
13+
LL | #![warn(unused_variables,unreachable_code)]
14+
| ^^^^^^^^^^^^^^^^
15+
note: this expression has type `Foo`, which is uninhabited
16+
--> $DIR/issue-85071-2.rs:18:13
17+
|
18+
LL | let x = s.f();
19+
| ^^^^^
20+
21+
warning: unused variable: `x`
22+
--> $DIR/issue-85071-2.rs:18:9
23+
|
24+
LL | let x = s.f();
25+
| ^ help: if this is intentional, prefix it with an underscore: `_x`
26+
|
27+
note: the lint level is defined here
28+
--> $DIR/issue-85071-2.rs:7:9
29+
|
30+
LL | #![warn(unused_variables,unreachable_code)]
31+
| ^^^^^^^^^^^^^^^^
32+
33+
warning: 2 warnings emitted
34+

Diff for: src/test/ui/lint/dead-code/issue-85071.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Checks that an unreachable code warning is emitted when an expression is
2+
// preceded by an expression with an uninhabited type. Previously, the
3+
// variable liveness analysis was "smarter" than the reachability analysis
4+
// in this regard, which led to confusing "unused variable" warnings
5+
// without an accompanying explanatory "unreachable expression" warning.
6+
7+
// check-pass
8+
9+
#![warn(unused_variables,unreachable_code)]
10+
11+
enum Foo {}
12+
fn f() -> Foo {todo!()}
13+
14+
fn main() {
15+
let x = f();
16+
//~^ WARNING: unused variable: `x`
17+
let _ = x;
18+
//~^ WARNING: unreachable expression
19+
}

Diff for: src/test/ui/lint/dead-code/issue-85071.stderr

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
warning: unreachable expression
2+
--> $DIR/issue-85071.rs:17:13
3+
|
4+
LL | let x = f();
5+
| --- any code following this expression is unreachable
6+
LL |
7+
LL | let _ = x;
8+
| ^ unreachable expression
9+
|
10+
note: the lint level is defined here
11+
--> $DIR/issue-85071.rs:9:26
12+
|
13+
LL | #![warn(unused_variables,unreachable_code)]
14+
| ^^^^^^^^^^^^^^^^
15+
note: this expression has type `Foo`, which is uninhabited
16+
--> $DIR/issue-85071.rs:15:13
17+
|
18+
LL | let x = f();
19+
| ^^^
20+
21+
warning: unused variable: `x`
22+
--> $DIR/issue-85071.rs:15:9
23+
|
24+
LL | let x = f();
25+
| ^ help: if this is intentional, prefix it with an underscore: `_x`
26+
|
27+
note: the lint level is defined here
28+
--> $DIR/issue-85071.rs:9:9
29+
|
30+
LL | #![warn(unused_variables,unreachable_code)]
31+
| ^^^^^^^^^^^^^^^^
32+
33+
warning: 2 warnings emitted
34+

0 commit comments

Comments
 (0)