Fix wrapped integer casts in constant expressions

8301d68e1a2257d46974b338491d4f20d34ed33d0af49bef3d846f8dd1054eba
Constant evaluation of integer casts only changed the recorded width
and signedness. It neither truncated values to the destination width
nor reinterpreted the destination sign bit, so narrowing followed by
widening produced incorrect constants.

Mask constant values to the target width and sign-extend them when the
target is signed before recording the cast result.

Assisted-by: Codex:gpt-5.6-sol
Alexis Sellier committed ago 1 parent 3c0dd6ed
lib/std/lang/resolver.rad +25 -10
2908 2908
/// Construct an integer constant descriptor.
2909 2909
fn constInt(magnitude: u64, bits: u8, signed: bool, negative: bool) -> ConstValue {
2910 2910
    return ConstValue::Int(ConstInt { magnitude, bits, signed, negative });
2911 2911
}
2912 2912
2913 +
/// Apply an integer cast to a constant value, including target-width
2914 +
/// truncation and signed interpretation.
2915 +
fn castConstInt(value: ConstInt, target: Type) -> ConstValue {
2916 +
    // Convert sign-magnitude metadata to its two's-complement bit pattern.
2917 +
    let raw = (0 - value.magnitude) if value.negative else value.magnitude;
2918 +
    let range = integerRange(target)
2919 +
        else panic "castConstInt: expected integer type";
2920 +
2921 +
    match range {
2922 +
        case IntegerRange::Unsigned { bits, max } =>
2923 +
            return constInt(raw & max, bits, false, false),
2924 +
        case IntegerRange::Signed { bits, max, lim, .. } => {
2925 +
            let mask = (max as u64) | lim;
2926 +
            let truncated = raw & mask;
2927 +
            if (truncated & lim) <> 0 {
2928 +
                return constInt((0 - truncated) & mask, bits, true, true);
2929 +
            }
2930 +
            return constInt(truncated, bits, true, false);
2931 +
        }
2932 +
    }
2933 +
}
2934 +
2913 2935
/// Return the constant `u32` value for a slice bound when known.
2914 2936
fn constSliceIndex(self: *mut Resolver, node: *ast::Node) -> ?u32 {
2915 2937
    let value = constValueEntry(self, node)
2916 2938
        else return nil;
2917 2939
    let case ConstValue::Int(int) = value
5952 5974
5953 5975
    assert sourceTy <> Type::Unknown;
5954 5976
    assert targetTy <> Type::Unknown;
5955 5977
5956 5978
    if isValidCast(sourceTy, targetTy) {
5957 -
        // Propagate constant value through the cast, adjusting integer
5958 -
        // metadata to match the target type.
5979 +
        // Propagate the constant value after applying the cast's target-width
5980 +
        // truncation and signed interpretation.
5959 5981
        if let value = constValueEntry(self, expr.value) {
5960 5982
            if let case ConstValue::Int(i) = value {
5961 -
                if let range = integerRange(targetTy) {
5962 -
                    match range {
5963 -
                        case IntegerRange::Signed { bits, .. } =>
5964 -
                            setNodeConstValue(self, node, constInt(i.magnitude, bits, true, i.negative)),
5965 -
                        case IntegerRange::Unsigned { bits, .. } =>
5966 -
                            setNodeConstValue(self, node, constInt(i.magnitude, bits, false, false)),
5967 -
                    }
5968 -
                }
5983 +
                setNodeConstValue(self, node, castConstInt(i, targetTy));
5969 5984
            }
5970 5985
        }
5971 5986
        return setNodeType(self, node, targetTy);
5972 5987
    }
5973 5988
    throw emitError(self, node, ErrorKind::InvalidAsCast(InvalidAsCast {
test/tests/const-cast-wrap.rad added +14 -0
1 +
//! returns: 0
2 +
3 +
/// Integer casts in constant expressions must apply target-width wrapping
4 +
/// before their values participate in subsequent casts or arithmetic.
5 +
constant NEG_ONE_WIDE: u16 = ((-1) as u8) as u16;
6 +
constant TRUNCATED_WIDE: u16 = (257 as u8) as u16;
7 +
constant SIGNED_WIDE: i16 = (255 as i8) as i16;
8 +
9 +
@default fn main() -> i32 {
10 +
    assert NEG_ONE_WIDE == 255;
11 +
    assert TRUNCATED_WIDE == 1;
12 +
    assert SIGNED_WIDE == -1;
13 +
    return 0;
14 +
}