diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index 200ce3099cb..0a8ce371708 100644 --- a/aztec_macros/src/utils/hir_utils.rs +++ b/aztec_macros/src/utils/hir_utils.rs @@ -195,7 +195,7 @@ pub fn inject_fn( let trait_id = None; items.functions.push(UnresolvedFunctions { file_id, functions, trait_id, self_type: None }); - let mut errors = Elaborator::elaborate(context, *crate_id, items, None); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None, false); errors.retain(|(error, _)| !CustomDiagnostic::from(error).is_warning()); if !errors.is_empty() { @@ -241,7 +241,7 @@ pub fn inject_global( let mut items = CollectedItems::default(); items.globals.push(UnresolvedGlobal { file_id, module_id, global_id, stmt_def: global }); - let _errors = Elaborator::elaborate(context, *crate_id, items, None); + let _errors = Elaborator::elaborate(context, *crate_id, items, None, false); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs index 6625eba5a0a..87181b285de 100644 --- a/compiler/noirc_driver/src/abi_gen.rs +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -100,6 +100,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { Type::Error | Type::Unit | Type::Constant(_) + | Type::InfixExpr(..) | Type::TraitAsType(..) | Type::TypeVariable(_, _) | Type::NamedGeneric(..) diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index f430eb8ad19..2e185c69461 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -113,6 +113,10 @@ pub struct CompileOptions { /// Outputs the paths to any modified artifacts #[arg(long, hide = true)] pub show_artifact_paths: bool, + + /// Temporary flag to enable the experimental arithmetic generics feature + #[arg(long, hide = true)] + pub arithmetic_generics: bool, } pub fn parse_expression_width(input: &str) -> Result { @@ -262,21 +266,28 @@ pub fn add_dep( pub fn check_crate( context: &mut Context, crate_id: CrateId, - deny_warnings: bool, - disable_macros: bool, - debug_comptime_in_file: Option<&str>, + options: &CompileOptions, ) -> CompilationResult<()> { - let macros: &[&dyn MacroProcessor] = - if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; + let macros: &[&dyn MacroProcessor] = if options.disable_macros { + &[] + } else { + &[&aztec_macros::AztecMacro as &dyn MacroProcessor] + }; let mut errors = vec![]; - let diagnostics = CrateDefMap::collect_defs(crate_id, context, debug_comptime_in_file, macros); + let diagnostics = CrateDefMap::collect_defs( + crate_id, + context, + options.debug_comptime_in_file.as_deref(), + options.arithmetic_generics, + macros, + ); errors.extend(diagnostics.into_iter().map(|(error, file_id)| { let diagnostic = CustomDiagnostic::from(&error); diagnostic.in_file(file_id) })); - if has_errors(&errors, deny_warnings) { + if has_errors(&errors, options.deny_warnings) { Err(errors) } else { Ok(((), errors)) @@ -302,13 +313,7 @@ pub fn compile_main( options: &CompileOptions, cached_program: Option, ) -> CompilationResult { - let (_, mut warnings) = check_crate( - context, - crate_id, - options.deny_warnings, - options.disable_macros, - options.debug_comptime_in_file.as_deref(), - )?; + let (_, mut warnings) = check_crate(context, crate_id, options)?; let main = context.get_main_function(&crate_id).ok_or_else(|| { // TODO(#2155): This error might be a better to exist in Nargo @@ -343,13 +348,7 @@ pub fn compile_contract( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult { - let (_, warnings) = check_crate( - context, - crate_id, - options.deny_warnings, - options.disable_macros, - options.debug_comptime_in_file.as_deref(), - )?; + let (_, warnings) = check_crate(context, crate_id, options)?; // TODO: We probably want to error if contracts is empty let contracts = context.get_all_contracts(&crate_id); diff --git a/compiler/noirc_driver/tests/stdlib_warnings.rs b/compiler/noirc_driver/tests/stdlib_warnings.rs index d2474444d13..e290842480d 100644 --- a/compiler/noirc_driver/tests/stdlib_warnings.rs +++ b/compiler/noirc_driver/tests/stdlib_warnings.rs @@ -25,7 +25,7 @@ fn stdlib_does_not_produce_constant_warnings() -> Result<(), ErrorsAndWarnings> let root_crate_id = prepare_crate(&mut context, file_name); let ((), warnings) = - noirc_driver::check_crate(&mut context, root_crate_id, false, false, None)?; + noirc_driver::check_crate(&mut context, root_crate_id, &Default::default())?; assert_eq!(warnings, Vec::new(), "stdlib is producing {} warnings", warnings.len()); diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 4fb5730e93b..f59d316950c 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -303,7 +303,7 @@ impl UnresolvedTypeData { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] pub enum Signedness { Unsigned, Signed, diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index 20d75a704d3..afa2e7fa7a8 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -45,6 +45,7 @@ impl<'context> Elaborator<'context> { self.def_maps, self.crate_id, self.debug_comptime_in_file, + self.enable_arithmetic_generics, ); elaborator.function_context.push(FunctionContext::default()); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index cca2af56664..9c2467d38e6 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -161,6 +161,9 @@ pub struct Elaborator<'context> { /// This map is used to lazily evaluate these globals if they're encountered before /// they are elaborated (e.g. in a function's type or another global's RHS). unresolved_globals: BTreeMap, + + /// Temporary flag to enable the experimental arithmetic generics feature + enable_arithmetic_generics: bool, } #[derive(Default)] @@ -183,6 +186,7 @@ impl<'context> Elaborator<'context> { def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Self { Self { scopes: ScopeForest::default(), @@ -203,6 +207,7 @@ impl<'context> Elaborator<'context> { current_trait_impl: None, debug_comptime_in_file, unresolved_globals: BTreeMap::new(), + enable_arithmetic_generics, } } @@ -210,12 +215,14 @@ impl<'context> Elaborator<'context> { context: &'context mut Context, crate_id: CrateId, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Self { Self::new( &mut context.def_interner, &mut context.def_maps, crate_id, debug_comptime_in_file, + enable_arithmetic_generics, ) } @@ -224,8 +231,16 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Vec<(CompilationError, FileId)> { - Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors + Self::elaborate_and_return_self( + context, + crate_id, + items, + debug_comptime_in_file, + enable_arithmetic_generics, + ) + .errors } pub fn elaborate_and_return_self( @@ -233,8 +248,14 @@ impl<'context> Elaborator<'context> { crate_id: CrateId, items: CollectedItems, debug_comptime_in_file: Option, + enable_arithmetic_generics: bool, ) -> Self { - let mut this = Self::from_context(context, crate_id, debug_comptime_in_file); + let mut this = Self::from_context( + context, + crate_id, + debug_comptime_in_file, + enable_arithmetic_generics, + ); this.elaborate_items(items); this.check_and_pop_function_context(); this diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 0973e592c1e..7448ccaa42b 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -403,13 +403,16 @@ impl<'context> Elaborator<'context> { match (lhs, rhs) { (Type::Constant(lhs), Type::Constant(rhs)) => { - Type::Constant(op.function()(lhs, rhs)) + Type::Constant(op.function(lhs, rhs)) } - (lhs, _) => { - let span = - if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; - self.push_err(ResolverError::InvalidArrayLengthExpr { span }); - Type::Constant(0) + (lhs, rhs) => { + if !self.enable_arithmetic_generics { + let span = + if !matches!(lhs, Type::Constant(_)) { lhs_span } else { rhs_span }; + self.push_err(ResolverError::InvalidArrayLengthExpr { span }); + } + + Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize() } } } @@ -1614,6 +1617,10 @@ impl<'context> Elaborator<'context> { } Self::find_numeric_generics_in_type(fields, found); } + Type::InfixExpr(lhs, _op, rhs) => { + Self::find_numeric_generics_in_type(lhs, found); + Self::find_numeric_generics_in_type(rhs, found); + } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 6328a164a2a..bc48b2875c8 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -358,6 +358,13 @@ impl Type { Type::Constant(_) => panic!("Type::Constant where a type was expected: {self:?}"), Type::Quoted(quoted_type) => UnresolvedTypeData::Quoted(*quoted_type), Type::Error => UnresolvedTypeData::Error, + Type::InfixExpr(lhs, op, rhs) => { + let lhs = Box::new(lhs.to_type_expression()); + let rhs = Box::new(rhs.to_type_expression()); + let span = Span::default(); + let expr = UnresolvedTypeExpression::BinaryOperation(lhs, *op, rhs, span); + UnresolvedTypeData::Expression(expr) + } }; UnresolvedType { typ, span: None } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index c92e9a73b5a..927468e35a6 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -662,6 +662,7 @@ fn zeroed(return_type: Type) -> IResult { Type::TypeVariable(_, _) | Type::Forall(_, _) | Type::Constant(_) + | Type::InfixExpr(..) | Type::Quoted(_) | Type::Error | Type::TraitAsType(_, _, _) diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index b4ffa1bd01d..4c1adf9fca0 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -45,7 +45,7 @@ fn interpret_helper(src: &str) -> Result { let main = context.get_main_function(&krate).expect("Expected 'main' function"); let mut elaborator = - Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None, false); assert_eq!(elaborator.errors.len(), 0); let mut interpreter = elaborator.setup_interpreter(); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 864cbef56c1..fabd76a2818 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -247,6 +247,7 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, + enable_arithmetic_generics: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; @@ -264,6 +265,7 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, + enable_arithmetic_generics, macro_processors, )); @@ -386,8 +388,14 @@ impl DefCollector { }) }); - let mut more_errors = - Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); + let mut more_errors = Elaborator::elaborate( + context, + crate_id, + def_collector.items, + debug_comptime_in_file, + enable_arithmetic_generics, + ); + errors.append(&mut more_errors); for macro_processor in macro_processors { diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 45f1f17940d..e607de52ff1 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -76,6 +76,7 @@ impl CrateDefMap { crate_id: CrateId, context: &mut Context, debug_comptime_in_file: Option<&str>, + enable_arithmetic_generics: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled @@ -125,6 +126,7 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, + enable_arithmetic_generics, macro_processors, )); diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index fc1af63540a..bf46917a58f 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -21,7 +21,7 @@ use crate::{ use super::expr::{HirCallExpression, HirExpression, HirIdent}; -#[derive(PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash, Ord, PartialOrd)] pub enum Type { /// A primitive Field type FieldElement, @@ -107,6 +107,8 @@ pub enum Type { /// The type of quoted code in macros. This is always a comptime-only type Quoted(QuotedType), + InfixExpr(Box, BinaryTypeOperator, Box), + /// The result of some type error. Remembering type errors as their own type variant lets /// us avoid issuing repeat type errors for the same item. For example, a lambda with /// an invalid type would otherwise issue a new error each time it is called @@ -120,7 +122,7 @@ pub enum Type { /// For example, the type of a struct field or a function parameter is expected to be /// a type of kind * (represented here as `Normal`). Types used in positions where a number /// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. -#[derive(PartialEq, Eq, Clone, Hash, Debug)] +#[derive(PartialEq, Eq, Clone, Hash, Debug, PartialOrd, Ord)] pub enum Kind { Normal, Numeric(Box), @@ -135,7 +137,7 @@ impl std::fmt::Display for Kind { } } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] pub enum QuotedType { Expr, Quoted, @@ -203,6 +205,18 @@ impl PartialEq for StructType { } } +impl PartialOrd for StructType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for StructType { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + impl StructType { pub fn new( id: StructId, @@ -333,6 +347,18 @@ impl PartialEq for TypeAlias { } } +impl Ord for TypeAlias { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.cmp(&other.id) + } +} + +impl PartialOrd for TypeAlias { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl std::fmt::Display for TypeAlias { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) @@ -425,7 +451,7 @@ impl Shared { /// A restricted subset of binary operators useable on /// type level integers for use in the array length positions of types. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum BinaryTypeOperator { Addition, Subtraction, @@ -434,7 +460,7 @@ pub enum BinaryTypeOperator { Modulo, } -#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub enum TypeVariableKind { /// Can bind to any type Normal, @@ -458,7 +484,7 @@ pub enum TypeVariableKind { /// A TypeVariable is a mutable reference that is either /// bound to some type, or unbound with a given TypeVariableId. -#[derive(PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub struct TypeVariable(TypeVariableId, Shared); impl TypeVariable { @@ -527,7 +553,7 @@ impl TypeVariable { /// TypeBindings are the mutable insides of a TypeVariable. /// They are either bound to some type, or are unbound. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum TypeBinding { Bound(Type), Unbound(TypeVariableId), @@ -540,7 +566,7 @@ impl TypeBinding { } /// A unique ID used to differentiate different type variables -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TypeVariableId(pub usize); impl std::fmt::Display for Type { @@ -644,6 +670,16 @@ impl std::fmt::Display for Type { write!(f, "&mut {element}") } Type::Quoted(quoted) => write!(f, "{}", quoted), + Type::InfixExpr(lhs, op, rhs) => { + let this = self.canonicalize(); + + // Prevent infinite recursion + if this != *self { + write!(f, "{this}") + } else { + write!(f, "({lhs} {op} {rhs})") + } + } } } } @@ -838,6 +874,9 @@ impl Type { elements.contains_numeric_typevar(target_id) || named_generic_id_matches_target(length) } + Type::InfixExpr(lhs, _op, rhs) => { + lhs.contains_numeric_typevar(target_id) || rhs.contains_numeric_typevar(target_id) + } } } @@ -917,6 +956,10 @@ impl Type { elements.find_numeric_type_vars(found_names); named_generic_is_numeric(length, found_names); } + Type::InfixExpr(lhs, _op, rhs) => { + lhs.find_numeric_type_vars(found_names); + rhs.find_numeric_type_vars(found_names); + } } } @@ -946,6 +989,7 @@ impl Type { | Type::Forall(_, _) | Type::Quoted(_) | Type::Slice(_) + | Type::InfixExpr(_, _, _) | Type::TraitAsType(..) => false, Type::Alias(alias, generics) => { @@ -983,6 +1027,7 @@ impl Type { | Type::Constant(_) | Type::TypeVariable(_, _) | Type::NamedGeneric(_, _, _) + | Type::InfixExpr(..) | Type::Error => true, Type::FmtString(_, _) @@ -1028,6 +1073,7 @@ impl Type { | Type::NamedGeneric(_, _, _) | Type::Function(_, _, _) | Type::FmtString(_, _) + | Type::InfixExpr(..) | Type::Error => true, // Quoted objects only exist at compile-time where the only execution @@ -1162,6 +1208,7 @@ impl Type { | Type::Constant(_) | Type::Quoted(_) | Type::Slice(_) + | Type::InfixExpr(..) | Type::Error => unreachable!("This type cannot exist as a parameter to main"), } } @@ -1416,7 +1463,17 @@ impl Type { use Type::*; use TypeVariableKind as Kind; - match (self, other) { + let lhs = match self { + Type::InfixExpr(..) => Cow::Owned(self.canonicalize()), + other => Cow::Borrowed(other), + }; + + let rhs = match other { + Type::InfixExpr(..) => Cow::Owned(other.canonicalize()), + other => Cow::Borrowed(other), + }; + + match (lhs.as_ref(), rhs.as_ref()) { (Error, _) | (_, Error) => Ok(()), (Alias(alias, args), other) | (other, Alias(alias, args)) => { @@ -1530,6 +1587,27 @@ impl Type { elem_a.try_unify(elem_b, bindings) } + (InfixExpr(lhs_a, op_a, rhs_a), InfixExpr(lhs_b, op_b, rhs_b)) => { + if op_a == op_b { + lhs_a.try_unify(lhs_b, bindings)?; + rhs_a.try_unify(rhs_b, bindings) + } else { + Err(UnificationError) + } + } + + (Constant(value), other) | (other, Constant(value)) => { + if let Some(other_value) = other.evaluate_to_u32() { + if *value == other_value { + Ok(()) + } else { + Err(UnificationError) + } + } else { + Err(UnificationError) + } + } + (other_a, other_b) => { if other_a == other_b { Ok(()) @@ -1540,6 +1618,107 @@ impl Type { } } + /// Try to canonicalize the representation of this type. + /// Currently the only type with a canonical representation is + /// `Type::Infix` where for each consecutive commutative operator + /// we sort the non-constant operands by `Type: Ord` and place all constant + /// operands at the end, constant folded. + /// + /// For example: + /// - `canonicalize[((1 + N) + M) + 2] = (M + N) + 3` + /// - `canonicalize[A + 2 * B + 3 - 2] = A + (B * 2) + 3 - 2` + pub fn canonicalize(&self) -> Type { + match self.follow_bindings() { + Type::InfixExpr(lhs, op, rhs) => { + if let Some(value) = self.evaluate_to_u32() { + return Type::Constant(value); + } + + let lhs = lhs.canonicalize(); + let rhs = rhs.canonicalize(); + + if let Some(result) = Self::try_simplify_subtraction(&lhs, op, &rhs) { + return result; + } + + if op.is_commutative() { + return Self::sort_commutative(&lhs, op, &rhs); + } + + Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)) + } + other => other, + } + } + + fn sort_commutative(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Type { + let mut queue = vec![lhs.clone(), rhs.clone()]; + + let mut sorted = BTreeSet::new(); + + let zero_value = if op == BinaryTypeOperator::Addition { 0 } else { 1 }; + let mut constant = zero_value; + + // Push each non-constant term to `sorted` to sort them. Recur on InfixExprs with the same operator. + while let Some(item) = queue.pop() { + match item.canonicalize() { + Type::InfixExpr(lhs, new_op, rhs) if new_op == op => { + queue.push(*lhs); + queue.push(*rhs); + } + Type::Constant(new_constant) => { + constant = op.function(constant, new_constant); + } + other => { + sorted.insert(other); + } + } + } + + if let Some(first) = sorted.pop_first() { + let mut typ = first.clone(); + + for rhs in sorted { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(rhs.clone())); + } + + if constant != zero_value { + typ = Type::InfixExpr(Box::new(typ), op, Box::new(Type::Constant(constant))); + } + + typ + } else { + // Every type must have been a constant + Type::Constant(constant) + } + } + + /// Try to simplify a subtraction expression of `lhs - rhs`. + /// + /// - Simplifies `(a + C1) - C2` to `a + (C1 - C2)` if C1 and C2 are constants. + fn try_simplify_subtraction(lhs: &Type, op: BinaryTypeOperator, rhs: &Type) -> Option { + use BinaryTypeOperator::*; + match lhs { + Type::InfixExpr(l_lhs, l_op, l_rhs) => { + // Simplify `(N + 2) - 1` + if op == Subtraction && *l_op == Addition { + if let (Some(lhs_const), Some(rhs_const)) = + (l_rhs.evaluate_to_u32(), rhs.evaluate_to_u32()) + { + if lhs_const > rhs_const { + let constant = Box::new(Type::Constant(lhs_const - rhs_const)); + return Some( + Type::InfixExpr(l_lhs.clone(), *l_op, constant).canonicalize(), + ); + } + } + } + None + } + _ => None, + } + } + /// Try to unify a type variable to `self`. /// This is a helper function factored out from try_unify. fn try_unify_to_type_variable( @@ -1637,6 +1816,11 @@ impl Type { Type::TypeVariable(_, TypeVariableKind::Constant(size)) => Some(*size), Type::Array(len, _elem) => len.evaluate_to_u32(), Type::Constant(x) => Some(*x), + Type::InfixExpr(lhs, op, rhs) => { + let lhs = lhs.evaluate_to_u32()?; + let rhs = rhs.evaluate_to_u32()?; + Some(op.function(lhs, rhs)) + } _ => None, } } @@ -1898,6 +2082,11 @@ impl Type { }); Type::TraitAsType(*s, name.clone(), args) } + Type::InfixExpr(lhs, op, rhs) => { + let lhs = lhs.substitute_helper(type_bindings, substitute_bound_typevars); + let rhs = rhs.substitute_helper(type_bindings, substitute_bound_typevars); + Type::InfixExpr(Box::new(lhs), *op, Box::new(rhs)) + } Type::FieldElement | Type::Integer(_, _) @@ -1943,6 +2132,7 @@ impl Type { || env.occurs(target_id) } Type::MutableReference(element) => element.occurs(target_id), + Type::InfixExpr(lhs, _op, rhs) => lhs.occurs(target_id) || rhs.occurs(target_id), Type::FieldElement | Type::Integer(_, _) @@ -2003,6 +2193,11 @@ impl Type { let args = vecmap(args, |arg| arg.follow_bindings()); TraitAsType(*s, name.clone(), args) } + InfixExpr(lhs, op, rhs) => { + let lhs = lhs.follow_bindings(); + let rhs = rhs.follow_bindings(); + InfixExpr(Box::new(lhs), *op, Box::new(rhs)) + } // Expect that this function should only be called on instantiated types Forall(..) => unreachable!(), @@ -2090,6 +2285,10 @@ impl Type { } Type::MutableReference(elem) => elem.replace_named_generics_with_type_variables(), Type::Forall(_, typ) => typ.replace_named_generics_with_type_variables(), + Type::InfixExpr(lhs, _op, rhs) => { + lhs.replace_named_generics_with_type_variables(); + rhs.replace_named_generics_with_type_variables(); + } } } @@ -2137,16 +2336,20 @@ fn convert_array_expression_to_slice( } impl BinaryTypeOperator { - /// Return the actual rust numeric function associated with this operator - pub fn function(self) -> fn(u32, u32) -> u32 { + /// Perform the actual rust numeric operation associated with this operator + pub fn function(self, a: u32, b: u32) -> u32 { match self { - BinaryTypeOperator::Addition => |a, b| a.wrapping_add(b), - BinaryTypeOperator::Subtraction => |a, b| a.wrapping_sub(b), - BinaryTypeOperator::Multiplication => |a, b| a.wrapping_mul(b), - BinaryTypeOperator::Division => |a, b| a.wrapping_div(b), - BinaryTypeOperator::Modulo => |a, b| a.wrapping_rem(b), // % b, + BinaryTypeOperator::Addition => a.wrapping_add(b), + BinaryTypeOperator::Subtraction => a.wrapping_sub(b), + BinaryTypeOperator::Multiplication => a.wrapping_mul(b), + BinaryTypeOperator::Division => a.wrapping_div(b), + BinaryTypeOperator::Modulo => a.wrapping_rem(b), } } + + fn is_commutative(self) -> bool { + matches!(self, BinaryTypeOperator::Addition | BinaryTypeOperator::Multiplication) + } } impl TypeVariableKind { @@ -2229,6 +2432,7 @@ impl From<&Type> for PrintableType { PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } } Type::Quoted(_) => unreachable!(), + Type::InfixExpr(..) => unreachable!(), } } } @@ -2321,6 +2525,7 @@ impl std::fmt::Debug for Type { write!(f, "&mut {element:?}") } Type::Quoted(quoted) => write!(f, "{}", quoted), + Type::InfixExpr(lhs, op, rhs) => write!(f, "({lhs:?} {op} {rhs:?})"), } } } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index e6506a5fde6..5ac730db400 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -1026,7 +1026,10 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::MutableReference(Box::new(element)) } - HirType::Forall(_, _) | HirType::Constant(_) | HirType::Error => { + HirType::Forall(_, _) + | HirType::Constant(_) + | HirType::InfixExpr(..) + | HirType::Error => { unreachable!("Unexpected type {} found", typ) } HirType::Quoted(_) => unreachable!("Tried to translate Code type into runtime code"), @@ -1107,6 +1110,10 @@ impl<'interner> Monomorphizer<'interner> { } HirType::MutableReference(element) => Self::check_type(element, location), + HirType::InfixExpr(lhs, _, rhs) => { + Self::check_type(lhs, location)?; + Self::check_type(rhs, location) + } } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index ce26b38b639..c701b29f898 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -2086,6 +2086,7 @@ fn get_type_method_key(typ: &Type) -> Option { | Type::Constant(_) | Type::Error | Type::Struct(_, _) + | Type::InfixExpr(..) | Type::TraitAsType(..) => None, } } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index df85cf0dda4..9124567b4e5 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -82,8 +82,9 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation &mut context, program.clone().into_sorted(), root_file_id, - None, // No debug_comptime_in_file - &[], // No macro processors + None, // No debug_comptime_in_file + false, // Disallow arithmetic generics + &[], // No macro processors )); } (program, context, errors) diff --git a/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml b/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml new file mode 100644 index 00000000000..2352ae0c562 --- /dev/null +++ b/test_programs/compile_success_empty/arithmetic_generics/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/arithmetic_generics/src/main.nr b/test_programs/compile_success_empty/arithmetic_generics/src/main.nr new file mode 100644 index 00000000000..d4f71d38413 --- /dev/null +++ b/test_programs/compile_success_empty/arithmetic_generics/src/main.nr @@ -0,0 +1,103 @@ +fn main() { + let (first, rest) = split_first([1, 2, 3, 4]); + assert_eq(first, 1); + assert_eq(rest, [2, 3, 4]); + + // Type inference works without the type constraints from assert_eq as well + let _ = split_first([1, 2, 3]); + + let _ = push_multiple([1, 2, 3]); +} + +fn split_first(array: [T; N]) -> (T, [T; N - 1]) { + std::static_assert(N != 0, "split_first called on empty array"); + let mut new_array: [T; N - 1] = std::unsafe::zeroed(); + + for i in 0..N - 1 { + new_array[i] = array[i + 1]; + } + + (array[0], new_array) +} + +fn push(array: [Field; N], element: Field) -> [Field; N + 1] { + let mut result: [_; N + 1] = std::unsafe::zeroed(); + result[array.len()] = element; + + for i in 0..array.len() { + result[i] = array[i]; + } + + result +} + +fn push_multiple(array: [Field; N]) -> [Field; N + 2] { + // : [Field; N + 1] + let array2 = push(array, 4); + + // : [Field; (N + 1) + 1] + let array3 = push(array2, 5); + + // [Field; (N + 1) + 1] = [Field; N + 2] + array3 +} + +// This signature fails because we can't match `_ + 1` to `3` at the call site +// fn push_multiple(array: [Field; 1 + N]) -> [Field; N + 3] { + +// ********************************************* +// The rest of this file is setup for demo_proof +// ********************************************* + +struct W { } + +struct Equiv { + // TODO(https://github.com/noir-lang/noir/issues/5644): + // Bug with struct_obj.field_thats_a_fn(x) + + to_: fn[TU](T) -> U, + fro_: fn[UT](U) -> T, + // .. other coherence conditions +} + +impl Equiv { + fn to(self, x: T) -> U { + (self.to_)(x) + } + + fn fro(self, x: U) -> T { + (self.fro_)(x) + } +} + +fn equiv_trans( + x: Equiv, + y: Equiv +) -> Equiv, Equiv), V, (Equiv, Equiv)> { + Equiv { to_: |z| { y.to(x.to(z)) }, fro_: |z| { x.fro(y.fro(z)) } } +} + +fn mul_one_r() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn add_equiv_r(_: Equiv, EN, W, EM>) -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn mul_comm() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +fn mul_add() -> Equiv, (), W, ()> { + Equiv { to_: |_x| { W {} }, fro_: |_x| { W {} } } +} + +// (N + 1) * N == N * N + N +fn demo_proof() -> Equiv, (Equiv, (), W, ()>, Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>), W, (Equiv, (Equiv, (), W, ()>, Equiv, (), W<(N * (N + 1))>, ()>), W, (Equiv, (), W<(N * (N + 1))>, ()>, Equiv, (), W, ()>)>, Equiv, (), W, ()>)> { + let p1: Equiv, (), W, ()> = mul_comm(); + let p2: Equiv, (), W, ()> = mul_add::(); + let p3_sub: Equiv, (), W, ()> = mul_one_r(); + let p3: Equiv, (), W, ()> = add_equiv_r::(p3_sub); + equiv_trans(equiv_trans(p1, p2), p3) +} diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 04af2855084..88aab65c6fa 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -404,7 +404,7 @@ fn prepare_package_from_source_string() { let mut state = LspState::new(&client, acvm::blackbox_solver::StubbedBlackBoxSolver); let (mut context, crate_id) = crate::prepare_source(source.to_string(), &mut state); - let _check_result = noirc_driver::check_crate(&mut context, crate_id, false, false, None); + let _check_result = noirc_driver::check_crate(&mut context, crate_id, &Default::default()); let main_func_id = context.get_main_function(&crate_id); assert!(main_func_id.is_some()); } diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs index b8ff4fb371f..56aef90cfde 100644 --- a/tooling/lsp/src/notifications/mod.rs +++ b/tooling/lsp/src/notifications/mod.rs @@ -132,7 +132,7 @@ pub(crate) fn process_workspace_for_noir_document( let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - let file_diagnostics = match check_crate(&mut context, crate_id, false, false, None) { + let file_diagnostics = match check_crate(&mut context, crate_id, &Default::default()) { Ok(((), warnings)) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; diff --git a/tooling/lsp/src/requests/code_lens_request.rs b/tooling/lsp/src/requests/code_lens_request.rs index 325392e150c..9799cf875a9 100644 --- a/tooling/lsp/src/requests/code_lens_request.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -72,7 +72,7 @@ fn on_code_lens_request_inner( let (mut context, crate_id) = prepare_source(source_string, state); // We ignore the warnings and errors produced by compilation for producing code lenses // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, None); + let _ = check_crate(&mut context, crate_id, &Default::default()); let collected_lenses = collect_lenses_for_package(&context, crate_id, &workspace, package, None); diff --git a/tooling/lsp/src/requests/inlay_hint.rs b/tooling/lsp/src/requests/inlay_hint.rs index 35ee36e11fa..2ed441c623e 100644 --- a/tooling/lsp/src/requests/inlay_hint.rs +++ b/tooling/lsp/src/requests/inlay_hint.rs @@ -599,6 +599,7 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File | Type::NamedGeneric(..) | Type::Forall(..) | Type::Constant(..) + | Type::InfixExpr(..) | Type::Quoted(..) | Type::Error => { parts.push(string_part(typ.to_string())); diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index 59ce91ea681..09794574709 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -387,7 +387,7 @@ where interner = def_interner; } else { // We ignore the warnings and errors produced by compilation while resolving the definition - let _ = noirc_driver::check_crate(&mut context, crate_id, false, false, None); + let _ = noirc_driver::check_crate(&mut context, crate_id, &Default::default()); interner = &context.def_interner; } diff --git a/tooling/lsp/src/requests/test_run.rs b/tooling/lsp/src/requests/test_run.rs index bf4d9763faf..fc4054633e2 100644 --- a/tooling/lsp/src/requests/test_run.rs +++ b/tooling/lsp/src/requests/test_run.rs @@ -61,7 +61,7 @@ fn on_test_run_request_inner( Some(package) => { let (mut context, crate_id) = crate::prepare_package(&workspace_file_manager, &parsed_files, package); - if check_crate(&mut context, crate_id, false, false, None).is_err() { + if check_crate(&mut context, crate_id, &Default::default()).is_err() { let result = NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), diff --git a/tooling/lsp/src/requests/tests.rs b/tooling/lsp/src/requests/tests.rs index 20b96029696..7203aca7f09 100644 --- a/tooling/lsp/src/requests/tests.rs +++ b/tooling/lsp/src/requests/tests.rs @@ -65,7 +65,7 @@ fn on_tests_request_inner( crate::prepare_package(&workspace_file_manager, &parsed_files, package); // We ignore the warnings and errors produced by compilation for producing tests // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false, false, None); + let _ = check_crate(&mut context, crate_id, &Default::default()); // We don't add test headings for a package if it contains no `#[test]` functions get_package_tests_in_crate(&context, &crate_id, &package.name) diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 74e07efb5c1..3f8cd055569 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -218,7 +218,7 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa &test_dir, &format!( r#" - nargo.arg("info").arg("--json").arg("--force"); + nargo.arg("info").arg("--arithmetic-generics").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ), diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index d40bae1ecfd..5239070b4d2 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -81,14 +81,7 @@ fn check_package( allow_overwrite: bool, ) -> Result { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; if package.is_library() || package.is_contract() { // Libraries do not have ABIs while contracts have many, so we cannot generate a `Prover.toml` file. @@ -157,14 +150,10 @@ fn create_input_toml_template( pub(crate) fn check_crate_and_report_errors( context: &mut Context, crate_id: CrateId, - deny_warnings: bool, - disable_macros: bool, - silence_warnings: bool, - debug_comptime_in_file: Option<&str>, + options: &CompileOptions, ) -> Result<(), CompileError> { - let result = - check_crate(context, crate_id, deny_warnings, disable_macros, debug_comptime_in_file); - report_errors(result, &context.file_manager, deny_warnings, silence_warnings) + let result = check_crate(context, crate_id, options); + report_errors(result, &context.file_manager, options.deny_warnings, options.silence_warnings) } #[cfg(test)] diff --git a/tooling/nargo_cli/src/cli/export_cmd.rs b/tooling/nargo_cli/src/cli/export_cmd.rs index 1b7ba97d68d..19add7f30dc 100644 --- a/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/tooling/nargo_cli/src/cli/export_cmd.rs @@ -83,14 +83,7 @@ fn compile_exported_functions( compile_options: &CompileOptions, ) -> Result<(), CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index c8848e2e304..1cf5b32c381 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -171,14 +171,8 @@ fn run_test + Default>( // We then need to construct a separate copy for each test. let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.debug_comptime_in_file.as_deref(), - ) - .expect("Any errors should have occurred when collecting test functions"); + check_crate(&mut context, crate_id, compile_options) + .expect("Any errors should have occurred when collecting test functions"); let test_functions = context .get_all_test_functions_in_crate_matching(&crate_id, FunctionNameMatch::Exact(fn_name)); @@ -237,14 +231,7 @@ fn get_tests_in_package( compile_options: &CompileOptions, ) -> Result, CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - check_crate_and_report_errors( - &mut context, - crate_id, - compile_options.deny_warnings, - compile_options.disable_macros, - compile_options.silence_warnings, - compile_options.debug_comptime_in_file.as_deref(), - )?; + check_crate_and_report_errors(&mut context, crate_id, compile_options)?; Ok(context .get_all_test_functions_in_crate_matching(&crate_id, fn_name) diff --git a/tooling/nargo_cli/tests/stdlib-tests.rs b/tooling/nargo_cli/tests/stdlib-tests.rs index c4cc792438e..0444f79d371 100644 --- a/tooling/nargo_cli/tests/stdlib-tests.rs +++ b/tooling/nargo_cli/tests/stdlib-tests.rs @@ -33,7 +33,7 @@ fn run_stdlib_tests() { let (mut context, dummy_crate_id) = prepare_package(&file_manager, &parsed_files, &dummy_package); - let result = check_crate(&mut context, dummy_crate_id, false, false, None); + let result = check_crate(&mut context, dummy_crate_id, &Default::default()); report_errors(result, &context.file_manager, true, false) .expect("Error encountered while compiling standard library");