Fix wrapped integer casts in constant expressions
b9457de15f59cd99c5bfb16f713459f65ec664e5a4ba02ea4f0a03fddc2f1dff
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
1 parent
f061b302
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 | + | } |