Skip to content

Remove a buggy cast from CheckEnums - #159447

Draft
saethlin wants to merge 1 commit into
rust-lang:mainfrom
saethlin:enum-check-casting-fix
Draft

Remove a buggy cast from CheckEnums#159447
saethlin wants to merge 1 commit into
rust-lang:mainfrom
saethlin:enum-check-casting-fix

Conversation

@saethlin

@saethlin saethlin commented Jul 17, 2026

Copy link
Copy Markdown
Member

This is a fix for #159433.

Based on the MIR that @theemathas reported:

bb2: {
    _1 = Option::<NonZero<i32>>::Some(move _2);
    _4 = copy _1 as u32 (Transmute);
    _5 = copy _4 as i32 (IntToInt);
    _6 = copy _5 as u128 (IntToInt);
    _7 = Sub(copy _6, const 0_u128);
    _8 = Le(copy _7, const 4294967295_u128);
    assert(copy _8, "trying to construct an enum from an invalid value {}", copy _6) -> [success: bb3, unwind unreachable];
}

I think the problem here is that on the full-size discriminant path we just have an unnecessary cast. We transmuted to u32 to read the discriminant value, then we should just cast directly to u128 to do the comparison. There's no need to get sidetracked by the fact that there is an i32 field.

We do still need the extra cast along our field-reading path that uses [MaybeUninit<u8>; N], so the fix here is to move the code into that arm of the if.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 17, 2026
@theemathas

This comment was marked as resolved.

@saethlin

This comment was marked as resolved.

@lukaslueg

Copy link
Copy Markdown
Contributor

The following reproducer is a variant that still breaks for me with the same error after applying this PR. Notice that Payload is larger than a NonZeroI32; as far as I understand this puts us into the // The discriminant is less wide than the operand branch, while the original reproducer is in the else branch.

use std::{mem, num::NonZeroI32};

#[repr(C)]
struct Payload {
    pad: u8,
    value: NonZeroI32,
}

enum Thing {
    Value(Payload),
    Empty,
}

fn main() {
    let value = Thing::Value(Payload {
        pad: 0,
        value: NonZeroI32::new(-1).unwrap(),
    });
    let _ = unsafe { mem::transmute::<Thing, Thing>(value) };
}

@scottmcm

Copy link
Copy Markdown
Member

It's not obvious to me that this should be going to u128 ever?

Seems like it should be emitting u32 - u32 <= u32 for a i32 or u32 scalar, like the assume does in transmute codegen:

/// Emits an `assume` that the integer value `imm` of type `ty` is contained in `range`.
///
/// This *always* emits the assumption, so you probably want to check the
/// optimization level and `Scalar::is_always_valid` before calling it.
fn assume_integer_range(&mut self, imm: Self::Value, ty: Self::Type, range: WrappingRange) {
let WrappingRange { start, end } = range;
// Perhaps one day we'll be able to use assume operand bundles for this,
// but for now this encoding with a single icmp+assume is best per
// <https://github.com/llvm/llvm-project/issues/123278#issuecomment-2597440158>
let shifted = if start == 0 {
imm
} else {
let low = self.const_uint_big(ty, start);
self.sub(imm, low)
};
let width = self.const_uint_big(ty, u128::wrapping_sub(end, start));
let cmp = self.icmp(IntPredicate::IntULE, shifted, width);
self.assume(cmp);
}

//@ run-pass
//@ compile-flags: -C debug-assertions

// This is a regression test for https://github.com/rust-lang/rust/issues/159433

@scottmcm scottmcm Jul 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: while these UI tests are good to have, I think we should have a mir-opt test showing and testing the MIR generated for various cases too.

There's a bunch of codegen-llvm tests around transmutes if you want inspiration of things to try.

View changes since the review

@saethlin

Copy link
Copy Markdown
Member Author

It's not obvious to me that this should be going to u128 ever?

It's u128 so that we can have a monomorphic panic entrypoint that prints the invalid value.

@theemathas

Copy link
Copy Markdown
Contributor

See also #143273, which fixes a similar-looking issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants