Skip to content

Commit 5cbf172

Browse files
committed
Print a trace through types to show how to get to the problematic type
1 parent 9909cb9 commit 5cbf172

File tree

4 files changed

+110
-55
lines changed

4 files changed

+110
-55
lines changed

compiler/rustc_lint/src/builtin.rs

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ use rustc_trait_selection::traits::{self, misc::can_type_implement_copy};
5757

5858
use crate::nonstandard_style::{method_context, MethodLateContext};
5959

60-
use std::fmt::Write;
61-
6260
// hardwired lints from librustc_middle
6361
pub use rustc_session::lint::builtin::*;
6462

@@ -2408,8 +2406,34 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24082406
}
24092407

24102408
/// Information about why a type cannot be initialized this way.
2411-
/// Contains an error message and optionally a span to point at.
2412-
type InitError = (String, Option<Span>);
2409+
struct InitError {
2410+
message: String,
2411+
/// Spans from struct fields and similar can be obtained from just the type.
2412+
span: Option<Span>,
2413+
/// Used to report a trace through adts.
2414+
nested: Option<Box<InitError>>,
2415+
}
2416+
impl InitError {
2417+
fn spanned(self, span: Span) -> InitError {
2418+
Self { span: Some(span), ..self }
2419+
}
2420+
2421+
fn nested(self, nested: InitError) -> InitError {
2422+
assert!(self.nested.is_none());
2423+
Self { nested: Some(Box::new(nested)), ..self }
2424+
}
2425+
}
2426+
2427+
impl<'a> From<&'a str> for InitError {
2428+
fn from(s: &'a str) -> Self {
2429+
s.to_owned().into()
2430+
}
2431+
}
2432+
impl From<String> for InitError {
2433+
fn from(message: String) -> Self {
2434+
Self { message, span: None, nested: None }
2435+
}
2436+
}
24132437

24142438
/// Test if this constant is all-0.
24152439
fn is_zero(expr: &hir::Expr<'_>) -> bool {
@@ -2471,17 +2495,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24712495
init: InitKind,
24722496
) -> Option<InitError> {
24732497
variant.fields.iter().find_map(|field| {
2474-
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|(mut msg, span)| {
2475-
if span.is_none() {
2476-
// Point to this field, should be helpful for figuring
2477-
// out where the source of the error is.
2478-
let span = cx.tcx.def_span(field.did);
2479-
write!(&mut msg, " (in this {descr})").unwrap();
2480-
(msg, Some(span))
2481-
} else {
2482-
// Just forward.
2483-
(msg, span)
2484-
}
2498+
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|err| {
2499+
InitError::from(format!("in this {descr}"))
2500+
.spanned(cx.tcx.def_span(field.did))
2501+
.nested(err)
24852502
})
24862503
})
24872504
}
@@ -2496,30 +2513,30 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24962513
use rustc_type_ir::sty::TyKind::*;
24972514
match ty.kind() {
24982515
// Primitive types that don't like 0 as a value.
2499-
Ref(..) => Some(("references must be non-null".to_string(), None)),
2500-
Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)),
2501-
FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)),
2502-
Never => Some(("the `!` type has no valid value".to_string(), None)),
2516+
Ref(..) => Some("references must be non-null".into()),
2517+
Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()),
2518+
FnPtr(..) => Some("function pointers must be non-null".into()),
2519+
Never => Some("the `!` type has no valid value".into()),
25032520
RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) =>
25042521
// raw ptr to dyn Trait
25052522
{
2506-
Some(("the vtable of a wide raw pointer must be non-null".to_string(), None))
2523+
Some("the vtable of a wide raw pointer must be non-null".into())
25072524
}
25082525
// Primitive types with other constraints.
25092526
Bool if init == InitKind::Uninit => {
2510-
Some(("booleans must be either `true` or `false`".to_string(), None))
2527+
Some("booleans must be either `true` or `false`".into())
25112528
}
25122529
Char if init == InitKind::Uninit => {
2513-
Some(("characters must be a valid Unicode codepoint".to_string(), None))
2530+
Some("characters must be a valid Unicode codepoint".into())
25142531
}
25152532
Int(_) | Uint(_) if init == InitKind::Uninit => {
2516-
Some(("integers must not be uninitialized".to_string(), None))
2533+
Some("integers must not be uninitialized".into())
25172534
}
25182535
Float(_) if init == InitKind::Uninit => {
2519-
Some(("floats must not be uninitialized".to_string(), None))
2536+
Some("floats must not be uninitialized".into())
25202537
}
25212538
RawPtr(_) if init == InitKind::Uninit => {
2522-
Some(("raw pointers must not be uninitialized".to_string(), None))
2539+
Some("raw pointers must not be uninitialized".into())
25232540
}
25242541
// Recurse and checks for some compound types. (but not unions)
25252542
Adt(adt_def, substs) if !adt_def.is_union() => {
@@ -2531,21 +2548,21 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
25312548
// handle the attribute correctly.)
25322549
// We don't add a span since users cannot declare such types anyway.
25332550
(Bound::Included(lo), Bound::Included(hi)) if 0 < lo && lo < hi => {
2534-
return Some((format!("`{}` must be non-null", ty), None));
2551+
return Some(format!("`{}` must be non-null", ty).into());
25352552
}
25362553
(Bound::Included(lo), Bound::Unbounded) if 0 < lo => {
2537-
return Some((format!("`{}` must be non-null", ty), None));
2554+
return Some(format!("`{}` must be non-null", ty).into());
25382555
}
25392556
(Bound::Included(_), _) | (_, Bound::Included(_))
25402557
if init == InitKind::Uninit =>
25412558
{
2542-
return Some((
2559+
return Some(
25432560
format!(
25442561
"`{}` must be initialized inside its custom valid range",
25452562
ty,
2546-
),
2547-
None,
2548-
));
2563+
)
2564+
.into(),
2565+
);
25492566
}
25502567
_ => {}
25512568
}
@@ -2576,7 +2593,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
25762593
Some((variant, definitely_inhabited))
25772594
});
25782595
let Some(first_variant) = potential_variants.next() else {
2579-
return Some(("enums with no inhabited variants have no valid value".to_string(), Some(span)));
2596+
return Some(InitError::from("enums with no inhabited variants have no valid value").spanned(span));
25802597
};
25812598
// So we have at least one potentially inhabited variant. Might we have two?
25822599
let Some(second_variant) = potential_variants.next() else {
@@ -2600,10 +2617,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
26002617
.filter(|(_variant, definitely_inhabited)| *definitely_inhabited)
26012618
.count();
26022619
if definitely_inhabited > 1 {
2603-
return Some((
2604-
"enums with multiple inhabited variants have to be initialized to a variant".to_string(),
2605-
Some(span),
2606-
));
2620+
return Some(InitError::from(
2621+
"enums with multiple inhabited variants have to be initialized to a variant",
2622+
).spanned(span));
26072623
}
26082624
}
26092625
// We couldn't find anything wrong here.
@@ -2632,8 +2648,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
26322648
// using zeroed or uninitialized memory.
26332649
// We are extremely conservative with what we warn about.
26342650
let conjured_ty = cx.typeck_results().expr_ty(expr);
2635-
if let Some((msg, span)) =
2636-
with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
2651+
if let Some(mut err) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
26372652
{
26382653
// FIXME(davidtwco): make translatable
26392654
cx.struct_span_lint(
@@ -2659,10 +2674,17 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
26592674
"help: use `MaybeUninit<T>` instead, \
26602675
and only call `assume_init` after initialization is done",
26612676
);
2662-
if let Some(span) = span {
2663-
lint.span_note(span, &msg);
2664-
} else {
2665-
lint.note(&msg);
2677+
loop {
2678+
if let Some(span) = err.span {
2679+
lint.span_note(span, &err.message);
2680+
} else {
2681+
lint.note(&err.message);
2682+
}
2683+
if let Some(e) = err.nested {
2684+
err = *e;
2685+
} else {
2686+
break;
2687+
}
26662688
}
26672689
lint
26682690
},

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +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: in this struct field
44+
--> $DIR/validate_uninhabited_zsts.rs:16:22
45+
|
46+
LL | pub struct Empty(Void);
47+
| ^^^^
4348
note: enums with no inhabited variants have no valid value
4449
--> $DIR/validate_uninhabited_zsts.rs:13:5
4550
|

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +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: in this struct field
44+
--> $DIR/validate_uninhabited_zsts.rs:16:22
45+
|
46+
LL | pub struct Empty(Void);
47+
| ^^^^
4348
note: enums with no inhabited variants have no valid value
4449
--> $DIR/validate_uninhabited_zsts.rs:13:5
4550
|

0 commit comments

Comments
 (0)