Change:
For example, after laundering interior mutability through a union, a resulting constant undergoes promotion: label +A-const-eval +F-untagged_unions
Yea, unions should not be "more powerful" than a plain transmute imo. cc
I assume with transmute one can also produce a similar example as the OP? So in which sense are unions more powerful than transmute, Why does this example behave any different than the following?
The union local initially starts without interior mutability qualif. This persist after the first assignment, since u32 does not have interior mutability The move from the cell variant is qualified based on the qualification of the local, so the overall constant is consider not to have interior mutability. The use of the constant in the main function is promoted and fails dynamic checks.
I totally forgot promotion of looks into the definition of ... that'll continue to haunt us for a while I think. :/ Yeah, the analysis clearly is wrong for reading from union fields. But even for values like , I have no idea what the wider consequences are (given that the type is considered interior mutable).
The example can be also modified by moving union access to the runtime code, in which case it will compile successfully, with promoted being placed in .rodata section: In the context of static analysis, I think it would make sense to use strictly type based analysis for unions, in which case the first assignment to a union local would already qualify the local as having interior mutability.
But also, there's supposed to be a 2nd line of defense in our interning code that is somehow failing.
Turns out interning relies in validity checking for this: The example in the OP was caught by validity, so that is good. But the 2nd example was not caught, because we never actually hit an UnsafeCell -- we stop at the union boundary. So maybe the union part of the visitor should also have the kind of ?
do we have an established approach to testing that this second line of defense works? Carefully crafted constant with undefined behaviour might do its job in this case (not sure yet), but maybe we have better alternatives?
We have -Zunleash-the-miri-inside-of-you but that only helps for checks, not for promoted restrictions... I don't think we have anything for promoteds (and it seems non-trivial, we would need a way to force something to be promoted despite it violating the usual conditions).

Discussion:
if Q::in_adt_inherently(cx, def, substs) { return true; } <ins> if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) { return true; } </ins> } // Otherwise, proceed structurally...

} } <del> fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, value: bool) { </del> <ins> fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, mut value: bool) { </ins> debug_assert!(!place.is_indirect()); <ins> if !value { for (base, _elem) in place.iter_projections() { let base_ty = base.ty(self.ccx.body, self.ccx.tcx); if base_ty.ty.is_union() && Q::in_any_value_of_ty(self.ccx, base_ty.ty) { value = true; break; } } } </ins> match (value, place.as_ref()) { (true, mir::PlaceRef { local, .. }) => { self.qualifs_per_local.insert(local);

<ins> // Checks that unions use type based qualification. Regression test for issue #90268. #![feature(untagged_unions)] use std::cell::Cell; union U { i: u32, c: Cell<u32> } const C1: Cell<u32> = { unsafe { U { c: Cell::new(0) }.c } }; const C2: Cell<u32> = { unsafe { U { i : 0 }.c } }; const C3: Cell<u32> = { let mut u = U { i: 0 }; u.i = 1; unsafe { u.c } }; const C4: U = U { i: 0 }; const C5: [U; 1] = [U {i : 0}; 1]; fn main() { // Interior mutability should prevent promotion. let _: &'static _ = &C1; //~ ERROR temporary value dropped while borrowed let _: &'static _ = &C2; //~ ERROR temporary value dropped while borrowed let _: &'static _ = &C3; //~ ERROR temporary value dropped while borrowed let _: &'static _ = &C4; //~ ERROR temporary value dropped while borrowed let _: &'static _ = &C5; //~ ERROR temporary value dropped while borrowed } </ins>

<ins> error[E0716]: temporary value dropped while borrowed --> $DIR/qualif-union.rs:27:26 | LL |     let _: &'static _ = &C1; |            ----------    ^^ creates a temporary which is freed while still in use |            | |            type annotation requires that borrow lasts for `'static` ... LL | } | - temporary value is freed at the end of this statement error[E0716]: temporary value dropped while borrowed --> $DIR/qualif-union.rs:28:26 | LL |     let _: &'static _ = &C2; |            ----------    ^^ creates a temporary which is freed while still in use |            | |            type annotation requires that borrow lasts for `'static` ... LL | } | - temporary value is freed at the end of this statement error[E0716]: temporary value dropped while borrowed --> $DIR/qualif-union.rs:29:26 | LL |     let _: &'static _ = &C3; |            ----------    ^^ creates a temporary which is freed while still in use |            | |            type annotation requires that borrow lasts for `'static` ... LL | } | - temporary value is freed at the end of this statement error[E0716]: temporary value dropped while borrowed --> $DIR/qualif-union.rs:30:26 | LL |     let _: &'static _ = &C4; |            ----------    ^^ creates a temporary which is freed while still in use |            | |            type annotation requires that borrow lasts for `'static` LL |     let _: &'static _ = &C5; LL | } | - temporary value is freed at the end of this statement error[E0716]: temporary value dropped while borrowed --> $DIR/qualif-union.rs:31:26 | LL |     let _: &'static _ = &C5; |            ----------    ^^ creates a temporary which is freed while still in use |            | |            type annotation requires that borrow lasts for `'static` LL | } | - temporary value is freed at the end of this statement error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0716`. </ins>