Skip to content

Commit c19daa4

Browse files
committed
make invalid_value lint a bit smarter around enums
1 parent f3fafbb commit c19daa4

File tree

6 files changed

+298
-127
lines changed

6 files changed

+298
-127
lines changed

compiler/rustc_lint/src/builtin.rs

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ use rustc_middle::lint::in_external_macro;
4646
use rustc_middle::ty::layout::{LayoutError, LayoutOf};
4747
use rustc_middle::ty::print::with_no_trimmed_paths;
4848
use rustc_middle::ty::subst::GenericArgKind;
49-
use rustc_middle::ty::Instance;
50-
use rustc_middle::ty::{self, Ty, TyCtxt};
49+
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef};
5150
use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
5251
use rustc_span::edition::Edition;
5352
use rustc_span::source_map::Spanned;
@@ -2425,12 +2424,56 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24252424
None
24262425
}
24272426

2428-
/// Test if this enum has several actually "existing" variants.
2429-
/// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist".
2430-
fn is_multi_variant<'tcx>(adt: ty::AdtDef<'tcx>) -> bool {
2431-
// As an approximation, we only count dataless variants. Those are definitely inhabited.
2432-
let existing_variants = adt.variants().iter().filter(|v| v.fields.is_empty()).count();
2433-
existing_variants > 1
2427+
/// Determines whether the given type is inhabited. `None` means that we don't know.
2428+
fn ty_inhabited(ty: Ty<'_>) -> Option<bool> {
2429+
use rustc_type_ir::sty::TyKind::*;
2430+
match ty.kind() {
2431+
Never => Some(false),
2432+
Int(_) | Uint(_) | Float(_) | Bool | Char | RawPtr(_) => Some(true),
2433+
// Fallback for more complicated types. (Note that `&!` might be considered
2434+
// uninhabited so references are "complicated", too.)
2435+
_ => None,
2436+
}
2437+
}
2438+
/// Determines whether a product type formed from a list of types is inhabited.
2439+
fn tys_inhabited<'tcx>(tys: impl Iterator<Item = Ty<'tcx>>) -> Option<bool> {
2440+
let mut definitely_inhabited = true; // with no fields, we are definitely inhabited.
2441+
for ty in tys {
2442+
match ty_inhabited(ty) {
2443+
// If any type is uninhabited, the product is uninhabited.
2444+
Some(false) => return Some(false),
2445+
// Otherwise go searching for a `None`.
2446+
None => {
2447+
// We don't know.
2448+
definitely_inhabited = false;
2449+
}
2450+
Some(true) => {}
2451+
}
2452+
}
2453+
if definitely_inhabited { Some(true) } else { None }
2454+
}
2455+
2456+
fn variant_find_init_error<'tcx>(
2457+
cx: &LateContext<'tcx>,
2458+
variant: &VariantDef,
2459+
substs: ty::SubstsRef<'tcx>,
2460+
descr: &str,
2461+
init: InitKind,
2462+
) -> Option<InitError> {
2463+
variant.fields.iter().find_map(|field| {
2464+
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|(mut msg, span)| {
2465+
if span.is_none() {
2466+
// Point to this field, should be helpful for figuring
2467+
// out where the source of the error is.
2468+
let span = cx.tcx.def_span(field.did);
2469+
write!(&mut msg, " (in this {descr})").unwrap();
2470+
(msg, Some(span))
2471+
} else {
2472+
// Just forward.
2473+
(msg, span)
2474+
}
2475+
})
2476+
})
24342477
}
24352478

24362479
/// Return `Some` only if we are sure this type does *not*
@@ -2468,14 +2511,15 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24682511
RawPtr(_) if init == InitKind::Uninit => {
24692512
Some(("raw pointers must not be uninitialized".to_string(), None))
24702513
}
2471-
// Recurse and checks for some compound types.
2514+
// Recurse and checks for some compound types. (but not unions)
24722515
Adt(adt_def, substs) if !adt_def.is_union() => {
24732516
// First check if this ADT has a layout attribute (like `NonNull` and friends).
24742517
use std::ops::Bound;
24752518
match cx.tcx.layout_scalar_valid_range(adt_def.did()) {
24762519
// We exploit here that `layout_scalar_valid_range` will never
24772520
// return `Bound::Excluded`. (And we have tests checking that we
24782521
// handle the attribute correctly.)
2522+
// We don't add a span since users cannot declare such types anyway.
24792523
(Bound::Included(lo), _) if lo > 0 => {
24802524
return Some((format!("`{}` must be non-null", ty), None));
24812525
}
@@ -2492,50 +2536,64 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24922536
}
24932537
_ => {}
24942538
}
2495-
// Now, recurse.
2496-
match adt_def.variants().len() {
2497-
0 => Some(("enums with no variants have no valid value".to_string(), None)),
2498-
1 => {
2499-
// Struct, or enum with exactly one variant.
2500-
// Proceed recursively, check all fields.
2501-
let variant = &adt_def.variant(VariantIdx::from_u32(0));
2502-
variant.fields.iter().find_map(|field| {
2503-
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(
2504-
|(mut msg, span)| {
2505-
if span.is_none() {
2506-
// Point to this field, should be helpful for figuring
2507-
// out where the source of the error is.
2508-
let span = cx.tcx.def_span(field.did);
2509-
write!(
2510-
&mut msg,
2511-
" (in this {} field)",
2512-
adt_def.descr()
2513-
)
2514-
.unwrap();
2515-
(msg, Some(span))
2516-
} else {
2517-
// Just forward.
2518-
(msg, span)
2519-
}
2520-
},
2521-
)
2522-
})
2523-
}
2524-
// Multi-variant enum.
2525-
_ => {
2526-
if init == InitKind::Uninit && is_multi_variant(*adt_def) {
2527-
let span = cx.tcx.def_span(adt_def.did());
2528-
Some((
2529-
"enums have to be initialized to a variant".to_string(),
2530-
Some(span),
2531-
))
2532-
} else {
2533-
// In principle, for zero-initialization we could figure out which variant corresponds
2534-
// to tag 0, and check that... but for now we just accept all zero-initializations.
2535-
None
2536-
}
2539+
// Handle structs.
2540+
if adt_def.is_struct() {
2541+
return variant_find_init_error(
2542+
cx,
2543+
adt_def.non_enum_variant(),
2544+
substs,
2545+
"struct field",
2546+
init,
2547+
);
2548+
}
2549+
// And now, enums.
2550+
let span = cx.tcx.def_span(adt_def.did());
2551+
let mut potential_variants = adt_def.variants().iter().filter_map(|variant| {
2552+
let inhabited = tys_inhabited(
2553+
variant.fields.iter().map(|field| field.ty(cx.tcx, substs)),
2554+
);
2555+
let definitely_inhabited = match inhabited {
2556+
// Entirely skip uninhbaited variants.
2557+
Some(false) => return None,
2558+
// Forward the others, but remember which ones are definitely inhabited.
2559+
Some(true) => true,
2560+
None => false,
2561+
};
2562+
Some((variant, definitely_inhabited))
2563+
});
2564+
let Some(first_variant) = potential_variants.next() else {
2565+
return Some(("enums with no inhabited variants have no valid value".to_string(), Some(span)));
2566+
};
2567+
// So we have at least one potentially inhabited variant. Might we have two?
2568+
let Some(second_variant) = potential_variants.next() else {
2569+
// There is only one potentially inhabited variant. So we can recursively check that variant!
2570+
return variant_find_init_error(
2571+
cx,
2572+
&first_variant.0,
2573+
substs,
2574+
"field of the only potentially inhabited enum variant",
2575+
init,
2576+
);
2577+
};
2578+
// So we have at least two potentially inhabited variants.
2579+
// If we can prove that we have at least two *definitely* inhabited variants,
2580+
// then we have a tag and hence leaving this uninit is definitely disallowed.
2581+
// (Leaving it zeroed could be okay, depending on which variant is encoded as zero tag.)
2582+
if init == InitKind::Uninit {
2583+
let definitely_inhabited = (first_variant.1 as usize)
2584+
+ (second_variant.1 as usize)
2585+
+ potential_variants
2586+
.filter(|(_variant, definitely_inhabited)| *definitely_inhabited)
2587+
.count();
2588+
if definitely_inhabited > 1 {
2589+
return Some((
2590+
"enums with multiple inhabited variants have to be initialized to a variant".to_string(),
2591+
Some(span),
2592+
));
25372593
}
25382594
}
2595+
// We couldn't find anything wrong here.
2596+
None
25392597
}
25402598
Tuple(..) => {
25412599
// Proceed recursively, check all fields.

src/test/ui/consts/const-eval/validate_uninhabited_zsts.32bit.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3];
4040
| this code causes undefined behavior when executed
4141
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
4242
|
43-
note: enums with no variants have no valid value (in this struct field)
44-
--> $DIR/validate_uninhabited_zsts.rs:16:22
43+
note: enums with no inhabited variants have no valid value
44+
--> $DIR/validate_uninhabited_zsts.rs:13:5
4545
|
46-
LL | pub struct Empty(Void);
47-
| ^^^^
46+
LL | enum Void {}
47+
| ^^^^^^^^^
4848

4949
error: aborting due to 2 previous errors; 2 warnings emitted
5050

src/test/ui/consts/const-eval/validate_uninhabited_zsts.64bit.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3];
4040
| this code causes undefined behavior when executed
4141
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
4242
|
43-
note: enums with no variants have no valid value (in this struct field)
44-
--> $DIR/validate_uninhabited_zsts.rs:16:22
43+
note: enums with no inhabited variants have no valid value
44+
--> $DIR/validate_uninhabited_zsts.rs:13:5
4545
|
46-
LL | pub struct Empty(Void);
47-
| ^^^^
46+
LL | enum Void {}
47+
| ^^^^^^^^^
4848

4949
error: aborting due to 2 previous errors; 2 warnings emitted
5050

src/test/ui/lint/uninitialized-zeroed.rs renamed to src/test/ui/lint/invalid_value.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ enum OneFruit {
3434
Banana,
3535
}
3636

37+
enum OneFruitNonZero {
38+
Apple(!),
39+
Banana(NonZeroU32),
40+
}
41+
42+
enum TwoUninhabited {
43+
A(!),
44+
B(!),
45+
}
46+
3747
#[allow(unused)]
3848
fn generic<T: 'static>() {
3949
unsafe {
@@ -84,6 +94,12 @@ fn main() {
8494
let _val: [fn(); 2] = mem::zeroed(); //~ ERROR: does not permit zero-initialization
8595
let _val: [fn(); 2] = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
8696

97+
let _val: TwoUninhabited = mem::zeroed(); //~ ERROR: does not permit zero-initialization
98+
let _val: TwoUninhabited = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
99+
100+
let _val: OneFruitNonZero = mem::zeroed(); //~ ERROR: does not permit zero-initialization
101+
let _val: OneFruitNonZero = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
102+
87103
// Things that can be zero, but not uninit.
88104
let _val: bool = mem::zeroed();
89105
let _val: bool = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
@@ -112,6 +128,16 @@ fn main() {
112128
let _val: *const [()] = mem::zeroed();
113129
let _val: *const [()] = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
114130

131+
// Things where 0 is okay due to rustc implementation details,
132+
// but that are not guaranteed to keep working.
133+
let _val: Result<i32, i32> = mem::zeroed();
134+
let _val: Result<i32, i32> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
135+
136+
// Some things that happen to work due to rustc implementation details,
137+
// but are not guaranteed to keep working.
138+
let _val: OneFruit = mem::zeroed();
139+
let _val: OneFruit = mem::uninitialized();
140+
115141
// Transmute-from-0
116142
let _val: &'static i32 = mem::transmute(0usize); //~ ERROR: does not permit zero-initialization
117143
let _val: &'static [i32] = mem::transmute((0usize, 0usize)); //~ ERROR: does not permit zero-initialization
@@ -129,9 +155,5 @@ fn main() {
129155
let _val: bool = MaybeUninit::zeroed().assume_init();
130156
let _val: [bool; 0] = MaybeUninit::uninit().assume_init();
131157
let _val: [!; 0] = MaybeUninit::zeroed().assume_init();
132-
133-
// Some things that happen to work due to rustc implementation details,
134-
// but are not guaranteed to keep working.
135-
let _val: OneFruit = mem::uninitialized();
136158
}
137159
}

0 commit comments

Comments
 (0)