Report uninhabited call return types on MIR.

This commit is contained in:
Camille GILLOT
2024-08-10 19:46:48 +00:00
committed by Camille Gillot
parent 360a3a4e65
commit 5620c82e53
14 changed files with 156 additions and 119 deletions

View File

@@ -372,6 +372,11 @@ mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
mir_build_union_pattern = cannot use unions in constant patterns
.label = can't use a `union` here
mir_build_unreachable_due_to_uninhabited = unreachable {$descr}
.label = unreachable {$descr}
.label_orig = any code following this expression is unreachable
.note = this expression has type `{$ty}`, which is uninhabited
mir_build_unreachable_making_this_unreachable = collectively making this unreachable
mir_build_unreachable_making_this_unreachable_n_more = ...and {$covered_by_many_n_more_count} other patterns collectively make this unreachable

View File

@@ -390,18 +390,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
args,
unwind: UnwindAction::Continue,
destination,
// The presence or absence of a return edge affects control-flow sensitive
// MIR checks and ultimately whether code is accepted or not. We can only
// omit the return edge if a return type is visibly uninhabited to a module
// that makes the call.
target: expr
.ty
.is_inhabited_from(
this.tcx,
this.parent_module,
this.infcx.typing_env(this.param_env),
)
.then_some(success),
target: Some(success),
call_source: if from_hir_call {
CallSource::Normal
} else {

View File

@@ -24,10 +24,12 @@ use rustc_middle::mir::*;
use rustc_middle::thir::{self, ExprId, LintLevel, LocalVarId, Param, ParamId, PatKind, Thir};
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt, TypeVisitableExt, TypingMode};
use rustc_middle::{bug, span_bug};
use rustc_session::lint;
use rustc_span::{Span, Symbol, sym};
use crate::builder::expr::as_place::PlaceBuilder;
use crate::builder::scope::DropKind;
use crate::errors;
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
tcx: TyCtxt<'tcx>,
@@ -531,6 +533,7 @@ fn construct_fn<'tcx>(
return_block.unit()
});
builder.lint_and_remove_uninhabited();
let mut body = builder.finish();
body.spread_arg = if abi == ExternAbi::RustCall {
@@ -588,6 +591,7 @@ fn construct_const<'a, 'tcx>(
builder.build_drop_trees();
builder.lint_and_remove_uninhabited();
builder.finish()
}
@@ -806,6 +810,78 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
writer.write_mir_fn(&body, &mut std::io::stdout()).unwrap();
}
fn lint_and_remove_uninhabited(&mut self) {
let mut lints = vec![];
for bbdata in self.cfg.basic_blocks.iter_mut() {
let term = bbdata.terminator_mut();
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
continue;
};
let Some(target_bb) = *target else { continue };
let ty = destination.ty(&self.local_decls, self.tcx).ty;
let ty_is_inhabited = ty.is_inhabited_from(
self.tcx,
self.parent_module,
self.infcx.typing_env(self.param_env),
);
if !ty_is_inhabited {
// Unreachable code warnings are already emitted during type checking.
// However, during type checking, full type information is being
// calculated but not yet available, so the check for diverging
// expressions due to uninhabited result types is pretty crude and
// only checks whether ty.is_never(). Here, we have full type
// information available and can issue warnings for less obviously
// uninhabited types (e.g. empty enums). The check above is used so
// that we do not emit the same warning twice if the uninhabited type
// is indeed `!`.
if !ty.is_never() {
lints.push((target_bb, ty, term.source_info.span));
}
// The presence or absence of a return edge affects control-flow sensitive
// MIR checks and ultimately whether code is accepted or not. We can only
// omit the return edge if a return type is visibly uninhabited to a module
// that makes the call.
*target = None;
}
}
for (target_bb, orig_ty, orig_span) in lints {
if orig_span.in_external_macro(self.tcx.sess.source_map()) {
continue;
}
let target_bb = &self.cfg.basic_blocks[target_bb];
let (target_loc, descr) = target_bb
.statements
.iter()
.find_map(|stmt| match stmt.kind {
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => None,
StatementKind::FakeRead(..) => Some((stmt.source_info, "definition")),
_ => Some((stmt.source_info, "expression")),
})
.unwrap_or_else(|| (target_bb.terminator().source_info, "expression"));
let lint_root = self.source_scopes[target_loc.scope]
.local_data
.as_ref()
.unwrap_crate_local()
.lint_root;
self.tcx.emit_node_span_lint(
lint::builtin::UNREACHABLE_CODE,
lint_root,
target_loc.span,
errors::UnreachableDueToUninhabited {
expr: target_loc.span,
orig: orig_span,
descr,
ty: orig_ty,
},
);
}
}
fn finish(self) -> Body<'tcx> {
let mut body = Body::new(
MirSource::item(self.def_id.to_def_id()),

View File

@@ -720,6 +720,18 @@ pub(crate) struct WantedConstant {
pub(crate) const_path: String,
}
#[derive(LintDiagnostic)]
#[diag(mir_build_unreachable_due_to_uninhabited)]
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
pub descr: &'desc str,
#[label]
pub expr: Span,
#[label(mir_build_label_orig)]
#[note]
pub orig: Span,
pub ty: Ty<'tcx>,
}
#[derive(Diagnostic)]
#[diag(mir_build_const_pattern_depends_on_generic_parameter, code = E0158)]
pub(crate) struct ConstPatternDependsOnGenericParameter {

View File

@@ -619,11 +619,6 @@ passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been pa
passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable
passes_unreachable_due_to_uninhabited = unreachable {$descr}
.label = unreachable {$descr}
.label_orig = any code following this expression is unreachable
.note = this expression has type `{$ty}`, which is uninhabited
passes_unrecognized_argument =
unrecognized argument

View File

@@ -1308,18 +1308,6 @@ pub(crate) struct ProcMacroBadSig {
pub kind: ProcMacroKind,
}
#[derive(LintDiagnostic)]
#[diag(passes_unreachable_due_to_uninhabited)]
pub(crate) struct UnreachableDueToUninhabited<'desc, 'tcx> {
pub descr: &'desc str,
#[label]
pub expr: Span,
#[label(passes_label_orig)]
#[note]
pub orig: Span,
pub ty: Ty<'tcx>,
}
#[derive(LintDiagnostic)]
#[diag(passes_unused_var_maybe_capture_ref)]
#[help]

View File

@@ -96,7 +96,7 @@ use rustc_index::IndexVec;
use rustc_middle::query::Providers;
use rustc_middle::span_bug;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
use rustc_middle::ty::{self, RootVariableMinCaptureList, TyCtxt};
use rustc_session::lint;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{BytePos, Span, Symbol};
@@ -1317,52 +1317,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
let ty = self.typeck_results.expr_ty(expr);
let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) {
return succ;
}
match self.ir.lnks[succ] {
LiveNodeKind::ExprNode(succ_span, succ_id) => {
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
}
LiveNodeKind::VarDefNode(succ_span, succ_id) => {
self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
}
_ => {}
};
self.exit_ln
}
fn warn_about_unreachable<'desc>(
&mut self,
orig_span: Span,
orig_ty: Ty<'tcx>,
expr_span: Span,
expr_id: HirId,
descr: &'desc str,
) {
if !orig_ty.is_never() {
// Unreachable code warnings are already emitted during type checking.
// However, during type checking, full type information is being
// calculated but not yet available, so the check for diverging
// expressions due to uninhabited result types is pretty crude and
// only checks whether ty.is_never(). Here, we have full type
// information available and can issue warnings for less obviously
// uninhabited types (e.g. empty enums). The check above is used so
// that we do not emit the same warning twice if the uninhabited type
// is indeed `!`.
self.ir.tcx.emit_node_span_lint(
lint::builtin::UNREACHABLE_CODE,
expr_id,
expr_span,
errors::UnreachableDueToUninhabited {
expr: expr_span,
orig: orig_span,
descr,
ty: orig_ty,
},
);
}
if ty.is_inhabited_from(self.ir.tcx, m, self.typing_env) { succ } else { self.exit_ln }
}
}

View File

@@ -7,6 +7,7 @@
#[should_panic(expected = "creating inhabited type")]
fn test() {
FontLanguageOverride::system_font(SystemFont::new());
//~^ WARNING unreachable expression
}
pub enum FontLanguageOverride {

View File

@@ -0,0 +1,18 @@
warning: unreachable expression
--> $DIR/issue-46519.rs:9:5
|
LL | FontLanguageOverride::system_font(SystemFont::new());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------^
| | |
| | any code following this expression is unreachable
| unreachable expression
|
note: this expression has type `SystemFont`, which is uninhabited
--> $DIR/issue-46519.rs:9:39
|
LL | FontLanguageOverride::system_font(SystemFont::new());
| ^^^^^^^^^^^^^^^^^
= note: `#[warn(unreachable_code)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted

View File

@@ -7,7 +7,7 @@
//@ ignore-backends: gcc
//@ edition:2024
#![allow(deprecated, invalid_value)]
#![allow(deprecated, invalid_value, unreachable_code)]
#![feature(never_type, rustc_private)]
use std::{

View File

@@ -17,6 +17,6 @@ fn main() {
let s = S;
let x = s.f();
//~^ WARNING: unused variable: `x`
//~| WARNING: unreachable definition
let _y = x;
//~^ WARNING: unreachable definition
}

View File

@@ -1,23 +1,3 @@
warning: unreachable definition
--> $DIR/issue-85071-2.rs:20:9
|
LL | let x = s.f();
| ----- any code following this expression is unreachable
LL |
LL | let _y = x;
| ^^ unreachable definition
|
note: this expression has type `Foo`, which is uninhabited
--> $DIR/issue-85071-2.rs:18:13
|
LL | let x = s.f();
| ^^^^^
note: the lint level is defined here
--> $DIR/issue-85071-2.rs:7:26
|
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: unused variable: `x`
--> $DIR/issue-85071-2.rs:18:9
|
@@ -30,5 +10,24 @@ note: the lint level is defined here
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: unreachable definition
--> $DIR/issue-85071-2.rs:18:9
|
LL | let x = s.f();
| ^ ----- any code following this expression is unreachable
| |
| unreachable definition
|
note: this expression has type `Foo`, which is uninhabited
--> $DIR/issue-85071-2.rs:18:13
|
LL | let x = s.f();
| ^^^^^
note: the lint level is defined here
--> $DIR/issue-85071-2.rs:7:26
|
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: 2 warnings emitted

View File

@@ -14,6 +14,6 @@ fn f() -> Foo {todo!()}
fn main() {
let x = f();
//~^ WARNING: unused variable: `x`
//~| WARNING: unreachable definition
let _ = x;
//~^ WARNING: unreachable expression
}

View File

@@ -1,23 +1,3 @@
warning: unreachable expression
--> $DIR/issue-85071.rs:17:13
|
LL | let x = f();
| --- any code following this expression is unreachable
LL |
LL | let _ = x;
| ^ unreachable expression
|
note: this expression has type `Foo`, which is uninhabited
--> $DIR/issue-85071.rs:15:13
|
LL | let x = f();
| ^^^
note: the lint level is defined here
--> $DIR/issue-85071.rs:9:26
|
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: unused variable: `x`
--> $DIR/issue-85071.rs:15:9
|
@@ -30,5 +10,24 @@ note: the lint level is defined here
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: unreachable definition
--> $DIR/issue-85071.rs:15:9
|
LL | let x = f();
| ^ --- any code following this expression is unreachable
| |
| unreachable definition
|
note: this expression has type `Foo`, which is uninhabited
--> $DIR/issue-85071.rs:15:13
|
LL | let x = f();
| ^^^
note: the lint level is defined here
--> $DIR/issue-85071.rs:9:26
|
LL | #![warn(unused_variables,unreachable_code)]
| ^^^^^^^^^^^^^^^^
warning: 2 warnings emitted