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
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 | + | } |