From c671b1ecb0f39e13e87d5b65c6468f9142f599e9 Mon Sep 17 00:00:00 2001
From: "Michael J. Klein" <lambdamichael@gmail.com>
Date: Tue, 7 Jan 2025 16:56:28 -0500
Subject: [PATCH 01/18] change two assertions on generics to static assertions,
 wip finding static_assert locations for field/mod.nr

---
 noir_stdlib/src/collections/bounded_vec.nr |  5 +++--
 noir_stdlib/src/field/mod.nr               | 13 +++++++++++--
 noir_stdlib/src/meta/ctstring.nr           |  3 ++-
 noir_stdlib/src/uint128.nr                 |  6 ++++--
 4 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr
index 7aed5e6a0e4..4b8820df8c8 100644
--- a/noir_stdlib/src/collections/bounded_vec.nr
+++ b/noir_stdlib/src/collections/bounded_vec.nr
@@ -1,4 +1,4 @@
-use crate::{cmp::Eq, convert::From};
+use crate::{cmp::Eq, convert::From, static_assert};
 
 /// A `BoundedVec<T, MaxLen>` is a growable storage similar to a `Vec<T>` except that it
 /// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented
@@ -339,7 +339,7 @@ impl<T, let MaxLen: u32> BoundedVec<T, MaxLen> {
     /// let bounded_vec: BoundedVec<Field, 10> = BoundedVec::from_array([1, 2, 3])
     /// ```
     pub fn from_array<let Len: u32>(array: [T; Len]) -> Self {
-        assert(Len <= MaxLen, "from array out of bounds");
+        static_assert(Len <= MaxLen, "from array out of bounds");
         let mut vec: BoundedVec<T, MaxLen> = BoundedVec::new();
         vec.extend_from_array(array);
         vec
@@ -612,6 +612,7 @@ mod bounded_vec_tests {
             assert_eq(bounded_vec.get(2), 3);
         }
 
+        // TODO: works with 'shoul_fail': does 'should_fail_with' support 'static_assert'?
         #[test(should_fail_with = "from array out of bounds")]
         fn max_len_lower_then_array_len() {
             let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]);
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 7e5ad97d64f..abbc34854c0 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -1,5 +1,5 @@
 pub mod bn254;
-use crate::runtime::is_unconstrained;
+use crate::{runtime::is_unconstrained, static_assert};
 use bn254::lt as bn254_lt;
 
 impl Field {
@@ -10,13 +10,14 @@ impl Field {
     // docs:start:assert_max_bit_size
     pub fn assert_max_bit_size<let BIT_SIZE: u32>(self) {
         // docs:end:assert_max_bit_size
-        assert(BIT_SIZE < modulus_num_bits() as u32);
+        static_assert(BIT_SIZE < modulus_num_bits() as u32, "BIT_SIZE must be less than modulus_num_bits");
         self.__assert_max_bit_size(BIT_SIZE);
     }
 
     #[builtin(apply_range_constraint)]
     fn __assert_max_bit_size(self, bit_size: u32) {}
 
+    // TODO: to_le_bits -> to_le_bits_unsafe
     /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array.
     /// This slice will be zero padded should not all bits be necessary to represent `self`.
     ///
@@ -33,6 +34,7 @@ impl Field {
     pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {}
     // docs:end:to_le_bits
 
+    // TODO: to_be_bits -> to_be_bits_unsafe
     /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array.
     /// This array will be zero padded should not all bits be necessary to represent `self`.
     ///
@@ -49,6 +51,7 @@ impl Field {
     pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {}
     // docs:end:to_be_bits
 
+    // TODO: static_assert for N
     /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array
     /// This array will be zero padded should not all bytes be necessary to represent `self`.
     ///
@@ -82,6 +85,7 @@ impl Field {
         bytes
     }
 
+    // TODO: static_assert for N
     /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus
     /// This array will be zero padded should not all bytes be necessary to represent `self`.
     ///
@@ -135,10 +139,13 @@ impl Field {
     }
     // docs:end:to_be_radix
 
+    // TODO: static_assert for N
     // `_radix` must be less than 256
     #[builtin(to_le_radix)]
     fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
 
+    // TODO: static_assert for N
+    // TODO: is this also true? "`_radix` must be less than 256"
     #[builtin(to_be_radix)]
     fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
 
@@ -169,6 +176,7 @@ impl Field {
         }
     }
 
+    // TODO: static_assert for N?
     /// Convert a little endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field {
@@ -182,6 +190,7 @@ impl Field {
         result
     }
 
+    // TODO: static_assert for N?
     /// Convert a big endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field {
diff --git a/noir_stdlib/src/meta/ctstring.nr b/noir_stdlib/src/meta/ctstring.nr
index b414b3418d9..8297af7dd24 100644
--- a/noir_stdlib/src/meta/ctstring.nr
+++ b/noir_stdlib/src/meta/ctstring.nr
@@ -7,7 +7,8 @@ impl CtString {
         "".as_ctstring()
     }
 
-    // Bug: using &mut self as the object results in this method not being found
+    // TODO(https://github.com/noir-lang/noir/issues/6980): Bug: using &mut self
+    // as the object results in this method not being found
     // docs:start:append_str
     pub comptime fn append_str<let N: u32>(self, s: str<N>) -> Self {
         // docs:end:append_str
diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr
index fab8cacc055..6176ff032cf 100644
--- a/noir_stdlib/src/uint128.nr
+++ b/noir_stdlib/src/uint128.nr
@@ -1,5 +1,6 @@
 use crate::cmp::{Eq, Ord, Ordering};
 use crate::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub};
+use crate::static_assert;
 use super::convert::AsPrimitive;
 
 global pow64: Field = 18446744073709551616; //2^64;
@@ -67,11 +68,12 @@ impl U128 {
     }
 
     pub fn from_hex<let N: u32>(hex: str<N>) -> U128 {
-        let N = N as u32;
+        // TODO cleanup if working
+        // let N = N as u32;
         let bytes = hex.as_bytes();
         // string must starts with "0x"
         assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string");
-        assert(N < 35, "Input does not fit into a U128");
+        static_assert(N < 35, "Input does not fit into a U128");
 
         let mut lo = 0;
         let mut hi = 0;

From e835376b3ecf6a77270c8252c99fa7f2e7b20a39 Mon Sep 17 00:00:00 2001
From: "Michael J. Klein" <lambdamichael@gmail.com>
Date: Thu, 9 Jan 2025 00:51:48 -0500
Subject: [PATCH 02/18] rename ToRadix -> ToRadixUnsafe, ToBits ->
 ToBitsUnsafe, add size checks for numeric generics in field/mod.nr, fixed
 printing in test_transform_program_is_idempotent

---
 compiler/noirc_evaluator/src/acir/mod.rs      |  4 +-
 .../src/brillig/brillig_gen/brillig_block.rs  |  4 +-
 .../check_for_underconstrained_values.rs      |  8 +-
 .../src/ssa/function_builder/mod.rs           |  2 +-
 .../noirc_evaluator/src/ssa/ir/instruction.rs | 28 +++----
 .../src/ssa/ir/instruction/call.rs            | 14 ++--
 .../src/ssa/opt/constant_folding.rs           | 16 ++--
 .../src/ssa/opt/flatten_cfg.rs                |  2 +-
 .../src/ssa/opt/remove_bit_shifts.rs          |  2 +-
 .../src/ssa/opt/remove_enable_side_effects.rs |  4 +-
 .../src/ssa/opt/remove_if_else.rs             |  4 +-
 .../src/hir/comptime/interpreter/builtin.rs   | 16 ++--
 noir_stdlib/src/collections/bounded_vec.nr    |  4 +-
 noir_stdlib/src/field/mod.nr                  | 76 +++++++++++++++----
 tooling/nargo_cli/src/cli/compile_cmd.rs      |  8 +-
 tooling/nargo_cli/src/cli/test_cmd.rs         |  2 +-
 .../nargo_cli/src/cli/test_cmd/formatters.rs  |  2 +-
 17 files changed, 124 insertions(+), 72 deletions(-)

diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs
index 7ecc4d28032..b1244aea1bd 100644
--- a/compiler/noirc_evaluator/src/acir/mod.rs
+++ b/compiler/noirc_evaluator/src/acir/mod.rs
@@ -2145,7 +2145,7 @@ impl<'a> Context<'a> {
             Intrinsic::ApplyRangeConstraint => {
                 unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`");
             }
-            Intrinsic::ToRadix(endian) => {
+            Intrinsic::ToRadixUnsafe(endian) => {
                 let field = self.convert_value(arguments[0], dfg).into_var()?;
                 let radix = self.convert_value(arguments[1], dfg).into_var()?;
 
@@ -2164,7 +2164,7 @@ impl<'a> Context<'a> {
                     )
                     .map(|array| vec![array])
             }
-            Intrinsic::ToBits(endian) => {
+            Intrinsic::ToBitsUnsafe(endian) => {
                 let field = self.convert_value(arguments[0], dfg).into_var()?;
 
                 let Type::Array(result_type, array_length) = dfg.type_of_value(result_ids[0])
diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
index 31c99bf433e..08199844158 100644
--- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
+++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
@@ -496,7 +496,7 @@ impl<'block> BrilligBlock<'block> {
                                 arguments,
                             );
                         }
-                        Intrinsic::ToBits(endianness) => {
+                        Intrinsic::ToBitsUnsafe(endianness) => {
                             let results = dfg.instruction_results(instruction_id);
 
                             let source = self.convert_ssa_single_addr_value(arguments[0], dfg);
@@ -526,7 +526,7 @@ impl<'block> BrilligBlock<'block> {
                             self.brillig_context.deallocate_single_addr(two);
                         }
 
-                        Intrinsic::ToRadix(endianness) => {
+                        Intrinsic::ToRadixUnsafe(endianness) => {
                             let results = dfg.instruction_results(instruction_id);
 
                             let source = self.convert_ssa_single_addr_value(arguments[0], dfg);
diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
index d61dd27d02a..fc5b6cf9575 100644
--- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
+++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
@@ -307,8 +307,8 @@ impl DependencyContext {
                             | Intrinsic::SliceRemove
                             | Intrinsic::StaticAssert
                             | Intrinsic::StrAsBytes
-                            | Intrinsic::ToBits(..)
-                            | Intrinsic::ToRadix(..)
+                            | Intrinsic::ToBitsUnsafe(..)
+                            | Intrinsic::ToRadixUnsafe(..)
                             | Intrinsic::FieldLessThan => {
                                 // Record all the function arguments as parents of the results
                                 self.update_children(&arguments, &results);
@@ -586,8 +586,8 @@ impl Context {
                             | Intrinsic::SliceRemove
                             | Intrinsic::StaticAssert
                             | Intrinsic::StrAsBytes
-                            | Intrinsic::ToBits(..)
-                            | Intrinsic::ToRadix(..)
+                            | Intrinsic::ToBitsUnsafe(..)
+                            | Intrinsic::ToRadixUnsafe(..)
                             | Intrinsic::FieldLessThan => {
                                 self.value_sets.push(instruction_arguments_and_results);
                             }
diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
index d08d5339237..1e060e1757b 100644
--- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
@@ -566,7 +566,7 @@ mod tests {
         let one = builder.numeric_constant(FieldElement::one(), NumericType::bool());
         let zero = builder.numeric_constant(FieldElement::zero(), NumericType::bool());
 
-        let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBits(Endian::Little));
+        let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBitsUnsafe(Endian::Little));
         let input = builder.field_constant(FieldElement::from(7_u128));
         let length = builder.field_constant(FieldElement::from(8_u128));
         let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), 8)];
diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
index aadd35040cf..cdc3c91d4bd 100644
--- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
+++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
@@ -61,8 +61,8 @@ pub(crate) enum Intrinsic {
     SliceRemove,
     ApplyRangeConstraint,
     StrAsBytes,
-    ToBits(Endian),
-    ToRadix(Endian),
+    ToBitsUnsafe(Endian),
+    ToRadixUnsafe(Endian),
     BlackBox(BlackBoxFunc),
     Hint(Hint),
     AsWitness,
@@ -89,10 +89,10 @@ impl std::fmt::Display for Intrinsic {
             Intrinsic::SliceRemove => write!(f, "slice_remove"),
             Intrinsic::StrAsBytes => write!(f, "str_as_bytes"),
             Intrinsic::ApplyRangeConstraint => write!(f, "apply_range_constraint"),
-            Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"),
-            Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"),
-            Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"),
-            Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"),
+            Intrinsic::ToBitsUnsafe(Endian::Big) => write!(f, "to_be_bits_unsafe"),
+            Intrinsic::ToBitsUnsafe(Endian::Little) => write!(f, "to_le_bits_unsafe"),
+            Intrinsic::ToRadixUnsafe(Endian::Big) => write!(f, "to_be_radix_unsafe"),
+            Intrinsic::ToRadixUnsafe(Endian::Little) => write!(f, "to_le_radix_unsafe"),
             Intrinsic::BlackBox(function) => write!(f, "{function}"),
             Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"),
             Intrinsic::AsWitness => write!(f, "as_witness"),
@@ -124,7 +124,7 @@ impl Intrinsic {
             | Intrinsic::AsWitness => true,
 
             // These apply a constraint that the input must fit into a specified number of limbs.
-            Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true,
+            Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_) => true,
 
             // These imply a check that the slice is non-empty and should fail otherwise.
             Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => true,
@@ -164,8 +164,8 @@ impl Intrinsic {
             // directly depend on the corresponding `enable_side_effect` instruction any more.
             // However, to conform with the expectations of `Instruction::can_be_deduplicated` and
             // `constant_folding` we only use this information if the caller shows interest in it.
-            Intrinsic::ToBits(_)
-            | Intrinsic::ToRadix(_)
+            Intrinsic::ToBitsUnsafe(_)
+            | Intrinsic::ToRadixUnsafe(_)
             | Intrinsic::BlackBox(
                 BlackBoxFunc::MultiScalarMul
                 | BlackBoxFunc::EmbeddedCurveAdd
@@ -203,10 +203,10 @@ impl Intrinsic {
             "slice_insert" => Some(Intrinsic::SliceInsert),
             "slice_remove" => Some(Intrinsic::SliceRemove),
             "str_as_bytes" => Some(Intrinsic::StrAsBytes),
-            "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)),
-            "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)),
-            "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)),
-            "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)),
+            "to_le_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Little)),
+            "to_be_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Big)),
+            "to_le_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Little)),
+            "to_be_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Big)),
             "as_witness" => Some(Intrinsic::AsWitness),
             "is_unconstrained" => Some(Intrinsic::IsUnconstrained),
             "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators),
@@ -220,7 +220,7 @@ impl Intrinsic {
     }
 }
 
-/// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix
+/// The endian-ness of bits when encoding values as bits in e.g. ToBitsUnsafe or ToRadixUnsafe
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub(crate) enum Endian {
     Big,
diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
index 951761e041e..74601f891e3 100644
--- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
+++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
@@ -52,16 +52,16 @@ pub(super) fn simplify_call(
         arguments.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect();
 
     let simplified_result = match intrinsic {
-        Intrinsic::ToBits(endian) => {
+        Intrinsic::ToBitsUnsafe(endian) => {
             // TODO: simplify to a range constraint if `limb_count == 1`
             if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) {
                 let field = constant_args[0];
                 let limb_count = if let Type::Array(_, array_len) = return_type {
                     array_len
                 } else {
-                    unreachable!("ICE: Intrinsic::ToRadix return type must be array")
+                    unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array")
                 };
-                constant_to_radix(endian, field, 2, limb_count, |values| {
+                constant_to_radix_unsafe(endian, field, 2, limb_count, |values| {
                     make_constant_array(
                         dfg,
                         values.into_iter(),
@@ -74,7 +74,7 @@ pub(super) fn simplify_call(
                 SimplifyResult::None
             }
         }
-        Intrinsic::ToRadix(endian) => {
+        Intrinsic::ToRadixUnsafe(endian) => {
             // TODO: simplify to a range constraint if `limb_count == 1`
             if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) {
                 let field = constant_args[0];
@@ -82,9 +82,9 @@ pub(super) fn simplify_call(
                 let limb_count = if let Type::Array(_, array_len) = return_type {
                     array_len
                 } else {
-                    unreachable!("ICE: Intrinsic::ToRadix return type must be array")
+                    unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array")
                 };
-                constant_to_radix(endian, field, radix, limb_count, |values| {
+                constant_to_radix_unsafe(endian, field, radix, limb_count, |values| {
                     make_constant_array(
                         dfg,
                         values.into_iter(),
@@ -620,7 +620,7 @@ fn make_array(
 }
 
 /// Returns a slice (represented by a tuple (len, slice)) of constants corresponding to the limbs of the radix decomposition.
-fn constant_to_radix(
+fn constant_to_radix_unsafe(
     endian: Endian,
     field: FieldElement,
     radix: u32,
diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
index 8dca867fc6d..5222dbc821b 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
@@ -1558,15 +1558,15 @@ mod test {
         brillig(inline) fn main f0 {
           b0(v0: Field, v1: Field, v2: u1):
             v4 = call is_unconstrained() -> u1
-            v7 = call to_be_radix(v0, u32 256) -> [u8; 1]    // `a.to_be_radix(256)`;
+            v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]    // `a.to_be_radix_unsafe(256)`;
             inc_rc v7
-            v8 = call to_be_radix(v0, u32 256) -> [u8; 1]    // duplicate load of `a`
+            v8 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]    // duplicate load of `a`
             inc_rc v8
-            v9 = cast v2 as Field                            // `if c { a.to_be_radix(256) }`
-            v10 = mul v0, v9                                 // attaching `c` to `a`
-            v11 = call to_be_radix(v10, u32 256) -> [u8; 1]  // calling `to_radix(c * a)`
+            v9 = cast v2 as Field                                   // `if c { a.to_be_radix_unsafe(256) }`
+            v10 = mul v0, v9                                        // attaching `c` to `a`
+            v11 = call to_be_radix_unsafe(v10, u32 256) -> [u8; 1]  // calling `to_radix(c * a)`
             inc_rc v11
-            enable_side_effects v2                           // side effect var for `c` shifted down by removal
+            enable_side_effects v2                                  // side effect var for `c` shifted down by removal
             return
         }
         ";
@@ -1575,12 +1575,12 @@ mod test {
         brillig(inline) fn main f0 {
           b0(v0: Field, v1: Field, v2: u1):
             v4 = call is_unconstrained() -> u1
-            v7 = call to_be_radix(v0, u32 256) -> [u8; 1]
+            v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]
             inc_rc v7
             inc_rc v7
             v8 = cast v2 as Field
             v9 = mul v0, v8
-            v10 = call to_be_radix(v9, u32 256) -> [u8; 1]
+            v10 = call to_be_radix_unsafe(v9, u32 256) -> [u8; 1]
             inc_rc v10
             enable_side_effects v2
             return
diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
index 748867c7409..6a6cb7aa24a 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
@@ -712,7 +712,7 @@ impl<'f> Context<'f> {
                 }
                 Instruction::Call { func, mut arguments } => match self.inserter.function.dfg[func]
                 {
-                    Value::Intrinsic(Intrinsic::ToBits(_) | Intrinsic::ToRadix(_)) => {
+                    Value::Intrinsic(Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_)) => {
                         let field = arguments[0];
                         let argument_type = self.inserter.function.dfg.type_of_value(field);
 
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
index 61d53cbd960..831e7cef926 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
@@ -190,7 +190,7 @@ impl Context<'_> {
     fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId {
         let typ = self.function.dfg.type_of_value(rhs);
         if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ {
-            let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBits(Endian::Little));
+            let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little));
             let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)];
             let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types);
 
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
index a22232ba49a..5c7317749f4 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
@@ -173,8 +173,8 @@ impl Context {
                     | Intrinsic::StaticAssert
                     | Intrinsic::ApplyRangeConstraint
                     | Intrinsic::StrAsBytes
-                    | Intrinsic::ToBits(_)
-                    | Intrinsic::ToRadix(_)
+                    | Intrinsic::ToBitsUnsafe(_)
+                    | Intrinsic::ToRadixUnsafe(_)
                     | Intrinsic::BlackBox(_)
                     | Intrinsic::Hint(Hint::BlackBox)
                     | Intrinsic::AsSlice
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
index 8dde79a3c60..160b9b54526 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
@@ -234,8 +234,8 @@ fn slice_capacity_change(
         | Intrinsic::AsWitness
         | Intrinsic::IsUnconstrained
         | Intrinsic::DerivePedersenGenerators
-        | Intrinsic::ToBits(_)
-        | Intrinsic::ToRadix(_)
+        | Intrinsic::ToBitsUnsafe(_)
+        | Intrinsic::ToRadixUnsafe(_)
         | Intrinsic::ArrayRefCount
         | Intrinsic::SliceRefCount
         | Intrinsic::FieldLessThan => SizeChange::None,
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 8a9fcf12e16..34dc742867a 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -190,8 +190,8 @@ impl<'local, 'context> Interpreter<'local, 'context> {
             "struct_def_module" => struct_def_module(self, arguments, location),
             "struct_def_name" => struct_def_name(interner, arguments, location),
             "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location),
-            "to_be_radix" => to_be_radix(arguments, return_type, location),
-            "to_le_radix" => to_le_radix(arguments, return_type, location),
+            "to_be_radix_unsafe" => to_be_radix_unsafe(arguments, return_type, location),
+            "to_le_radix_unsafe" => to_le_radix_unsafe(arguments, return_type, location),
             "trait_constraint_eq" => trait_constraint_eq(arguments, location),
             "trait_constraint_hash" => trait_constraint_hash(arguments, location),
             "trait_def_as_trait_constraint" => {
@@ -776,22 +776,22 @@ fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResu
     ))
 }
 
-fn to_be_radix(
+fn to_be_radix_unsafe(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
 ) -> IResult<Value> {
-    let le_radix_limbs = to_le_radix(arguments, return_type, location)?;
+    let le_radix_limbs = to_le_radix_unsafe(arguments, return_type, location)?;
 
     let Value::Array(limbs, typ) = le_radix_limbs else {
-        unreachable!("`to_le_radix` should always return an array");
+        unreachable!("`to_le_radix_unsafe` should always return an array");
     };
     let be_radix_limbs = limbs.into_iter().rev().collect();
 
     Ok(Value::Array(be_radix_limbs, typ))
 }
 
-fn to_le_radix(
+fn to_le_radix_unsafe(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
@@ -815,7 +815,7 @@ fn to_le_radix(
     };
 
     // Decompose the integer into its radix digits in little endian form.
-    let decomposed_integer = compute_to_radix_le(value, radix);
+    let decomposed_integer = compute_to_radix_le_unsafe(value, radix);
     let decomposed_integer =
         vecmap(0..limb_count.to_u128() as usize, |i| match decomposed_integer.get(i) {
             Some(digit) => Value::U8(*digit),
@@ -825,7 +825,7 @@ fn to_le_radix(
     Ok(Value::Array(decomposed_integer.into(), result_type))
 }
 
-fn compute_to_radix_le(field: FieldElement, radix: u32) -> Vec<u8> {
+fn compute_to_radix_le_unsafe(field: FieldElement, radix: u32) -> Vec<u8> {
     let bit_size = u32::BITS - (radix - 1).leading_zeros();
     let radix_big = BigUint::from(radix);
     assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2");
diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr
index 4b8820df8c8..a6760f4071d 100644
--- a/noir_stdlib/src/collections/bounded_vec.nr
+++ b/noir_stdlib/src/collections/bounded_vec.nr
@@ -612,8 +612,8 @@ mod bounded_vec_tests {
             assert_eq(bounded_vec.get(2), 3);
         }
 
-        // TODO: works with 'shoul_fail': does 'should_fail_with' support 'static_assert'?
-        #[test(should_fail_with = "from array out of bounds")]
+        // TODO(https://github.com/noir-lang/noir/issues/6995): #[test(should_fail_with = "from array out of bounds")]
+        #[test(should_fail_with = "Argument is false")]
         fn max_len_lower_then_array_len() {
             let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]);
         }
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index abbc34854c0..b6429d78a09 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -17,7 +17,6 @@ impl Field {
     #[builtin(apply_range_constraint)]
     fn __assert_max_bit_size(self, bit_size: u32) {}
 
-    // TODO: to_le_bits -> to_le_bits_unsafe
     /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array.
     /// This slice will be zero padded should not all bits be necessary to represent `self`.
     ///
@@ -29,12 +28,28 @@ impl Field {
     /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
     /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will
     /// wrap around due to overflow when verifying the decomposition.
-    #[builtin(to_le_bits)]
+    #[builtin(to_le_bits_unsafe)]
+    // docs:start:to_le_bits_unsafe
+    pub fn to_le_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {}
+    // docs:end:to_le_bits_unsafe
+
+    /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array.
+    /// This slice will be zero padded should not all bits be necessary to represent `self`.
+    ///
+    /// # Failures
+    /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not
+    /// be able to represent the original `Field`.
+    ///
+    /// # Safety
+    /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
+    /// (e.g. 254 for the BN254 field) cause a compile-time error.
     // docs:start:to_le_bits
-    pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {}
+    pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {
+        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        self.to_le_bits_unsafe()
+    }
     // docs:end:to_le_bits
 
-    // TODO: to_be_bits -> to_be_bits_unsafe
     /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array.
     /// This array will be zero padded should not all bits be necessary to represent `self`.
     ///
@@ -46,12 +61,28 @@ impl Field {
     /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
     /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will
     /// wrap around due to overflow when verifying the decomposition.
-    #[builtin(to_be_bits)]
+    #[builtin(to_be_bits_unsafe)]
+    // docs:start:to_be_bits_unsafe
+    pub fn to_be_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {}
+    // docs:end:to_be_bits_unsafe
+
+    /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array.
+    /// This array will be zero padded should not all bits be necessary to represent `self`.
+    ///
+    /// # Failures
+    /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not
+    /// be able to represent the original `Field`.
+    ///
+    /// # Safety
+    /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
+    /// (e.g. 254 for the BN254 field) cause a compile-time error.
     // docs:start:to_be_bits
-    pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {}
+    pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {
+        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        self.to_be_bits_unsafe()
+    }
     // docs:end:to_be_bits
 
-    // TODO: static_assert for N
     /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array
     /// This array will be zero padded should not all bytes be necessary to represent `self`.
     ///
@@ -64,6 +95,7 @@ impl Field {
     // docs:start:to_le_bytes
     pub fn to_le_bytes<let N: u32>(self: Self) -> [u8; N] {
         // docs:end:to_le_bytes
+        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
         // Compute the byte decomposition
         let bytes = self.to_le_radix(256);
 
@@ -85,7 +117,6 @@ impl Field {
         bytes
     }
 
-    // TODO: static_assert for N
     /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus
     /// This array will be zero padded should not all bytes be necessary to represent `self`.
     ///
@@ -98,6 +129,7 @@ impl Field {
     // docs:start:to_be_bytes
     pub fn to_be_bytes<let N: u32>(self: Self) -> [u8; N] {
         // docs:end:to_be_bytes
+        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
         // Compute the byte decomposition
         let bytes = self.to_be_radix(256);
 
@@ -139,15 +171,27 @@ impl Field {
     }
     // docs:end:to_be_radix
 
-    // TODO: static_assert for N
     // `_radix` must be less than 256
-    #[builtin(to_le_radix)]
-    fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
+    #[builtin(to_le_radix_unsafe)]
+    fn __to_le_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {}
 
-    // TODO: static_assert for N
-    // TODO: is this also true? "`_radix` must be less than 256"
-    #[builtin(to_be_radix)]
-    fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
+    // `_radix` must be less than 256
+    fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {
+        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
+        self.__to_le_radix_unsafe(radix)
+    }
+
+    // `_radix` must be less than 256
+    #[builtin(to_be_radix_unsafe)]
+    fn __to_be_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {}
+
+    // `_radix` must be less than 256
+    fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {
+        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
+        self.__to_be_radix_unsafe(radix)
+    }
 
     // Returns self to the power of the given exponent value.
     // Caution: we assume the exponent fits into 32 bits
@@ -180,6 +224,7 @@ impl Field {
     /// Convert a little endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field {
+        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
         let mut v = 1;
         let mut result = 0;
 
@@ -194,6 +239,7 @@ impl Field {
     /// Convert a big endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field {
+        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
         let mut v = 1;
         let mut result = 0;
 
diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index 0af05703c9a..8d95f9917cd 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -335,6 +335,7 @@ mod tests {
     use noirc_driver::{CompileOptions, CrateName};
 
     use crate::cli::compile_cmd::{get_target_width, parse_workspace, read_workspace};
+    use crate::cli::test_cmd::formatters::diagnostic_to_string;
 
     /// Try to find the directory that Cargo sets when it is running;
     /// otherwise fallback to assuming the CWD is the root of the repository
@@ -414,7 +415,12 @@ mod tests {
                     &CompileOptions::default(),
                     None,
                 )
-                .expect("failed to compile");
+                .unwrap_or_else(|err| {
+                    let error_string: String = err.iter().map(|diagnostic| {
+                        format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager))
+                    }).collect();
+                    panic!("Failed to compile:\n\n{}", error_string)
+                });
 
                 let width = get_target_width(package.expression_width, None);
 
diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs
index 43f1c306d88..f7fe7715d19 100644
--- a/tooling/nargo_cli/src/cli/test_cmd.rs
+++ b/tooling/nargo_cli/src/cli/test_cmd.rs
@@ -26,7 +26,7 @@ use crate::{cli::check_cmd::check_crate_and_report_errors, errors::CliError};
 
 use super::{NargoConfig, PackageOptions};
 
-mod formatters;
+pub(crate) mod formatters;
 
 /// Run the tests for this program
 #[derive(Debug, Clone, Args)]
diff --git a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
index 75cf14ba120..7651d663c19 100644
--- a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
+++ b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
@@ -514,7 +514,7 @@ fn package_start(package_name: &str, test_count: usize) -> std::io::Result<()> {
     Ok(())
 }
 
-fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String {
+pub(crate) fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String {
     let file_map = file_manager.as_file_map();
 
     let custom_diagnostic = &file_diagnostic.diagnostic;

From 0896b0879161d62c7c253a6f00cf46b71e2879c1 Mon Sep 17 00:00:00 2001
From: "Michael J. Klein" <lambdamichael@gmail.com>
Date: Thu, 9 Jan 2025 11:59:27 -0500
Subject: [PATCH 03/18] wip implementing comptime static_assert, add tests for
 comptime static_assert, ensure dynamic vars rejected by static_assert

---
 .../src/hir/comptime/interpreter/builtin.rs   | 22 ++++++++++++++++++-
 .../src/tests/metaprogramming.rs              | 21 ++++++++++++++++++
 noir_stdlib/src/field/mod.nr                  |  2 --
 .../comptime_static_assert_failure/Nargo.toml |  6 +++++
 .../src/main.nr                               | 13 +++++++++++
 .../comptime_static_assert/Nargo.toml         |  6 +++++
 .../comptime_static_assert/src/main.nr        | 19 ++++++++++++++++
 tooling/nargo_cli/src/cli/compile_cmd.rs      |  1 +
 8 files changed, 87 insertions(+), 3 deletions(-)
 create mode 100644 test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml
 create mode 100644 test_programs/compile_failure/comptime_static_assert_failure/src/main.nr
 create mode 100644 test_programs/compile_success_empty/comptime_static_assert/Nargo.toml
 create mode 100644 test_programs/compile_success_empty/comptime_static_assert/src/main.nr

diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 34dc742867a..0084e4dcdfe 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -66,7 +66,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
             "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location),
             "array_len" => array_len(interner, arguments, location),
             "array_refcount" => Ok(Value::U32(0)),
-            "assert_constant" => Ok(Value::Bool(true)),
+            "assert_constant" => Ok(Value::Unit),
             "as_slice" => as_slice(interner, arguments, location),
             "ctstring_eq" => ctstring_eq(arguments, location),
             "ctstring_hash" => ctstring_hash(arguments, location),
@@ -175,6 +175,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
             "slice_push_front" => slice_push_front(interner, arguments, location),
             "slice_refcount" => Ok(Value::U32(0)),
             "slice_remove" => slice_remove(interner, arguments, location, call_stack),
+            "static_assert" => static_assert(interner, arguments, location, call_stack),
             "str_as_bytes" => str_as_bytes(interner, arguments, location),
             "str_as_ctstring" => str_as_ctstring(interner, arguments, location),
             "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location),
@@ -320,6 +321,25 @@ fn slice_push_back(
     Ok(Value::Slice(values, typ))
 }
 
+// static_assert<let N: u32>(predicate: bool, message: str<N>)
+fn static_assert(
+    interner: &NodeInterner,
+    arguments: Vec<(Value, Location)>,
+    location: Location,
+    call_stack: &im::Vector<Location>,
+) -> IResult<Value> {
+    let (predicate, message) = check_two_arguments(arguments, location)?;
+    let predicate = get_bool(predicate)?;
+    let message = get_str(interner, message)?;
+
+    "TODO need to evaluate static_assert's predicate more?!";
+    if predicate {
+        Ok(Value::Unit)
+    } else {
+        failing_constraint((*message).clone(), location, call_stack)
+    }
+}
+
 fn str_as_bytes(
     interner: &NodeInterner,
     arguments: Vec<(Value, Location)>,
diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs
index 8256744e18f..6142271c502 100644
--- a/compiler/noirc_frontend/src/tests/metaprogramming.rs
+++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs
@@ -3,6 +3,7 @@ use noirc_errors::Spanned;
 use crate::{
     ast::Ident,
     hir::{
+        comptime::InterpreterError,
         def_collector::{
             dc_crate::CompilationError,
             errors::{DefCollectorErrorKind, DuplicateType},
@@ -26,6 +27,26 @@ fn comptime_let() {
     assert_eq!(errors.len(), 0);
 }
 
+#[test]
+fn comptime_code_rejects_dynamic_variable() {
+    let src = r#"fn main(x: Field) {
+        comptime let my_var = (x - x) + 2;
+        assert_eq(my_var, 2);
+    }"#;
+    let errors = get_program_errors(src);
+
+    // TODO cleanup
+    dbg!(&errors);
+
+    assert_eq!(errors.len(), 1);
+    match &errors[0].0 {
+        CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { name, .. }) => {
+            assert_eq!(name, "x");
+        }
+        _ => panic!("expected an InterpreterError"),
+    }
+}
+
 #[test]
 fn comptime_type_in_runtime_code() {
     let source = "pub fn foo(_f: FunctionDefinition) {}";
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index b6429d78a09..8f6177e858e 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -220,7 +220,6 @@ impl Field {
         }
     }
 
-    // TODO: static_assert for N?
     /// Convert a little endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field {
@@ -235,7 +234,6 @@ impl Field {
         result
     }
 
-    // TODO: static_assert for N?
     /// Convert a big endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field {
diff --git a/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml b/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml
new file mode 100644
index 00000000000..006fd9f7ffe
--- /dev/null
+++ b/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "comptime_static_assert_failure"
+type = "bin"
+authors = [""]
+
+[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr b/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr
new file mode 100644
index 00000000000..fcd757f4c94
--- /dev/null
+++ b/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr
@@ -0,0 +1,13 @@
+use std::static_assert;
+
+comptime fn foo(x: Field) -> bool {
+    static_assert(x == 4, "x != 4");
+    x == 4
+}
+
+fn main() {
+    comptime {
+        static_assert(foo(3), "expected message");
+    }
+}
+
diff --git a/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml b/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml
new file mode 100644
index 00000000000..4c969fe7a79
--- /dev/null
+++ b/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "comptime_static_assert"
+type = "bin"
+authors = [""]
+
+[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_success_empty/comptime_static_assert/src/main.nr b/test_programs/compile_success_empty/comptime_static_assert/src/main.nr
new file mode 100644
index 00000000000..2ddbba7b0de
--- /dev/null
+++ b/test_programs/compile_success_empty/comptime_static_assert/src/main.nr
@@ -0,0 +1,19 @@
+use std::static_assert;
+
+comptime fn foo(x: Field) -> bool {
+    static_assert(x == 4, "x != 4");
+    x == 4
+}
+
+global C: bool = {
+    let out = foo(2 + 2);
+    static_assert(out, "foo did not pass in C");
+    out
+};
+
+fn main() {
+    comptime {
+        static_assert(foo(4), "foo did not pass in main");
+        static_assert(C, "C did not pass")
+    }
+}
diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index 8d95f9917cd..b33df08bd04 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -415,6 +415,7 @@ mod tests {
                     &CompileOptions::default(),
                     None,
                 )
+                // TODO: collect all errors and only error at the end! (i.e. don't fail fast)
                 .unwrap_or_else(|err| {
                     let error_string: String = err.iter().map(|diagnostic| {
                         format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager))

From 89a025327a46b373677d993e53cd78cc38cea46f Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Thu, 9 Jan 2025 14:14:46 -0500
Subject: [PATCH 04/18] rename after merge

---
 .../noirc_frontend/src/hir/comptime/interpreter/builtin.rs    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 111269023a4..024e5fc3e65 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -805,7 +805,7 @@ fn to_be_bits_unsafe(
 ) -> IResult<Value> {
     let value = check_one_argument(arguments, location)?;
     let radix = (Value::U32(2), value.1);
-    to_be_radix(vec![value, radix], return_type, location)
+    to_be_radix_unsafe(vec![value, radix], return_type, location)
 }
 
 fn to_le_bits_unsafe(
@@ -815,7 +815,7 @@ fn to_le_bits_unsafe(
 ) -> IResult<Value> {
     let value = check_one_argument(arguments, location)?;
     let radix = (Value::U32(2), value.1);
-    to_le_radix(vec![value, radix], return_type, location)
+    to_le_radix_unsafe(vec![value, radix], return_type, location)
 }
 
 fn to_be_radix_unsafe(

From 3cc4f7d84e16d163ae31a0a9ff847f7dc304d681 Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Thu, 9 Jan 2025 15:56:36 -0500
Subject: [PATCH 05/18] debug failing tests by updating '<' -> '<=' where
 needed, cargo clippy/fmt

---
 .../noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs     | 3 ++-
 .../src/hir/comptime/interpreter/builtin.rs              | 7 +++++--
 compiler/noirc_frontend/src/tests/metaprogramming.rs     | 5 ++++-
 noir_stdlib/src/field/mod.nr                             | 8 ++++----
 tooling/nargo_cli/src/cli/compile_cmd.rs                 | 9 ++++++---
 tooling/nargo_cli/src/cli/test_cmd/formatters.rs         | 5 ++++-
 6 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
index b5d1868db1e..e24e4246d67 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
@@ -193,7 +193,8 @@ impl Context<'_> {
     fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId {
         let typ = self.function.dfg.type_of_value(rhs);
         if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ {
-            let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little));
+            let to_bits =
+                self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little));
             let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)];
             let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types);
 
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 024e5fc3e65..e4998ee3c48 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -334,11 +334,14 @@ fn static_assert(
     let predicate = get_bool(predicate)?;
     let message = get_str(interner, message)?;
 
-    "TODO need to evaluate static_assert's predicate more?!";
     if predicate {
         Ok(Value::Unit)
     } else {
-        failing_constraint((*message).clone(), location, call_stack)
+        failing_constraint(
+            format!("static_assert failed: {}", message).clone(),
+            location,
+            call_stack,
+        )
     }
 }
 
diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs
index 6142271c502..1efbb4efcd7 100644
--- a/compiler/noirc_frontend/src/tests/metaprogramming.rs
+++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs
@@ -40,7 +40,10 @@ fn comptime_code_rejects_dynamic_variable() {
 
     assert_eq!(errors.len(), 1);
     match &errors[0].0 {
-        CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { name, .. }) => {
+        CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced {
+            name,
+            ..
+        }) => {
             assert_eq!(name, "x");
         }
         _ => panic!("expected an InterpreterError"),
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 1c3a484a379..0aa19942846 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -45,7 +45,7 @@ impl Field {
     /// (e.g. 254 for the BN254 field) cause a compile-time error.
     // docs:start:to_le_bits
     pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {
-        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
         self.to_le_bits_unsafe()
     }
     // docs:end:to_le_bits
@@ -78,7 +78,7 @@ impl Field {
     /// (e.g. 254 for the BN254 field) cause a compile-time error.
     // docs:start:to_be_bits
     pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {
-        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
         self.to_be_bits_unsafe()
     }
     // docs:end:to_be_bits
@@ -177,7 +177,7 @@ impl Field {
 
     // `_radix` must be less than 256
     fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {
-        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
         // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
         self.__to_le_radix_unsafe(radix)
     }
@@ -188,7 +188,7 @@ impl Field {
 
     // `_radix` must be less than 256
     fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {
-        static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits");
+        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
         // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
         self.__to_be_radix_unsafe(radix)
     }
diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index b33df08bd04..fb46752115c 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -417,9 +417,12 @@ mod tests {
                 )
                 // TODO: collect all errors and only error at the end! (i.e. don't fail fast)
                 .unwrap_or_else(|err| {
-                    let error_string: String = err.iter().map(|diagnostic| {
-                        format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager))
-                    }).collect();
+                    let error_string: String = err
+                        .iter()
+                        .map(|diagnostic| {
+                            format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager))
+                        })
+                        .collect();
                     panic!("Failed to compile:\n\n{}", error_string)
                 });
 
diff --git a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
index 7651d663c19..bc4621c92ea 100644
--- a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
+++ b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs
@@ -514,7 +514,10 @@ fn package_start(package_name: &str, test_count: usize) -> std::io::Result<()> {
     Ok(())
 }
 
-pub(crate) fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String {
+pub(crate) fn diagnostic_to_string(
+    file_diagnostic: &FileDiagnostic,
+    file_manager: &FileManager,
+) -> String {
     let file_map = file_manager.as_file_map();
 
     let custom_diagnostic = &file_diagnostic.diagnostic;

From 75eb48ca0e90e71ca0613b8c27021a3fa6dfb5fc Mon Sep 17 00:00:00 2001
From: "Michael J. Klein" <lambdamichael@gmail.com>
Date: Tue, 14 Jan 2025 14:05:13 -0500
Subject: [PATCH 06/18] reverting To(Bits|Radix)Unsafe changes, wip testing
 'self.range_constraint(input_expr_witness, bit_size)?;' for to_radix

---
 .../src/acir/generated_acir.rs                |  5 ++
 compiler/noirc_evaluator/src/acir/mod.rs      |  4 +-
 .../src/brillig/brillig_gen/brillig_block.rs  |  4 +-
 .../check_for_underconstrained_values.rs      |  8 +--
 .../src/ssa/function_builder/mod.rs           |  2 +-
 .../noirc_evaluator/src/ssa/ir/instruction.rs | 28 ++++----
 .../src/ssa/ir/instruction/call.rs            | 14 ++--
 .../src/ssa/opt/constant_folding.rs           | 14 ++--
 .../src/ssa/opt/flatten_cfg.rs                |  2 +-
 .../src/ssa/opt/remove_bit_shifts.rs          |  3 +-
 .../src/ssa/opt/remove_enable_side_effects.rs |  4 +-
 .../src/ssa/opt/remove_if_else.rs             |  4 +-
 .../src/hir/comptime/interpreter/builtin.rs   | 28 ++++----
 noir_stdlib/src/field/mod.nr                  | 64 +++----------------
 14 files changed, 70 insertions(+), 114 deletions(-)

diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs
index 14ceac62461..ee938a77dd9 100644
--- a/compiler/noirc_evaluator/src/acir/generated_acir.rs
+++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs
@@ -368,12 +368,17 @@ impl<F: AcirField> GeneratedAcir<F> {
             "ICE: Radix must be a power of 2"
         );
 
+        // TODO: is this range_constraint useful?
+        let input_expr_witness = self.create_witness_for_expression(input_expr);
+        self.range_constraint(input_expr_witness, bit_size)?;
+
         let limb_witnesses = self.brillig_to_radix(input_expr, radix, limb_count);
 
         let mut composed_limbs = Expression::default();
 
         let mut radix_pow = BigUint::from(1u128);
         for limb_witness in &limb_witnesses {
+            // TODO: do we need both range_constraint's?
             self.range_constraint(*limb_witness, bit_size)?;
 
             composed_limbs = composed_limbs.add_mul(
diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs
index 55553d77adb..a250189d3f1 100644
--- a/compiler/noirc_evaluator/src/acir/mod.rs
+++ b/compiler/noirc_evaluator/src/acir/mod.rs
@@ -2148,7 +2148,7 @@ impl<'a> Context<'a> {
             Intrinsic::ApplyRangeConstraint => {
                 unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`");
             }
-            Intrinsic::ToRadixUnsafe(endian) => {
+            Intrinsic::ToRadix(endian) => {
                 let field = self.convert_value(arguments[0], dfg).into_var()?;
                 let radix = self.convert_value(arguments[1], dfg).into_var()?;
 
@@ -2167,7 +2167,7 @@ impl<'a> Context<'a> {
                     )
                     .map(|array| vec![array])
             }
-            Intrinsic::ToBitsUnsafe(endian) => {
+            Intrinsic::ToBits(endian) => {
                 let field = self.convert_value(arguments[0], dfg).into_var()?;
 
                 let Type::Array(result_type, array_length) = dfg.type_of_value(result_ids[0])
diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
index 575a2229807..e9bc6b127f7 100644
--- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
+++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
@@ -496,7 +496,7 @@ impl<'block> BrilligBlock<'block> {
                                 arguments,
                             );
                         }
-                        Intrinsic::ToBitsUnsafe(endianness) => {
+                        Intrinsic::ToBits(endianness) => {
                             let results = dfg.instruction_results(instruction_id);
 
                             let source = self.convert_ssa_single_addr_value(arguments[0], dfg);
@@ -526,7 +526,7 @@ impl<'block> BrilligBlock<'block> {
                             self.brillig_context.deallocate_single_addr(two);
                         }
 
-                        Intrinsic::ToRadixUnsafe(endianness) => {
+                        Intrinsic::ToRadix(endianness) => {
                             let results = dfg.instruction_results(instruction_id);
 
                             let source = self.convert_ssa_single_addr_value(arguments[0], dfg);
diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
index 90921e9f706..80dde5e27f3 100644
--- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
+++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs
@@ -307,8 +307,8 @@ impl DependencyContext {
                             | Intrinsic::SliceRemove
                             | Intrinsic::StaticAssert
                             | Intrinsic::StrAsBytes
-                            | Intrinsic::ToBitsUnsafe(..)
-                            | Intrinsic::ToRadixUnsafe(..)
+                            | Intrinsic::ToBits(..)
+                            | Intrinsic::ToRadix(..)
                             | Intrinsic::FieldLessThan => {
                                 // Record all the function arguments as parents of the results
                                 self.update_children(&arguments, &results);
@@ -587,8 +587,8 @@ impl Context {
                             | Intrinsic::SliceRemove
                             | Intrinsic::StaticAssert
                             | Intrinsic::StrAsBytes
-                            | Intrinsic::ToBitsUnsafe(..)
-                            | Intrinsic::ToRadixUnsafe(..)
+                            | Intrinsic::ToBits(..)
+                            | Intrinsic::ToRadix(..)
                             | Intrinsic::FieldLessThan => {
                                 self.value_sets.push(instruction_arguments_and_results);
                             }
diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
index be6532c2579..3d7e0b06d5b 100644
--- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
+++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
@@ -558,7 +558,7 @@ mod tests {
         let one = builder.numeric_constant(FieldElement::one(), NumericType::bool());
         let zero = builder.numeric_constant(FieldElement::zero(), NumericType::bool());
 
-        let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBitsUnsafe(Endian::Little));
+        let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBits(Endian::Little));
         let input = builder.field_constant(FieldElement::from(7_u128));
         let length = builder.field_constant(FieldElement::from(8_u128));
         let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), 8)];
diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
index c63f280a9ca..786c3671d38 100644
--- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
+++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs
@@ -61,8 +61,8 @@ pub(crate) enum Intrinsic {
     SliceRemove,
     ApplyRangeConstraint,
     StrAsBytes,
-    ToBitsUnsafe(Endian),
-    ToRadixUnsafe(Endian),
+    ToBits(Endian),
+    ToRadix(Endian),
     BlackBox(BlackBoxFunc),
     Hint(Hint),
     AsWitness,
@@ -89,10 +89,10 @@ impl std::fmt::Display for Intrinsic {
             Intrinsic::SliceRemove => write!(f, "slice_remove"),
             Intrinsic::StrAsBytes => write!(f, "str_as_bytes"),
             Intrinsic::ApplyRangeConstraint => write!(f, "apply_range_constraint"),
-            Intrinsic::ToBitsUnsafe(Endian::Big) => write!(f, "to_be_bits_unsafe"),
-            Intrinsic::ToBitsUnsafe(Endian::Little) => write!(f, "to_le_bits_unsafe"),
-            Intrinsic::ToRadixUnsafe(Endian::Big) => write!(f, "to_be_radix_unsafe"),
-            Intrinsic::ToRadixUnsafe(Endian::Little) => write!(f, "to_le_radix_unsafe"),
+            Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"),
+            Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"),
+            Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"),
+            Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"),
             Intrinsic::BlackBox(function) => write!(f, "{function}"),
             Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"),
             Intrinsic::AsWitness => write!(f, "as_witness"),
@@ -124,7 +124,7 @@ impl Intrinsic {
             | Intrinsic::AsWitness => true,
 
             // These apply a constraint that the input must fit into a specified number of limbs.
-            Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_) => true,
+            Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true,
 
             // These imply a check that the slice is non-empty and should fail otherwise.
             Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => true,
@@ -164,8 +164,8 @@ impl Intrinsic {
             // directly depend on the corresponding `enable_side_effect` instruction any more.
             // However, to conform with the expectations of `Instruction::can_be_deduplicated` and
             // `constant_folding` we only use this information if the caller shows interest in it.
-            Intrinsic::ToBitsUnsafe(_)
-            | Intrinsic::ToRadixUnsafe(_)
+            Intrinsic::ToBits(_)
+            | Intrinsic::ToRadix(_)
             | Intrinsic::BlackBox(
                 BlackBoxFunc::MultiScalarMul
                 | BlackBoxFunc::EmbeddedCurveAdd
@@ -203,10 +203,10 @@ impl Intrinsic {
             "slice_insert" => Some(Intrinsic::SliceInsert),
             "slice_remove" => Some(Intrinsic::SliceRemove),
             "str_as_bytes" => Some(Intrinsic::StrAsBytes),
-            "to_le_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Little)),
-            "to_be_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Big)),
-            "to_le_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Little)),
-            "to_be_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Big)),
+            "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)),
+            "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)),
+            "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)),
+            "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)),
             "as_witness" => Some(Intrinsic::AsWitness),
             "is_unconstrained" => Some(Intrinsic::IsUnconstrained),
             "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators),
@@ -220,7 +220,7 @@ impl Intrinsic {
     }
 }
 
-/// The endian-ness of bits when encoding values as bits in e.g. ToBitsUnsafe or ToRadixUnsafe
+/// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub(crate) enum Endian {
     Big,
diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
index 0efab65138c..992c633ffcd 100644
--- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
+++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
@@ -52,16 +52,16 @@ pub(super) fn simplify_call(
         arguments.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect();
 
     let simplified_result = match intrinsic {
-        Intrinsic::ToBitsUnsafe(endian) => {
+        Intrinsic::ToBits(endian) => {
             // TODO: simplify to a range constraint if `limb_count == 1`
             if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) {
                 let field = constant_args[0];
                 let limb_count = if let Type::Array(_, array_len) = return_type {
                     array_len
                 } else {
-                    unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array")
+                    unreachable!("ICE: Intrinsic::ToRadix return type must be array")
                 };
-                constant_to_radix_unsafe(endian, field, 2, limb_count, |values| {
+                constant_to_radix(endian, field, 2, limb_count, |values| {
                     make_constant_array(
                         dfg,
                         values.into_iter(),
@@ -74,7 +74,7 @@ pub(super) fn simplify_call(
                 SimplifyResult::None
             }
         }
-        Intrinsic::ToRadixUnsafe(endian) => {
+        Intrinsic::ToRadix(endian) => {
             // TODO: simplify to a range constraint if `limb_count == 1`
             if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) {
                 let field = constant_args[0];
@@ -82,9 +82,9 @@ pub(super) fn simplify_call(
                 let limb_count = if let Type::Array(_, array_len) = return_type {
                     array_len
                 } else {
-                    unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array")
+                    unreachable!("ICE: Intrinsic::ToRadix return type must be array")
                 };
-                constant_to_radix_unsafe(endian, field, radix, limb_count, |values| {
+                constant_to_radix(endian, field, radix, limb_count, |values| {
                     make_constant_array(
                         dfg,
                         values.into_iter(),
@@ -641,7 +641,7 @@ fn make_array(
 }
 
 /// Returns a slice (represented by a tuple (len, slice)) of constants corresponding to the limbs of the radix decomposition.
-fn constant_to_radix_unsafe(
+fn constant_to_radix(
     endian: Endian,
     field: FieldElement,
     radix: u32,
diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
index 4b00bbd05d2..5b75055c09a 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
@@ -1558,13 +1558,13 @@ mod test {
         // After EnableSideEffectsIf removal:
         brillig(inline) fn main f0 {
           b0(v0: Field, v1: Field, v2: u1):
-            v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]    // `a.to_be_radix_unsafe(256)`;
+            v7 = call to_be_radix(v0, u32 256) -> [u8; 1]    // `a.to_be_radix(256)`;
             inc_rc v7
-            v8 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]    // duplicate load of `a`
+            v8 = call to_be_radix(v0, u32 256) -> [u8; 1]    // duplicate load of `a`
             inc_rc v8
-            v9 = cast v2 as Field                                   // `if c { a.to_be_radix_unsafe(256) }`
-            v10 = mul v0, v9                                        // attaching `c` to `a`
-            v11 = call to_be_radix_unsafe(v10, u32 256) -> [u8; 1]  // calling `to_radix(c * a)`
+            v9 = cast v2 as Field                            // `if c { a.to_be_radix(256) }`
+            v10 = mul v0, v9                                 // attaching `c` to `a`
+            v11 = call to_be_radix(v10, u32 256) -> [u8; 1]  // calling `to_radix(c * a)`
             inc_rc v11
             return
         }
@@ -1573,12 +1573,12 @@ mod test {
         let expected = "
         brillig(inline) fn main f0 {
           b0(v0: Field, v1: Field, v2: u1):
-            v5 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1]
+            v5 = call to_be_radix(v0, u32 256) -> [u8; 1]
             inc_rc v5
             inc_rc v5
             v6 = cast v2 as Field
             v7 = mul v0, v6
-            v8 = call to_be_radix_unsafe(v7, u32 256) -> [u8; 1]
+            v8 = call to_be_radix(v7, u32 256) -> [u8; 1]
             inc_rc v8
             return
         }
diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
index d4276b4a214..76f8495c009 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
@@ -697,7 +697,7 @@ impl<'f> Context<'f> {
                 }
                 Instruction::Call { func, mut arguments } => match self.inserter.function.dfg[func]
                 {
-                    Value::Intrinsic(Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_)) => {
+                    Value::Intrinsic(Intrinsic::ToBits(_) | Intrinsic::ToRadix(_)) => {
                         let field = arguments[0];
                         let casted_condition =
                             self.cast_condition_to_value_type(condition, field, call_stack);
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
index 11439ed964f..e36be71aeea 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
@@ -217,8 +217,7 @@ impl Context<'_> {
     fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId {
         let typ = self.function.dfg.type_of_value(rhs);
         if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ {
-            let to_bits =
-                self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little));
+            let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBits(Endian::Little));
             let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)];
             let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types);
 
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
index 52e0c1cd68a..ca9b75643bc 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs
@@ -173,8 +173,8 @@ impl Context {
                     | Intrinsic::StaticAssert
                     | Intrinsic::ApplyRangeConstraint
                     | Intrinsic::StrAsBytes
-                    | Intrinsic::ToBitsUnsafe(_)
-                    | Intrinsic::ToRadixUnsafe(_)
+                    | Intrinsic::ToBits(_)
+                    | Intrinsic::ToRadix(_)
                     | Intrinsic::BlackBox(_)
                     | Intrinsic::Hint(Hint::BlackBox)
                     | Intrinsic::AsSlice
diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
index 160b9b54526..8dde79a3c60 100644
--- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
+++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
@@ -234,8 +234,8 @@ fn slice_capacity_change(
         | Intrinsic::AsWitness
         | Intrinsic::IsUnconstrained
         | Intrinsic::DerivePedersenGenerators
-        | Intrinsic::ToBitsUnsafe(_)
-        | Intrinsic::ToRadixUnsafe(_)
+        | Intrinsic::ToBits(_)
+        | Intrinsic::ToRadix(_)
         | Intrinsic::ArrayRefCount
         | Intrinsic::SliceRefCount
         | Intrinsic::FieldLessThan => SizeChange::None,
diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
index 13eadad3bd9..83ef4077fb5 100644
--- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
+++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
@@ -191,10 +191,10 @@ impl<'local, 'context> Interpreter<'local, 'context> {
             "struct_def_module" => struct_def_module(self, arguments, location),
             "struct_def_name" => struct_def_name(interner, arguments, location),
             "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location),
-            "to_be_radix_unsafe" => to_be_radix_unsafe(arguments, return_type, location),
-            "to_le_radix_unsafe" => to_le_radix_unsafe(arguments, return_type, location),
-            "to_be_bits_unsafe" => to_be_bits_unsafe(arguments, return_type, location),
-            "to_le_bits_unsafe" => to_le_bits_unsafe(arguments, return_type, location),
+            "to_be_radix" => to_be_radix(arguments, return_type, location),
+            "to_le_radix" => to_le_radix(arguments, return_type, location),
+            "to_be_bits" => to_be_bits(arguments, return_type, location),
+            "to_le_bits" => to_le_bits(arguments, return_type, location),
             "trait_constraint_eq" => trait_constraint_eq(arguments, location),
             "trait_constraint_hash" => trait_constraint_hash(arguments, location),
             "trait_def_as_trait_constraint" => {
@@ -801,42 +801,42 @@ fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResu
     ))
 }
 
-fn to_be_bits_unsafe(
+fn to_be_bits(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
 ) -> IResult<Value> {
     let value = check_one_argument(arguments, location)?;
     let radix = (Value::U32(2), value.1);
-    to_be_radix_unsafe(vec![value, radix], return_type, location)
+    to_be_radix(vec![value, radix], return_type, location)
 }
 
-fn to_le_bits_unsafe(
+fn to_le_bits(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
 ) -> IResult<Value> {
     let value = check_one_argument(arguments, location)?;
     let radix = (Value::U32(2), value.1);
-    to_le_radix_unsafe(vec![value, radix], return_type, location)
+    to_le_radix(vec![value, radix], return_type, location)
 }
 
-fn to_be_radix_unsafe(
+fn to_be_radix(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
 ) -> IResult<Value> {
-    let le_radix_limbs = to_le_radix_unsafe(arguments, return_type, location)?;
+    let le_radix_limbs = to_le_radix(arguments, return_type, location)?;
 
     let Value::Array(limbs, typ) = le_radix_limbs else {
-        unreachable!("`to_le_radix_unsafe` should always return an array");
+        unreachable!("`to_le_radix` should always return an array");
     };
     let be_radix_limbs = limbs.into_iter().rev().collect();
 
     Ok(Value::Array(be_radix_limbs, typ))
 }
 
-fn to_le_radix_unsafe(
+fn to_le_radix(
     arguments: Vec<(Value, Location)>,
     return_type: Type,
     location: Location,
@@ -863,7 +863,7 @@ fn to_le_radix_unsafe(
         *element_type == Type::Integer(Signedness::Unsigned, IntegerBitSize::One);
 
     // Decompose the integer into its radix digits in little endian form.
-    let decomposed_integer = compute_to_radix_le_unsafe(value, radix);
+    let decomposed_integer = compute_to_radix_le(value, radix);
     let decomposed_integer = vecmap(0..limb_count.to_u128() as usize, |i| {
         let digit = match decomposed_integer.get(i) {
             Some(digit) => *digit,
@@ -885,7 +885,7 @@ fn to_le_radix_unsafe(
     Ok(Value::Array(decomposed_integer.into(), result_type))
 }
 
-fn compute_to_radix_le_unsafe(field: FieldElement, radix: u32) -> Vec<u8> {
+fn compute_to_radix_le(field: FieldElement, radix: u32) -> Vec<u8> {
     let bit_size = u32::BITS - (radix - 1).leading_zeros();
     let radix_big = BigUint::from(radix);
     assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2");
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 0aa19942846..92aae5cbc56 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -28,26 +28,9 @@ impl Field {
     /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
     /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will
     /// wrap around due to overflow when verifying the decomposition.
-    #[builtin(to_le_bits_unsafe)]
-    // docs:start:to_le_bits_unsafe
-    pub fn to_le_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {}
-    // docs:end:to_le_bits_unsafe
-
-    /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array.
-    /// This slice will be zero padded should not all bits be necessary to represent `self`.
-    ///
-    /// # Failures
-    /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not
-    /// be able to represent the original `Field`.
-    ///
-    /// # Safety
-    /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
-    /// (e.g. 254 for the BN254 field) cause a compile-time error.
+    #[builtin(to_le_bits)]
     // docs:start:to_le_bits
-    pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {
-        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
-        self.to_le_bits_unsafe()
-    }
+    pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {}
     // docs:end:to_le_bits
 
     /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array.
@@ -61,26 +44,9 @@ impl Field {
     /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
     /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will
     /// wrap around due to overflow when verifying the decomposition.
-    #[builtin(to_be_bits_unsafe)]
-    // docs:start:to_be_bits_unsafe
-    pub fn to_be_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {}
-    // docs:end:to_be_bits_unsafe
-
-    /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array.
-    /// This array will be zero padded should not all bits be necessary to represent `self`.
-    ///
-    /// # Failures
-    /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not
-    /// be able to represent the original `Field`.
-    ///
-    /// # Safety
-    /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus
-    /// (e.g. 254 for the BN254 field) cause a compile-time error.
+    #[builtin(to_be_bits)]
     // docs:start:to_be_bits
-    pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {
-        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
-        self.to_be_bits_unsafe()
-    }
+    pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {}
     // docs:end:to_be_bits
 
     /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array
@@ -172,26 +138,12 @@ impl Field {
     // docs:end:to_be_radix
 
     // `_radix` must be less than 256
-    #[builtin(to_le_radix_unsafe)]
-    fn __to_le_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {}
-
-    // `_radix` must be less than 256
-    fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {
-        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
-        // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
-        self.__to_le_radix_unsafe(radix)
-    }
+    #[builtin(to_le_radix)]
+    fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
 
     // `_radix` must be less than 256
-    #[builtin(to_be_radix_unsafe)]
-    fn __to_be_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {}
-
-    // `_radix` must be less than 256
-    fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {
-        static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits");
-        // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256");
-        self.__to_be_radix_unsafe(radix)
-    }
+    #[builtin(to_be_radix)]
+    fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {}
 
     // Returns self to the power of the given exponent value.
     // Caution: we assume the exponent fits into 32 bits

From fbc93ca8dd0667d06d3e8e1b1859f4e8bda67fe0 Mon Sep 17 00:00:00 2001
From: "Michael J. Klein" <lambdamichael@gmail.com>
Date: Tue, 14 Jan 2025 14:19:14 -0500
Subject: [PATCH 07/18] cargo clippy

---
 tooling/nargo_cli/src/cli/compile_cmd.rs | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index fb46752115c..0c7ad5a1857 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -325,6 +325,7 @@ pub(crate) fn get_target_width(
 #[cfg(test)]
 mod tests {
     use std::{
+        fmt::Write,
         path::{Path, PathBuf},
         str::FromStr,
     };
@@ -419,10 +420,10 @@ mod tests {
                 .unwrap_or_else(|err| {
                     let error_string: String = err
                         .iter()
-                        .map(|diagnostic| {
-                            format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager))
-                        })
-                        .collect();
+                        .fold(String::new(), |mut output, diagnostic| {
+                            let _ = write!(output, "{}\n---\n", diagnostic_to_string(diagnostic, &file_manager));
+                            output
+                        });
                     panic!("Failed to compile:\n\n{}", error_string)
                 });
 

From 713d9b771bba846dbef68b09fc9b27ff6dd15e42 Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Wed, 15 Jan 2025 14:36:39 -0500
Subject: [PATCH 08/18] update test fixed by recent PR, remove broken wip range
 constraint, cargo clippy/fmt

---
 compiler/noirc_evaluator/src/acir/generated_acir.rs |  6 ------
 noir_stdlib/src/collections/bounded_vec.nr          |  3 +--
 tooling/nargo_cli/src/cli/compile_cmd.rs            | 11 +++++++----
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs
index ee938a77dd9..21c8bfe7d5c 100644
--- a/compiler/noirc_evaluator/src/acir/generated_acir.rs
+++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs
@@ -367,18 +367,12 @@ impl<F: AcirField> GeneratedAcir<F> {
             radix_big,
             "ICE: Radix must be a power of 2"
         );
-
-        // TODO: is this range_constraint useful?
-        let input_expr_witness = self.create_witness_for_expression(input_expr);
-        self.range_constraint(input_expr_witness, bit_size)?;
-
         let limb_witnesses = self.brillig_to_radix(input_expr, radix, limb_count);
 
         let mut composed_limbs = Expression::default();
 
         let mut radix_pow = BigUint::from(1u128);
         for limb_witness in &limb_witnesses {
-            // TODO: do we need both range_constraint's?
             self.range_constraint(*limb_witness, bit_size)?;
 
             composed_limbs = composed_limbs.add_mul(
diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr
index a6760f4071d..8624233d398 100644
--- a/noir_stdlib/src/collections/bounded_vec.nr
+++ b/noir_stdlib/src/collections/bounded_vec.nr
@@ -612,8 +612,7 @@ mod bounded_vec_tests {
             assert_eq(bounded_vec.get(2), 3);
         }
 
-        // TODO(https://github.com/noir-lang/noir/issues/6995): #[test(should_fail_with = "from array out of bounds")]
-        #[test(should_fail_with = "Argument is false")]
+        #[test(should_fail_with = "from array out of bounds")]
         fn max_len_lower_then_array_len() {
             let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]);
         }
diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index 0c7ad5a1857..35303866a72 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -418,10 +418,13 @@ mod tests {
                 )
                 // TODO: collect all errors and only error at the end! (i.e. don't fail fast)
                 .unwrap_or_else(|err| {
-                    let error_string: String = err
-                        .iter()
-                        .fold(String::new(), |mut output, diagnostic| {
-                            let _ = write!(output, "{}\n---\n", diagnostic_to_string(diagnostic, &file_manager));
+                    let error_string: String =
+                        err.iter().fold(String::new(), |mut output, diagnostic| {
+                            let _ = write!(
+                                output,
+                                "{}\n---\n",
+                                diagnostic_to_string(diagnostic, &file_manager)
+                            );
                             output
                         });
                     panic!("Failed to compile:\n\n{}", error_string)

From 68c306040b46a9f9277bcc835c7cf33d9df1dd79 Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Wed, 15 Jan 2025 16:24:34 -0500
Subject: [PATCH 09/18] nargo fmt

---
 noir_stdlib/src/field/mod.nr | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 92aae5cbc56..957c90ace25 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -10,7 +10,10 @@ impl Field {
     // docs:start:assert_max_bit_size
     pub fn assert_max_bit_size<let BIT_SIZE: u32>(self) {
         // docs:end:assert_max_bit_size
-        static_assert(BIT_SIZE < modulus_num_bits() as u32, "BIT_SIZE must be less than modulus_num_bits");
+        static_assert(
+            BIT_SIZE < modulus_num_bits() as u32,
+            "BIT_SIZE must be less than modulus_num_bits",
+        );
         self.__assert_max_bit_size(BIT_SIZE);
     }
 
@@ -61,7 +64,10 @@ impl Field {
     // docs:start:to_le_bytes
     pub fn to_le_bytes<let N: u32>(self: Self) -> [u8; N] {
         // docs:end:to_le_bytes
-        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
+        static_assert(
+            N <= modulus_le_bytes().len(),
+            "N must be less than or equal to modulus_le_bytes().len()",
+        );
         // Compute the byte decomposition
         let bytes = self.to_le_radix(256);
 
@@ -95,7 +101,10 @@ impl Field {
     // docs:start:to_be_bytes
     pub fn to_be_bytes<let N: u32>(self: Self) -> [u8; N] {
         // docs:end:to_be_bytes
-        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
+        static_assert(
+            N <= modulus_le_bytes().len(),
+            "N must be less than or equal to modulus_le_bytes().len()",
+        );
         // Compute the byte decomposition
         let bytes = self.to_be_radix(256);
 
@@ -175,7 +184,10 @@ impl Field {
     /// Convert a little endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field {
-        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
+        static_assert(
+            N <= modulus_le_bytes().len(),
+            "N must be less than or equal to modulus_le_bytes().len()",
+        );
         let mut v = 1;
         let mut result = 0;
 
@@ -189,7 +201,10 @@ impl Field {
     /// Convert a big endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field {
-        static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()");
+        static_assert(
+            N <= modulus_le_bytes().len(),
+            "N must be less than or equal to modulus_le_bytes().len()",
+        );
         let mut v = 1;
         let mut result = 0;
 

From b4c8c364386d3a39322aa9f5cbad339b7e9dfe6d Mon Sep 17 00:00:00 2001
From: Michael J Klein <michaeljklein@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:18:10 -0500
Subject: [PATCH 10/18] Update noir_stdlib/src/uint128.nr

---
 noir_stdlib/src/uint128.nr | 1 -
 1 file changed, 1 deletion(-)

diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr
index 4bb869df6ee..c1f4fd62715 100644
--- a/noir_stdlib/src/uint128.nr
+++ b/noir_stdlib/src/uint128.nr
@@ -68,7 +68,6 @@ impl U128 {
     }
 
     pub fn from_hex<let N: u32>(hex: str<N>) -> U128 {
-        // TODO cleanup if working
         // let N = N as u32;
         let bytes = hex.as_bytes();
         // string must starts with "0x"

From ba79d2f8931a9f0c44efa2f88db271637a6097f2 Mon Sep 17 00:00:00 2001
From: Michael J Klein <michaeljklein@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:18:16 -0500
Subject: [PATCH 11/18] Update noir_stdlib/src/uint128.nr

---
 noir_stdlib/src/uint128.nr | 1 -
 1 file changed, 1 deletion(-)

diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr
index c1f4fd62715..446ed4fb092 100644
--- a/noir_stdlib/src/uint128.nr
+++ b/noir_stdlib/src/uint128.nr
@@ -68,7 +68,6 @@ impl U128 {
     }
 
     pub fn from_hex<let N: u32>(hex: str<N>) -> U128 {
-        // let N = N as u32;
         let bytes = hex.as_bytes();
         // string must starts with "0x"
         assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string");

From 5b4c5d45775ac47f7482aab426a2f71fde987ec8 Mon Sep 17 00:00:00 2001
From: Michael J Klein <michaeljklein@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:18:34 -0500
Subject: [PATCH 12/18] Update
 compiler/noirc_frontend/src/tests/metaprogramming.rs

---
 compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs
index 1efbb4efcd7..65c32edc5a2 100644
--- a/compiler/noirc_frontend/src/tests/metaprogramming.rs
+++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs
@@ -36,7 +36,6 @@ fn comptime_code_rejects_dynamic_variable() {
     let errors = get_program_errors(src);
 
     // TODO cleanup
-    dbg!(&errors);
 
     assert_eq!(errors.len(), 1);
     match &errors[0].0 {

From c7c3e8fe78e27f188b46db00139a3024da542329 Mon Sep 17 00:00:00 2001
From: Michael J Klein <michaeljklein@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:18:45 -0500
Subject: [PATCH 13/18] Update tooling/nargo_cli/src/cli/compile_cmd.rs

---
 tooling/nargo_cli/src/cli/compile_cmd.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index 35303866a72..3c55d8c2164 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -416,7 +416,6 @@ mod tests {
                     &CompileOptions::default(),
                     None,
                 )
-                // TODO: collect all errors and only error at the end! (i.e. don't fail fast)
                 .unwrap_or_else(|err| {
                     let error_string: String =
                         err.iter().fold(String::new(), |mut output, diagnostic| {

From 8214dad7ad11f9497f2a25acf6370fa4cd8467c2 Mon Sep 17 00:00:00 2001
From: Michael J Klein <michaeljklein@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:19:01 -0500
Subject: [PATCH 14/18] Update
 compiler/noirc_frontend/src/tests/metaprogramming.rs

---
 compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs
index 65c32edc5a2..93426c1348c 100644
--- a/compiler/noirc_frontend/src/tests/metaprogramming.rs
+++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs
@@ -35,7 +35,6 @@ fn comptime_code_rejects_dynamic_variable() {
     }"#;
     let errors = get_program_errors(src);
 
-    // TODO cleanup
 
     assert_eq!(errors.len(), 1);
     match &errors[0].0 {

From d1536d5ca96a3dbd5cabff5c616e19ac97817879 Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Tue, 21 Jan 2025 12:57:49 -0500
Subject: [PATCH 15/18] cargo fmt

---
 compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs
index 93426c1348c..b42342fa47d 100644
--- a/compiler/noirc_frontend/src/tests/metaprogramming.rs
+++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs
@@ -35,7 +35,6 @@ fn comptime_code_rejects_dynamic_variable() {
     }"#;
     let errors = get_program_errors(src);
 
-
     assert_eq!(errors.len(), 1);
     match &errors[0].0 {
         CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced {

From 8a1e7d27935aa9c904e13ae72efa1b213ebec72d Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Tue, 21 Jan 2025 13:39:45 -0500
Subject: [PATCH 16/18] nargo fmt

---
 noir_stdlib/src/collections/bounded_vec.nr | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr
index 6c4772f6d55..c030544e791 100644
--- a/noir_stdlib/src/collections/bounded_vec.nr
+++ b/noir_stdlib/src/collections/bounded_vec.nr
@@ -1,4 +1,4 @@
-use crate::{cmp::Eq, convert::From, static_assert, runtime::is_unconstrained};
+use crate::{cmp::Eq, convert::From, runtime::is_unconstrained, static_assert};
 
 /// A `BoundedVec<T, MaxLen>` is a growable storage similar to a `Vec<T>` except that it
 /// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented

From 7d442d8afcc493cb0cd03e07e2218c558797df27 Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Wed, 22 Jan 2025 12:43:45 -0500
Subject: [PATCH 17/18] add static_assert's to to_le_radix, add noir stdlib and
 compile failure tests, wip debugging test failures, cleanup diagnostic
 printing

---
 noir_stdlib/src/field/mod.nr                  | 45 ++++++++++++++++---
 .../compile_failure/to_le_radix_1/Nargo.toml  |  6 +++
 .../compile_failure/to_le_radix_1/src/main.nr |  5 +++
 .../to_le_radix_257/Nargo.toml                |  6 +++
 .../to_le_radix_257/src/main.nr               |  5 +++
 .../compile_failure/to_le_radix_3/Nargo.toml  |  6 +++
 .../compile_failure/to_le_radix_3/src/main.nr |  5 +++
 tooling/nargo_cli/src/cli/compile_cmd.rs      | 15 ++-----
 8 files changed, 75 insertions(+), 18 deletions(-)
 create mode 100644 test_programs/compile_failure/to_le_radix_1/Nargo.toml
 create mode 100644 test_programs/compile_failure/to_le_radix_1/src/main.nr
 create mode 100644 test_programs/compile_failure/to_le_radix_257/Nargo.toml
 create mode 100644 test_programs/compile_failure/to_le_radix_257/src/main.nr
 create mode 100644 test_programs/compile_failure/to_le_radix_3/Nargo.toml
 create mode 100644 test_programs/compile_failure/to_le_radix_3/src/main.nr

diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 957c90ace25..0317b3d7552 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -129,9 +129,23 @@ impl Field {
     // docs:start:to_le_radix
     pub fn to_le_radix<let N: u32>(self: Self, radix: u32) -> [u8; N] {
         // Brillig does not need an immediate radix
-        if !crate::runtime::is_unconstrained() {
-            crate::assert_constant(radix);
-        }
+        // if !crate::runtime::is_unconstrained() {
+        static_assert(false, "hi!");
+        static_assert(radix == 256, "radix must be 256");
+
+        static_assert(
+            1 < radix,
+            "radix must be greater than 1",
+        );
+        static_assert(
+            radix <= 256,
+            "radix must be less than or equal to 256",
+        );
+        static_assert(
+            radix & (radix - 1) == 0,
+            "radix must be a power of 2",
+        );
+        // }
         self.__to_le_radix(radix)
     }
     // docs:end:to_le_radix
@@ -201,10 +215,6 @@ impl Field {
     /// Convert a big endian byte array to a field element.
     /// If the provided byte array overflows the field modulus then the Field will silently wrap around.
     pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field {
-        static_assert(
-            N <= modulus_le_bytes().len(),
-            "N must be less than or equal to modulus_le_bytes().len()",
-        );
         let mut v = 1;
         let mut result = 0;
 
@@ -342,6 +352,27 @@ mod tests {
     }
     // docs:end:to_le_radix_example
 
+    #[test]
+    fn test_to_le_radix_1() {
+        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
+        let field = 2;
+        let _: [u8; 8] = field.to_le_radix(1);
+    }
+
+    #[test]
+    fn test_to_le_radix_3() {
+        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
+        let field = 2;
+        let _: [u8; 8] = field.to_le_radix(3);
+    }
+
+    #[test]
+    fn test_to_le_radix_257() {
+        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
+        let field = 2;
+        let _: [u8; 8] = field.to_le_radix(257);
+    }
+
     #[test]
     unconstrained fn test_field_less_than() {
         assert(field_less_than(0, 1));
diff --git a/test_programs/compile_failure/to_le_radix_1/Nargo.toml b/test_programs/compile_failure/to_le_radix_1/Nargo.toml
new file mode 100644
index 00000000000..d1683bad4c2
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_1/Nargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "to_le_radix_1"
+type = "bin"
+authors = [""]
+
+[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_1/src/main.nr b/test_programs/compile_failure/to_le_radix_1/src/main.nr
new file mode 100644
index 00000000000..9772451d569
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_1/src/main.nr
@@ -0,0 +1,5 @@
+fn main() {
+    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
+    let field = 2;
+    let _: [u8; 8] = field.to_le_radix(1);
+}
diff --git a/test_programs/compile_failure/to_le_radix_257/Nargo.toml b/test_programs/compile_failure/to_le_radix_257/Nargo.toml
new file mode 100644
index 00000000000..ae1eae8ae61
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_257/Nargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "to_le_radix_257"
+type = "bin"
+authors = [""]
+
+[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_257/src/main.nr b/test_programs/compile_failure/to_le_radix_257/src/main.nr
new file mode 100644
index 00000000000..a399809ec8f
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_257/src/main.nr
@@ -0,0 +1,5 @@
+fn main() {
+    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
+    let field = 2;
+    let _: [u8; 8] = field.to_le_radix(512);
+}
diff --git a/test_programs/compile_failure/to_le_radix_3/Nargo.toml b/test_programs/compile_failure/to_le_radix_3/Nargo.toml
new file mode 100644
index 00000000000..7b1d862806b
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_3/Nargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "to_le_radix_3"
+type = "bin"
+authors = [""]
+
+[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_3/src/main.nr b/test_programs/compile_failure/to_le_radix_3/src/main.nr
new file mode 100644
index 00000000000..365732fd861
--- /dev/null
+++ b/test_programs/compile_failure/to_le_radix_3/src/main.nr
@@ -0,0 +1,5 @@
+fn main() {
+    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
+    let field = 2;
+    let _: [u8; 8] = field.to_le_radix(3);
+}
diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs
index 3c55d8c2164..8a4b991a234 100644
--- a/tooling/nargo_cli/src/cli/compile_cmd.rs
+++ b/tooling/nargo_cli/src/cli/compile_cmd.rs
@@ -325,7 +325,6 @@ pub(crate) fn get_target_width(
 #[cfg(test)]
 mod tests {
     use std::{
-        fmt::Write,
         path::{Path, PathBuf},
         str::FromStr,
     };
@@ -417,16 +416,10 @@ mod tests {
                     None,
                 )
                 .unwrap_or_else(|err| {
-                    let error_string: String =
-                        err.iter().fold(String::new(), |mut output, diagnostic| {
-                            let _ = write!(
-                                output,
-                                "{}\n---\n",
-                                diagnostic_to_string(diagnostic, &file_manager)
-                            );
-                            output
-                        });
-                    panic!("Failed to compile:\n\n{}", error_string)
+                    for diagnostic in err {
+                        println!("{}", diagnostic_to_string(&diagnostic, &file_manager));
+                    }
+                    panic!("Failed to compile")
                 });
 
                 let width = get_target_width(package.expression_width, None);

From 99710bd5a43754f17ea32ea3ed642673179fe38b Mon Sep 17 00:00:00 2001
From: Michael Klein <lambdamichael@gmail.com>
Date: Wed, 22 Jan 2025 15:33:52 -0500
Subject: [PATCH 18/18] remove compile_failure tests now that stdlib tests are
 working, remove debug 'static_assert's and only check radix when not
 is_unconstrained, simplify to nothing when invalid radix, check radix range
 when generating ACIR, add brillig-specific to_radix tests, add handling for
 unconstrained mode for acir to_radix tests

---
 .../src/acir/generated_acir.rs                |  6 ++
 .../src/ssa/ir/instruction/call.rs            |  7 +-
 noir_stdlib/src/field/mod.nr                  | 93 +++++++++++++------
 .../compile_failure/to_le_radix_1/Nargo.toml  |  6 --
 .../compile_failure/to_le_radix_1/src/main.nr |  5 -
 .../to_le_radix_257/Nargo.toml                |  6 --
 .../to_le_radix_257/src/main.nr               |  5 -
 .../compile_failure/to_le_radix_3/Nargo.toml  |  6 --
 .../compile_failure/to_le_radix_3/src/main.nr |  5 -
 9 files changed, 77 insertions(+), 62 deletions(-)
 delete mode 100644 test_programs/compile_failure/to_le_radix_1/Nargo.toml
 delete mode 100644 test_programs/compile_failure/to_le_radix_1/src/main.nr
 delete mode 100644 test_programs/compile_failure/to_le_radix_257/Nargo.toml
 delete mode 100644 test_programs/compile_failure/to_le_radix_257/src/main.nr
 delete mode 100644 test_programs/compile_failure/to_le_radix_3/Nargo.toml
 delete mode 100644 test_programs/compile_failure/to_le_radix_3/src/main.nr

diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs
index 21c8bfe7d5c..8b29888a7ad 100644
--- a/compiler/noirc_evaluator/src/acir/generated_acir.rs
+++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs
@@ -362,6 +362,12 @@ impl<F: AcirField> GeneratedAcir<F> {
         bit_size: u32,
     ) -> Result<Vec<Witness>, RuntimeError> {
         let radix_big = BigUint::from(radix);
+        let radix_range = BigUint::from(2u128)..=BigUint::from(256u128);
+        assert!(
+            radix_range.contains(&radix_big),
+            "ICE: Radix must be in the range 2..=256, but found: {:?}",
+            radix
+        );
         assert_eq!(
             BigUint::from(2u128).pow(bit_size),
             radix_big,
diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
index 992c633ffcd..6ee7aa0192c 100644
--- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
+++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
@@ -650,7 +650,12 @@ fn constant_to_radix(
 ) -> SimplifyResult {
     let bit_size = u32::BITS - (radix - 1).leading_zeros();
     let radix_big = BigUint::from(radix);
-    assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2");
+    let radix_range = BigUint::from(2u128)..=BigUint::from(256u128);
+    if !radix_range.contains(&radix_big) || BigUint::from(2u128).pow(bit_size) != radix_big {
+        // NOTE: expect an error to be thrown later in
+        // acir::generated_acir::radix_le_decompose
+        return SimplifyResult::None;
+    }
     let big_integer = BigUint::from_bytes_be(&field.to_be_bytes());
 
     // Decompose the integer into its radix digits in little endian form.
diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr
index 0317b3d7552..255236477ff 100644
--- a/noir_stdlib/src/field/mod.nr
+++ b/noir_stdlib/src/field/mod.nr
@@ -129,23 +129,11 @@ impl Field {
     // docs:start:to_le_radix
     pub fn to_le_radix<let N: u32>(self: Self, radix: u32) -> [u8; N] {
         // Brillig does not need an immediate radix
-        // if !crate::runtime::is_unconstrained() {
-        static_assert(false, "hi!");
-        static_assert(radix == 256, "radix must be 256");
-
-        static_assert(
-            1 < radix,
-            "radix must be greater than 1",
-        );
-        static_assert(
-            radix <= 256,
-            "radix must be less than or equal to 256",
-        );
-        static_assert(
-            radix & (radix - 1) == 0,
-            "radix must be a power of 2",
-        );
-        // }
+        if !crate::runtime::is_unconstrained() {
+            static_assert(1 < radix, "radix must be greater than 1");
+            static_assert(radix <= 256, "radix must be less than or equal to 256");
+            static_assert(radix & (radix - 1) == 0, "radix must be a power of 2");
+        }
         self.__to_le_radix(radix)
     }
     // docs:end:to_le_radix
@@ -292,6 +280,7 @@ fn lt_fallback(x: Field, y: Field) -> bool {
 }
 
 mod tests {
+    use crate::{panic::panic, runtime};
     use super::field_less_than;
 
     #[test]
@@ -352,25 +341,73 @@ mod tests {
     }
     // docs:end:to_le_radix_example
 
-    #[test]
+    #[test(should_fail_with = "radix must be greater than 1")]
     fn test_to_le_radix_1() {
-        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
-        let field = 2;
-        let _: [u8; 8] = field.to_le_radix(1);
+        // this test should only fail in constrained mode
+        if !runtime::is_unconstrained() {
+            let field = 2;
+            let _: [u8; 8] = field.to_le_radix(1);
+        } else {
+            panic(f"radix must be greater than 1");
+        }
     }
 
     #[test]
+    fn test_to_le_radix_brillig_1() {
+        // this test should only fail in constrained mode
+        if runtime::is_unconstrained() {
+            let field = 1;
+            let out: [u8; 8] = field.to_le_radix(1);
+            crate::println(out);
+            let expected = [0; 8];
+            assert(out == expected, "unexpected result");
+        }
+    }
+
+    #[test(should_fail_with = "radix must be a power of 2")]
     fn test_to_le_radix_3() {
-        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
-        let field = 2;
-        let _: [u8; 8] = field.to_le_radix(3);
+        // this test should only fail in constrained mode
+        if !runtime::is_unconstrained() {
+            let field = 2;
+            let _: [u8; 8] = field.to_le_radix(3);
+        } else {
+            panic(f"radix must be a power of 2");
+        }
     }
 
     #[test]
-    fn test_to_le_radix_257() {
-        assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained");
-        let field = 2;
-        let _: [u8; 8] = field.to_le_radix(257);
+    fn test_to_le_radix_brillig_3() {
+        // this test should only fail in constrained mode
+        if runtime::is_unconstrained() {
+            let field = 1;
+            let out: [u8; 8] = field.to_le_radix(3);
+            let mut expected = [0; 8];
+            expected[0] = 1;
+            assert(out == expected, "unexpected result");
+        }
+    }
+
+    #[test(should_fail_with = "radix must be less than or equal to 256")]
+    fn test_to_le_radix_512() {
+        // this test should only fail in constrained mode
+        if !runtime::is_unconstrained() {
+            let field = 2;
+            let _: [u8; 8] = field.to_le_radix(512);
+        } else {
+            panic(f"radix must be less than or equal to 256")
+        }
+    }
+
+    #[test]
+    fn test_to_le_radix_brillig_512() {
+        // this test should only fail in constrained mode
+        if runtime::is_unconstrained() {
+            let field = 1;
+            let out: [u8; 8] = field.to_le_radix(512);
+            let mut expected = [0; 8];
+            expected[0] = 1;
+            assert(out == expected, "unexpected result");
+        }
     }
 
     #[test]
diff --git a/test_programs/compile_failure/to_le_radix_1/Nargo.toml b/test_programs/compile_failure/to_le_radix_1/Nargo.toml
deleted file mode 100644
index d1683bad4c2..00000000000
--- a/test_programs/compile_failure/to_le_radix_1/Nargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "to_le_radix_1"
-type = "bin"
-authors = [""]
-
-[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_1/src/main.nr b/test_programs/compile_failure/to_le_radix_1/src/main.nr
deleted file mode 100644
index 9772451d569..00000000000
--- a/test_programs/compile_failure/to_le_radix_1/src/main.nr
+++ /dev/null
@@ -1,5 +0,0 @@
-fn main() {
-    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
-    let field = 2;
-    let _: [u8; 8] = field.to_le_radix(1);
-}
diff --git a/test_programs/compile_failure/to_le_radix_257/Nargo.toml b/test_programs/compile_failure/to_le_radix_257/Nargo.toml
deleted file mode 100644
index ae1eae8ae61..00000000000
--- a/test_programs/compile_failure/to_le_radix_257/Nargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "to_le_radix_257"
-type = "bin"
-authors = [""]
-
-[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_257/src/main.nr b/test_programs/compile_failure/to_le_radix_257/src/main.nr
deleted file mode 100644
index a399809ec8f..00000000000
--- a/test_programs/compile_failure/to_le_radix_257/src/main.nr
+++ /dev/null
@@ -1,5 +0,0 @@
-fn main() {
-    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
-    let field = 2;
-    let _: [u8; 8] = field.to_le_radix(512);
-}
diff --git a/test_programs/compile_failure/to_le_radix_3/Nargo.toml b/test_programs/compile_failure/to_le_radix_3/Nargo.toml
deleted file mode 100644
index 7b1d862806b..00000000000
--- a/test_programs/compile_failure/to_le_radix_3/Nargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "to_le_radix_3"
-type = "bin"
-authors = [""]
-
-[dependencies]
\ No newline at end of file
diff --git a/test_programs/compile_failure/to_le_radix_3/src/main.nr b/test_programs/compile_failure/to_le_radix_3/src/main.nr
deleted file mode 100644
index 365732fd861..00000000000
--- a/test_programs/compile_failure/to_le_radix_3/src/main.nr
+++ /dev/null
@@ -1,5 +0,0 @@
-fn main() {
-    assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained");
-    let field = 2;
-    let _: [u8; 8] = field.to_le_radix(3);
-}