diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index b3180e0dd20..62099d2d6a6 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -565,6 +565,7 @@ impl<'interner> TypeChecker<'interner> { Type::Integer(..) | Type::FieldElement | Type::TypeVariable(_, TypeVariableKind::IntegerOrField) + | Type::TypeVariable(_, TypeVariableKind::Integer) | Type::Bool => (), Type::TypeVariable(_, _) => { @@ -805,7 +806,7 @@ impl<'interner> TypeChecker<'interner> { // Matches on TypeVariable must be first to follow any type // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { + (TypeVariable(int, int_kind), other) | (other, TypeVariable(int, int_kind)) => { if let TypeBinding::Bound(binding) = &*int.borrow() { return self.comparator_operand_type_rules(other, binding, op, span); } @@ -823,7 +824,13 @@ impl<'interner> TypeChecker<'interner> { } let mut bindings = TypeBindings::new(); - if other.try_bind_to_polymorphic_int(int, &mut bindings).is_ok() + if other + .try_bind_to_polymorphic_int( + int, + &mut bindings, + *int_kind == TypeVariableKind::Integer, + ) + .is_ok() || other == &Type::Error { Type::apply_type_bindings(bindings); @@ -1081,7 +1088,7 @@ impl<'interner> TypeChecker<'interner> { // Matches on TypeVariable must be first so that we follow any type // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { + (TypeVariable(int, int_kind), other) | (other, TypeVariable(int, int_kind)) => { if let TypeBinding::Bound(binding) = &*int.borrow() { return self.infix_operand_type_rules(binding, op, other, span); } @@ -1114,7 +1121,13 @@ impl<'interner> TypeChecker<'interner> { } let mut bindings = TypeBindings::new(); - if other.try_bind_to_polymorphic_int(int, &mut bindings).is_ok() + if other + .try_bind_to_polymorphic_int( + int, + &mut bindings, + *int_kind == TypeVariableKind::Integer, + ) + .is_ok() || other == &Type::Error { Type::apply_type_bindings(bindings); diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 14f8a8e8639..1f8f236a818 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -442,6 +442,10 @@ pub enum TypeVariableKind { /// type annotations on each integer literal. IntegerOrField, + /// A generic integer type. This is a more specific kind of TypeVariable + /// that can only be bound to Type::Integer, or other polymorphic integers. + Integer, + /// A potentially constant array size. This will only bind to itself, Type::NotConstant, or /// Type::Constant(n) with a matching size. This defaults to Type::Constant(n) if still unbound /// during monomorphization. @@ -747,6 +751,13 @@ impl std::fmt::Display for Type { Signedness::Unsigned => write!(f, "u{num_bits}"), }, Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), + Type::TypeVariable(binding, TypeVariableKind::Integer) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + write!(f, "{}", TypeVariableKind::Integer.default_type()) + } else { + write!(f, "{}", binding.borrow()) + } + } Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { if let TypeBinding::Unbound(_) = &*binding.borrow() { // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is @@ -911,6 +922,7 @@ impl Type { Ok(()) } TypeVariableKind::IntegerOrField => Err(UnificationError), + TypeVariableKind::Integer => Err(UnificationError), }, } } @@ -925,6 +937,7 @@ impl Type { &self, var: &TypeVariable, bindings: &mut TypeBindings, + only_integer: bool, ) -> Result<(), UnificationError> { let target_id = match &*var.borrow() { TypeBinding::Bound(_) => unreachable!(), @@ -940,7 +953,30 @@ impl Type { Type::TypeVariable(self_var, TypeVariableKind::IntegerOrField) => { let borrow = self_var.borrow(); match &*borrow { - TypeBinding::Bound(typ) => typ.try_bind_to_polymorphic_int(var, bindings), + TypeBinding::Bound(typ) => { + typ.try_bind_to_polymorphic_int(var, bindings, only_integer) + } + // Avoid infinitely recursive bindings + TypeBinding::Unbound(id) if *id == target_id => Ok(()), + TypeBinding::Unbound(new_target_id) => { + if only_integer { + // Integer is more specific than IntegerOrField so we bind the type + // variable to Integer instead. + let clone = Type::TypeVariable(var.clone(), TypeVariableKind::Integer); + bindings.insert(*new_target_id, (self_var.clone(), clone)); + } else { + bindings.insert(target_id, (var.clone(), this.clone())); + } + Ok(()) + } + } + } + Type::TypeVariable(self_var, TypeVariableKind::Integer) => { + let borrow = self_var.borrow(); + match &*borrow { + TypeBinding::Bound(typ) => { + typ.try_bind_to_polymorphic_int(var, bindings, only_integer) + } // Avoid infinitely recursive bindings TypeBinding::Unbound(id) if *id == target_id => Ok(()), TypeBinding::Unbound(_) => { @@ -949,18 +985,23 @@ impl Type { } } } - Type::TypeVariable(binding, TypeVariableKind::Normal) => { - let borrow = binding.borrow(); + Type::TypeVariable(self_var, TypeVariableKind::Normal) => { + let borrow = self_var.borrow(); match &*borrow { - TypeBinding::Bound(typ) => typ.try_bind_to_polymorphic_int(var, bindings), + TypeBinding::Bound(typ) => { + typ.try_bind_to_polymorphic_int(var, bindings, only_integer) + } // Avoid infinitely recursive bindings TypeBinding::Unbound(id) if *id == target_id => Ok(()), TypeBinding::Unbound(new_target_id) => { - // IntegerOrField is more specific than TypeVariable so we bind the type - // variable to IntegerOrField instead. - let clone = - Type::TypeVariable(var.clone(), TypeVariableKind::IntegerOrField); - bindings.insert(*new_target_id, (binding.clone(), clone)); + // Bind to the most specific type variable kind + let clone_kind = if only_integer { + TypeVariableKind::Integer + } else { + TypeVariableKind::IntegerOrField + }; + let clone = Type::TypeVariable(var.clone(), clone_kind); + bindings.insert(*new_target_id, (self_var.clone(), clone)); Ok(()) } } @@ -1050,7 +1091,16 @@ impl Type { (TypeVariable(var, Kind::IntegerOrField), other) | (other, TypeVariable(var, Kind::IntegerOrField)) => { other.try_unify_to_type_variable(var, bindings, |bindings| { - other.try_bind_to_polymorphic_int(var, bindings) + let only_integer = false; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }) + } + + (TypeVariable(var, Kind::Integer), other) + | (other, TypeVariable(var, Kind::Integer)) => { + other.try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = true; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) }) } @@ -1599,6 +1649,7 @@ impl TypeVariableKind { pub(crate) fn default_type(&self) -> Type { match self { TypeVariableKind::IntegerOrField | TypeVariableKind::Normal => Type::default_int_type(), + TypeVariableKind::Integer => Type::default_range_loop_type(), TypeVariableKind::Constant(length) => Type::Constant(*length), } } @@ -1627,6 +1678,10 @@ impl From<&Type> for PrintableType { } Signedness::Signed => PrintableType::SignedInteger { width: (*bit_width).into() }, }, + Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { + TypeBinding::Bound(typ) => typ.into(), + TypeBinding::Unbound(_) => Type::default_range_loop_type().into(), + }, Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { match &*binding.borrow() { TypeBinding::Bound(typ) => typ.into(), @@ -1685,6 +1740,9 @@ impl std::fmt::Debug for Type { Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { write!(f, "IntOrField{:?}", binding) } + Type::TypeVariable(binding, TypeVariableKind::Integer) => { + write!(f, "Int{:?}", binding) + } Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { write!(f, "{}{:?}", n, binding) } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 62950c9d4f7..e9adf26ec98 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -803,12 +803,14 @@ impl<'interner> Monomorphizer<'interner> { // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = - if self.is_range_loop && matches!(kind, TypeVariableKind::IntegerOrField) { - Type::default_range_loop_type() - } else { - kind.default_type() - }; + let default = if self.is_range_loop + && (matches!(kind, TypeVariableKind::IntegerOrField) + || matches!(kind, TypeVariableKind::Integer)) + { + Type::default_range_loop_type() + } else { + kind.default_type() + }; let monomorphized_default = self.convert_type(&default); binding.bind(default); diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 0051c1b4f5f..7420d4598d9 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1700,6 +1700,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Array(_, _) => Some(Array), Type::Integer(_, _) => Some(FieldOrInt), Type::TypeVariable(_, TypeVariableKind::IntegerOrField) => Some(FieldOrInt), + Type::TypeVariable(_, TypeVariableKind::Integer) => Some(FieldOrInt), Type::Bool => Some(Bool), Type::String(_) => Some(String), Type::FmtString(_, _) => Some(FmtString), diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index 2560e46b01d..52a3e3a19e9 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -144,12 +144,11 @@ impl AbiType { Self::Integer { sign, width: (*bit_width).into() } } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - match &*binding.borrow() { - TypeBinding::Bound(typ) => Self::from_type(context, typ), - TypeBinding::Unbound(_) => Self::from_type(context, &Type::default_int_type()), - } - } + Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) + | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { + TypeBinding::Bound(typ) => Self::from_type(context, typ), + TypeBinding::Unbound(_) => Self::from_type(context, &Type::default_int_type()), + }, Type::Bool => Self::Boolean, Type::String(size) => { let size = size