diff --git a/aztec_macros/src/utils/hir_utils.rs b/aztec_macros/src/utils/hir_utils.rs index 0a8ce371708..200ce3099cb 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, false); + let mut errors = Elaborator::elaborate(context, *crate_id, items, None); 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, false); + let _errors = Elaborator::elaborate(context, *crate_id, items, None); } pub fn fully_qualified_note_path(context: &HirContext, note_id: StructId) -> Option { diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index a315e7ed397..31c279bc0f3 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -120,10 +120,6 @@ pub struct CompileOptions { #[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, - /// Flag to turn off the compiler check for under constrained values. /// Warning: This can improve compilation speed but can also lead to correctness errors. /// This check should always be run on production code. @@ -289,7 +285,6 @@ pub fn check_crate( crate_id, context, options.debug_comptime_in_file.as_deref(), - options.arithmetic_generics, error_on_unused_imports, macros, ); diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index e9650a625e8..fb5d6651cda 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -64,7 +64,6 @@ impl<'context> Elaborator<'context> { self.def_maps, self.crate_id, self.debug_comptime_in_file, - self.enable_arithmetic_generics, self.interpreter_call_stack.clone(), ); diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 6b23336b5ea..9925206003b 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -168,9 +168,6 @@ pub struct Elaborator<'context> { /// 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, - pub(crate) interpreter_call_stack: im::Vector, } @@ -194,7 +191,6 @@ impl<'context> Elaborator<'context> { def_maps: &'context mut DefMaps, crate_id: CrateId, debug_comptime_in_file: Option, - enable_arithmetic_generics: bool, interpreter_call_stack: im::Vector, ) -> Self { Self { @@ -217,7 +213,6 @@ impl<'context> Elaborator<'context> { current_trait_impl: None, debug_comptime_in_file, unresolved_globals: BTreeMap::new(), - enable_arithmetic_generics, current_trait: None, interpreter_call_stack, } @@ -227,14 +222,12 @@ 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, im::Vector::new(), ) } @@ -244,16 +237,8 @@ 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, - enable_arithmetic_generics, - ) - .errors + Self::elaborate_and_return_self(context, crate_id, items, debug_comptime_in_file).errors } pub fn elaborate_and_return_self( @@ -261,14 +246,8 @@ 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, - enable_arithmetic_generics, - ); + let mut this = Self::from_context(context, crate_id, debug_comptime_in_file); 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 8dccd5f0344..39ef4e0bb8e 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -450,7 +450,6 @@ impl<'context> Elaborator<'context> { } UnresolvedTypeExpression::Constant(int, _) => Type::Constant(int), UnresolvedTypeExpression::BinaryOperation(lhs, op, rhs, span) => { - let (lhs_span, rhs_span) = (lhs.span(), rhs.span()); let lhs = self.convert_expression_type(*lhs); let rhs = self.convert_expression_type(*rhs); @@ -463,15 +462,7 @@ impl<'context> Elaborator<'context> { Type::Error } } - (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() - } + (lhs, rhs) => Type::InfixExpr(Box::new(lhs), op, Box::new(rhs)).canonicalize(), } } UnresolvedTypeExpression::AsTraitPath(path) => self.resolve_as_trait_path(*path), diff --git a/compiler/noirc_frontend/src/hir/comptime/tests.rs b/compiler/noirc_frontend/src/hir/comptime/tests.rs index 64b489422a0..a47dbeace50 100644 --- a/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -51,7 +51,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, false); + Elaborator::elaborate_and_return_self(&mut context, krate, collector.items, None); 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 3cfa0989d7d..7ee1840690a 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -271,7 +271,6 @@ impl DefCollector { ast: SortedModule, root_file_id: FileId, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_items: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -291,7 +290,6 @@ impl DefCollector { dep.crate_id, context, debug_comptime_in_file, - enable_arithmetic_generics, error_on_usage_tracker, macro_processors, )); @@ -471,13 +469,8 @@ impl DefCollector { }) }); - let mut more_errors = Elaborator::elaborate( - context, - crate_id, - def_collector.items, - debug_comptime_in_file, - enable_arithmetic_generics, - ); + let mut more_errors = + Elaborator::elaborate(context, crate_id, def_collector.items, debug_comptime_in_file); errors.append(&mut more_errors); diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index a1c4d04cb30..75b860bf2c6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -76,7 +76,6 @@ impl CrateDefMap { crate_id: CrateId, context: &mut Context, debug_comptime_in_file: Option<&str>, - enable_arithmetic_generics: bool, error_on_unused_imports: bool, macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { @@ -133,7 +132,6 @@ impl CrateDefMap { ast, root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index cd075d2c374..3e01c370154 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -96,7 +96,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation }; let debug_comptime_in_file = None; - let enable_arithmetic_generics = false; let error_on_unused_imports = true; let macro_processors = &[]; @@ -107,7 +106,6 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation program.clone().into_sorted(), root_file_id, debug_comptime_in_file, - enable_arithmetic_generics, error_on_unused_imports, macro_processors, )); diff --git a/docs/docs/noir/concepts/generics.md b/docs/docs/noir/concepts/generics.md index 3e416eee093..f05540f9f55 100644 --- a/docs/docs/noir/concepts/generics.md +++ b/docs/docs/noir/concepts/generics.md @@ -45,17 +45,20 @@ fn main() { The `print` function will print `Hello!` an arbitrary number of times, twice in this case. +## Numeric Generics + If we want to be generic over array lengths (which are type-level integers), we can use numeric -generics. Using these looks just like using regular generics, but these generics can resolve to -integers at compile-time, rather than resolving to types. Here's an example of a struct that is -generic over the size of the array it contains internally: +generics. Using these looks similar to using regular generics, but introducing them into scope +requires declaring them with `let MyGenericName: IntegerType`. This can be done anywhere a normal +generic is declared. Instead of types, these generics resolve to integers at compile-time. +Here's an example of a struct that is generic over the size of the array it contains internally: ```rust -struct BigInt { +struct BigInt { limbs: [u32; N], } -impl BigInt { +impl BigInt { // `N` is in scope of all methods in the impl fn first(first: BigInt, second: BigInt) -> Self { assert(first.limbs != second.limbs); @@ -77,7 +80,7 @@ This is what [traits](../concepts/traits.md) are for in Noir. Here's an example any type `T` that implements the `Eq` trait for equality: ```rust -fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool where T: Eq { if (array1.len() == 0) | (array2.len() == 0) { @@ -161,3 +164,47 @@ fn example() { assert(10 as u32 == foo.generic_method::()); } ``` + +## Arithmetic Generics + +In addition to numeric generics, Noir also allows a limited form of arithmetic on generics. +When you have a numeric generic such as `N`, you can use the following operators on it in a +type position: `+`, `-`, `*`, `/`, and `%`. + +Note that type checking arithmetic generics is a best effort guess from the compiler and there +are many cases of types that are equal that the compiler may not see as such. For example, +we know that `T * (N + M)` should be equal to `T*N + T*M` but the compiler does not currently +apply the distributive law and thus sees these as different types. + +Even with this limitation though, the compiler can handle common cases decently well: + +```rust +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +impl Serialize<1> for Field { + fn serialize(self) -> [Field; 1] { + [self] + } +} + +impl Serialize for [T; N] + where T: Serialize { .. } + +impl Serialize for (T, U) + where T: Serialize, U: Serialize { .. } + +fn main() { + let data = (1, [2, 3, 4]); + assert(data.serialize().len(), 4); +} +``` + +Note that if there is any over or underflow the types will fail to unify: + +#include_code underflow-example test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr rust + +This also applies if there is underflow in an intermediate calculation: + +#include_code intermediate-underflow-example test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr rust diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml new file mode 100644 index 00000000000..0c5d98628a1 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_intermediate_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr new file mode 100644 index 00000000000..58cf2f648e5 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_intermediate_underflow/src/main.nr @@ -0,0 +1,32 @@ +// docs:start:intermediate-underflow-example +fn main() { + // From main it looks like there's nothing sketchy going on + seems_fine([]); +} + +// Since `seems_fine` says it can receive and return any length N +fn seems_fine(array: [Field; N]) -> [Field; N] { + // But inside `seems_fine` we pop from the array which + // requires the length to be greater than zero. + + // error: Could not determine array length `(0 - 1)` + push_zero(pop(array)) +} + +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn push_zero(array: [Field; N]) -> [Field; N + 1] { + let mut result: [Field; N + 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + // index N is already zeroed + result +} +// docs:end:intermediate-underflow-example diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml new file mode 100644 index 00000000000..f024f4c3b59 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arithmetic_generics_underflow" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr new file mode 100644 index 00000000000..4df83ac56e0 --- /dev/null +++ b/test_programs/compile_failure/arithmetic_generics_underflow/src/main.nr @@ -0,0 +1,14 @@ +// docs:start:underflow-example +fn pop(array: [Field; N]) -> [Field; N - 1] { + let mut result: [Field; N - 1] = std::mem::zeroed(); + for i in 0..N { + result[i] = array[i]; + } + result +} + +fn main() { + // error: Could not determine array length `(0 - 1)` + pop([]); +} +// docs:end:underflow-example diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 4dcfccdf085..7469c8be0f8 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("--arithmetic-generics").arg("--json").arg("--force"); + nargo.arg("info").arg("--json").arg("--force"); {assert_zero_opcodes}"#, ),