Support casting in constant expressions
62ca04d4d6dbfbddffcc08ebfd9c93f766147331f2a933710f0cbbe59c5e8176
1 parent
60ca87f0
lib/std/lang/resolver.rad
+18 -0
| 2890 | 2890 | }, |
|
| 2891 | 2891 | case ast::NodeValue::UnOp(unop) => { |
|
| 2892 | 2892 | // Unary expressions are constant if the operand is constant. |
|
| 2893 | 2893 | return isConstExpr(self, unop.value); |
|
| 2894 | 2894 | }, |
|
| 2895 | + | case ast::NodeValue::As(expr) => { |
|
| 2896 | + | // Cast expressions are constant if the source value is constant. |
|
| 2897 | + | return isConstExpr(self, expr.value); |
|
| 2898 | + | }, |
|
| 2895 | 2899 | else => { |
|
| 2896 | 2900 | return false; |
|
| 2897 | 2901 | } |
|
| 2898 | 2902 | } |
|
| 2899 | 2903 | } |
| 5665 | 5669 | ||
| 5666 | 5670 | assert sourceTy != Type::Unknown; |
|
| 5667 | 5671 | assert targetTy != Type::Unknown; |
|
| 5668 | 5672 | ||
| 5669 | 5673 | if isValidCast(sourceTy, targetTy) { |
|
| 5674 | + | // Propagate constant value through the cast, adjusting integer |
|
| 5675 | + | // metadata to match the target type. |
|
| 5676 | + | if let value = constValueEntry(self, expr.value) { |
|
| 5677 | + | if let case ConstValue::Int(i) = value { |
|
| 5678 | + | if let range = integerRange(targetTy) { |
|
| 5679 | + | match range { |
|
| 5680 | + | case IntegerRange::Signed { bits, .. } => |
|
| 5681 | + | setNodeConstValue(self, node, constInt(i.magnitude, bits, true, i.negative)), |
|
| 5682 | + | case IntegerRange::Unsigned { bits, .. } => |
|
| 5683 | + | setNodeConstValue(self, node, constInt(i.magnitude, bits, false, false)), |
|
| 5684 | + | } |
|
| 5685 | + | } |
|
| 5686 | + | } |
|
| 5687 | + | } |
|
| 5670 | 5688 | return setNodeType(self, node, targetTy); |
|
| 5671 | 5689 | } |
|
| 5672 | 5690 | throw emitError(self, node, ErrorKind::InvalidAsCast(InvalidAsCast { |
|
| 5673 | 5691 | from: sourceTy, |
|
| 5674 | 5692 | to: targetTy, |
lib/std/lang/resolver/tests.rad
+25 -0
| 5023 | 5023 | let mut a = testResolver(); |
|
| 5024 | 5024 | let program = "const A: bool = true; const B: bool = not A;"; |
|
| 5025 | 5025 | let result = try resolveProgramStr(&mut a, program); |
|
| 5026 | 5026 | try expectNoErrors(&result); |
|
| 5027 | 5027 | } |
|
| 5028 | + | ||
| 5029 | + | /// Test `as` casts in constant expressions: widening, narrowing, sign changes, chaining. |
|
| 5030 | + | @test fn testConstExprCast() throws (testing::TestError) { |
|
| 5031 | + | try expectConstFold("const A: i32 = 42; const B: u64 = A as u64;", 1, 42); |
|
| 5032 | + | try expectConstFold("const A: u64 = 10; const B: u8 = A as u8;", 1, 10); |
|
| 5033 | + | try expectConstFold("const A: i32 = 7; const B: u32 = A as u32;", 1, 7); |
|
| 5034 | + | try expectConstFold("const A: u32 = 100; const B: i32 = A as i32;", 1, 100); |
|
| 5035 | + | try expectConstFold("const A: u8 = 5; const B: u64 = (A as u32) as u64;", 1, 5); |
|
| 5036 | + | try expectConstFold("const A: u8 = 3; const B: u8 = 4; const C: i32 = (A as i32) + (B as i32);", 2, 7); |
|
| 5037 | + | } |
|
| 5038 | + | ||
| 5039 | + | /// Test `as` cast in constant expressions used as array size. |
|
| 5040 | + | @test fn testConstExprCastAsArraySize() throws (testing::TestError) { |
|
| 5041 | + | let mut a = testResolver(); |
|
| 5042 | + | let program = "const LEN: u64 = 4; const SIZE: u32 = LEN as u32; const ARR: [i32; SIZE] = [1, 2, 3, 4];"; |
|
| 5043 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 5044 | + | try expectNoErrors(&result); |
|
| 5045 | + | ||
| 5046 | + | let arrStmt = try getBlockStmt(result.root, 2); |
|
| 5047 | + | let sym = super::symbolFor(&a, arrStmt) |
|
| 5048 | + | else throw testing::TestError::Failed; |
|
| 5049 | + | let case super::SymbolData::Constant { type: super::Type::Array(arrType), .. } = sym.data |
|
| 5050 | + | else throw testing::TestError::Failed; |
|
| 5051 | + | try testing::expect(arrType.length == 4); |
|
| 5052 | + | } |
test/tests/const-expr-cast.rad
added
+32 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Test that `as` casts work in constant expressions. |
|
| 4 | + | ||
| 5 | + | const A: i32 = 42; |
|
| 6 | + | const B: u64 = A as u64; |
|
| 7 | + | const C: u8 = 10; |
|
| 8 | + | const D: i32 = C as i32; |
|
| 9 | + | const SIZE: u32 = 4; |
|
| 10 | + | const SHIFTED: u64 = SIZE as u64; |
|
| 11 | + | ||
| 12 | + | // Use a cast const expression as an array size. |
|
| 13 | + | const LEN: u32 = 8; |
|
| 14 | + | const LEN2: u32 = (LEN as u64) as u32; |
|
| 15 | + | static BUF: [u8; LEN2] = undefined; |
|
| 16 | + | ||
| 17 | + | // Cast const used in static data initializer. |
|
| 18 | + | const INIT_VAL: u8 = 0xFF; |
|
| 19 | + | const WIDE: u32 = INIT_VAL as u32; |
|
| 20 | + | ||
| 21 | + | // Cast with arithmetic. |
|
| 22 | + | const X: u8 = 3; |
|
| 23 | + | const Y: u8 = 4; |
|
| 24 | + | const Z: i32 = (X as i32) * (Y as i32); |
|
| 25 | + | ||
| 26 | + | @default fn main() -> i32 { |
|
| 27 | + | assert BUF.len == 8; |
|
| 28 | + | assert WIDE == 255; |
|
| 29 | + | assert Z == 12; |
|
| 30 | + | ||
| 31 | + | return 0; |
|
| 32 | + | } |