From 85b209935fff44896b0c36dc265fefc35de10ce9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Nov 2025 17:05:40 -0500 Subject: [PATCH 1/5] Remaining integer operations --- src/build/builtin_compiler/main.zig | 468 ++++++++++++++++++++++++++++ src/build/roc/Builtin.roc | 198 ++++++++++++ src/canonicalize/Expression.zig | 166 ++++++++++ src/eval/interpreter.zig | 171 +++++++++- 4 files changed, 1002 insertions(+), 1 deletion(-) diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index fa6cee12bf..8fe20d4e84 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -454,7 +454,475 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { try low_level_map.put(ident, .i8_to_dec); } + // U16 conversion operations + if (env.common.findIdent("Builtin.Num.U16.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .u16_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.U16.to_i8_try")) |ident| { + try low_level_map.put(ident, .u16_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.U16.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .u16_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.U16.to_i16_try")) |ident| { + try low_level_map.put(ident, .u16_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.U16.to_i32")) |ident| { + try low_level_map.put(ident, .u16_to_i32); + } + if (env.common.findIdent("Builtin.Num.U16.to_i64")) |ident| { + try low_level_map.put(ident, .u16_to_i64); + } + if (env.common.findIdent("Builtin.Num.U16.to_i128")) |ident| { + try low_level_map.put(ident, .u16_to_i128); + } + if (env.common.findIdent("Builtin.Num.U16.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .u16_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.U16.to_u8_try")) |ident| { + try low_level_map.put(ident, .u16_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.U16.to_u32")) |ident| { + try low_level_map.put(ident, .u16_to_u32); + } + if (env.common.findIdent("Builtin.Num.U16.to_u64")) |ident| { + try low_level_map.put(ident, .u16_to_u64); + } + if (env.common.findIdent("Builtin.Num.U16.to_u128")) |ident| { + try low_level_map.put(ident, .u16_to_u128); + } + if (env.common.findIdent("Builtin.Num.U16.to_f32")) |ident| { + try low_level_map.put(ident, .u16_to_f32); + } + if (env.common.findIdent("Builtin.Num.U16.to_f64")) |ident| { + try low_level_map.put(ident, .u16_to_f64); + } + if (env.common.findIdent("Builtin.Num.U16.to_dec")) |ident| { + try low_level_map.put(ident, .u16_to_dec); + } + + // I16 conversion operations + if (env.common.findIdent("Builtin.Num.I16.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_i8_try")) |ident| { + try low_level_map.put(ident, .i16_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_i32")) |ident| { + try low_level_map.put(ident, .i16_to_i32); + } + if (env.common.findIdent("Builtin.Num.I16.to_i64")) |ident| { + try low_level_map.put(ident, .i16_to_i64); + } + if (env.common.findIdent("Builtin.Num.I16.to_i128")) |ident| { + try low_level_map.put(ident, .i16_to_i128); + } + if (env.common.findIdent("Builtin.Num.I16.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_u8_try")) |ident| { + try low_level_map.put(ident, .i16_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_u16_try")) |ident| { + try low_level_map.put(ident, .i16_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_u32_try")) |ident| { + try low_level_map.put(ident, .i16_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_u64_try")) |ident| { + try low_level_map.put(ident, .i16_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .i16_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.I16.to_u128_try")) |ident| { + try low_level_map.put(ident, .i16_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.I16.to_f32")) |ident| { + try low_level_map.put(ident, .i16_to_f32); + } + if (env.common.findIdent("Builtin.Num.I16.to_f64")) |ident| { + try low_level_map.put(ident, .i16_to_f64); + } + if (env.common.findIdent("Builtin.Num.I16.to_dec")) |ident| { + try low_level_map.put(ident, .i16_to_dec); + } + + // U32 conversion operations + if (env.common.findIdent("Builtin.Num.U32.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .u32_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.U32.to_i8_try")) |ident| { + try low_level_map.put(ident, .u32_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.U32.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .u32_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.U32.to_i16_try")) |ident| { + try low_level_map.put(ident, .u32_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.U32.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .u32_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.U32.to_i32_try")) |ident| { + try low_level_map.put(ident, .u32_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.U32.to_i64")) |ident| { + try low_level_map.put(ident, .u32_to_i64); + } + if (env.common.findIdent("Builtin.Num.U32.to_i128")) |ident| { + try low_level_map.put(ident, .u32_to_i128); + } + if (env.common.findIdent("Builtin.Num.U32.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .u32_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.U32.to_u8_try")) |ident| { + try low_level_map.put(ident, .u32_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.U32.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .u32_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.U32.to_u16_try")) |ident| { + try low_level_map.put(ident, .u32_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.U32.to_u64")) |ident| { + try low_level_map.put(ident, .u32_to_u64); + } + if (env.common.findIdent("Builtin.Num.U32.to_u128")) |ident| { + try low_level_map.put(ident, .u32_to_u128); + } + if (env.common.findIdent("Builtin.Num.U32.to_f32")) |ident| { + try low_level_map.put(ident, .u32_to_f32); + } + if (env.common.findIdent("Builtin.Num.U32.to_f64")) |ident| { + try low_level_map.put(ident, .u32_to_f64); + } + if (env.common.findIdent("Builtin.Num.U32.to_dec")) |ident| { + try low_level_map.put(ident, .u32_to_dec); + } + + // I32 conversion operations + if (env.common.findIdent("Builtin.Num.I32.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_i8_try")) |ident| { + try low_level_map.put(ident, .i32_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_i16_try")) |ident| { + try low_level_map.put(ident, .i32_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_i64")) |ident| { + try low_level_map.put(ident, .i32_to_i64); + } + if (env.common.findIdent("Builtin.Num.I32.to_i128")) |ident| { + try low_level_map.put(ident, .i32_to_i128); + } + if (env.common.findIdent("Builtin.Num.I32.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_u8_try")) |ident| { + try low_level_map.put(ident, .i32_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_u16_try")) |ident| { + try low_level_map.put(ident, .i32_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_u32_try")) |ident| { + try low_level_map.put(ident, .i32_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_u64_try")) |ident| { + try low_level_map.put(ident, .i32_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .i32_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.I32.to_u128_try")) |ident| { + try low_level_map.put(ident, .i32_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.I32.to_f32")) |ident| { + try low_level_map.put(ident, .i32_to_f32); + } + if (env.common.findIdent("Builtin.Num.I32.to_f64")) |ident| { + try low_level_map.put(ident, .i32_to_f64); + } + if (env.common.findIdent("Builtin.Num.I32.to_dec")) |ident| { + try low_level_map.put(ident, .i32_to_dec); + } + + // U64 conversion operations + if (env.common.findIdent("Builtin.Num.U64.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_i8_try")) |ident| { + try low_level_map.put(ident, .u64_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_i16_try")) |ident| { + try low_level_map.put(ident, .u64_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_i32_try")) |ident| { + try low_level_map.put(ident, .u64_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_i64_try")) |ident| { + try low_level_map.put(ident, .u64_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_i128")) |ident| { + try low_level_map.put(ident, .u64_to_i128); + } + if (env.common.findIdent("Builtin.Num.U64.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_u8_try")) |ident| { + try low_level_map.put(ident, .u64_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_u16_try")) |ident| { + try low_level_map.put(ident, .u64_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .u64_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.U64.to_u32_try")) |ident| { + try low_level_map.put(ident, .u64_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.U64.to_u128")) |ident| { + try low_level_map.put(ident, .u64_to_u128); + } + if (env.common.findIdent("Builtin.Num.U64.to_f32")) |ident| { + try low_level_map.put(ident, .u64_to_f32); + } + if (env.common.findIdent("Builtin.Num.U64.to_f64")) |ident| { + try low_level_map.put(ident, .u64_to_f64); + } + if (env.common.findIdent("Builtin.Num.U64.to_dec")) |ident| { + try low_level_map.put(ident, .u64_to_dec); + } + + // I64 conversion operations + if (env.common.findIdent("Builtin.Num.I64.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_i8_try")) |ident| { + try low_level_map.put(ident, .i64_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_i16_try")) |ident| { + try low_level_map.put(ident, .i64_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_i32_try")) |ident| { + try low_level_map.put(ident, .i64_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_i128")) |ident| { + try low_level_map.put(ident, .i64_to_i128); + } + if (env.common.findIdent("Builtin.Num.I64.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_u8_try")) |ident| { + try low_level_map.put(ident, .i64_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_u16_try")) |ident| { + try low_level_map.put(ident, .i64_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_u32_try")) |ident| { + try low_level_map.put(ident, .i64_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_u64_try")) |ident| { + try low_level_map.put(ident, .i64_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .i64_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.I64.to_u128_try")) |ident| { + try low_level_map.put(ident, .i64_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.I64.to_f32")) |ident| { + try low_level_map.put(ident, .i64_to_f32); + } + if (env.common.findIdent("Builtin.Num.I64.to_f64")) |ident| { + try low_level_map.put(ident, .i64_to_f64); + } + if (env.common.findIdent("Builtin.Num.I64.to_dec")) |ident| { + try low_level_map.put(ident, .i64_to_dec); + } + + // U128 conversion operations + if (env.common.findIdent("Builtin.Num.U128.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_i8_try")) |ident| { + try low_level_map.put(ident, .u128_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_i16_try")) |ident| { + try low_level_map.put(ident, .u128_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_i32_try")) |ident| { + try low_level_map.put(ident, .u128_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_i64_try")) |ident| { + try low_level_map.put(ident, .u128_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_i128_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_i128_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_i128_try")) |ident| { + try low_level_map.put(ident, .u128_to_i128_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_u8_try")) |ident| { + try low_level_map.put(ident, .u128_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_u16_try")) |ident| { + try low_level_map.put(ident, .u128_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_u32_try")) |ident| { + try low_level_map.put(ident, .u128_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .u128_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.U128.to_u64_try")) |ident| { + try low_level_map.put(ident, .u128_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.U128.to_f32")) |ident| { + try low_level_map.put(ident, .u128_to_f32); + } + if (env.common.findIdent("Builtin.Num.U128.to_f64")) |ident| { + try low_level_map.put(ident, .u128_to_f64); + } + if (env.common.findIdent("Builtin.Num.U128.to_dec")) |ident| { + try low_level_map.put(ident, .u128_to_dec); + } + + // I128 conversion operations + if (env.common.findIdent("Builtin.Num.I128.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_i8_try")) |ident| { + try low_level_map.put(ident, .i128_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_i16_try")) |ident| { + try low_level_map.put(ident, .i128_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_i32_try")) |ident| { + try low_level_map.put(ident, .i128_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_i64_try")) |ident| { + try low_level_map.put(ident, .i128_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_u8_try")) |ident| { + try low_level_map.put(ident, .i128_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_u16_try")) |ident| { + try low_level_map.put(ident, .i128_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_u32_try")) |ident| { + try low_level_map.put(ident, .i128_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_u64_try")) |ident| { + try low_level_map.put(ident, .i128_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .i128_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.I128.to_u128_try")) |ident| { + try low_level_map.put(ident, .i128_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.I128.to_f32")) |ident| { + try low_level_map.put(ident, .i128_to_f32); + } + if (env.common.findIdent("Builtin.Num.I128.to_f64")) |ident| { + try low_level_map.put(ident, .i128_to_f64); + } + if (env.common.findIdent("Builtin.Num.I128.to_dec")) |ident| { + try low_level_map.put(ident, .i128_to_dec); + } + // Iterate through all defs and replace matching anno-only defs with low-level implementations + // NOTE: We copy def indices to a separate list first, because operations inside the loop + // may reallocate extra_data, which would invalidate any slice taken from it. const all_defs_slice = env.store.sliceDefs(env.all_defs); var def_indices = std.ArrayList(CIR.Def.Idx).empty; defer def_indices.deinit(gpa); diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index 9fc188f3a1..0623062542 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -295,6 +295,27 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(U16, [OutOfRange]) from_numeral : Numeral -> Try(U16, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : U16 -> I8 + to_i8_try : U16 -> Try(I8, [OutOfRange]) + to_i16_wrap : U16 -> I16 + to_i16_try : U16 -> Try(I16, [OutOfRange]) + to_i32 : U16 -> I32 + to_i64 : U16 -> I64 + to_i128 : U16 -> I128 + + # Conversions to unsigned integers + to_u8_wrap : U16 -> U8 + to_u8_try : U16 -> Try(U8, [OutOfRange]) + to_u32 : U16 -> U32 + to_u64 : U16 -> U64 + to_u128 : U16 -> U128 + + # Conversions to floating point (all safe) + to_f32 : U16 -> F32 + to_f64 : U16 -> F64 + to_dec : U16 -> Dec } I16 :: [].{ @@ -318,6 +339,30 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(I16, [OutOfRange]) from_numeral : Numeral -> Try(I16, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : I16 -> I8 + to_i8_try : I16 -> Try(I8, [OutOfRange]) + to_i32 : I16 -> I32 + to_i64 : I16 -> I64 + to_i128 : I16 -> I128 + + # Conversions to unsigned integers (all lossy for negative values) + to_u8_wrap : I16 -> U8 + to_u8_try : I16 -> Try(U8, [OutOfRange]) + to_u16_wrap : I16 -> U16 + to_u16_try : I16 -> Try(U16, [OutOfRange]) + to_u32_wrap : I16 -> U32 + to_u32_try : I16 -> Try(U32, [OutOfRange]) + to_u64_wrap : I16 -> U64 + to_u64_try : I16 -> Try(U64, [OutOfRange]) + to_u128_wrap : I16 -> U128 + to_u128_try : I16 -> Try(U128, [OutOfRange]) + + # Conversions to floating point (all safe) + to_f32 : I16 -> F32 + to_f64 : I16 -> F64 + to_dec : I16 -> Dec } U32 :: [].{ @@ -338,6 +383,29 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(U32, [OutOfRange]) from_numeral : Numeral -> Try(U32, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : U32 -> I8 + to_i8_try : U32 -> Try(I8, [OutOfRange]) + to_i16_wrap : U32 -> I16 + to_i16_try : U32 -> Try(I16, [OutOfRange]) + to_i32_wrap : U32 -> I32 + to_i32_try : U32 -> Try(I32, [OutOfRange]) + to_i64 : U32 -> I64 + to_i128 : U32 -> I128 + + # Conversions to unsigned integers + to_u8_wrap : U32 -> U8 + to_u8_try : U32 -> Try(U8, [OutOfRange]) + to_u16_wrap : U32 -> U16 + to_u16_try : U32 -> Try(U16, [OutOfRange]) + to_u64 : U32 -> U64 + to_u128 : U32 -> U128 + + # Conversions to floating point (all safe) + to_f32 : U32 -> F32 + to_f64 : U32 -> F64 + to_dec : U32 -> Dec } I32 :: [].{ @@ -361,6 +429,31 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(I32, [OutOfRange]) from_numeral : Numeral -> Try(I32, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : I32 -> I8 + to_i8_try : I32 -> Try(I8, [OutOfRange]) + to_i16_wrap : I32 -> I16 + to_i16_try : I32 -> Try(I16, [OutOfRange]) + to_i64 : I32 -> I64 + to_i128 : I32 -> I128 + + # Conversions to unsigned integers (all lossy for negative values) + to_u8_wrap : I32 -> U8 + to_u8_try : I32 -> Try(U8, [OutOfRange]) + to_u16_wrap : I32 -> U16 + to_u16_try : I32 -> Try(U16, [OutOfRange]) + to_u32_wrap : I32 -> U32 + to_u32_try : I32 -> Try(U32, [OutOfRange]) + to_u64_wrap : I32 -> U64 + to_u64_try : I32 -> Try(U64, [OutOfRange]) + to_u128_wrap : I32 -> U128 + to_u128_try : I32 -> Try(U128, [OutOfRange]) + + # Conversions to floating point (all safe) + to_f32 : I32 -> F32 + to_f64 : I32 -> F64 + to_dec : I32 -> Dec } U64 :: [].{ @@ -381,6 +474,31 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(U64, [OutOfRange]) from_numeral : Numeral -> Try(U64, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : U64 -> I8 + to_i8_try : U64 -> Try(I8, [OutOfRange]) + to_i16_wrap : U64 -> I16 + to_i16_try : U64 -> Try(I16, [OutOfRange]) + to_i32_wrap : U64 -> I32 + to_i32_try : U64 -> Try(I32, [OutOfRange]) + to_i64_wrap : U64 -> I64 + to_i64_try : U64 -> Try(I64, [OutOfRange]) + to_i128 : U64 -> I128 + + # Conversions to unsigned integers + to_u8_wrap : U64 -> U8 + to_u8_try : U64 -> Try(U8, [OutOfRange]) + to_u16_wrap : U64 -> U16 + to_u16_try : U64 -> Try(U16, [OutOfRange]) + to_u32_wrap : U64 -> U32 + to_u32_try : U64 -> Try(U32, [OutOfRange]) + to_u128 : U64 -> U128 + + # Conversions to floating point (all safe) + to_f32 : U64 -> F32 + to_f64 : U64 -> F64 + to_dec : U64 -> Dec } I64 :: [].{ @@ -404,6 +522,32 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(I64, [OutOfRange]) from_numeral : Numeral -> Try(I64, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : I64 -> I8 + to_i8_try : I64 -> Try(I8, [OutOfRange]) + to_i16_wrap : I64 -> I16 + to_i16_try : I64 -> Try(I16, [OutOfRange]) + to_i32_wrap : I64 -> I32 + to_i32_try : I64 -> Try(I32, [OutOfRange]) + to_i128 : I64 -> I128 + + # Conversions to unsigned integers (all lossy for negative values) + to_u8_wrap : I64 -> U8 + to_u8_try : I64 -> Try(U8, [OutOfRange]) + to_u16_wrap : I64 -> U16 + to_u16_try : I64 -> Try(U16, [OutOfRange]) + to_u32_wrap : I64 -> U32 + to_u32_try : I64 -> Try(U32, [OutOfRange]) + to_u64_wrap : I64 -> U64 + to_u64_try : I64 -> Try(U64, [OutOfRange]) + to_u128_wrap : I64 -> U128 + to_u128_try : I64 -> Try(U128, [OutOfRange]) + + # Conversions to floating point (all safe) + to_f32 : I64 -> F32 + to_f64 : I64 -> F64 + to_dec : I64 -> Dec } U128 :: [].{ @@ -424,6 +568,33 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(U128, [OutOfRange]) from_numeral : Numeral -> Try(U128, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : U128 -> I8 + to_i8_try : U128 -> Try(I8, [OutOfRange]) + to_i16_wrap : U128 -> I16 + to_i16_try : U128 -> Try(I16, [OutOfRange]) + to_i32_wrap : U128 -> I32 + to_i32_try : U128 -> Try(I32, [OutOfRange]) + to_i64_wrap : U128 -> I64 + to_i64_try : U128 -> Try(I64, [OutOfRange]) + to_i128_wrap : U128 -> I128 + to_i128_try : U128 -> Try(I128, [OutOfRange]) + + # Conversions to unsigned integers + to_u8_wrap : U128 -> U8 + to_u8_try : U128 -> Try(U8, [OutOfRange]) + to_u16_wrap : U128 -> U16 + to_u16_try : U128 -> Try(U16, [OutOfRange]) + to_u32_wrap : U128 -> U32 + to_u32_try : U128 -> Try(U32, [OutOfRange]) + to_u64_wrap : U128 -> U64 + to_u64_try : U128 -> Try(U64, [OutOfRange]) + + # Conversions to floating point (all safe) + to_f32 : U128 -> F32 + to_f64 : U128 -> F64 + to_dec : U128 -> Dec } I128 :: [].{ @@ -447,6 +618,33 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(I128, [OutOfRange]) from_numeral : Numeral -> Try(I128, [InvalidNumeral(Str)]) + + # Conversions to signed integers + to_i8_wrap : I128 -> I8 + to_i8_try : I128 -> Try(I8, [OutOfRange]) + to_i16_wrap : I128 -> I16 + to_i16_try : I128 -> Try(I16, [OutOfRange]) + to_i32_wrap : I128 -> I32 + to_i32_try : I128 -> Try(I32, [OutOfRange]) + to_i64_wrap : I128 -> I64 + to_i64_try : I128 -> Try(I64, [OutOfRange]) + + # Conversions to unsigned integers (all lossy for negative values) + to_u8_wrap : I128 -> U8 + to_u8_try : I128 -> Try(U8, [OutOfRange]) + to_u16_wrap : I128 -> U16 + to_u16_try : I128 -> Try(U16, [OutOfRange]) + to_u32_wrap : I128 -> U32 + to_u32_try : I128 -> Try(U32, [OutOfRange]) + to_u64_wrap : I128 -> U64 + to_u64_try : I128 -> Try(U64, [OutOfRange]) + to_u128_wrap : I128 -> U128 + to_u128_try : I128 -> Try(U128, [OutOfRange]) + + # Conversions to floating point (all safe) + to_f32 : I128 -> F32 + to_f64 : I128 -> F64 + to_dec : I128 -> Dec } Dec :: [].{ diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index d610521143..05483c21f4 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -538,6 +538,172 @@ pub const Expr = union(enum) { i8_to_f32, // I8 -> F32 (safe) i8_to_f64, // I8 -> F64 (safe) i8_to_dec, // I8 -> Dec (safe) + + // Numeric conversion operations (U16) + u16_to_i8_wrap, // U16 -> I8 (wrapping) + u16_to_i8_try, // U16 -> Try(I8, [OutOfRange]) + u16_to_i16_wrap, // U16 -> I16 (wrapping) + u16_to_i16_try, // U16 -> Try(I16, [OutOfRange]) + u16_to_i32, // U16 -> I32 (safe) + u16_to_i64, // U16 -> I64 (safe) + u16_to_i128, // U16 -> I128 (safe) + u16_to_u8_wrap, // U16 -> U8 (wrapping) + u16_to_u8_try, // U16 -> Try(U8, [OutOfRange]) + u16_to_u32, // U16 -> U32 (safe) + u16_to_u64, // U16 -> U64 (safe) + u16_to_u128, // U16 -> U128 (safe) + u16_to_f32, // U16 -> F32 (safe) + u16_to_f64, // U16 -> F64 (safe) + u16_to_dec, // U16 -> Dec (safe) + + // Numeric conversion operations (I16) + i16_to_i8_wrap, // I16 -> I8 (wrapping) + i16_to_i8_try, // I16 -> Try(I8, [OutOfRange]) + i16_to_i32, // I16 -> I32 (safe) + i16_to_i64, // I16 -> I64 (safe) + i16_to_i128, // I16 -> I128 (safe) + i16_to_u8_wrap, // I16 -> U8 (wrapping) + i16_to_u8_try, // I16 -> Try(U8, [OutOfRange]) + i16_to_u16_wrap, // I16 -> U16 (wrapping) + i16_to_u16_try, // I16 -> Try(U16, [OutOfRange]) + i16_to_u32_wrap, // I16 -> U32 (wrapping) + i16_to_u32_try, // I16 -> Try(U32, [OutOfRange]) + i16_to_u64_wrap, // I16 -> U64 (wrapping) + i16_to_u64_try, // I16 -> Try(U64, [OutOfRange]) + i16_to_u128_wrap, // I16 -> U128 (wrapping) + i16_to_u128_try, // I16 -> Try(U128, [OutOfRange]) + i16_to_f32, // I16 -> F32 (safe) + i16_to_f64, // I16 -> F64 (safe) + i16_to_dec, // I16 -> Dec (safe) + + // Numeric conversion operations (U32) + u32_to_i8_wrap, // U32 -> I8 (wrapping) + u32_to_i8_try, // U32 -> Try(I8, [OutOfRange]) + u32_to_i16_wrap, // U32 -> I16 (wrapping) + u32_to_i16_try, // U32 -> Try(I16, [OutOfRange]) + u32_to_i32_wrap, // U32 -> I32 (wrapping) + u32_to_i32_try, // U32 -> Try(I32, [OutOfRange]) + u32_to_i64, // U32 -> I64 (safe) + u32_to_i128, // U32 -> I128 (safe) + u32_to_u8_wrap, // U32 -> U8 (wrapping) + u32_to_u8_try, // U32 -> Try(U8, [OutOfRange]) + u32_to_u16_wrap, // U32 -> U16 (wrapping) + u32_to_u16_try, // U32 -> Try(U16, [OutOfRange]) + u32_to_u64, // U32 -> U64 (safe) + u32_to_u128, // U32 -> U128 (safe) + u32_to_f32, // U32 -> F32 (safe) + u32_to_f64, // U32 -> F64 (safe) + u32_to_dec, // U32 -> Dec (safe) + + // Numeric conversion operations (I32) + i32_to_i8_wrap, // I32 -> I8 (wrapping) + i32_to_i8_try, // I32 -> Try(I8, [OutOfRange]) + i32_to_i16_wrap, // I32 -> I16 (wrapping) + i32_to_i16_try, // I32 -> Try(I16, [OutOfRange]) + i32_to_i64, // I32 -> I64 (safe) + i32_to_i128, // I32 -> I128 (safe) + i32_to_u8_wrap, // I32 -> U8 (wrapping) + i32_to_u8_try, // I32 -> Try(U8, [OutOfRange]) + i32_to_u16_wrap, // I32 -> U16 (wrapping) + i32_to_u16_try, // I32 -> Try(U16, [OutOfRange]) + i32_to_u32_wrap, // I32 -> U32 (wrapping) + i32_to_u32_try, // I32 -> Try(U32, [OutOfRange]) + i32_to_u64_wrap, // I32 -> U64 (wrapping) + i32_to_u64_try, // I32 -> Try(U64, [OutOfRange]) + i32_to_u128_wrap, // I32 -> U128 (wrapping) + i32_to_u128_try, // I32 -> Try(U128, [OutOfRange]) + i32_to_f32, // I32 -> F32 (safe) + i32_to_f64, // I32 -> F64 (safe) + i32_to_dec, // I32 -> Dec (safe) + + // Numeric conversion operations (U64) + u64_to_i8_wrap, // U64 -> I8 (wrapping) + u64_to_i8_try, // U64 -> Try(I8, [OutOfRange]) + u64_to_i16_wrap, // U64 -> I16 (wrapping) + u64_to_i16_try, // U64 -> Try(I16, [OutOfRange]) + u64_to_i32_wrap, // U64 -> I32 (wrapping) + u64_to_i32_try, // U64 -> Try(I32, [OutOfRange]) + u64_to_i64_wrap, // U64 -> I64 (wrapping) + u64_to_i64_try, // U64 -> Try(I64, [OutOfRange]) + u64_to_i128, // U64 -> I128 (safe) + u64_to_u8_wrap, // U64 -> U8 (wrapping) + u64_to_u8_try, // U64 -> Try(U8, [OutOfRange]) + u64_to_u16_wrap, // U64 -> U16 (wrapping) + u64_to_u16_try, // U64 -> Try(U16, [OutOfRange]) + u64_to_u32_wrap, // U64 -> U32 (wrapping) + u64_to_u32_try, // U64 -> Try(U32, [OutOfRange]) + u64_to_u128, // U64 -> U128 (safe) + u64_to_f32, // U64 -> F32 (safe) + u64_to_f64, // U64 -> F64 (safe) + u64_to_dec, // U64 -> Dec (safe) + + // Numeric conversion operations (I64) + i64_to_i8_wrap, // I64 -> I8 (wrapping) + i64_to_i8_try, // I64 -> Try(I8, [OutOfRange]) + i64_to_i16_wrap, // I64 -> I16 (wrapping) + i64_to_i16_try, // I64 -> Try(I16, [OutOfRange]) + i64_to_i32_wrap, // I64 -> I32 (wrapping) + i64_to_i32_try, // I64 -> Try(I32, [OutOfRange]) + i64_to_i128, // I64 -> I128 (safe) + i64_to_u8_wrap, // I64 -> U8 (wrapping) + i64_to_u8_try, // I64 -> Try(U8, [OutOfRange]) + i64_to_u16_wrap, // I64 -> U16 (wrapping) + i64_to_u16_try, // I64 -> Try(U16, [OutOfRange]) + i64_to_u32_wrap, // I64 -> U32 (wrapping) + i64_to_u32_try, // I64 -> Try(U32, [OutOfRange]) + i64_to_u64_wrap, // I64 -> U64 (wrapping) + i64_to_u64_try, // I64 -> Try(U64, [OutOfRange]) + i64_to_u128_wrap, // I64 -> U128 (wrapping) + i64_to_u128_try, // I64 -> Try(U128, [OutOfRange]) + i64_to_f32, // I64 -> F32 (safe) + i64_to_f64, // I64 -> F64 (safe) + i64_to_dec, // I64 -> Dec (safe) + + // Numeric conversion operations (U128) + u128_to_i8_wrap, // U128 -> I8 (wrapping) + u128_to_i8_try, // U128 -> Try(I8, [OutOfRange]) + u128_to_i16_wrap, // U128 -> I16 (wrapping) + u128_to_i16_try, // U128 -> Try(I16, [OutOfRange]) + u128_to_i32_wrap, // U128 -> I32 (wrapping) + u128_to_i32_try, // U128 -> Try(I32, [OutOfRange]) + u128_to_i64_wrap, // U128 -> I64 (wrapping) + u128_to_i64_try, // U128 -> Try(I64, [OutOfRange]) + u128_to_i128_wrap, // U128 -> I128 (wrapping) + u128_to_i128_try, // U128 -> Try(I128, [OutOfRange]) + u128_to_u8_wrap, // U128 -> U8 (wrapping) + u128_to_u8_try, // U128 -> Try(U8, [OutOfRange]) + u128_to_u16_wrap, // U128 -> U16 (wrapping) + u128_to_u16_try, // U128 -> Try(U16, [OutOfRange]) + u128_to_u32_wrap, // U128 -> U32 (wrapping) + u128_to_u32_try, // U128 -> Try(U32, [OutOfRange]) + u128_to_u64_wrap, // U128 -> U64 (wrapping) + u128_to_u64_try, // U128 -> Try(U64, [OutOfRange]) + u128_to_f32, // U128 -> F32 (safe) + u128_to_f64, // U128 -> F64 (safe) + u128_to_dec, // U128 -> Dec (safe) + + // Numeric conversion operations (I128) + i128_to_i8_wrap, // I128 -> I8 (wrapping) + i128_to_i8_try, // I128 -> Try(I8, [OutOfRange]) + i128_to_i16_wrap, // I128 -> I16 (wrapping) + i128_to_i16_try, // I128 -> Try(I16, [OutOfRange]) + i128_to_i32_wrap, // I128 -> I32 (wrapping) + i128_to_i32_try, // I128 -> Try(I32, [OutOfRange]) + i128_to_i64_wrap, // I128 -> I64 (wrapping) + i128_to_i64_try, // I128 -> Try(I64, [OutOfRange]) + i128_to_u8_wrap, // I128 -> U8 (wrapping) + i128_to_u8_try, // I128 -> Try(U8, [OutOfRange]) + i128_to_u16_wrap, // I128 -> U16 (wrapping) + i128_to_u16_try, // I128 -> Try(U16, [OutOfRange]) + i128_to_u32_wrap, // I128 -> U32 (wrapping) + i128_to_u32_try, // I128 -> Try(U32, [OutOfRange]) + i128_to_u64_wrap, // I128 -> U64 (wrapping) + i128_to_u64_try, // I128 -> Try(U64, [OutOfRange]) + i128_to_u128_wrap, // I128 -> U128 (wrapping) + i128_to_u128_try, // I128 -> Try(U128, [OutOfRange]) + i128_to_f32, // I128 -> F32 (safe) + i128_to_f64, // I128 -> F64 (safe) + i128_to_dec, // I128 -> Dec (safe) }; pub const Idx = enum(u32) { _ }; diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 19d6d15137..81a8922f12 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -4711,6 +4711,172 @@ pub const Interpreter = struct { .i8_to_f32 => return self.intToFloat(i8, f32, args, roc_ops), .i8_to_f64 => return self.intToFloat(i8, f64, args, roc_ops), .i8_to_dec => return self.intToDec(i8, args, roc_ops), + + // U16 conversion operations + .u16_to_i8_wrap => return self.intConvertWrap(u16, i8, args, roc_ops), + .u16_to_i8_try => return self.intConvertTry(u16, i8, args, roc_ops, return_rt_var), + .u16_to_i16_wrap => return self.intConvertWrap(u16, i16, args, roc_ops), + .u16_to_i16_try => return self.intConvertTry(u16, i16, args, roc_ops, return_rt_var), + .u16_to_i32 => return self.intConvert(u16, i32, args, roc_ops), + .u16_to_i64 => return self.intConvert(u16, i64, args, roc_ops), + .u16_to_i128 => return self.intConvert(u16, i128, args, roc_ops), + .u16_to_u8_wrap => return self.intConvertWrap(u16, u8, args, roc_ops), + .u16_to_u8_try => return self.intConvertTry(u16, u8, args, roc_ops, return_rt_var), + .u16_to_u32 => return self.intConvert(u16, u32, args, roc_ops), + .u16_to_u64 => return self.intConvert(u16, u64, args, roc_ops), + .u16_to_u128 => return self.intConvert(u16, u128, args, roc_ops), + .u16_to_f32 => return self.intToFloat(u16, f32, args, roc_ops), + .u16_to_f64 => return self.intToFloat(u16, f64, args, roc_ops), + .u16_to_dec => return self.intToDec(u16, args, roc_ops), + + // I16 conversion operations + .i16_to_i8_wrap => return self.intConvertWrap(i16, i8, args, roc_ops), + .i16_to_i8_try => return self.intConvertTry(i16, i8, args, roc_ops, return_rt_var), + .i16_to_i32 => return self.intConvert(i16, i32, args, roc_ops), + .i16_to_i64 => return self.intConvert(i16, i64, args, roc_ops), + .i16_to_i128 => return self.intConvert(i16, i128, args, roc_ops), + .i16_to_u8_wrap => return self.intConvertWrap(i16, u8, args, roc_ops), + .i16_to_u8_try => return self.intConvertTry(i16, u8, args, roc_ops, return_rt_var), + .i16_to_u16_wrap => return self.intConvertWrap(i16, u16, args, roc_ops), + .i16_to_u16_try => return self.intConvertTry(i16, u16, args, roc_ops, return_rt_var), + .i16_to_u32_wrap => return self.intConvertWrap(i16, u32, args, roc_ops), + .i16_to_u32_try => return self.intConvertTry(i16, u32, args, roc_ops, return_rt_var), + .i16_to_u64_wrap => return self.intConvertWrap(i16, u64, args, roc_ops), + .i16_to_u64_try => return self.intConvertTry(i16, u64, args, roc_ops, return_rt_var), + .i16_to_u128_wrap => return self.intConvertWrap(i16, u128, args, roc_ops), + .i16_to_u128_try => return self.intConvertTry(i16, u128, args, roc_ops, return_rt_var), + .i16_to_f32 => return self.intToFloat(i16, f32, args, roc_ops), + .i16_to_f64 => return self.intToFloat(i16, f64, args, roc_ops), + .i16_to_dec => return self.intToDec(i16, args, roc_ops), + + // U32 conversion operations + .u32_to_i8_wrap => return self.intConvertWrap(u32, i8, args, roc_ops), + .u32_to_i8_try => return self.intConvertTry(u32, i8, args, roc_ops, return_rt_var), + .u32_to_i16_wrap => return self.intConvertWrap(u32, i16, args, roc_ops), + .u32_to_i16_try => return self.intConvertTry(u32, i16, args, roc_ops, return_rt_var), + .u32_to_i32_wrap => return self.intConvertWrap(u32, i32, args, roc_ops), + .u32_to_i32_try => return self.intConvertTry(u32, i32, args, roc_ops, return_rt_var), + .u32_to_i64 => return self.intConvert(u32, i64, args, roc_ops), + .u32_to_i128 => return self.intConvert(u32, i128, args, roc_ops), + .u32_to_u8_wrap => return self.intConvertWrap(u32, u8, args, roc_ops), + .u32_to_u8_try => return self.intConvertTry(u32, u8, args, roc_ops, return_rt_var), + .u32_to_u16_wrap => return self.intConvertWrap(u32, u16, args, roc_ops), + .u32_to_u16_try => return self.intConvertTry(u32, u16, args, roc_ops, return_rt_var), + .u32_to_u64 => return self.intConvert(u32, u64, args, roc_ops), + .u32_to_u128 => return self.intConvert(u32, u128, args, roc_ops), + .u32_to_f32 => return self.intToFloat(u32, f32, args, roc_ops), + .u32_to_f64 => return self.intToFloat(u32, f64, args, roc_ops), + .u32_to_dec => return self.intToDec(u32, args, roc_ops), + + // I32 conversion operations + .i32_to_i8_wrap => return self.intConvertWrap(i32, i8, args, roc_ops), + .i32_to_i8_try => return self.intConvertTry(i32, i8, args, roc_ops, return_rt_var), + .i32_to_i16_wrap => return self.intConvertWrap(i32, i16, args, roc_ops), + .i32_to_i16_try => return self.intConvertTry(i32, i16, args, roc_ops, return_rt_var), + .i32_to_i64 => return self.intConvert(i32, i64, args, roc_ops), + .i32_to_i128 => return self.intConvert(i32, i128, args, roc_ops), + .i32_to_u8_wrap => return self.intConvertWrap(i32, u8, args, roc_ops), + .i32_to_u8_try => return self.intConvertTry(i32, u8, args, roc_ops, return_rt_var), + .i32_to_u16_wrap => return self.intConvertWrap(i32, u16, args, roc_ops), + .i32_to_u16_try => return self.intConvertTry(i32, u16, args, roc_ops, return_rt_var), + .i32_to_u32_wrap => return self.intConvertWrap(i32, u32, args, roc_ops), + .i32_to_u32_try => return self.intConvertTry(i32, u32, args, roc_ops, return_rt_var), + .i32_to_u64_wrap => return self.intConvertWrap(i32, u64, args, roc_ops), + .i32_to_u64_try => return self.intConvertTry(i32, u64, args, roc_ops, return_rt_var), + .i32_to_u128_wrap => return self.intConvertWrap(i32, u128, args, roc_ops), + .i32_to_u128_try => return self.intConvertTry(i32, u128, args, roc_ops, return_rt_var), + .i32_to_f32 => return self.intToFloat(i32, f32, args, roc_ops), + .i32_to_f64 => return self.intToFloat(i32, f64, args, roc_ops), + .i32_to_dec => return self.intToDec(i32, args, roc_ops), + + // U64 conversion operations + .u64_to_i8_wrap => return self.intConvertWrap(u64, i8, args, roc_ops), + .u64_to_i8_try => return self.intConvertTry(u64, i8, args, roc_ops, return_rt_var), + .u64_to_i16_wrap => return self.intConvertWrap(u64, i16, args, roc_ops), + .u64_to_i16_try => return self.intConvertTry(u64, i16, args, roc_ops, return_rt_var), + .u64_to_i32_wrap => return self.intConvertWrap(u64, i32, args, roc_ops), + .u64_to_i32_try => return self.intConvertTry(u64, i32, args, roc_ops, return_rt_var), + .u64_to_i64_wrap => return self.intConvertWrap(u64, i64, args, roc_ops), + .u64_to_i64_try => return self.intConvertTry(u64, i64, args, roc_ops, return_rt_var), + .u64_to_i128 => return self.intConvert(u64, i128, args, roc_ops), + .u64_to_u8_wrap => return self.intConvertWrap(u64, u8, args, roc_ops), + .u64_to_u8_try => return self.intConvertTry(u64, u8, args, roc_ops, return_rt_var), + .u64_to_u16_wrap => return self.intConvertWrap(u64, u16, args, roc_ops), + .u64_to_u16_try => return self.intConvertTry(u64, u16, args, roc_ops, return_rt_var), + .u64_to_u32_wrap => return self.intConvertWrap(u64, u32, args, roc_ops), + .u64_to_u32_try => return self.intConvertTry(u64, u32, args, roc_ops, return_rt_var), + .u64_to_u128 => return self.intConvert(u64, u128, args, roc_ops), + .u64_to_f32 => return self.intToFloat(u64, f32, args, roc_ops), + .u64_to_f64 => return self.intToFloat(u64, f64, args, roc_ops), + .u64_to_dec => return self.intToDec(u64, args, roc_ops), + + // I64 conversion operations + .i64_to_i8_wrap => return self.intConvertWrap(i64, i8, args, roc_ops), + .i64_to_i8_try => return self.intConvertTry(i64, i8, args, roc_ops, return_rt_var), + .i64_to_i16_wrap => return self.intConvertWrap(i64, i16, args, roc_ops), + .i64_to_i16_try => return self.intConvertTry(i64, i16, args, roc_ops, return_rt_var), + .i64_to_i32_wrap => return self.intConvertWrap(i64, i32, args, roc_ops), + .i64_to_i32_try => return self.intConvertTry(i64, i32, args, roc_ops, return_rt_var), + .i64_to_i128 => return self.intConvert(i64, i128, args, roc_ops), + .i64_to_u8_wrap => return self.intConvertWrap(i64, u8, args, roc_ops), + .i64_to_u8_try => return self.intConvertTry(i64, u8, args, roc_ops, return_rt_var), + .i64_to_u16_wrap => return self.intConvertWrap(i64, u16, args, roc_ops), + .i64_to_u16_try => return self.intConvertTry(i64, u16, args, roc_ops, return_rt_var), + .i64_to_u32_wrap => return self.intConvertWrap(i64, u32, args, roc_ops), + .i64_to_u32_try => return self.intConvertTry(i64, u32, args, roc_ops, return_rt_var), + .i64_to_u64_wrap => return self.intConvertWrap(i64, u64, args, roc_ops), + .i64_to_u64_try => return self.intConvertTry(i64, u64, args, roc_ops, return_rt_var), + .i64_to_u128_wrap => return self.intConvertWrap(i64, u128, args, roc_ops), + .i64_to_u128_try => return self.intConvertTry(i64, u128, args, roc_ops, return_rt_var), + .i64_to_f32 => return self.intToFloat(i64, f32, args, roc_ops), + .i64_to_f64 => return self.intToFloat(i64, f64, args, roc_ops), + .i64_to_dec => return self.intToDec(i64, args, roc_ops), + + // U128 conversion operations + .u128_to_i8_wrap => return self.intConvertWrap(u128, i8, args, roc_ops), + .u128_to_i8_try => return self.intConvertTry(u128, i8, args, roc_ops, return_rt_var), + .u128_to_i16_wrap => return self.intConvertWrap(u128, i16, args, roc_ops), + .u128_to_i16_try => return self.intConvertTry(u128, i16, args, roc_ops, return_rt_var), + .u128_to_i32_wrap => return self.intConvertWrap(u128, i32, args, roc_ops), + .u128_to_i32_try => return self.intConvertTry(u128, i32, args, roc_ops, return_rt_var), + .u128_to_i64_wrap => return self.intConvertWrap(u128, i64, args, roc_ops), + .u128_to_i64_try => return self.intConvertTry(u128, i64, args, roc_ops, return_rt_var), + .u128_to_i128_wrap => return self.intConvertWrap(u128, i128, args, roc_ops), + .u128_to_i128_try => return self.intConvertTry(u128, i128, args, roc_ops, return_rt_var), + .u128_to_u8_wrap => return self.intConvertWrap(u128, u8, args, roc_ops), + .u128_to_u8_try => return self.intConvertTry(u128, u8, args, roc_ops, return_rt_var), + .u128_to_u16_wrap => return self.intConvertWrap(u128, u16, args, roc_ops), + .u128_to_u16_try => return self.intConvertTry(u128, u16, args, roc_ops, return_rt_var), + .u128_to_u32_wrap => return self.intConvertWrap(u128, u32, args, roc_ops), + .u128_to_u32_try => return self.intConvertTry(u128, u32, args, roc_ops, return_rt_var), + .u128_to_u64_wrap => return self.intConvertWrap(u128, u64, args, roc_ops), + .u128_to_u64_try => return self.intConvertTry(u128, u64, args, roc_ops, return_rt_var), + .u128_to_f32 => return self.intToFloat(u128, f32, args, roc_ops), + .u128_to_f64 => return self.intToFloat(u128, f64, args, roc_ops), + .u128_to_dec => return self.intToDec(u128, args, roc_ops), + + // I128 conversion operations + .i128_to_i8_wrap => return self.intConvertWrap(i128, i8, args, roc_ops), + .i128_to_i8_try => return self.intConvertTry(i128, i8, args, roc_ops, return_rt_var), + .i128_to_i16_wrap => return self.intConvertWrap(i128, i16, args, roc_ops), + .i128_to_i16_try => return self.intConvertTry(i128, i16, args, roc_ops, return_rt_var), + .i128_to_i32_wrap => return self.intConvertWrap(i128, i32, args, roc_ops), + .i128_to_i32_try => return self.intConvertTry(i128, i32, args, roc_ops, return_rt_var), + .i128_to_i64_wrap => return self.intConvertWrap(i128, i64, args, roc_ops), + .i128_to_i64_try => return self.intConvertTry(i128, i64, args, roc_ops, return_rt_var), + .i128_to_u8_wrap => return self.intConvertWrap(i128, u8, args, roc_ops), + .i128_to_u8_try => return self.intConvertTry(i128, u8, args, roc_ops, return_rt_var), + .i128_to_u16_wrap => return self.intConvertWrap(i128, u16, args, roc_ops), + .i128_to_u16_try => return self.intConvertTry(i128, u16, args, roc_ops, return_rt_var), + .i128_to_u32_wrap => return self.intConvertWrap(i128, u32, args, roc_ops), + .i128_to_u32_try => return self.intConvertTry(i128, u32, args, roc_ops, return_rt_var), + .i128_to_u64_wrap => return self.intConvertWrap(i128, u64, args, roc_ops), + .i128_to_u64_try => return self.intConvertTry(i128, u64, args, roc_ops, return_rt_var), + .i128_to_u128_wrap => return self.intConvertWrap(i128, u128, args, roc_ops), + .i128_to_u128_try => return self.intConvertTry(i128, u128, args, roc_ops, return_rt_var), + .i128_to_f32 => return self.intToFloat(i128, f32, args, roc_ops), + .i128_to_f64 => return self.intToFloat(i128, f64, args, roc_ops), + .i128_to_dec => return self.intToDec(i128, args, roc_ops), } } @@ -4976,7 +5142,10 @@ pub const Interpreter = struct { std.debug.assert(int_arg.ptr != null); const from_value: From = @as(*const From, @ptrCast(@alignCast(int_arg.ptr.?))).*; - const dec_value = RocDec{ .num = @as(i128, from_value) * RocDec.one_point_zero_i128 }; + // For unsigned types that can't fit in i128, use @intCast which will panic at runtime + // if the value is too large (which would overflow Dec's range anyway) + const signed_value: i128 = @intCast(from_value); + const dec_value = RocDec{ .num = signed_value * RocDec.one_point_zero_i128 }; const dec_layout = Layout.frac(.dec); var out = try self.pushRaw(dec_layout, 0); From 71b072b81dcf82a98878da1774751d86c84f4574 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Nov 2025 17:14:22 -0500 Subject: [PATCH 2/5] Add F32 and F64 --- src/build/builtin_compiler/main.zig | 133 +++++++++++ src/build/roc/Builtin.roc | 55 +++++ src/canonicalize/Expression.zig | 47 ++++ src/eval/interpreter.zig | 336 ++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+) diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index 8fe20d4e84..311b146658 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -920,6 +920,139 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { try low_level_map.put(ident, .i128_to_dec); } + // F32 conversion operations + if (env.common.findIdent("Builtin.Num.F32.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_i8_try")) |ident| { + try low_level_map.put(ident, .f32_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_i16_try")) |ident| { + try low_level_map.put(ident, .f32_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_i32_try")) |ident| { + try low_level_map.put(ident, .f32_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_i64_try")) |ident| { + try low_level_map.put(ident, .f32_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_i128_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_i128_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_i128_try")) |ident| { + try low_level_map.put(ident, .f32_to_i128_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_u8_try")) |ident| { + try low_level_map.put(ident, .f32_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_u16_try")) |ident| { + try low_level_map.put(ident, .f32_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_u32_try")) |ident| { + try low_level_map.put(ident, .f32_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_u64_try")) |ident| { + try low_level_map.put(ident, .f32_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .f32_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.F32.to_u128_try")) |ident| { + try low_level_map.put(ident, .f32_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.F32.to_f64")) |ident| { + try low_level_map.put(ident, .f32_to_f64); + } + + // F64 conversion operations + if (env.common.findIdent("Builtin.Num.F64.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_i8_try")) |ident| { + try low_level_map.put(ident, .f64_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_i16_try")) |ident| { + try low_level_map.put(ident, .f64_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_i32_try")) |ident| { + try low_level_map.put(ident, .f64_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_i64_try")) |ident| { + try low_level_map.put(ident, .f64_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_i128_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_i128_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_i128_try")) |ident| { + try low_level_map.put(ident, .f64_to_i128_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_u8_try")) |ident| { + try low_level_map.put(ident, .f64_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_u16_try")) |ident| { + try low_level_map.put(ident, .f64_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_u32_try")) |ident| { + try low_level_map.put(ident, .f64_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_u64_try")) |ident| { + try low_level_map.put(ident, .f64_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_u128_try")) |ident| { + try low_level_map.put(ident, .f64_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.F64.to_f32_wrap")) |ident| { + try low_level_map.put(ident, .f64_to_f32_wrap); + } + if (env.common.findIdent("Builtin.Num.F64.to_f32_try")) |ident| { + try low_level_map.put(ident, .f64_to_f32_try); + } + // Iterate through all defs and replace matching anno-only defs with low-level implementations // NOTE: We copy def indices to a separate list first, because operations inside the loop // may reallocate extra_data, which would invalidate any slice taken from it. diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index 0623062542..baff6fedb8 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -692,6 +692,33 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(F32, [OutOfRange]) from_dec_digits : (List(U8), List(U8)) -> Try(F32, [OutOfRange]) from_numeral : Numeral -> Try(F32, [InvalidNumeral(Str)]) + + # Conversions to signed integers (all lossy - truncation + range check) + to_i8_wrap : F32 -> I8 + to_i8_try : F32 -> Try(I8, [OutOfRange]) + to_i16_wrap : F32 -> I16 + to_i16_try : F32 -> Try(I16, [OutOfRange]) + to_i32_wrap : F32 -> I32 + to_i32_try : F32 -> Try(I32, [OutOfRange]) + to_i64_wrap : F32 -> I64 + to_i64_try : F32 -> Try(I64, [OutOfRange]) + to_i128_wrap : F32 -> I128 + to_i128_try : F32 -> Try(I128, [OutOfRange]) + + # Conversions to unsigned integers (all lossy - truncation + range check) + to_u8_wrap : F32 -> U8 + to_u8_try : F32 -> Try(U8, [OutOfRange]) + to_u16_wrap : F32 -> U16 + to_u16_try : F32 -> Try(U16, [OutOfRange]) + to_u32_wrap : F32 -> U32 + to_u32_try : F32 -> Try(U32, [OutOfRange]) + to_u64_wrap : F32 -> U64 + to_u64_try : F32 -> Try(U64, [OutOfRange]) + to_u128_wrap : F32 -> U128 + to_u128_try : F32 -> Try(U128, [OutOfRange]) + + # Conversion to F64 (safe widening) + to_f64 : F32 -> F64 } F64 :: [].{ @@ -715,6 +742,34 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(F64, [OutOfRange]) from_dec_digits : (List(U8), List(U8)) -> Try(F64, [OutOfRange]) from_numeral : Numeral -> Try(F64, [InvalidNumeral(Str)]) + + # Conversions to signed integers (all lossy - truncation + range check) + to_i8_wrap : F64 -> I8 + to_i8_try : F64 -> Try(I8, [OutOfRange]) + to_i16_wrap : F64 -> I16 + to_i16_try : F64 -> Try(I16, [OutOfRange]) + to_i32_wrap : F64 -> I32 + to_i32_try : F64 -> Try(I32, [OutOfRange]) + to_i64_wrap : F64 -> I64 + to_i64_try : F64 -> Try(I64, [OutOfRange]) + to_i128_wrap : F64 -> I128 + to_i128_try : F64 -> Try(I128, [OutOfRange]) + + # Conversions to unsigned integers (all lossy - truncation + range check) + to_u8_wrap : F64 -> U8 + to_u8_try : F64 -> Try(U8, [OutOfRange]) + to_u16_wrap : F64 -> U16 + to_u16_try : F64 -> Try(U16, [OutOfRange]) + to_u32_wrap : F64 -> U32 + to_u32_try : F64 -> Try(U32, [OutOfRange]) + to_u64_wrap : F64 -> U64 + to_u64_try : F64 -> Try(U64, [OutOfRange]) + to_u128_wrap : F64 -> U128 + to_u128_try : F64 -> Try(U128, [OutOfRange]) + + # Conversion to F32 (lossy narrowing) + to_f32_wrap : F64 -> F32 + to_f32_try : F64 -> Try(F32, [OutOfRange]) } } } diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index 05483c21f4..a8034b7455 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -704,6 +704,53 @@ pub const Expr = union(enum) { i128_to_f32, // I128 -> F32 (safe) i128_to_f64, // I128 -> F64 (safe) i128_to_dec, // I128 -> Dec (safe) + + // Numeric conversion operations (F32) + f32_to_i8_wrap, // F32 -> I8 (wrapping) + f32_to_i8_try, // F32 -> Try(I8, [OutOfRange]) + f32_to_i16_wrap, // F32 -> I16 (wrapping) + f32_to_i16_try, // F32 -> Try(I16, [OutOfRange]) + f32_to_i32_wrap, // F32 -> I32 (wrapping) + f32_to_i32_try, // F32 -> Try(I32, [OutOfRange]) + f32_to_i64_wrap, // F32 -> I64 (wrapping) + f32_to_i64_try, // F32 -> Try(I64, [OutOfRange]) + f32_to_i128_wrap, // F32 -> I128 (wrapping) + f32_to_i128_try, // F32 -> Try(I128, [OutOfRange]) + f32_to_u8_wrap, // F32 -> U8 (wrapping) + f32_to_u8_try, // F32 -> Try(U8, [OutOfRange]) + f32_to_u16_wrap, // F32 -> U16 (wrapping) + f32_to_u16_try, // F32 -> Try(U16, [OutOfRange]) + f32_to_u32_wrap, // F32 -> U32 (wrapping) + f32_to_u32_try, // F32 -> Try(U32, [OutOfRange]) + f32_to_u64_wrap, // F32 -> U64 (wrapping) + f32_to_u64_try, // F32 -> Try(U64, [OutOfRange]) + f32_to_u128_wrap, // F32 -> U128 (wrapping) + f32_to_u128_try, // F32 -> Try(U128, [OutOfRange]) + f32_to_f64, // F32 -> F64 (safe widening) + + // Numeric conversion operations (F64) + f64_to_i8_wrap, // F64 -> I8 (wrapping) + f64_to_i8_try, // F64 -> Try(I8, [OutOfRange]) + f64_to_i16_wrap, // F64 -> I16 (wrapping) + f64_to_i16_try, // F64 -> Try(I16, [OutOfRange]) + f64_to_i32_wrap, // F64 -> I32 (wrapping) + f64_to_i32_try, // F64 -> Try(I32, [OutOfRange]) + f64_to_i64_wrap, // F64 -> I64 (wrapping) + f64_to_i64_try, // F64 -> Try(I64, [OutOfRange]) + f64_to_i128_wrap, // F64 -> I128 (wrapping) + f64_to_i128_try, // F64 -> Try(I128, [OutOfRange]) + f64_to_u8_wrap, // F64 -> U8 (wrapping) + f64_to_u8_try, // F64 -> Try(U8, [OutOfRange]) + f64_to_u16_wrap, // F64 -> U16 (wrapping) + f64_to_u16_try, // F64 -> Try(U16, [OutOfRange]) + f64_to_u32_wrap, // F64 -> U32 (wrapping) + f64_to_u32_try, // F64 -> Try(U32, [OutOfRange]) + f64_to_u64_wrap, // F64 -> U64 (wrapping) + f64_to_u64_try, // F64 -> Try(U64, [OutOfRange]) + f64_to_u128_wrap, // F64 -> U128 (wrapping) + f64_to_u128_try, // F64 -> Try(U128, [OutOfRange]) + f64_to_f32_wrap, // F64 -> F32 (lossy narrowing) + f64_to_f32_try, // F64 -> Try(F32, [OutOfRange]) }; pub const Idx = enum(u32) { _ }; diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 81a8922f12..96d26854cf 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -4877,6 +4877,53 @@ pub const Interpreter = struct { .i128_to_f32 => return self.intToFloat(i128, f32, args, roc_ops), .i128_to_f64 => return self.intToFloat(i128, f64, args, roc_ops), .i128_to_dec => return self.intToDec(i128, args, roc_ops), + + // F32 conversion operations + .f32_to_i8_wrap => return self.floatToIntWrap(f32, i8, args, roc_ops), + .f32_to_i8_try => return self.floatToIntTry(f32, i8, args, roc_ops, return_rt_var), + .f32_to_i16_wrap => return self.floatToIntWrap(f32, i16, args, roc_ops), + .f32_to_i16_try => return self.floatToIntTry(f32, i16, args, roc_ops, return_rt_var), + .f32_to_i32_wrap => return self.floatToIntWrap(f32, i32, args, roc_ops), + .f32_to_i32_try => return self.floatToIntTry(f32, i32, args, roc_ops, return_rt_var), + .f32_to_i64_wrap => return self.floatToIntWrap(f32, i64, args, roc_ops), + .f32_to_i64_try => return self.floatToIntTry(f32, i64, args, roc_ops, return_rt_var), + .f32_to_i128_wrap => return self.floatToIntWrap(f32, i128, args, roc_ops), + .f32_to_i128_try => return self.floatToIntTry(f32, i128, args, roc_ops, return_rt_var), + .f32_to_u8_wrap => return self.floatToIntWrap(f32, u8, args, roc_ops), + .f32_to_u8_try => return self.floatToIntTry(f32, u8, args, roc_ops, return_rt_var), + .f32_to_u16_wrap => return self.floatToIntWrap(f32, u16, args, roc_ops), + .f32_to_u16_try => return self.floatToIntTry(f32, u16, args, roc_ops, return_rt_var), + .f32_to_u32_wrap => return self.floatToIntWrap(f32, u32, args, roc_ops), + .f32_to_u32_try => return self.floatToIntTry(f32, u32, args, roc_ops, return_rt_var), + .f32_to_u64_wrap => return self.floatToIntWrap(f32, u64, args, roc_ops), + .f32_to_u64_try => return self.floatToIntTry(f32, u64, args, roc_ops, return_rt_var), + .f32_to_u128_wrap => return self.floatToIntWrap(f32, u128, args, roc_ops), + .f32_to_u128_try => return self.floatToIntTry(f32, u128, args, roc_ops, return_rt_var), + .f32_to_f64 => return self.floatWiden(f32, f64, args, roc_ops), + + // F64 conversion operations + .f64_to_i8_wrap => return self.floatToIntWrap(f64, i8, args, roc_ops), + .f64_to_i8_try => return self.floatToIntTry(f64, i8, args, roc_ops, return_rt_var), + .f64_to_i16_wrap => return self.floatToIntWrap(f64, i16, args, roc_ops), + .f64_to_i16_try => return self.floatToIntTry(f64, i16, args, roc_ops, return_rt_var), + .f64_to_i32_wrap => return self.floatToIntWrap(f64, i32, args, roc_ops), + .f64_to_i32_try => return self.floatToIntTry(f64, i32, args, roc_ops, return_rt_var), + .f64_to_i64_wrap => return self.floatToIntWrap(f64, i64, args, roc_ops), + .f64_to_i64_try => return self.floatToIntTry(f64, i64, args, roc_ops, return_rt_var), + .f64_to_i128_wrap => return self.floatToIntWrap(f64, i128, args, roc_ops), + .f64_to_i128_try => return self.floatToIntTry(f64, i128, args, roc_ops, return_rt_var), + .f64_to_u8_wrap => return self.floatToIntWrap(f64, u8, args, roc_ops), + .f64_to_u8_try => return self.floatToIntTry(f64, u8, args, roc_ops, return_rt_var), + .f64_to_u16_wrap => return self.floatToIntWrap(f64, u16, args, roc_ops), + .f64_to_u16_try => return self.floatToIntTry(f64, u16, args, roc_ops, return_rt_var), + .f64_to_u32_wrap => return self.floatToIntWrap(f64, u32, args, roc_ops), + .f64_to_u32_try => return self.floatToIntTry(f64, u32, args, roc_ops, return_rt_var), + .f64_to_u64_wrap => return self.floatToIntWrap(f64, u64, args, roc_ops), + .f64_to_u64_try => return self.floatToIntTry(f64, u64, args, roc_ops, return_rt_var), + .f64_to_u128_wrap => return self.floatToIntWrap(f64, u128, args, roc_ops), + .f64_to_u128_try => return self.floatToIntTry(f64, u128, args, roc_ops, return_rt_var), + .f64_to_f32_wrap => return self.floatNarrowWrap(f64, f32, args, roc_ops), + .f64_to_f32_try => return self.floatNarrowTry(f64, f32, args, roc_ops, return_rt_var), } } @@ -5155,6 +5202,295 @@ pub const Interpreter = struct { return out; } + /// Helper for wrapping float to integer conversions (truncates toward zero) + fn floatToIntWrap(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const float_arg = args[0]; + std.debug.assert(float_arg.ptr != null); + + const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; + + // For wrapping conversion, we need to handle out-of-range floats + // Zig's @intFromFloat will panic on out-of-range, so we need to clamp or wrap + const to_value: To = blk: { + // Check for NaN - convert to 0 + if (std.math.isNan(from_value)) { + break :blk 0; + } + // Check for infinity or out of range + const min_float: From = @floatFromInt(std.math.minInt(To)); + const max_float: From = @floatFromInt(std.math.maxInt(To)); + if (from_value < min_float) { + break :blk std.math.minInt(To); + } + if (from_value > max_float) { + break :blk std.math.maxInt(To); + } + // Truncate toward zero + break :blk @intFromFloat(from_value); + }; + + const to_layout = Layout.int(comptime intTypeFromZigType(To)); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*To, @ptrCast(@alignCast(out.ptr.?))).* = to_value; + out.is_initialized = true; + return out; + } + + /// Helper for try float to integer conversions (returns Try(To, [OutOfRange])) + fn floatToIntTry(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const float_arg = args[0]; + std.debug.assert(float_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const result_layout = try self.getRuntimeLayout(result_rt_var); + + const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; + + // Check if conversion would succeed + const min_float: From = @floatFromInt(std.math.minInt(To)); + const max_float: From = @floatFromInt(std.math.maxInt(To)); + const in_range = !std.math.isNan(from_value) and from_value >= min_float and from_value <= max_float; + + // Resolve the Try type to get Ok's payload type + const resolved = self.resolveBaseVar(result_rt_var); + std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); + + // Find tag indices for Ok and Err + var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); + defer tag_list.deinit(); + try self.appendUnionTags(result_rt_var, &tag_list); + + var ok_index: ?usize = null; + var err_index: ?usize = null; + + const ok_ident = self.env.idents.ok; + const err_ident = self.env.idents.err; + + for (tag_list.items, 0..) |tag_info, i| { + if (tag_info.name == ok_ident) { + ok_index = i; + } else if (tag_info.name == err_ident) { + err_index = i; + } + } + + // Construct the result tag union + if (result_layout.tag == .scalar) { + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try out.setInt(@intCast(tag_idx)); + out.is_initialized = true; + return out; + } else if (result_layout.tag == .record) { + var dest = try self.pushRaw(result_layout, 0); + var acc = try dest.asRecord(&self.runtime_layout_store); + const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; + const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; + + const tag_field = try acc.getFieldByIndex(tag_field_idx); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try acc.getFieldByIndex(payload_field_idx); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + const to_value: To = @intFromFloat(from_value); + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + } + } + return dest; + } else if (result_layout.tag == .tuple) { + var dest = try self.pushRaw(result_layout, 0); + var result_acc = try dest.asTuple(&self.runtime_layout_store); + + const tag_field = try result_acc.getElement(1); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try result_acc.getElement(0); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + const to_value: To = @intFromFloat(from_value); + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + } + } + return dest; + } else { + unreachable; + } + } + + /// Helper for float widening conversion (F32 -> F64) + fn floatWiden(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const float_arg = args[0]; + std.debug.assert(float_arg.ptr != null); + + const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; + const to_value: To = @floatCast(from_value); + + const to_layout = Layout.frac(comptime fracTypeFromZigType(To)); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*To, @ptrCast(@alignCast(out.ptr.?))).* = to_value; + out.is_initialized = true; + return out; + } + + /// Helper for wrapping float narrowing conversion (F64 -> F32) + fn floatNarrowWrap(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const float_arg = args[0]; + std.debug.assert(float_arg.ptr != null); + + const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; + // @floatCast handles overflow by producing infinity + const to_value: To = @floatCast(from_value); + + const to_layout = Layout.frac(comptime fracTypeFromZigType(To)); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*To, @ptrCast(@alignCast(out.ptr.?))).* = to_value; + out.is_initialized = true; + return out; + } + + /// Helper for try float narrowing conversion (F64 -> F32) + fn floatNarrowTry(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const float_arg = args[0]; + std.debug.assert(float_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const result_layout = try self.getRuntimeLayout(result_rt_var); + + const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; + + // Check if conversion would produce infinity from a finite value + const to_value: To = @floatCast(from_value); + const in_range = !std.math.isInf(to_value) or std.math.isInf(from_value); + + // Resolve the Try type to get Ok's payload type + const resolved = self.resolveBaseVar(result_rt_var); + std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); + + // Find tag indices for Ok and Err + var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); + defer tag_list.deinit(); + try self.appendUnionTags(result_rt_var, &tag_list); + + var ok_index: ?usize = null; + var err_index: ?usize = null; + + const ok_ident = self.env.idents.ok; + const err_ident = self.env.idents.err; + + for (tag_list.items, 0..) |tag_info, i| { + if (tag_info.name == ok_ident) { + ok_index = i; + } else if (tag_info.name == err_ident) { + err_index = i; + } + } + + // Construct the result tag union + if (result_layout.tag == .scalar) { + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try out.setInt(@intCast(tag_idx)); + out.is_initialized = true; + return out; + } else if (result_layout.tag == .record) { + var dest = try self.pushRaw(result_layout, 0); + var acc = try dest.asRecord(&self.runtime_layout_store); + const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; + const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; + + const tag_field = try acc.getFieldByIndex(tag_field_idx); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try acc.getFieldByIndex(payload_field_idx); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + } + } + return dest; + } else if (result_layout.tag == .tuple) { + var dest = try self.pushRaw(result_layout, 0); + var result_acc = try dest.asTuple(&self.runtime_layout_store); + + const tag_field = try result_acc.getElement(1); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try result_acc.getElement(0); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + } + } + return dest; + } else { + unreachable; + } + } + /// Convert Zig integer type to types.Int.Precision fn intTypeFromZigType(comptime T: type) types.Int.Precision { return switch (T) { From 5cb02b97172f6365b832151762072e4c23ae7e3d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Nov 2025 17:26:17 -0500 Subject: [PATCH 3/5] Dec conversion functions --- src/build/builtin_compiler/main.zig | 71 +++++++ src/build/roc/Builtin.roc | 29 +++ src/builtins/dec.zig | 42 ++++ src/canonicalize/Expression.zig | 25 +++ src/eval/interpreter.zig | 302 +++++++++++++++++++++++++++- 5 files changed, 468 insertions(+), 1 deletion(-) diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index 311b146658..80b9a6e854 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -1053,6 +1053,77 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { try low_level_map.put(ident, .f64_to_f32_try); } + // Dec conversion functions + if (env.common.findIdent("Builtin.Num.Dec.to_i8_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_i8_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i8_try")) |ident| { + try low_level_map.put(ident, .dec_to_i8_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i16_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_i16_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i16_try")) |ident| { + try low_level_map.put(ident, .dec_to_i16_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i32_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_i32_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i32_try")) |ident| { + try low_level_map.put(ident, .dec_to_i32_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i64_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_i64_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i64_try")) |ident| { + try low_level_map.put(ident, .dec_to_i64_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i128_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_i128_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_i128_try")) |ident| { + try low_level_map.put(ident, .dec_to_i128_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u8_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_u8_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u8_try")) |ident| { + try low_level_map.put(ident, .dec_to_u8_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u16_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_u16_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u16_try")) |ident| { + try low_level_map.put(ident, .dec_to_u16_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u32_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_u32_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u32_try")) |ident| { + try low_level_map.put(ident, .dec_to_u32_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u64_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_u64_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u64_try")) |ident| { + try low_level_map.put(ident, .dec_to_u64_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u128_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_u128_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_u128_try")) |ident| { + try low_level_map.put(ident, .dec_to_u128_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_f32_wrap")) |ident| { + try low_level_map.put(ident, .dec_to_f32_wrap); + } + if (env.common.findIdent("Builtin.Num.Dec.to_f32_try")) |ident| { + try low_level_map.put(ident, .dec_to_f32_try); + } + if (env.common.findIdent("Builtin.Num.Dec.to_f64")) |ident| { + try low_level_map.put(ident, .dec_to_f64); + } + // Iterate through all defs and replace matching anno-only defs with low-level implementations // NOTE: We copy def indices to a separate list first, because operations inside the loop // may reallocate extra_data, which would invalidate any slice taken from it. diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index baff6fedb8..2f8518fefe 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -669,6 +669,35 @@ Builtin :: [].{ from_int_digits : List(U8) -> Try(Dec, [OutOfRange]) from_dec_digits : (List(U8), List(U8)) -> Try(Dec, [OutOfRange]) from_numeral : Numeral -> Try(Dec, [InvalidNumeral(Str)]) + + # Conversions to signed integers (all lossy - truncates fractional part) + to_i8_wrap : Dec -> I8 + to_i8_try : Dec -> Try(I8, [OutOfRange]) + to_i16_wrap : Dec -> I16 + to_i16_try : Dec -> Try(I16, [OutOfRange]) + to_i32_wrap : Dec -> I32 + to_i32_try : Dec -> Try(I32, [OutOfRange]) + to_i64_wrap : Dec -> I64 + to_i64_try : Dec -> Try(I64, [OutOfRange]) + to_i128_wrap : Dec -> I128 + to_i128_try : Dec -> Try(I128, [OutOfRange]) + + # Conversions to unsigned integers (all lossy - truncates fractional part) + to_u8_wrap : Dec -> U8 + to_u8_try : Dec -> Try(U8, [OutOfRange]) + to_u16_wrap : Dec -> U16 + to_u16_try : Dec -> Try(U16, [OutOfRange]) + to_u32_wrap : Dec -> U32 + to_u32_try : Dec -> Try(U32, [OutOfRange]) + to_u64_wrap : Dec -> U64 + to_u64_try : Dec -> Try(U64, [OutOfRange]) + to_u128_wrap : Dec -> U128 + to_u128_try : Dec -> Try(U128, [OutOfRange]) + + # Conversions to floating point (lossy - Dec has more precision) + to_f32_wrap : Dec -> F32 + to_f32_try : Dec -> Try(F32, [OutOfRange]) + to_f64 : Dec -> F64 } F32 :: [].{ diff --git a/src/builtins/dec.zig b/src/builtins/dec.zig index 0de142c989..8e09a54c3a 100644 --- a/src/builtins/dec.zig +++ b/src/builtins/dec.zig @@ -1058,6 +1058,48 @@ pub fn toF64(arg: RocDec) callconv(.c) f64 { return @call(.always_inline, RocDec.toF64, .{arg}); } +/// Convert Dec to F32 (lossy conversion) +pub fn toF32(arg: RocDec) callconv(.c) f32 { + return @floatCast(arg.toF64()); +} + +/// Convert Dec to F32 with range check - returns null if out of range +pub fn toF32Try(arg: RocDec) ?f32 { + const f64_val = arg.toF64(); + // Check if the value is within F32 range + if (f64_val > math.floatMax(f32) or f64_val < -math.floatMax(f32)) { + return null; + } + // Also check for infinity (which would indicate overflow) + const f32_val: f32 = @floatCast(f64_val); + if (math.isInf(f32_val) and !math.isInf(f64_val)) { + return null; + } + return f32_val; +} + +/// Convert Dec to integer by truncating the fractional part (wrapping on overflow) +pub fn toIntWrap(comptime T: type, arg: RocDec) T { + // Divide by one_point_zero_i128 to get the integer part + const whole_part = @divTrunc(arg.num, RocDec.one_point_zero_i128); + // Truncate to the target type (wrapping) + // First cast the i128 to u128, then truncate to the target size, then cast back to T if needed + const as_u128: u128 = @bitCast(whole_part); + const truncated = @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @truncate(as_u128)); + return @bitCast(truncated); +} + +/// Convert Dec to integer by truncating the fractional part (returns null if out of range) +pub fn toIntTry(comptime T: type, arg: RocDec) ?T { + // Divide by one_point_zero_i128 to get the integer part + const whole_part = @divTrunc(arg.num, RocDec.one_point_zero_i128); + // Check if it fits in the target type + if (whole_part < math.minInt(T) or whole_part > math.maxInt(T)) { + return null; + } + return @intCast(whole_part); +} + /// TODO: Document exportFromInt. pub fn exportFromInt(comptime T: type, comptime name: []const u8) void { const f = struct { diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index a8034b7455..ce1d15244e 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -751,6 +751,31 @@ pub const Expr = union(enum) { f64_to_u128_try, // F64 -> Try(U128, [OutOfRange]) f64_to_f32_wrap, // F64 -> F32 (lossy narrowing) f64_to_f32_try, // F64 -> Try(F32, [OutOfRange]) + + // Numeric conversion operations (Dec) + dec_to_i8_wrap, // Dec -> I8 (wrapping) + dec_to_i8_try, // Dec -> Try(I8, [OutOfRange]) + dec_to_i16_wrap, // Dec -> I16 (wrapping) + dec_to_i16_try, // Dec -> Try(I16, [OutOfRange]) + dec_to_i32_wrap, // Dec -> I32 (wrapping) + dec_to_i32_try, // Dec -> Try(I32, [OutOfRange]) + dec_to_i64_wrap, // Dec -> I64 (wrapping) + dec_to_i64_try, // Dec -> Try(I64, [OutOfRange]) + dec_to_i128_wrap, // Dec -> I128 (wrapping) + dec_to_i128_try, // Dec -> Try(I128, [OutOfRange]) + dec_to_u8_wrap, // Dec -> U8 (wrapping) + dec_to_u8_try, // Dec -> Try(U8, [OutOfRange]) + dec_to_u16_wrap, // Dec -> U16 (wrapping) + dec_to_u16_try, // Dec -> Try(U16, [OutOfRange]) + dec_to_u32_wrap, // Dec -> U32 (wrapping) + dec_to_u32_try, // Dec -> Try(U32, [OutOfRange]) + dec_to_u64_wrap, // Dec -> U64 (wrapping) + dec_to_u64_try, // Dec -> Try(U64, [OutOfRange]) + dec_to_u128_wrap, // Dec -> U128 (wrapping) + dec_to_u128_try, // Dec -> Try(U128, [OutOfRange]) + dec_to_f32_wrap, // Dec -> F32 (lossy narrowing) + dec_to_f32_try, // Dec -> Try(F32, [OutOfRange]) + dec_to_f64, // Dec -> F64 (lossy conversion) }; pub const Idx = enum(u32) { _ }; diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 96d26854cf..51ed90f3d6 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -23,7 +23,8 @@ const RocOps = builtins.host_abi.RocOps; const RocExpectFailed = builtins.host_abi.RocExpectFailed; const RocCrashed = builtins.host_abi.RocCrashed; const RocStr = builtins.str.RocStr; -const RocDec = builtins.dec.RocDec; +const dec = builtins.dec; +const RocDec = dec.RocDec; const RocList = builtins.list.RocList; const utils = builtins.utils; const Layout = layout.Layout; @@ -4924,6 +4925,33 @@ pub const Interpreter = struct { .f64_to_u128_try => return self.floatToIntTry(f64, u128, args, roc_ops, return_rt_var), .f64_to_f32_wrap => return self.floatNarrowWrap(f64, f32, args, roc_ops), .f64_to_f32_try => return self.floatNarrowTry(f64, f32, args, roc_ops, return_rt_var), + + // Dec -> integer conversions + .dec_to_i8_wrap => return self.decToIntWrap(i8, args, roc_ops), + .dec_to_i8_try => return self.decToIntTry(i8, args, roc_ops, return_rt_var), + .dec_to_i16_wrap => return self.decToIntWrap(i16, args, roc_ops), + .dec_to_i16_try => return self.decToIntTry(i16, args, roc_ops, return_rt_var), + .dec_to_i32_wrap => return self.decToIntWrap(i32, args, roc_ops), + .dec_to_i32_try => return self.decToIntTry(i32, args, roc_ops, return_rt_var), + .dec_to_i64_wrap => return self.decToIntWrap(i64, args, roc_ops), + .dec_to_i64_try => return self.decToIntTry(i64, args, roc_ops, return_rt_var), + .dec_to_i128_wrap => return self.decToIntWrap(i128, args, roc_ops), + .dec_to_i128_try => return self.decToIntTry(i128, args, roc_ops, return_rt_var), + .dec_to_u8_wrap => return self.decToIntWrap(u8, args, roc_ops), + .dec_to_u8_try => return self.decToIntTry(u8, args, roc_ops, return_rt_var), + .dec_to_u16_wrap => return self.decToIntWrap(u16, args, roc_ops), + .dec_to_u16_try => return self.decToIntTry(u16, args, roc_ops, return_rt_var), + .dec_to_u32_wrap => return self.decToIntWrap(u32, args, roc_ops), + .dec_to_u32_try => return self.decToIntTry(u32, args, roc_ops, return_rt_var), + .dec_to_u64_wrap => return self.decToIntWrap(u64, args, roc_ops), + .dec_to_u64_try => return self.decToIntTry(u64, args, roc_ops, return_rt_var), + .dec_to_u128_wrap => return self.decToIntWrap(u128, args, roc_ops), + .dec_to_u128_try => return self.decToIntTry(u128, args, roc_ops, return_rt_var), + + // Dec -> float conversions + .dec_to_f32_wrap => return self.decToF32Wrap(args, roc_ops), + .dec_to_f32_try => return self.decToF32Try(args, roc_ops, return_rt_var), + .dec_to_f64 => return self.decToF64(args, roc_ops), } } @@ -5491,6 +5519,278 @@ pub const Interpreter = struct { } } + /// Dec to integer conversion (wrapping) + fn decToIntWrap(self: *Interpreter, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const from_arg = args[0]; + std.debug.assert(from_arg.ptr != null); + + // Dec is stored as i128 internally + const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; + const result = dec.toIntWrap(To, dec.RocDec{ .num = dec_value }); + + // Create result value + const to_layout = Layout.int(intTypeFromZigType(To)); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*To, @ptrCast(@alignCast(out.ptr.?))).* = result; + out.is_initialized = true; + return out; + } + + /// Dec to integer conversion (try - returns error if out of range) + fn decToIntTry(self: *Interpreter, comptime To: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const from_arg = args[0]; + std.debug.assert(from_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const result_layout = try self.getRuntimeLayout(result_rt_var); + + // Dec is stored as i128 internally + const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; + const maybe_result = dec.toIntTry(To, dec.RocDec{ .num = dec_value }); + const in_range = maybe_result != null; + + // Resolve the Try type to get Ok's payload type + const resolved = self.resolveBaseVar(result_rt_var); + std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); + + // Find tag indices for Ok and Err + var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); + defer tag_list.deinit(); + try self.appendUnionTags(result_rt_var, &tag_list); + + var ok_index: ?usize = null; + var err_index: ?usize = null; + + const ok_ident = self.env.idents.ok; + const err_ident = self.env.idents.err; + + for (tag_list.items, 0..) |tag_info, i| { + if (tag_info.name == ok_ident) { + ok_index = i; + } else if (tag_info.name == err_ident) { + err_index = i; + } + } + + // Construct the result tag union + if (result_layout.tag == .scalar) { + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try out.setInt(@intCast(tag_idx)); + out.is_initialized = true; + return out; + } else if (result_layout.tag == .record) { + var dest = try self.pushRaw(result_layout, 0); + var acc = try dest.asRecord(&self.runtime_layout_store); + const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; + const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; + + const tag_field = try acc.getFieldByIndex(tag_field_idx); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try acc.getFieldByIndex(payload_field_idx); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; + } + } + return dest; + } else if (result_layout.tag == .tuple) { + var dest = try self.pushRaw(result_layout, 0); + var result_acc = try dest.asTuple(&self.runtime_layout_store); + + // Tag field is element 1, payload is element 0 (following floatToIntTry pattern) + const tag_field = try result_acc.getElement(1); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try result_acc.getElement(0); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*To, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; + } + } + return dest; + } else { + unreachable; + } + } + + /// Dec to F32 conversion (wrapping - lossy) + fn decToF32Wrap(self: *Interpreter, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const from_arg = args[0]; + std.debug.assert(from_arg.ptr != null); + + // Dec is stored as i128 internally + const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; + const result = dec.toF32(dec.RocDec{ .num = dec_value }); + + // Create result value + const to_layout = Layout.frac(.f32); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*f32, @ptrCast(@alignCast(out.ptr.?))).* = result; + out.is_initialized = true; + return out; + } + + /// Dec to F32 conversion (try - returns error if out of range) + fn decToF32Try(self: *Interpreter, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const from_arg = args[0]; + std.debug.assert(from_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const result_layout = try self.getRuntimeLayout(result_rt_var); + + // Dec is stored as i128 internally + const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; + const maybe_result = dec.toF32Try(dec.RocDec{ .num = dec_value }); + const in_range = maybe_result != null; + + // Resolve the Try type to get Ok's payload type + const resolved = self.resolveBaseVar(result_rt_var); + std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); + + // Find tag indices for Ok and Err + var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); + defer tag_list.deinit(); + try self.appendUnionTags(result_rt_var, &tag_list); + + var ok_index: ?usize = null; + var err_index: ?usize = null; + + const ok_ident = self.env.idents.ok; + const err_ident = self.env.idents.err; + + for (tag_list.items, 0..) |tag_info, i| { + if (tag_info.name == ok_ident) { + ok_index = i; + } else if (tag_info.name == err_ident) { + err_index = i; + } + } + + // Construct the result tag union + if (result_layout.tag == .scalar) { + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try out.setInt(@intCast(tag_idx)); + out.is_initialized = true; + return out; + } else if (result_layout.tag == .record) { + var dest = try self.pushRaw(result_layout, 0); + var acc = try dest.asRecord(&self.runtime_layout_store); + const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; + const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; + + const tag_field = try acc.getFieldByIndex(tag_field_idx); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try acc.getFieldByIndex(payload_field_idx); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*f32, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; + } + } + return dest; + } else if (result_layout.tag == .tuple) { + var dest = try self.pushRaw(result_layout, 0); + var result_acc = try dest.asTuple(&self.runtime_layout_store); + + // Tag field is element 1, payload is element 0 (following floatToIntTry pattern) + const tag_field = try result_acc.getElement(1); + std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); + var tmp = tag_field; + tmp.is_initialized = false; + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + try tmp.setInt(@intCast(tag_idx)); + + const payload_field = try result_acc.getElement(0); + if (payload_field.ptr) |payload_ptr| { + const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); + if (payload_bytes_len > 0) { + const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; + @memset(bytes, 0); + } + } + + if (in_range) { + if (payload_field.ptr) |payload_ptr| { + @as(*f32, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; + } + } + return dest; + } else { + unreachable; + } + } + + /// Dec to F64 conversion (lossy but always succeeds for Dec range) + fn decToF64(self: *Interpreter, args: []const StackValue, roc_ops: *RocOps) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const from_arg = args[0]; + std.debug.assert(from_arg.ptr != null); + + // Dec is stored as i128 internally + const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; + const result = dec.toF64(dec.RocDec{ .num = dec_value }); + + // Create result value + const to_layout = Layout.frac(.f64); + var out = try self.pushRaw(to_layout, 0); + out.is_initialized = false; + @as(*f64, @ptrCast(@alignCast(out.ptr.?))).* = result; + out.is_initialized = true; + return out; + } + /// Convert Zig integer type to types.Int.Precision fn intTypeFromZigType(comptime T: type) types.Int.Precision { return switch (T) { From b9ec067f0a9fa478246d0d177270456e6d6ea65d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Nov 2025 18:49:59 -0500 Subject: [PATCH 4/5] Reduce code duplication --- src/eval/interpreter.zig | 426 ++++----------------------------------- 1 file changed, 38 insertions(+), 388 deletions(-) diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 51ed90f3d6..3369ca4c79 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -5059,33 +5059,26 @@ pub const Interpreter = struct { return out; } - /// Helper for try integer conversions (returns Try(To, [OutOfRange])) - fn intConvertTry(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { - _ = roc_ops; - std.debug.assert(args.len == 1); - const int_arg = args[0]; - // Null argument is a compiler bug - the compiler should never produce code with null args - std.debug.assert(int_arg.ptr != null); - - // Return type info is required - missing it is a compiler bug - const result_rt_var = return_rt_var orelse unreachable; - - const result_layout = try self.getRuntimeLayout(result_rt_var); - - const from_value: From = @as(*const From, @ptrCast(@alignCast(int_arg.ptr.?))).*; - - // Check if conversion is in range - const in_range = std.math.cast(To, from_value) != null; + /// Generic helper for building Try(T, [OutOfRange]) results. + /// Used by all "try" conversion functions to construct the result tag union. + fn buildTryResult( + self: *Interpreter, + comptime PayloadType: type, + in_range: bool, + payload_value: PayloadType, + return_rt_var: types.Var, + ) !StackValue { + const result_layout = try self.getRuntimeLayout(return_rt_var); // Resolve the Try type to get Ok's payload type - const resolved = self.resolveBaseVar(result_rt_var); + const resolved = self.resolveBaseVar(return_rt_var); // Type system should guarantee this is a tag union - if not, it's a compiler bug std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); // Find tag indices for Ok and Err var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); defer tag_list.deinit(); - try self.appendUnionTags(result_rt_var, &tag_list); + try self.appendUnionTags(return_rt_var, &tag_list); var ok_index: ?usize = null; var err_index: ?usize = null; @@ -5101,12 +5094,13 @@ pub const Interpreter = struct { } } + const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; + // Construct the result tag union if (result_layout.tag == .scalar) { // Simple tag with no payload (shouldn't happen for Try with payload) var out = try self.pushRaw(result_layout, 0); out.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; try out.setInt(@intCast(tag_idx)); out.is_initialized = true; return out; @@ -5124,7 +5118,6 @@ pub const Interpreter = struct { std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); var tmp = tag_field; tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; try tmp.setInt(@intCast(tag_idx)); // Clear payload area @@ -5139,9 +5132,8 @@ pub const Interpreter = struct { // Write payload for Ok case if (in_range) { - const to_value: To = @intCast(from_value); if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + @as(*PayloadType, @ptrCast(@alignCast(payload_ptr))).* = payload_value; } } // For Err case, payload is OutOfRange which is a zero-arg tag (already zeroed) @@ -5160,7 +5152,6 @@ pub const Interpreter = struct { std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); var tmp = tag_field; tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; try tmp.setInt(@intCast(tag_idx)); // Clear payload area (element 0) @@ -5175,9 +5166,8 @@ pub const Interpreter = struct { // Write payload for Ok case if (in_range) { - const to_value: To = @intCast(from_value); if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; + @as(*PayloadType, @ptrCast(@alignCast(payload_ptr))).* = payload_value; } } // For Err case, payload is OutOfRange which is a zero-arg tag (already zeroed) @@ -5189,6 +5179,21 @@ pub const Interpreter = struct { unreachable; } + /// Helper for try integer conversions (returns Try(To, [OutOfRange])) + fn intConvertTry(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const int_arg = args[0]; + std.debug.assert(int_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const from_value: From = @as(*const From, @ptrCast(@alignCast(int_arg.ptr.?))).*; + const in_range = std.math.cast(To, from_value) != null; + const to_value: To = if (in_range) @intCast(from_value) else undefined; + + return self.buildTryResult(To, in_range, to_value, result_rt_var); + } + /// Helper for integer to float conversions fn intToFloat(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { _ = roc_ops; @@ -5275,105 +5280,15 @@ pub const Interpreter = struct { std.debug.assert(float_arg.ptr != null); const result_rt_var = return_rt_var orelse unreachable; - const result_layout = try self.getRuntimeLayout(result_rt_var); - const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; // Check if conversion would succeed const min_float: From = @floatFromInt(std.math.minInt(To)); const max_float: From = @floatFromInt(std.math.maxInt(To)); const in_range = !std.math.isNan(from_value) and from_value >= min_float and from_value <= max_float; + const to_value: To = if (in_range) @intFromFloat(from_value) else undefined; - // Resolve the Try type to get Ok's payload type - const resolved = self.resolveBaseVar(result_rt_var); - std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); - - // Find tag indices for Ok and Err - var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); - defer tag_list.deinit(); - try self.appendUnionTags(result_rt_var, &tag_list); - - var ok_index: ?usize = null; - var err_index: ?usize = null; - - const ok_ident = self.env.idents.ok; - const err_ident = self.env.idents.err; - - for (tag_list.items, 0..) |tag_info, i| { - if (tag_info.name == ok_ident) { - ok_index = i; - } else if (tag_info.name == err_ident) { - err_index = i; - } - } - - // Construct the result tag union - if (result_layout.tag == .scalar) { - var out = try self.pushRaw(result_layout, 0); - out.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try out.setInt(@intCast(tag_idx)); - out.is_initialized = true; - return out; - } else if (result_layout.tag == .record) { - var dest = try self.pushRaw(result_layout, 0); - var acc = try dest.asRecord(&self.runtime_layout_store); - const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; - const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; - - const tag_field = try acc.getFieldByIndex(tag_field_idx); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try acc.getFieldByIndex(payload_field_idx); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - const to_value: To = @intFromFloat(from_value); - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; - } - } - return dest; - } else if (result_layout.tag == .tuple) { - var dest = try self.pushRaw(result_layout, 0); - var result_acc = try dest.asTuple(&self.runtime_layout_store); - - const tag_field = try result_acc.getElement(1); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try result_acc.getElement(0); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - const to_value: To = @intFromFloat(from_value); - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; - } - } - return dest; - } else { - unreachable; - } + return self.buildTryResult(To, in_range, to_value, result_rt_var); } /// Helper for float widening conversion (F32 -> F64) @@ -5421,102 +5336,13 @@ pub const Interpreter = struct { std.debug.assert(float_arg.ptr != null); const result_rt_var = return_rt_var orelse unreachable; - const result_layout = try self.getRuntimeLayout(result_rt_var); - const from_value: From = @as(*const From, @ptrCast(@alignCast(float_arg.ptr.?))).*; // Check if conversion would produce infinity from a finite value const to_value: To = @floatCast(from_value); const in_range = !std.math.isInf(to_value) or std.math.isInf(from_value); - // Resolve the Try type to get Ok's payload type - const resolved = self.resolveBaseVar(result_rt_var); - std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); - - // Find tag indices for Ok and Err - var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); - defer tag_list.deinit(); - try self.appendUnionTags(result_rt_var, &tag_list); - - var ok_index: ?usize = null; - var err_index: ?usize = null; - - const ok_ident = self.env.idents.ok; - const err_ident = self.env.idents.err; - - for (tag_list.items, 0..) |tag_info, i| { - if (tag_info.name == ok_ident) { - ok_index = i; - } else if (tag_info.name == err_ident) { - err_index = i; - } - } - - // Construct the result tag union - if (result_layout.tag == .scalar) { - var out = try self.pushRaw(result_layout, 0); - out.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try out.setInt(@intCast(tag_idx)); - out.is_initialized = true; - return out; - } else if (result_layout.tag == .record) { - var dest = try self.pushRaw(result_layout, 0); - var acc = try dest.asRecord(&self.runtime_layout_store); - const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; - const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; - - const tag_field = try acc.getFieldByIndex(tag_field_idx); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try acc.getFieldByIndex(payload_field_idx); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; - } - } - return dest; - } else if (result_layout.tag == .tuple) { - var dest = try self.pushRaw(result_layout, 0); - var result_acc = try dest.asTuple(&self.runtime_layout_store); - - const tag_field = try result_acc.getElement(1); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try result_acc.getElement(0); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = to_value; - } - } - return dest; - } else { - unreachable; - } + return self.buildTryResult(To, in_range, to_value, result_rt_var); } /// Dec to integer conversion (wrapping) @@ -5547,102 +5373,14 @@ pub const Interpreter = struct { std.debug.assert(from_arg.ptr != null); const result_rt_var = return_rt_var orelse unreachable; - const result_layout = try self.getRuntimeLayout(result_rt_var); // Dec is stored as i128 internally const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; const maybe_result = dec.toIntTry(To, dec.RocDec{ .num = dec_value }); const in_range = maybe_result != null; + const to_value: To = maybe_result orelse undefined; - // Resolve the Try type to get Ok's payload type - const resolved = self.resolveBaseVar(result_rt_var); - std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); - - // Find tag indices for Ok and Err - var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); - defer tag_list.deinit(); - try self.appendUnionTags(result_rt_var, &tag_list); - - var ok_index: ?usize = null; - var err_index: ?usize = null; - - const ok_ident = self.env.idents.ok; - const err_ident = self.env.idents.err; - - for (tag_list.items, 0..) |tag_info, i| { - if (tag_info.name == ok_ident) { - ok_index = i; - } else if (tag_info.name == err_ident) { - err_index = i; - } - } - - // Construct the result tag union - if (result_layout.tag == .scalar) { - var out = try self.pushRaw(result_layout, 0); - out.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try out.setInt(@intCast(tag_idx)); - out.is_initialized = true; - return out; - } else if (result_layout.tag == .record) { - var dest = try self.pushRaw(result_layout, 0); - var acc = try dest.asRecord(&self.runtime_layout_store); - const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; - const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; - - const tag_field = try acc.getFieldByIndex(tag_field_idx); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try acc.getFieldByIndex(payload_field_idx); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; - } - } - return dest; - } else if (result_layout.tag == .tuple) { - var dest = try self.pushRaw(result_layout, 0); - var result_acc = try dest.asTuple(&self.runtime_layout_store); - - // Tag field is element 1, payload is element 0 (following floatToIntTry pattern) - const tag_field = try result_acc.getElement(1); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try result_acc.getElement(0); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*To, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; - } - } - return dest; - } else { - unreachable; - } + return self.buildTryResult(To, in_range, to_value, result_rt_var); } /// Dec to F32 conversion (wrapping - lossy) @@ -5673,102 +5411,14 @@ pub const Interpreter = struct { std.debug.assert(from_arg.ptr != null); const result_rt_var = return_rt_var orelse unreachable; - const result_layout = try self.getRuntimeLayout(result_rt_var); // Dec is stored as i128 internally const dec_value: i128 = @as(*const i128, @ptrCast(@alignCast(from_arg.ptr.?))).*; const maybe_result = dec.toF32Try(dec.RocDec{ .num = dec_value }); const in_range = maybe_result != null; + const to_value: f32 = maybe_result orelse undefined; - // Resolve the Try type to get Ok's payload type - const resolved = self.resolveBaseVar(result_rt_var); - std.debug.assert(resolved.desc.content == .structure and resolved.desc.content.structure == .tag_union); - - // Find tag indices for Ok and Err - var tag_list = std.array_list.AlignedManaged(types.Tag, null).init(self.allocator); - defer tag_list.deinit(); - try self.appendUnionTags(result_rt_var, &tag_list); - - var ok_index: ?usize = null; - var err_index: ?usize = null; - - const ok_ident = self.env.idents.ok; - const err_ident = self.env.idents.err; - - for (tag_list.items, 0..) |tag_info, i| { - if (tag_info.name == ok_ident) { - ok_index = i; - } else if (tag_info.name == err_ident) { - err_index = i; - } - } - - // Construct the result tag union - if (result_layout.tag == .scalar) { - var out = try self.pushRaw(result_layout, 0); - out.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try out.setInt(@intCast(tag_idx)); - out.is_initialized = true; - return out; - } else if (result_layout.tag == .record) { - var dest = try self.pushRaw(result_layout, 0); - var acc = try dest.asRecord(&self.runtime_layout_store); - const tag_field_idx = acc.findFieldIndex(self.env.idents.tag) orelse unreachable; - const payload_field_idx = acc.findFieldIndex(self.env.idents.payload) orelse unreachable; - - const tag_field = try acc.getFieldByIndex(tag_field_idx); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try acc.getFieldByIndex(payload_field_idx); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*f32, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; - } - } - return dest; - } else if (result_layout.tag == .tuple) { - var dest = try self.pushRaw(result_layout, 0); - var result_acc = try dest.asTuple(&self.runtime_layout_store); - - // Tag field is element 1, payload is element 0 (following floatToIntTry pattern) - const tag_field = try result_acc.getElement(1); - std.debug.assert(tag_field.layout.tag == .scalar and tag_field.layout.data.scalar.tag == .int); - var tmp = tag_field; - tmp.is_initialized = false; - const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1; - try tmp.setInt(@intCast(tag_idx)); - - const payload_field = try result_acc.getElement(0); - if (payload_field.ptr) |payload_ptr| { - const payload_bytes_len = self.runtime_layout_store.layoutSize(payload_field.layout); - if (payload_bytes_len > 0) { - const bytes = @as([*]u8, @ptrCast(payload_ptr))[0..payload_bytes_len]; - @memset(bytes, 0); - } - } - - if (in_range) { - if (payload_field.ptr) |payload_ptr| { - @as(*f32, @ptrCast(@alignCast(payload_ptr))).* = maybe_result.?; - } - } - return dest; - } else { - unreachable; - } + return self.buildTryResult(f32, in_range, to_value, result_rt_var); } /// Dec to F64 conversion (lossy but always succeeds for Dec range) From 364ab5a982f8bcec3868a18b39c8e9aa7c5abf08 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Nov 2025 20:37:18 -0500 Subject: [PATCH 5/5] Revise a bunch of conversions --- src/build/builtin_compiler/main.zig | 8 +- src/build/roc/Builtin.roc | 15 +++- src/canonicalize/Expression.zig | 124 +++++++++++++-------------- src/eval/interpreter.zig | 126 +++++++++++++++++++--------- 4 files changed, 164 insertions(+), 109 deletions(-) diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index 80b9a6e854..24fcc86707 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -851,8 +851,8 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { if (env.common.findIdent("Builtin.Num.U128.to_f64")) |ident| { try low_level_map.put(ident, .u128_to_f64); } - if (env.common.findIdent("Builtin.Num.U128.to_dec")) |ident| { - try low_level_map.put(ident, .u128_to_dec); + if (env.common.findIdent("Builtin.Num.U128.to_dec_try")) |ident| { + try low_level_map.put(ident, .u128_to_dec_try); } // I128 conversion operations @@ -916,8 +916,8 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { if (env.common.findIdent("Builtin.Num.I128.to_f64")) |ident| { try low_level_map.put(ident, .i128_to_f64); } - if (env.common.findIdent("Builtin.Num.I128.to_dec")) |ident| { - try low_level_map.put(ident, .i128_to_dec); + if (env.common.findIdent("Builtin.Num.I128.to_dec_try")) |ident| { + try low_level_map.put(ident, .i128_to_dec_try); } // F32 conversion operations diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index 2f8518fefe..bc32eda8e0 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -594,7 +594,9 @@ Builtin :: [].{ # Conversions to floating point (all safe) to_f32 : U128 -> F32 to_f64 : U128 -> F64 - to_dec : U128 -> Dec + + # Conversion to Dec (can overflow) + to_dec_try : U128 -> Try(Dec, [OutOfRange]) } I128 :: [].{ @@ -644,7 +646,9 @@ Builtin :: [].{ # Conversions to floating point (all safe) to_f32 : I128 -> F32 to_f64 : I128 -> F64 - to_dec : I128 -> Dec + + # Conversion to Dec (can overflow) + to_dec_try : I128 -> Try(Dec, [OutOfRange]) } Dec :: [].{ @@ -798,7 +802,12 @@ Builtin :: [].{ # Conversion to F32 (lossy narrowing) to_f32_wrap : F64 -> F32 + to_f32_try : F64 -> Try(F32, [OutOfRange]) + to_f32_try = |num| { + answer = f64_to_f32_try_unsafe(num) + if (answer.success) answer.val_or_memory_garbage else Err(OutOfRange) + } } } } @@ -806,3 +815,5 @@ Builtin :: [].{ # Private top-level function for unsafe list access # This is a low-level operation that gets replaced by the compiler list_get_unsafe : List(item), U64 -> item + +f64_to_f32_try_unsafe : F64 -> { success : Bool, val_or_memory_garbage : F32 } diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index ce1d15244e..d0cd5f28f0 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -680,7 +680,7 @@ pub const Expr = union(enum) { u128_to_u64_try, // U128 -> Try(U64, [OutOfRange]) u128_to_f32, // U128 -> F32 (safe) u128_to_f64, // U128 -> F64 (safe) - u128_to_dec, // U128 -> Dec (safe) + u128_to_dec_try, // U128 -> Try(Dec, [OutOfRange]) // Numeric conversion operations (I128) i128_to_i8_wrap, // I128 -> I8 (wrapping) @@ -703,76 +703,76 @@ pub const Expr = union(enum) { i128_to_u128_try, // I128 -> Try(U128, [OutOfRange]) i128_to_f32, // I128 -> F32 (safe) i128_to_f64, // I128 -> F64 (safe) - i128_to_dec, // I128 -> Dec (safe) + i128_to_dec_try, // I128 -> Try(Dec, [OutOfRange]) // Numeric conversion operations (F32) - f32_to_i8_wrap, // F32 -> I8 (wrapping) - f32_to_i8_try, // F32 -> Try(I8, [OutOfRange]) - f32_to_i16_wrap, // F32 -> I16 (wrapping) - f32_to_i16_try, // F32 -> Try(I16, [OutOfRange]) - f32_to_i32_wrap, // F32 -> I32 (wrapping) - f32_to_i32_try, // F32 -> Try(I32, [OutOfRange]) - f32_to_i64_wrap, // F32 -> I64 (wrapping) - f32_to_i64_try, // F32 -> Try(I64, [OutOfRange]) - f32_to_i128_wrap, // F32 -> I128 (wrapping) - f32_to_i128_try, // F32 -> Try(I128, [OutOfRange]) - f32_to_u8_wrap, // F32 -> U8 (wrapping) - f32_to_u8_try, // F32 -> Try(U8, [OutOfRange]) - f32_to_u16_wrap, // F32 -> U16 (wrapping) - f32_to_u16_try, // F32 -> Try(U16, [OutOfRange]) - f32_to_u32_wrap, // F32 -> U32 (wrapping) - f32_to_u32_try, // F32 -> Try(U32, [OutOfRange]) - f32_to_u64_wrap, // F32 -> U64 (wrapping) - f32_to_u64_try, // F32 -> Try(U64, [OutOfRange]) - f32_to_u128_wrap, // F32 -> U128 (wrapping) - f32_to_u128_try, // F32 -> Try(U128, [OutOfRange]) + f32_to_i8_trunc, // F32 -> I8 (truncating) + f32_to_i8_try, // F32 -> Try(I8, [NotInt, OutOfRange]) + f32_to_i16_trunc, // F32 -> I16 (truncating) + f32_to_i16_try, // F32 -> Try(I16, [NotInt, OutOfRange]) + f32_to_i32_trunc, // F32 -> I32 (truncating) + f32_to_i32_try, // F32 -> Try(I32, [NotInt, OutOfRange]) + f32_to_i64_trunc, // F32 -> I64 (truncating) + f32_to_i64_try, // F32 -> Try(I64, [NotInt, OutOfRange]) + f32_to_i128_trunc, // F32 -> I128 (truncating) + f32_to_i128_try, // F32 -> Try(I128, [NotInt, OutOfRange]) + f32_to_u8_trunc, // F32 -> U8 (truncating) + f32_to_u8_try, // F32 -> Try(U8, [NotInt, OutOfRange]) + f32_to_u16_trunc, // F32 -> U16 (truncating) + f32_to_u16_try, // F32 -> Try(U16, [NotInt, OutOfRange]) + f32_to_u32_trunc, // F32 -> U32 (truncating) + f32_to_u32_try, // F32 -> Try(U32, [NotInt, OutOfRange]) + f32_to_u64_trunc, // F32 -> U64 (truncating) + f32_to_u64_try, // F32 -> Try(U64, [NotInt, OutOfRange]) + f32_to_u128_trunc, // F32 -> U128 (truncating) + f32_to_u128_try, // F32 -> Try(U128, [NotInt, OutOfRange]) f32_to_f64, // F32 -> F64 (safe widening) // Numeric conversion operations (F64) - f64_to_i8_wrap, // F64 -> I8 (wrapping) - f64_to_i8_try, // F64 -> Try(I8, [OutOfRange]) - f64_to_i16_wrap, // F64 -> I16 (wrapping) - f64_to_i16_try, // F64 -> Try(I16, [OutOfRange]) - f64_to_i32_wrap, // F64 -> I32 (wrapping) - f64_to_i32_try, // F64 -> Try(I32, [OutOfRange]) - f64_to_i64_wrap, // F64 -> I64 (wrapping) - f64_to_i64_try, // F64 -> Try(I64, [OutOfRange]) - f64_to_i128_wrap, // F64 -> I128 (wrapping) - f64_to_i128_try, // F64 -> Try(I128, [OutOfRange]) - f64_to_u8_wrap, // F64 -> U8 (wrapping) - f64_to_u8_try, // F64 -> Try(U8, [OutOfRange]) - f64_to_u16_wrap, // F64 -> U16 (wrapping) - f64_to_u16_try, // F64 -> Try(U16, [OutOfRange]) - f64_to_u32_wrap, // F64 -> U32 (wrapping) - f64_to_u32_try, // F64 -> Try(U32, [OutOfRange]) - f64_to_u64_wrap, // F64 -> U64 (wrapping) - f64_to_u64_try, // F64 -> Try(U64, [OutOfRange]) - f64_to_u128_wrap, // F64 -> U128 (wrapping) - f64_to_u128_try, // F64 -> Try(U128, [OutOfRange]) + f64_to_i8_trunc, // F64 -> I8 (truncating) + f64_to_i8_try, // F64 -> Try(I8, [NotInt, OutOfRange]) + f64_to_i16_trunc, // F64 -> I16 (truncating) + f64_to_i16_try, // F64 -> Try(I16, [NotInt, OutOfRange]) + f64_to_i32_trunc, // F64 -> I32 (truncating) + f64_to_i32_try, // F64 -> Try(I32, [NotInt, OutOfRange]) + f64_to_i64_trunc, // F64 -> I64 (truncating) + f64_to_i64_try, // F64 -> Try(I64, [NotInt, OutOfRange]) + f64_to_i128_trunc, // F64 -> I128 (truncating) + f64_to_i128_try, // F64 -> Try(I128, [NotInt, OutOfRange]) + f64_to_u8_trunc, // F64 -> U8 (truncating) + f64_to_u8_try, // F64 -> Try(U8, [NotInt, OutOfRange]) + f64_to_u16_trunc, // F64 -> U16 (truncating) + f64_to_u16_try, // F64 -> Try(U16, [NotInt, OutOfRange]) + f64_to_u32_trunc, // F64 -> U32 (truncating) + f64_to_u32_try, // F64 -> Try(U32, [NotInt, OutOfRange]) + f64_to_u64_trunc, // F64 -> U64 (truncating) + f64_to_u64_try, // F64 -> Try(U64, [NotInt, OutOfRange]) + f64_to_u128_trunc, // F64 -> U128 (truncating) + f64_to_u128_try, // F64 -> Try(U128, [NotInt, OutOfRange]) f64_to_f32_wrap, // F64 -> F32 (lossy narrowing) f64_to_f32_try, // F64 -> Try(F32, [OutOfRange]) // Numeric conversion operations (Dec) - dec_to_i8_wrap, // Dec -> I8 (wrapping) - dec_to_i8_try, // Dec -> Try(I8, [OutOfRange]) - dec_to_i16_wrap, // Dec -> I16 (wrapping) - dec_to_i16_try, // Dec -> Try(I16, [OutOfRange]) - dec_to_i32_wrap, // Dec -> I32 (wrapping) - dec_to_i32_try, // Dec -> Try(I32, [OutOfRange]) - dec_to_i64_wrap, // Dec -> I64 (wrapping) - dec_to_i64_try, // Dec -> Try(I64, [OutOfRange]) - dec_to_i128_wrap, // Dec -> I128 (wrapping) - dec_to_i128_try, // Dec -> Try(I128, [OutOfRange]) - dec_to_u8_wrap, // Dec -> U8 (wrapping) - dec_to_u8_try, // Dec -> Try(U8, [OutOfRange]) - dec_to_u16_wrap, // Dec -> U16 (wrapping) - dec_to_u16_try, // Dec -> Try(U16, [OutOfRange]) - dec_to_u32_wrap, // Dec -> U32 (wrapping) - dec_to_u32_try, // Dec -> Try(U32, [OutOfRange]) - dec_to_u64_wrap, // Dec -> U64 (wrapping) - dec_to_u64_try, // Dec -> Try(U64, [OutOfRange]) - dec_to_u128_wrap, // Dec -> U128 (wrapping) - dec_to_u128_try, // Dec -> Try(U128, [OutOfRange]) + dec_to_i8_trunc, // Dec -> I8 (truncating) + dec_to_i8_try, // Dec -> Try(I8, [NotInt, OutOfRange]) + dec_to_i16_trunc, // Dec -> I16 (truncating) + dec_to_i16_try, // Dec -> Try(I16, [NotInt, OutOfRange]) + dec_to_i32_trunc, // Dec -> I32 (truncating) + dec_to_i32_try, // Dec -> Try(I32, [NotInt, OutOfRange]) + dec_to_i64_trunc, // Dec -> I64 (truncating) + dec_to_i64_try, // Dec -> Try(I64, [NotInt, OutOfRange]) + dec_to_i128_trunc, // Dec -> I128 (truncating) + dec_to_i128_try, // Dec -> Try(I128, [NotInt]) - always fits + dec_to_u8_trunc, // Dec -> U8 (truncating) + dec_to_u8_try, // Dec -> Try(U8, [NotInt, OutOfRange]) + dec_to_u16_trunc, // Dec -> U16 (truncating) + dec_to_u16_try, // Dec -> Try(U16, [NotInt, OutOfRange]) + dec_to_u32_trunc, // Dec -> U32 (truncating) + dec_to_u32_try, // Dec -> Try(U32, [NotInt, OutOfRange]) + dec_to_u64_trunc, // Dec -> U64 (truncating) + dec_to_u64_try, // Dec -> Try(U64, [NotInt, OutOfRange]) + dec_to_u128_trunc, // Dec -> U128 (truncating) + dec_to_u128_try, // Dec -> Try(U128, [NotInt]) - always fits if positive dec_to_f32_wrap, // Dec -> F32 (lossy narrowing) dec_to_f32_try, // Dec -> Try(F32, [OutOfRange]) dec_to_f64, // Dec -> F64 (lossy conversion) diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 3369ca4c79..e85cbc0692 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -4854,7 +4854,7 @@ pub const Interpreter = struct { .u128_to_u64_try => return self.intConvertTry(u128, u64, args, roc_ops, return_rt_var), .u128_to_f32 => return self.intToFloat(u128, f32, args, roc_ops), .u128_to_f64 => return self.intToFloat(u128, f64, args, roc_ops), - .u128_to_dec => return self.intToDec(u128, args, roc_ops), + .u128_to_dec_try => return self.intToDecTry(u128, args, roc_ops, return_rt_var), // I128 conversion operations .i128_to_i8_wrap => return self.intConvertWrap(i128, i8, args, roc_ops), @@ -4877,75 +4877,75 @@ pub const Interpreter = struct { .i128_to_u128_try => return self.intConvertTry(i128, u128, args, roc_ops, return_rt_var), .i128_to_f32 => return self.intToFloat(i128, f32, args, roc_ops), .i128_to_f64 => return self.intToFloat(i128, f64, args, roc_ops), - .i128_to_dec => return self.intToDec(i128, args, roc_ops), + .i128_to_dec_try => return self.intToDecTry(i128, args, roc_ops, return_rt_var), // F32 conversion operations - .f32_to_i8_wrap => return self.floatToIntWrap(f32, i8, args, roc_ops), + .f32_to_i8_trunc => return self.floatToIntTrunc(f32, i8, args, roc_ops), .f32_to_i8_try => return self.floatToIntTry(f32, i8, args, roc_ops, return_rt_var), - .f32_to_i16_wrap => return self.floatToIntWrap(f32, i16, args, roc_ops), + .f32_to_i16_trunc => return self.floatToIntTrunc(f32, i16, args, roc_ops), .f32_to_i16_try => return self.floatToIntTry(f32, i16, args, roc_ops, return_rt_var), - .f32_to_i32_wrap => return self.floatToIntWrap(f32, i32, args, roc_ops), + .f32_to_i32_trunc => return self.floatToIntTrunc(f32, i32, args, roc_ops), .f32_to_i32_try => return self.floatToIntTry(f32, i32, args, roc_ops, return_rt_var), - .f32_to_i64_wrap => return self.floatToIntWrap(f32, i64, args, roc_ops), + .f32_to_i64_trunc => return self.floatToIntTrunc(f32, i64, args, roc_ops), .f32_to_i64_try => return self.floatToIntTry(f32, i64, args, roc_ops, return_rt_var), - .f32_to_i128_wrap => return self.floatToIntWrap(f32, i128, args, roc_ops), + .f32_to_i128_trunc => return self.floatToIntTrunc(f32, i128, args, roc_ops), .f32_to_i128_try => return self.floatToIntTry(f32, i128, args, roc_ops, return_rt_var), - .f32_to_u8_wrap => return self.floatToIntWrap(f32, u8, args, roc_ops), + .f32_to_u8_trunc => return self.floatToIntTrunc(f32, u8, args, roc_ops), .f32_to_u8_try => return self.floatToIntTry(f32, u8, args, roc_ops, return_rt_var), - .f32_to_u16_wrap => return self.floatToIntWrap(f32, u16, args, roc_ops), + .f32_to_u16_trunc => return self.floatToIntTrunc(f32, u16, args, roc_ops), .f32_to_u16_try => return self.floatToIntTry(f32, u16, args, roc_ops, return_rt_var), - .f32_to_u32_wrap => return self.floatToIntWrap(f32, u32, args, roc_ops), + .f32_to_u32_trunc => return self.floatToIntTrunc(f32, u32, args, roc_ops), .f32_to_u32_try => return self.floatToIntTry(f32, u32, args, roc_ops, return_rt_var), - .f32_to_u64_wrap => return self.floatToIntWrap(f32, u64, args, roc_ops), + .f32_to_u64_trunc => return self.floatToIntTrunc(f32, u64, args, roc_ops), .f32_to_u64_try => return self.floatToIntTry(f32, u64, args, roc_ops, return_rt_var), - .f32_to_u128_wrap => return self.floatToIntWrap(f32, u128, args, roc_ops), + .f32_to_u128_trunc => return self.floatToIntTrunc(f32, u128, args, roc_ops), .f32_to_u128_try => return self.floatToIntTry(f32, u128, args, roc_ops, return_rt_var), .f32_to_f64 => return self.floatWiden(f32, f64, args, roc_ops), // F64 conversion operations - .f64_to_i8_wrap => return self.floatToIntWrap(f64, i8, args, roc_ops), + .f64_to_i8_trunc => return self.floatToIntTrunc(f64, i8, args, roc_ops), .f64_to_i8_try => return self.floatToIntTry(f64, i8, args, roc_ops, return_rt_var), - .f64_to_i16_wrap => return self.floatToIntWrap(f64, i16, args, roc_ops), + .f64_to_i16_trunc => return self.floatToIntTrunc(f64, i16, args, roc_ops), .f64_to_i16_try => return self.floatToIntTry(f64, i16, args, roc_ops, return_rt_var), - .f64_to_i32_wrap => return self.floatToIntWrap(f64, i32, args, roc_ops), + .f64_to_i32_trunc => return self.floatToIntTrunc(f64, i32, args, roc_ops), .f64_to_i32_try => return self.floatToIntTry(f64, i32, args, roc_ops, return_rt_var), - .f64_to_i64_wrap => return self.floatToIntWrap(f64, i64, args, roc_ops), + .f64_to_i64_trunc => return self.floatToIntTrunc(f64, i64, args, roc_ops), .f64_to_i64_try => return self.floatToIntTry(f64, i64, args, roc_ops, return_rt_var), - .f64_to_i128_wrap => return self.floatToIntWrap(f64, i128, args, roc_ops), + .f64_to_i128_trunc => return self.floatToIntTrunc(f64, i128, args, roc_ops), .f64_to_i128_try => return self.floatToIntTry(f64, i128, args, roc_ops, return_rt_var), - .f64_to_u8_wrap => return self.floatToIntWrap(f64, u8, args, roc_ops), + .f64_to_u8_trunc => return self.floatToIntTrunc(f64, u8, args, roc_ops), .f64_to_u8_try => return self.floatToIntTry(f64, u8, args, roc_ops, return_rt_var), - .f64_to_u16_wrap => return self.floatToIntWrap(f64, u16, args, roc_ops), + .f64_to_u16_trunc => return self.floatToIntTrunc(f64, u16, args, roc_ops), .f64_to_u16_try => return self.floatToIntTry(f64, u16, args, roc_ops, return_rt_var), - .f64_to_u32_wrap => return self.floatToIntWrap(f64, u32, args, roc_ops), + .f64_to_u32_trunc => return self.floatToIntTrunc(f64, u32, args, roc_ops), .f64_to_u32_try => return self.floatToIntTry(f64, u32, args, roc_ops, return_rt_var), - .f64_to_u64_wrap => return self.floatToIntWrap(f64, u64, args, roc_ops), + .f64_to_u64_trunc => return self.floatToIntTrunc(f64, u64, args, roc_ops), .f64_to_u64_try => return self.floatToIntTry(f64, u64, args, roc_ops, return_rt_var), - .f64_to_u128_wrap => return self.floatToIntWrap(f64, u128, args, roc_ops), + .f64_to_u128_trunc => return self.floatToIntTrunc(f64, u128, args, roc_ops), .f64_to_u128_try => return self.floatToIntTry(f64, u128, args, roc_ops, return_rt_var), .f64_to_f32_wrap => return self.floatNarrowWrap(f64, f32, args, roc_ops), .f64_to_f32_try => return self.floatNarrowTry(f64, f32, args, roc_ops, return_rt_var), // Dec -> integer conversions - .dec_to_i8_wrap => return self.decToIntWrap(i8, args, roc_ops), + .dec_to_i8_trunc => return self.decToIntTrunc(i8, args, roc_ops), .dec_to_i8_try => return self.decToIntTry(i8, args, roc_ops, return_rt_var), - .dec_to_i16_wrap => return self.decToIntWrap(i16, args, roc_ops), + .dec_to_i16_trunc => return self.decToIntTrunc(i16, args, roc_ops), .dec_to_i16_try => return self.decToIntTry(i16, args, roc_ops, return_rt_var), - .dec_to_i32_wrap => return self.decToIntWrap(i32, args, roc_ops), + .dec_to_i32_trunc => return self.decToIntTrunc(i32, args, roc_ops), .dec_to_i32_try => return self.decToIntTry(i32, args, roc_ops, return_rt_var), - .dec_to_i64_wrap => return self.decToIntWrap(i64, args, roc_ops), + .dec_to_i64_trunc => return self.decToIntTrunc(i64, args, roc_ops), .dec_to_i64_try => return self.decToIntTry(i64, args, roc_ops, return_rt_var), - .dec_to_i128_wrap => return self.decToIntWrap(i128, args, roc_ops), + .dec_to_i128_trunc => return self.decToIntTrunc(i128, args, roc_ops), .dec_to_i128_try => return self.decToIntTry(i128, args, roc_ops, return_rt_var), - .dec_to_u8_wrap => return self.decToIntWrap(u8, args, roc_ops), + .dec_to_u8_trunc => return self.decToIntTrunc(u8, args, roc_ops), .dec_to_u8_try => return self.decToIntTry(u8, args, roc_ops, return_rt_var), - .dec_to_u16_wrap => return self.decToIntWrap(u16, args, roc_ops), + .dec_to_u16_trunc => return self.decToIntTrunc(u16, args, roc_ops), .dec_to_u16_try => return self.decToIntTry(u16, args, roc_ops, return_rt_var), - .dec_to_u32_wrap => return self.decToIntWrap(u32, args, roc_ops), + .dec_to_u32_trunc => return self.decToIntTrunc(u32, args, roc_ops), .dec_to_u32_try => return self.decToIntTry(u32, args, roc_ops, return_rt_var), - .dec_to_u64_wrap => return self.decToIntWrap(u64, args, roc_ops), + .dec_to_u64_trunc => return self.decToIntTrunc(u64, args, roc_ops), .dec_to_u64_try => return self.decToIntTry(u64, args, roc_ops, return_rt_var), - .dec_to_u128_wrap => return self.decToIntWrap(u128, args, roc_ops), + .dec_to_u128_trunc => return self.decToIntTrunc(u128, args, roc_ops), .dec_to_u128_try => return self.decToIntTry(u128, args, roc_ops, return_rt_var), // Dec -> float conversions @@ -5059,6 +5059,13 @@ pub const Interpreter = struct { return out; } + /// Possible errors for float/Dec to integer conversions + const NumConversionError = enum { + none, // Success - not an error + not_int, // Value has fractional part + out_of_range, // Value outside target type's range + }; + /// Generic helper for building Try(T, [OutOfRange]) results. /// Used by all "try" conversion functions to construct the result tag union. fn buildTryResult( @@ -5067,6 +5074,19 @@ pub const Interpreter = struct { in_range: bool, payload_value: PayloadType, return_rt_var: types.Var, + ) !StackValue { + const err = if (in_range) NumConversionError.none else NumConversionError.out_of_range; + return self.buildTryResultWithError(PayloadType, err, payload_value, return_rt_var); + } + + /// Generic helper for building Try(T, [NotInt, OutOfRange]) or Try(T, [OutOfRange]) results. + /// Handles conversions where there can be multiple error types. + fn buildTryResultWithError( + self: *Interpreter, + comptime PayloadType: type, + err: NumConversionError, + payload_value: PayloadType, + return_rt_var: types.Var, ) !StackValue { const result_layout = try self.getRuntimeLayout(return_rt_var); @@ -5213,18 +5233,15 @@ pub const Interpreter = struct { return out; } - /// Helper for integer to Dec conversions + /// Helper for integer to Dec conversions (for types that always fit in Dec's range) fn intToDec(self: *Interpreter, comptime From: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { _ = roc_ops; std.debug.assert(args.len == 1); const int_arg = args[0]; - // Null argument is a compiler bug - the compiler should never produce code with null args std.debug.assert(int_arg.ptr != null); const from_value: From = @as(*const From, @ptrCast(@alignCast(int_arg.ptr.?))).*; - // For unsigned types that can't fit in i128, use @intCast which will panic at runtime - // if the value is too large (which would overflow Dec's range anyway) - const signed_value: i128 = @intCast(from_value); + const signed_value: i128 = from_value; const dec_value = RocDec{ .num = signed_value * RocDec.one_point_zero_i128 }; const dec_layout = Layout.frac(.dec); @@ -5235,8 +5252,35 @@ pub const Interpreter = struct { return out; } - /// Helper for wrapping float to integer conversions (truncates toward zero) - fn floatToIntWrap(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + /// Helper for integer to Dec conversions that may overflow (i128, u128) + fn intToDecTry(self: *Interpreter, comptime From: type, args: []const StackValue, roc_ops: *RocOps, return_rt_var: ?types.Var) !StackValue { + _ = roc_ops; + std.debug.assert(args.len == 1); + const int_arg = args[0]; + std.debug.assert(int_arg.ptr != null); + + const result_rt_var = return_rt_var orelse unreachable; + const from_value: From = @as(*const From, @ptrCast(@alignCast(int_arg.ptr.?))).*; + + // Check if value fits in Dec's range: must fit in i128 and not overflow when multiplied by 10^18 + const max_dec_whole: i128 = @divTrunc(std.math.maxInt(i128), RocDec.one_point_zero_i128); + const min_dec_whole: i128 = @divTrunc(std.math.minInt(i128), RocDec.one_point_zero_i128); + + const in_range = if (From == u128) + from_value <= @as(u128, @intCast(max_dec_whole)) + else + from_value >= min_dec_whole and from_value <= max_dec_whole; + + const dec_value: RocDec = if (in_range) + RocDec{ .num = @as(i128, @intCast(from_value)) * RocDec.one_point_zero_i128 } + else + undefined; + + return self.buildTryResult(RocDec, in_range, dec_value, result_rt_var); + } + + /// Helper for truncating float to integer conversions (truncates toward zero, clamps to range) + fn floatToIntTrunc(self: *Interpreter, comptime From: type, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { _ = roc_ops; std.debug.assert(args.len == 1); const float_arg = args[0]; @@ -5345,8 +5389,8 @@ pub const Interpreter = struct { return self.buildTryResult(To, in_range, to_value, result_rt_var); } - /// Dec to integer conversion (wrapping) - fn decToIntWrap(self: *Interpreter, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { + /// Dec to integer conversion (truncating - drops fractional part) + fn decToIntTrunc(self: *Interpreter, comptime To: type, args: []const StackValue, roc_ops: *RocOps) !StackValue { _ = roc_ops; std.debug.assert(args.len == 1); const from_arg = args[0];