Repository: noir-lang/noir (Rust)

Issue:
Proptest arithmetic generics
### Problem

Arithmetic generics have gotten more complex recently and we only have a handful of unit tests to check them. 

This seems insufficient since we have 5 `BinaryTypeOperator`'s, rules involving pairs of operators, and at least two conditions for simplification (`== 0` and `divides_evenly`). This could result in appx. `5^2 * 2^2 = 100` potentially interesting cases, but we have far fewer than a hundred tests for them.

### Happy Case

Property test `canonicalize` and `try_unify_by_moving_constant_terms` by:
- generating a single `InfixExpr`, canonicalizing it, instantiating all inputs in both terms, and asserting equality of the results
- generating pairs of `InfixExpr`'s, canonicalizing each, attempting to unify, and if they unify, instantiating all inputs in both terms, and asserting equality of the results

### Workaround

None

### Workaround Description

_No response_

### Additional Context

_No response_

### Project Impact

Nice-to-have

### Blocker Context

_No response_

### Would you like to submit a PR for this Issue?

None

### Support Needs

_No response_



Fix (patch):
diff --git a/Cargo.lock b/Cargo.lock
index aca7f199bb5..936aa636e86 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2840,7 +2840,7 @@ dependencies = [
  "num-bigint",
  "num-traits",
  "proptest",
- "proptest-derive",
+ "proptest-derive 0.4.0",
  "serde",
  "serde_json",
  "strum",
@@ -2961,6 +2961,8 @@ dependencies = [
  "num-bigint",
  "num-traits",
  "petgraph",
+ "proptest",
+ "proptest-derive 0.5.0",
  "rangemap",
  "regex",
  "rustc-hash",
@@ -3511,6 +3513,17 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "proptest-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.64",
+]
+
 [[package]]
 name = "quick-error"
 version = "1.2.3"
diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml
index d729dabcb04..581d7f1b61d 100644
--- a/compiler/noirc_frontend/Cargo.toml
+++ b/compiler/noirc_frontend/Cargo.toml
@@ -36,6 +36,8 @@ strum_macros = "0.24"
 
 [dev-dependencies]
 base64.workspace = true
+proptest.workspace = true
+proptest-derive = "0.5.0"
 
 [features]
 experimental_parser = []
diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs
index 17fae63bc35..2c8a9b6508d 100644
--- a/compiler/noirc_frontend/src/ast/expression.rs
+++ b/compiler/noirc_frontend/src/ast/expression.rs
@@ -98,7 +98,7 @@ impl UnresolvedGeneric {
             UnresolvedGeneric::Variable(_) => Ok(Kind::Normal),
             UnresolvedGeneric::Numeric { typ, .. } => {
                 let typ = self.resolve_numeric_kind_type(typ)?;
-                Ok(Kind::Numeric(Box::new(typ)))
+                Ok(Kind::numeric(typ))
             }
             UnresolvedGeneric::Resolved(..) => {
                 panic!("Don't know the kind of a resolved generic here")
diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs
index e85563691ba..3c6664dd569 100644
--- a/compiler/noirc_frontend/src/ast/mod.rs
+++ b/compiler/noirc_frontend/src/ast/mod.rs
@@ -19,6 +19,9 @@ pub use visitor::Visitor;
 pub use expression::*;
 pub use function::*;
 
+#[cfg(test)]
+use proptest_derive::Arbitrary;
+
 use acvm::FieldElement;
 pub use docs::*;
 use noirc_errors::Span;
@@ -37,6 +40,7 @@ use crate::{
 use acvm::acir::AcirField;
 use iter_extended::vecmap;
 
+#[cfg_attr(test, derive(Arbitrary))]
 #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Ord, PartialOrd)]
 pub enum IntegerBitSize {
     One,
diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs
index 9d6d04c07b0..ee79b62671f 100644
--- a/compiler/noirc_frontend/src/elaborator/mod.rs
+++ b/compiler/noirc_frontend/src/elaborator/mod.rs
@@ -641,7 +641,7 @@ impl<'context> Elaborator<'context> {
             let typ = if unresolved_typ.is_type_expression() {
                 self.resolve_type_inner(
                     unresolved_typ.clone(),
-                    &Kind::Numeric(Box::new(Type::default_int_type())),
+                    &Kind::numeric(Type::default_int_type()),
                 )
             } else {
                 self.resolve_type(unresolved_typ.clone())
@@ -654,7 +654,7 @@ impl<'context> Elaborator<'context> {
                     });
                 self.push_err(unsupported_typ_err);
             }
-            Kind::Numeric(Box::new(typ))
+            Kind::numeric(typ)
         } else {
             Kind::Normal
         }
diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs
index 722573bcd38..b296c4f1805 100644
--- a/compiler/noirc_frontend/src/elaborator/types.rs
+++ b/compiler/noirc_frontend/src/elaborator/types.rs
@@ -414,7 +414,7 @@ impl<'context> Elaborator<'context> {
                 let kind = self
                     .interner
                     .get_global_let_statement(id)
-                    .map(|let_statement| Kind::Numeric(Box::new(let_statement.r#type)))
+                    .map(|let_statement| Kind::numeric(let_statement.r#type))
                     .unwrap_or(Kind::u32());
 
                 // TODO(https://github.com/noir-lang/noir/issues/6238):
diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
index 2d15d4927c9..a373441b4e0 100644
--- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
+++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
@@ -541,7 +541,7 @@ impl<'a> ModCollector<'a> {
                                 name: Rc::new(name.to_string()),
                                 type_var: TypeVariable::unbound(
                                     type_variable_id,
-                                    Kind::Numeric(Box::new(typ)),
+                                    Kind::numeric(typ),
                                 ),
                                 span: name.span(),
                             });
diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs
index 88fc87cc500..a0fea3aa774 100644
--- a/compiler/noirc_frontend/src/hir_def/types.rs
+++ b/compiler/noirc_frontend/src/hir_def/types.rs
@@ -5,6 +5,9 @@ use std::{
     rc::Rc,
 };
 
+#[cfg(test)]
+use proptest_derive::Arbitrary;
+
 use acvm::{AcirField, FieldElement};
 
 use crate::{
@@ -169,6 +172,11 @@ pub enum Kind {
 }
 
 impl Kind {
+    // Kind::Numeric constructor helper
+    pub fn numeric(typ: Type) -> Kind {
+        Kind::Numeric(Box::new(typ))
+    }
+
     pub(crate) fn is_error(&self) -> bool {
         match self.follow_bindings() {
             Self::Numeric(typ) => *typ == Type::Error,
@@ -196,7 +204,7 @@ impl Kind {
     }
 
     pub(crate) fn u32() -> Self {
-        Self::Numeric(Box::new(Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo)))
+        Self::numeric(Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo))
     }
 
     pub(crate) fn follow_bindings(&self) -> Self {
@@ -205,7 +213,7 @@ impl Kind {
             Self::Normal => Self::Normal,
             Self::Integer => Self::Integer,
             Self::IntegerOrField => Self::IntegerOrField,
-            Self::Numeric(typ) => Self::Numeric(Box::new(typ.follow_bindings())),
+            Self::Numeric(typ) => Self::numeric(typ.follow_bindings()),
         }
     }
 
@@ -671,6 +679,7 @@ impl<T> Shared<T> {
 
 /// A restricted subset of binary operators useable on
 /// type level integers for use in the array length positions of types.
+#[cfg_attr(test, derive(Arbitrary))]
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub enum BinaryTypeOperator {
     Addition,
@@ -1422,7 +1431,7 @@ impl Type {
         if self_kind.unifies(&other_kind) {
             self_kind
         } else {
-            Kind::Numeric(Box::new(Type::Error))
+            Kind::numeric(Type::Error)
         }
     }
 
diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs
index df687782afb..ce2c58e71c1 100644
--- a/compiler/noirc_frontend/src/monomorphization/mod.rs
+++ b/compiler/noirc_frontend/src/monomorphization/mod.rs
@@ -944,9 +944,7 @@ impl<'interner> Monomorphizer<'interner> {
                 };
                 let location = self.interner.id_location(expr_id);
 
-                if !Kind::Numeric(numeric_typ.clone())
-                    .unifies(&Kind::Numeric(Box::new(typ.clone())))
-                {
+                if !Kind::Numeric(numeric_typ.clone()).unifies(&Kind::numeric(typ.clone())) {
                     let message = "ICE: Generic's kind does not match expected type";
                     return Err(MonomorphizationError::InternalError { location, message });
                 }