diff --git a/noir/.github/scripts/acvm_js-build.sh b/noir/.github/scripts/acvm_js-build.sh index 0565a9bb89f..95bd1efc8b9 100755 --- a/noir/.github/scripts/acvm_js-build.sh +++ b/noir/.github/scripts/acvm_js-build.sh @@ -1,5 +1,5 @@ #!/bin/bash set -eu -.github/scripts/install_wasm-bindgen.sh +.github/scripts/wasm-bindgen-install.sh yarn workspace @noir-lang/acvm_js build diff --git a/noir/.github/scripts/noir-wasm-build.sh b/noir/.github/scripts/noir-wasm-build.sh index 7c7e86e8c03..f799387b6f6 100755 --- a/noir/.github/scripts/noir-wasm-build.sh +++ b/noir/.github/scripts/noir-wasm-build.sh @@ -1,6 +1,5 @@ #!/bin/bash set -eu -.github/scripts/noirc-abi-build.sh - +.github/scripts/wasm-pack-install.sh yarn workspace @noir-lang/noir_wasm build diff --git a/noir/.github/scripts/noirc-abi-build.sh b/noir/.github/scripts/noirc-abi-build.sh index d5da6deaa0f..23b8393088e 100755 --- a/noir/.github/scripts/noirc-abi-build.sh +++ b/noir/.github/scripts/noirc-abi-build.sh @@ -1,5 +1,5 @@ #!/bin/bash set -eu -.github/scripts/install_wasm-bindgen.sh +.github/scripts/wasm-bindgen-install.sh yarn workspace @noir-lang/noirc_abi build diff --git a/noir/.github/scripts/install_wasm-bindgen.sh b/noir/.github/scripts/wasm-bindgen-install.sh similarity index 100% rename from noir/.github/scripts/install_wasm-bindgen.sh rename to noir/.github/scripts/wasm-bindgen-install.sh diff --git a/noir/.github/scripts/wasm-pack-install.sh b/noir/.github/scripts/wasm-pack-install.sh new file mode 100755 index 00000000000..f9b2fe160d5 --- /dev/null +++ b/noir/.github/scripts/wasm-pack-install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -eu + +curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash +cargo-binstall wasm-pack --version 0.12.1 -y diff --git a/noir/.github/workflows/docker-test-flow.yml b/noir/.github/workflows/docker-test-flow.yml index ad93d08e852..4b4a2ac2add 100644 --- a/noir/.github/workflows/docker-test-flow.yml +++ b/noir/.github/workflows/docker-test-flow.yml @@ -124,13 +124,19 @@ jobs: build-noir-wasm: name: Build noir wasm runs-on: ubuntu-latest - needs: [build-base-js] + needs: [build-base-js, build-noirc-abi] container: image: ghcr.io/noir-lang/noir:${{ github.sha }}-js credentials: username: ${{ github.actor }} password: ${{ secrets.github_token }} steps: + - name: Download noirc abi + uses: actions/download-artifact@v4 + with: + name: noirc_abi_wasm + path: | + /usr/src/noir/tooling/noirc_abi_wasm - name: Build working-directory: /usr/src/noir run: | @@ -160,6 +166,12 @@ jobs: - name: Prep downloaded artifact run: | chmod +x /usr/src/noir/target/release/nargo + - name: Download noirc abi + uses: actions/download-artifact@v4 + with: + name: noirc_abi_wasm + path: | + /usr/src/noir/tooling/noirc_abi_wasm - name: Download noir_wasm artifact uses: actions/download-artifact@v4 with: @@ -188,6 +200,12 @@ jobs: - name: Prep downloaded artifact run: | chmod +x /usr/src/noir/target/release/nargo + - name: Download noirc abi + uses: actions/download-artifact@v4 + with: + name: noirc_abi_wasm + path: | + /usr/src/noir/tooling/noirc_abi_wasm - name: Download noir_wasm artifact uses: actions/download-artifact@v4 with: diff --git a/noir/.gitrepo b/noir/.gitrepo index 9fda7270af2..2294b2d2435 100644 --- a/noir/.gitrepo +++ b/noir/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/noir-lang/noir branch = aztec-packages - commit = a4b6635fca3749d463ac0a77ee4cfbdc48f7cfd4 - parent = 54b929950fa3ae0f0cf95272fee23368240e4f30 + commit = 0c185c29d4ba50486d481ecec5b9fc27097dbb0a + parent = 09090e877f1535c5badc5cb3740653f91646391e method = merge cmdver = 0.4.6 diff --git a/noir/acvm-repo/acvm/tests/solver.rs b/noir/acvm-repo/acvm/tests/solver.rs index 4a4b75df146..486e04d5bf1 100644 --- a/noir/acvm-repo/acvm/tests/solver.rs +++ b/noir/acvm-repo/acvm/tests/solver.rs @@ -17,7 +17,6 @@ use acvm_blackbox_solver::StubbedBlackBoxSolver; // Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. #[test] -#[ignore] fn inversion_brillig_oracle_equivalence() { // Opcodes below describe the following: // fn main(x : Field, y : pub Field) { @@ -126,7 +125,6 @@ fn inversion_brillig_oracle_equivalence() { } #[test] -#[ignore] fn double_inversion_brillig_oracle() { // Opcodes below describe the following: // fn main(x : Field, y : pub Field) { @@ -453,6 +451,7 @@ fn brillig_oracle_predicate() { // ACVM should be able to be finalized in `Solved` state. acvm.finalize(); } + #[test] fn unsatisfied_opcode_resolved() { let a = Witness(0); diff --git a/noir/compiler/fm/src/lib.rs b/noir/compiler/fm/src/lib.rs index 55fb762479f..bb080c62c0e 100644 --- a/noir/compiler/fm/src/lib.rs +++ b/noir/compiler/fm/src/lib.rs @@ -88,15 +88,15 @@ impl FileManager { assert!(old_value.is_none(), "ice: the same path was inserted into the file manager twice"); } - pub fn fetch_file(&self, file_id: FileId) -> &str { + pub fn fetch_file(&self, file_id: FileId) -> Option<&str> { // Unwrap as we ensure that all file_id's map to a corresponding file in the file map - self.file_map.get_file(file_id).unwrap().source() + self.file_map.get_file(file_id).map(|file| file.source()) } - pub fn path(&self, file_id: FileId) -> &Path { + pub fn path(&self, file_id: FileId) -> Option<&Path> { // Unwrap as we ensure that all file_ids are created by the file manager // So all file_ids will points to a corresponding path - self.id_to_path.get(&file_id).unwrap().as_path() + self.id_to_path.get(&file_id).map(|path| path.as_path()) } // TODO: This should accept a &Path instead of a PathBuf @@ -204,7 +204,7 @@ mod tests { let file_id = fm.add_file_with_source(file_name, "fn foo() {}".to_string()).unwrap(); - assert!(fm.path(file_id).ends_with("foo.nr")); + assert!(fm.path(file_id).unwrap().ends_with("foo.nr")); } /// Tests that two identical files that have different paths are treated as the same file diff --git a/noir/compiler/noirc_driver/src/debug.rs b/noir/compiler/noirc_driver/src/debug.rs index 84a3e143357..5e309398cc5 100644 --- a/noir/compiler/noirc_driver/src/debug.rs +++ b/noir/compiler/noirc_driver/src/debug.rs @@ -31,14 +31,12 @@ pub(crate) fn filter_relevant_files( let mut file_map = BTreeMap::new(); for file_id in files_with_debug_symbols { - let file_source = file_manager.fetch_file(file_id); + let file_path = file_manager.path(file_id).expect("file should exist"); + let file_source = file_manager.fetch_file(file_id).expect("file should exist"); file_map.insert( file_id, - DebugFile { - source: file_source.to_string(), - path: file_manager.path(file_id).to_path_buf(), - }, + DebugFile { source: file_source.to_string(), path: file_path.to_path_buf() }, ); } file_map diff --git a/noir/compiler/noirc_driver/src/lib.rs b/noir/compiler/noirc_driver/src/lib.rs index 7136580b770..db69d41c704 100644 --- a/noir/compiler/noirc_driver/src/lib.rs +++ b/noir/compiler/noirc_driver/src/lib.rs @@ -45,6 +45,10 @@ pub const NOIR_ARTIFACT_VERSION_STRING: &str = #[derive(Args, Clone, Debug, Default, Serialize, Deserialize)] pub struct CompileOptions { + /// Force a full recompilation. + #[arg(long = "force")] + pub force_compile: bool, + /// Emit debug information for the intermediate SSA IR #[arg(long, hide = true)] pub show_ssa: bool, @@ -206,7 +210,6 @@ pub fn compile_main( crate_id: CrateId, options: &CompileOptions, cached_program: Option, - force_compile: bool, ) -> CompilationResult { let (_, mut warnings) = check_crate(context, crate_id, options.deny_warnings, options.disable_macros)?; @@ -220,8 +223,9 @@ pub fn compile_main( vec![err] })?; - let compiled_program = compile_no_check(context, options, main, cached_program, force_compile) - .map_err(FileDiagnostic::from)?; + let compiled_program = + compile_no_check(context, options, main, cached_program, options.force_compile) + .map_err(FileDiagnostic::from)?; let compilation_warnings = vecmap(compiled_program.warnings.clone(), FileDiagnostic::from); if options.deny_warnings && !compilation_warnings.is_empty() { return Err(compilation_warnings); diff --git a/noir/compiler/noirc_evaluator/src/errors.rs b/noir/compiler/noirc_evaluator/src/errors.rs index 33ecc794f76..d7229c0adc5 100644 --- a/noir/compiler/noirc_evaluator/src/errors.rs +++ b/noir/compiler/noirc_evaluator/src/errors.rs @@ -42,6 +42,8 @@ pub enum RuntimeError { UnknownLoopBound { call_stack: CallStack }, #[error("Argument is not constant")] AssertConstantFailed { call_stack: CallStack }, + #[error("Nested slices are not supported")] + NestedSlice { call_stack: CallStack }, } // We avoid showing the actual lhs and rhs since most of the time they are just 0 @@ -129,7 +131,8 @@ impl RuntimeError { | RuntimeError::UnknownLoopBound { call_stack } | RuntimeError::AssertConstantFailed { call_stack } | RuntimeError::IntegerOutOfBounds { call_stack, .. } - | RuntimeError::UnsupportedIntegerSize { call_stack, .. } => call_stack, + | RuntimeError::UnsupportedIntegerSize { call_stack, .. } + | RuntimeError::NestedSlice { call_stack, .. } => call_stack, } } } diff --git a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index b0ee8468bd8..d1991abab37 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -47,13 +47,8 @@ pub(super) fn simplify_call( let field = constant_args[0]; let limb_count = constant_args[1].to_u128() as u32; - let result_slice = constant_to_radix(endian, field, 2, limb_count, dfg); - - let length = dfg - .try_get_array_length(result_slice) - .expect("ICE: a constant array should have an associated length"); - let len_value = - dfg.make_constant(FieldElement::from(length as u128), Type::field()); + let (len_value, result_slice) = + constant_to_radix(endian, field, 2, limb_count, dfg); // `Intrinsic::ToBits` returns slices which are represented // by tuples with the structure (length, slice contents) @@ -68,13 +63,8 @@ pub(super) fn simplify_call( let radix = constant_args[1].to_u128() as u32; let limb_count = constant_args[2].to_u128() as u32; - let result_slice = constant_to_radix(endian, field, radix, limb_count, dfg); - - let length = dfg - .try_get_array_length(result_slice) - .expect("ICE: a constant array should have an associated length"); - let len_value = - dfg.make_constant(FieldElement::from(length as u128), Type::field()); + let (len_value, result_slice) = + constant_to_radix(endian, field, radix, limb_count, dfg); // `Intrinsic::ToRadix` returns slices which are represented // by tuples with the structure (length, slice contents) @@ -468,14 +458,26 @@ fn make_constant_array(dfg: &mut DataFlowGraph, results: Vec, typ: dfg.make_array(result_constants.into(), typ) } -/// Returns a Value::Array of constants corresponding to the limbs of the radix decomposition. +fn make_constant_slice( + dfg: &mut DataFlowGraph, + results: Vec, + typ: Type, +) -> (ValueId, ValueId) { + let result_constants = vecmap(results, |element| dfg.make_constant(element, typ.clone())); + + let typ = Type::Slice(Rc::new(vec![typ])); + let length = FieldElement::from(result_constants.len() as u128); + (dfg.make_constant(length, Type::field()), dfg.make_array(result_constants.into(), typ)) +} + +/// Returns a slice (represented by a tuple (len, slice)) of constants corresponding to the limbs of the radix decomposition. fn constant_to_radix( endian: Endian, field: FieldElement, radix: u32, limb_count: u32, dfg: &mut DataFlowGraph, -) -> ValueId { +) -> (ValueId, ValueId) { 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"); @@ -490,8 +492,7 @@ fn constant_to_radix( if endian == Endian::Big { limbs.reverse(); } - - make_constant_array(dfg, limbs, Type::unsigned(bit_size)) + make_constant_slice(dfg, limbs, Type::unsigned(bit_size)) } fn to_u8_vec(dfg: &DataFlowGraph, values: im::Vector>) -> Vec { diff --git a/noir/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 0b8c3a37ef9..eb35ba9a65b 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -187,12 +187,14 @@ impl<'a> FunctionContext<'a> { let typ = Self::convert_type(&array.typ).flatten(); Ok(match array.typ { - ast::Type::Array(_, _) => self.codegen_array(elements, typ[0].clone()), + ast::Type::Array(_, _) => { + self.codegen_array_checked(elements, typ[0].clone())? + } ast::Type::Slice(_) => { let slice_length = self.builder.field_constant(array.contents.len() as u128); - - let slice_contents = self.codegen_array(elements, typ[1].clone()); + let slice_contents = + self.codegen_array_checked(elements, typ[1].clone())?; Tree::Branch(vec![slice_length.into(), slice_contents]) } _ => unreachable!( @@ -231,6 +233,18 @@ impl<'a> FunctionContext<'a> { self.codegen_array(elements, typ) } + // Codegen an array but make sure that we do not have a nested slice + fn codegen_array_checked( + &mut self, + elements: Vec, + typ: Type, + ) -> Result { + if typ.is_nested_slice() { + return Err(RuntimeError::NestedSlice { call_stack: self.builder.get_call_stack() }); + } + Ok(self.codegen_array(elements, typ)) + } + /// Codegen an array by allocating enough space for each element and inserting separate /// store instructions until each element is stored. The store instructions will be separated /// by add instructions to calculate the new offset address to store to next. diff --git a/noir/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index b1e559895b8..f0fc482cae0 100644 --- a/noir/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -20,8 +20,8 @@ use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TraitId, Type use crate::parser::{ParserError, SortedModule}; use crate::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, Path, PathKind, Type, UnresolvedGenerics, UnresolvedTraitConstraint, - UnresolvedType, + NoirTypeAlias, Path, PathKind, Type, TypeBindings, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, }; use fm::FileId; use iter_extended::vecmap; @@ -90,6 +90,7 @@ pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, pub trait_id: Option, + pub trait_generics: Vec, pub trait_path: Path, pub object_type: UnresolvedType, pub methods: UnresolvedFunctions, @@ -456,19 +457,44 @@ fn type_check_functions( } // TODO(vitkov): Move this out of here and into type_check +#[allow(clippy::too_many_arguments)] pub(crate) fn check_methods_signatures( resolver: &mut Resolver, impl_methods: &Vec<(FileId, FuncId)>, trait_id: TraitId, + trait_name_span: Span, + // These are the generics on the trait itself from the impl. + // E.g. in `impl Foo for Bar`, this is `vec![A, B]`. + trait_generics: Vec, trait_impl_generic_count: usize, + file_id: FileId, errors: &mut Vec<(CompilationError, FileId)>, ) { let self_type = resolver.get_self_type().expect("trait impl must have a Self type").clone(); + let trait_generics = vecmap(trait_generics, |typ| resolver.resolve_type(typ)); // Temporarily bind the trait's Self type to self_type so we can type check let the_trait = resolver.interner.get_trait_mut(trait_id); the_trait.self_type_typevar.bind(self_type); + if trait_generics.len() != the_trait.generics.len() { + let error = DefCollectorErrorKind::MismatchGenericCount { + actual_generic_count: trait_generics.len(), + expected_generic_count: the_trait.generics.len(), + // Preferring to use 'here' over a more precise term like 'this reference' + // to try to make the error easier to understand for newer users. + location: "here it", + origin: the_trait.name.to_string(), + span: trait_name_span, + }; + errors.push((error.into(), file_id)); + } + + // We also need to bind the traits generics to the trait's generics on the impl + for ((_, generic), binding) in the_trait.generics.iter().zip(trait_generics) { + generic.bind(binding); + } + // Temporarily take the trait's methods so we can use both them and a mutable reference // to the interner within the loop. let trait_methods = std::mem::take(&mut the_trait.methods); @@ -482,49 +508,44 @@ pub(crate) fn check_methods_signatures( if let Some(trait_method) = trait_methods.iter().find(|method| method.name.0.contents == func_name) { - let mut typecheck_errors = Vec::new(); let impl_method = resolver.interner.function_meta(func_id); - let (impl_function_type, _) = impl_method.typ.instantiate(resolver.interner); - let impl_method_generic_count = impl_method.typ.generic_count() - trait_impl_generic_count; // We subtract 1 here to account for the implicit generic `Self` type that is on all // traits (and thus trait methods) but is not required (or allowed) for users to specify. - let trait_method_generic_count = trait_method.generics().len() - 1; + let the_trait = resolver.interner.get_trait(trait_id); + let trait_method_generic_count = + trait_method.generics().len() - 1 - the_trait.generics.len(); if impl_method_generic_count != trait_method_generic_count { - let error = DefCollectorErrorKind::MismatchTraitImplementationNumGenerics { - impl_method_generic_count, - trait_method_generic_count, - trait_name: resolver.interner.get_trait(trait_id).name.to_string(), - method_name: func_name.to_string(), + let trait_name = resolver.interner.get_trait(trait_id).name.clone(); + + let error = DefCollectorErrorKind::MismatchGenericCount { + actual_generic_count: impl_method_generic_count, + expected_generic_count: trait_method_generic_count, + origin: format!("{}::{}", trait_name, func_name), + location: "this method", span: impl_method.location.span, }; errors.push((error.into(), *file_id)); } - if let Type::Function(impl_params, _, _) = impl_function_type { - if trait_method.arguments().len() == impl_params.len() { - // Check the parameters of the impl method against the parameters of the trait method - let args = trait_method.arguments().iter(); - let args_and_params = args.zip(&impl_params).zip(&impl_method.parameters.0); - - for (parameter_index, ((expected, actual), (hir_pattern, _, _))) in - args_and_params.enumerate() - { - expected.unify(actual, &mut typecheck_errors, || { - TypeCheckError::TraitMethodParameterTypeMismatch { - method_name: func_name.to_string(), - expected_typ: expected.to_string(), - actual_typ: actual.to_string(), - parameter_span: hir_pattern.span(), - parameter_index: parameter_index + 1, - } - }); - } - } else { + // This instantiation is technically not needed. We could bind each generic in the + // trait function to the impl's corresponding generic but to do so we'd have to rely + // on the trait function's generics being first in the generic list, since the same + // list also contains the generic `Self` variable, and any generics on the trait itself. + // + // Instantiating the impl method's generics here instead is a bit less precise but + // doesn't rely on any orderings that may be changed. + let impl_function_type = impl_method.typ.instantiate(resolver.interner).0; + + let mut bindings = TypeBindings::new(); + let mut typecheck_errors = Vec::new(); + + if let Type::Function(impl_params, impl_return, _) = impl_function_type.as_monotype() { + if trait_method.arguments().len() != impl_params.len() { let error = DefCollectorErrorKind::MismatchTraitImplementationNumParameters { actual_num_parameters: impl_method.parameters.0.len(), expected_num_parameters: trait_method.arguments().len(), @@ -534,28 +555,51 @@ pub(crate) fn check_methods_signatures( }; errors.push((error.into(), *file_id)); } - } - // Check that impl method return type matches trait return type: - let resolved_return_type = - resolver.resolve_type(impl_method.return_type.get_type().into_owned()); + // Check the parameters of the impl method against the parameters of the trait method + let args = trait_method.arguments().iter(); + let args_and_params = args.zip(impl_params).zip(&impl_method.parameters.0); - // TODO: This is not right since it may bind generic return types - trait_method.return_type().unify(&resolved_return_type, &mut typecheck_errors, || { - let impl_method = resolver.interner.function_meta(func_id); - let ret_type_span = impl_method.return_type.get_type().span; - let expr_span = ret_type_span.expect("return type must always have a span"); + for (parameter_index, ((expected, actual), (hir_pattern, _, _))) in + args_and_params.enumerate() + { + if expected.try_unify(actual, &mut bindings).is_err() { + typecheck_errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { + method_name: func_name.to_string(), + expected_typ: expected.to_string(), + actual_typ: actual.to_string(), + parameter_span: hir_pattern.span(), + parameter_index: parameter_index + 1, + }); + } + } - let expected_typ = trait_method.return_type().to_string(); - let expr_typ = impl_method.return_type().to_string(); - TypeCheckError::TypeMismatch { expr_typ, expected_typ, expr_span } - }); + if trait_method.return_type().try_unify(impl_return, &mut bindings).is_err() { + let impl_method = resolver.interner.function_meta(func_id); + let ret_type_span = impl_method.return_type.get_type().span; + let expr_span = ret_type_span.expect("return type must always have a span"); + + let expected_typ = trait_method.return_type().to_string(); + let expr_typ = impl_method.return_type().to_string(); + let error = TypeCheckError::TypeMismatch { expr_typ, expected_typ, expr_span }; + typecheck_errors.push(error); + } + } else { + unreachable!( + "impl_function_type is not a function type, it is: {impl_function_type}" + ); + } errors.extend(typecheck_errors.iter().cloned().map(|e| (e.into(), *file_id))); } } + // Now unbind `Self` and the trait's generics let the_trait = resolver.interner.get_trait_mut(trait_id); the_trait.set_methods(trait_methods); the_trait.self_type_typevar.unbind(the_trait.self_type_typevar_id); + + for (old_id, generic) in &the_trait.generics { + generic.unbind(*old_id); + } } diff --git a/noir/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 04791b11b2a..2e6eb3992ff 100644 --- a/noir/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -72,7 +72,7 @@ pub fn collect_defs( errors.extend(collector.collect_functions(context, ast.functions, crate_id)); - errors.extend(collector.collect_trait_impls(context, ast.trait_impls, crate_id)); + collector.collect_trait_impls(context, ast.trait_impls, crate_id); collector.collect_impls(context, ast.impls, crate_id); @@ -144,7 +144,7 @@ impl<'a> ModCollector<'a> { context: &mut Context, impls: Vec, krate: CrateId, - ) -> Vec<(CompilationError, fm::FileId)> { + ) { for trait_impl in impls { let trait_name = trait_impl.trait_name.clone(); @@ -168,11 +168,11 @@ impl<'a> ModCollector<'a> { generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, trait_id: None, // will be filled later + trait_generics: trait_impl.trait_generics, }; self.def_collector.collected_traits_impls.push(unresolved_trait_impl); } - vec![] } fn collect_trait_impl_function_overrides( @@ -634,7 +634,10 @@ fn find_module( anchor: FileId, mod_name: &str, ) -> Result { - let anchor_path = file_manager.path(anchor).with_extension(""); + let anchor_path = file_manager + .path(anchor) + .expect("File must exist in file manager in order for us to be resolving its imports.") + .with_extension(""); let anchor_dir = anchor_path.parent().unwrap(); // if `anchor` is a `main.nr`, `lib.nr`, `mod.nr` or `{mod_name}.nr`, we check siblings of diff --git a/noir/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 2b91c4b36c5..de45be48c4e 100644 --- a/noir/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -49,12 +49,12 @@ pub enum DefCollectorErrorKind { method_name: String, span: Span, }, - #[error("Mismatched number of generics in impl method")] - MismatchTraitImplementationNumGenerics { - impl_method_generic_count: usize, - trait_method_generic_count: usize, - trait_name: String, - method_name: String, + #[error("Mismatched number of generics in {location}")] + MismatchGenericCount { + actual_generic_count: usize, + expected_generic_count: usize, + location: &'static str, + origin: String, span: Span, }, #[error("Method is not defined in trait")] @@ -188,16 +188,16 @@ impl From for Diagnostic { "`{trait_name}::{method_name}` expects {expected_num_parameters} parameter{plural}, but this method has {actual_num_parameters}"); Diagnostic::simple_error(primary_message, "".to_string(), span) } - DefCollectorErrorKind::MismatchTraitImplementationNumGenerics { - impl_method_generic_count, - trait_method_generic_count, - trait_name, - method_name, + DefCollectorErrorKind::MismatchGenericCount { + actual_generic_count, + expected_generic_count, + location, + origin, span, } => { - let plural = if trait_method_generic_count == 1 { "" } else { "s" }; + let plural = if expected_generic_count == 1 { "" } else { "s" }; let primary_message = format!( - "`{trait_name}::{method_name}` expects {trait_method_generic_count} generic{plural}, but this method has {impl_method_generic_count}"); + "`{origin}` expects {expected_generic_count} generic{plural}, but {location} has {actual_generic_count}"); Diagnostic::simple_error(primary_message, "".to_string(), span) } DefCollectorErrorKind::MethodNotInTrait { trait_name, impl_method } => { diff --git a/noir/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/compiler/noirc_frontend/src/hir/def_map/mod.rs index e86d4efede7..d60ceffa9af 100644 --- a/noir/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -295,7 +295,7 @@ pub struct Contract { /// Given a FileId, fetch the File, from the FileManager and parse it's content pub fn parse_file(fm: &FileManager, file_id: FileId) -> (ParsedModule, Vec) { - let file_source = fm.fetch_file(file_id); + let file_source = fm.fetch_file(file_id).expect("File does not exist"); parse_program(file_source) } diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs index c2f787313c6..390807afd17 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -60,8 +60,8 @@ pub enum ResolverError { NonStructWithGenerics { span: Span }, #[error("Cannot apply generics on Self type")] GenericsOnSelfType { span: Span }, - #[error("Incorrect amount of arguments to generic type constructor")] - IncorrectGenericCount { span: Span, struct_type: String, actual: usize, expected: usize }, + #[error("Incorrect amount of arguments to {item_name}")] + IncorrectGenericCount { span: Span, item_name: String, actual: usize, expected: usize }, #[error("{0}")] ParserError(Box), #[error("Function is not defined in a contract yet sets its contract visibility")] @@ -82,6 +82,8 @@ pub enum ResolverError { NonCrateFunctionCalled { name: String, span: Span }, #[error("Only sized types may be used in the entry point to a program")] InvalidTypeForEntryPoint { span: Span }, + #[error("Nested slices are not supported")] + NestedSlices { span: Span }, } impl ResolverError { @@ -259,12 +261,12 @@ impl From for Diagnostic { "Use an explicit type name or apply the generics at the start of the impl instead".into(), span, ), - ResolverError::IncorrectGenericCount { span, struct_type, actual, expected } => { + ResolverError::IncorrectGenericCount { span, item_name, actual, expected } => { let expected_plural = if expected == 1 { "" } else { "s" }; let actual_plural = if actual == 1 { "is" } else { "are" }; Diagnostic::simple_error( - format!("The struct type {struct_type} has {expected} generic{expected_plural} but {actual} {actual_plural} given here"), + format!("`{item_name}` has {expected} generic argument{expected_plural} but {actual} {actual_plural} given here"), "Incorrect number of generic arguments".into(), span, ) @@ -304,6 +306,11 @@ impl From for Diagnostic { ResolverError::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error( "Only sized types may be used in the entry point to a program".to_string(), "Slices, references, or any type containing them may not be used in main or a contract function".to_string(), span), + ResolverError::NestedSlices { span } => Diagnostic::simple_error( + "Nested slices are not supported".into(), + "Try to use a constant sized array instead".into(), + span, + ), } } } diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs index b9770f34e1e..bb7acf1037b 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -15,7 +15,7 @@ use crate::hir_def::expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCapturedVar, HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, - HirMethodCallExpression, HirPrefixExpression, + HirMethodCallExpression, HirPrefixExpression, ImplKind, }; use crate::hir_def::traits::{Trait, TraitConstraint}; @@ -29,7 +29,7 @@ use crate::hir::def_map::{LocalModuleId, ModuleDefId, TryFromModuleDefId, MAIN_F use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; use crate::node_interner::{ DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId, StructId, TraitId, - TraitImplId, TraitImplKind, + TraitImplId, TraitMethodId, }; use crate::{ hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}, @@ -40,8 +40,8 @@ use crate::{ ArrayLiteral, ContractFunctionType, Distinctness, ForRange, FunctionDefinition, FunctionReturnType, FunctionVisibility, Generics, LValue, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, Shared, StructType, Type, TypeAliasType, TypeBinding, TypeVariable, - UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, Visibility, ERROR_IDENT, + TypeVariableKind, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, Visibility, ERROR_IDENT, }; use fm::FileId; use iter_extended::vecmap; @@ -267,7 +267,7 @@ impl<'a> Resolver<'a> { let has_underscore_prefix = variable_name.starts_with('_'); // XXX: This is used for development mode, and will be removed metadata.warn_if_unused && metadata.num_times_used == 0 && !has_underscore_prefix }); - unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident)); + unused_vars.extend(unused_variables.map(|(_, meta)| meta.ident.clone())); } /// Run the given function in a new scope. @@ -304,8 +304,9 @@ impl<'a> Resolver<'a> { let location = Location::new(name.span(), self.file); let id = self.interner.push_definition(name.0.contents.clone(), mutable, definition, location); - let ident = HirIdent { location, id }; - let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused }; + let ident = HirIdent::non_trait_method(id, location); + let resolver_meta = + ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused }; let scope = self.scopes.get_mut_scope(); let old_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); @@ -325,8 +326,6 @@ impl<'a> Resolver<'a> { fn add_global_variable_decl(&mut self, name: Ident, definition: DefinitionKind) -> HirIdent { let scope = self.scopes.get_mut_scope(); - let ident; - let resolver_meta; // This check is necessary to maintain the same definition ids in the interner. Currently, each function uses a new resolver that has its own ScopeForest and thus global scope. // We must first check whether an existing definition ID has been inserted as otherwise there will be multiple definitions for the same global statement. @@ -341,17 +340,20 @@ impl<'a> Resolver<'a> { } } - if let Some(id) = stmt_id { + let (ident, resolver_meta) = if let Some(id) = stmt_id { let hir_let_stmt = self.interner.let_statement(&id); - ident = hir_let_stmt.ident(); - resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; + let ident = hir_let_stmt.ident(); + let resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; + (hir_let_stmt.ident(), resolver_meta) } else { let location = Location::new(name.span(), self.file); let id = self.interner.push_definition(name.0.contents.clone(), false, definition, location); - ident = HirIdent { location, id }; - resolver_meta = ResolverMeta { num_times_used: 0, ident, warn_if_unused: true }; - } + let ident = HirIdent::non_trait_method(id, location); + let resolver_meta = + ResolverMeta { num_times_used: 0, ident: ident.clone(), warn_if_unused: true }; + (ident, resolver_meta) + }; let old_global_value = scope.add_key_value(name.0.contents.clone(), resolver_meta); if let Some(old_global_value) = old_global_value { @@ -376,7 +378,7 @@ impl<'a> Resolver<'a> { self.push_err(error); let id = DefinitionId::dummy_id(); let location = Location::new(name.span(), self.file); - (HirIdent { location, id }, 0) + (HirIdent::non_trait_method(id, location), 0) }) } @@ -389,7 +391,7 @@ impl<'a> Resolver<'a> { if let Some((variable_found, scope)) = variable { variable_found.num_times_used += 1; let id = variable_found.ident.id; - Ok((HirIdent { location, id }, scope)) + Ok((HirIdent::non_trait_method(id, location), scope)) } else { Err(ResolverError::VariableNotDeclared { name: name.0.contents.clone(), @@ -419,8 +421,27 @@ impl<'a> Resolver<'a> { constraint: UnresolvedTraitConstraint, ) -> Option { let typ = self.resolve_type(constraint.typ); - let trait_id = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?.id; - Some(TraitConstraint { typ, trait_id }) + let trait_generics = + vecmap(constraint.trait_bound.trait_generics, |typ| self.resolve_type(typ)); + + let span = constraint.trait_bound.trait_path.span(); + let the_trait = self.lookup_trait_or_error(constraint.trait_bound.trait_path)?; + let trait_id = the_trait.id; + + let expected_generics = the_trait.generics.len(); + let actual_generics = trait_generics.len(); + + if actual_generics != expected_generics { + let item_name = the_trait.name.to_string(); + self.push_err(ResolverError::IncorrectGenericCount { + span, + item_name, + actual: actual_generics, + expected: expected_generics, + }); + } + + Some(TraitConstraint { typ, trait_id, trait_generics }) } /// Translates an UnresolvedType into a Type and appends any @@ -564,11 +585,13 @@ impl<'a> Resolver<'a> { fn resolve_trait_as_type( &mut self, path: Path, - _args: Vec, - _new_variables: &mut Generics, + args: Vec, + new_variables: &mut Generics, ) -> Type { + let args = vecmap(args, |arg| self.resolve_type_inner(arg, new_variables)); + if let Some(t) = self.lookup_trait_or_error(path) { - Type::TraitAsType(t.id, Rc::new(t.name.to_string())) + Type::TraitAsType(t.id, Rc::new(t.name.to_string()), args) } else { Type::Error } @@ -584,7 +607,7 @@ impl<'a> Resolver<'a> { if args.len() != expected_count { self.errors.push(ResolverError::IncorrectGenericCount { span, - struct_type: type_name(), + item_name: type_name(), actual: args.len(), expected: expected_count, }); @@ -667,22 +690,27 @@ impl<'a> Resolver<'a> { Some(Ok(found)) => return found, // Try to look it up as a global, but still issue the first error if we fail Some(Err(error)) => match self.lookup_global(path) { - Ok(id) => return (HirIdent { location, id }, 0), + Ok(id) => return (HirIdent::non_trait_method(id, location), 0), Err(_) => error, }, None => match self.lookup_global(path) { - Ok(id) => return (HirIdent { location, id }, 0), + Ok(id) => return (HirIdent::non_trait_method(id, location), 0), Err(error) => error, }, }; self.push_err(error); let id = DefinitionId::dummy_id(); - (HirIdent { location, id }, 0) + (HirIdent::non_trait_method(id, location), 0) } /// Translates an UnresolvedType to a Type pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type { - self.resolve_type_inner(typ, &mut vec![]) + let span = typ.span; + let resolved_type = self.resolve_type_inner(typ, &mut vec![]); + if resolved_type.is_nested_slice() { + self.errors.push(ResolverError::NestedSlices { span: span.unwrap() }); + } + resolved_type } pub fn resolve_type_aliases( @@ -736,7 +764,6 @@ impl<'a> Resolver<'a> { let name = Rc::new(generic.0.contents.clone()); if let Some((_, _, first_span)) = self.find_generic(&name) { - let span = generic.0.span(); self.errors.push(ResolverError::DuplicateDefinition { name: generic.0.contents.clone(), first_span: *first_span, @@ -750,6 +777,32 @@ impl<'a> Resolver<'a> { }) } + /// Add the given existing generics to scope. + /// This is useful for adding the same generics to many items. E.g. apply impl generics + /// to each function in the impl or trait generics to each item in the trait. + pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { + assert_eq!(names.len(), generics.len()); + + for (name, (_id, typevar)) in names.iter().zip(generics) { + self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + } + } + + pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { + // Check for name collisions of this generic + let rc_name = Rc::new(name.to_owned()); + + if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + self.errors.push(ResolverError::DuplicateDefinition { + name: name.to_owned(), + first_span: *first_span, + second_span: span, + }); + } else { + self.generics.push((rc_name, typevar, span)); + } + } + pub fn resolve_struct_fields( mut self, unresolved: NoirStruct, @@ -778,12 +831,13 @@ impl<'a> Resolver<'a> { /// there's a bunch of other places where trait constraints can pop up fn resolve_trait_constraints( &mut self, - where_clause: &Vec, + where_clause: &[UnresolvedTraitConstraint], ) -> Vec { - vecmap(where_clause, |constraint| TraitConstraint { - typ: self.resolve_type(constraint.typ.clone()), - trait_id: constraint.trait_bound.trait_id.unwrap_or_else(TraitId::dummy_id), - }) + where_clause + .iter() + .cloned() + .filter_map(|constraint| self.resolve_trait_constraint(constraint)) + .collect() } /// Extract metadata from a NoirFunction @@ -793,7 +847,7 @@ impl<'a> Resolver<'a> { fn extract_meta(&mut self, func: &NoirFunction, func_id: FuncId) -> FuncMeta { let location = Location::new(func.name_ident().span(), self.file); let id = self.interner.function_definition_id(func_id); - let name_ident = HirIdent { id, location }; + let name_ident = HirIdent::non_trait_method(id, location); let attributes = func.attributes().clone(); @@ -1115,7 +1169,7 @@ impl<'a> Resolver<'a> { match lvalue { LValue::Ident(ident) => { let ident = self.find_variable_or_default(&ident); - self.resolve_local_variable(ident.0, ident.1); + self.resolve_local_variable(ident.0.clone(), ident.1); HirLValue::Ident(ident.0, Type::Error) } @@ -1201,9 +1255,10 @@ impl<'a> Resolver<'a> { .position(|capture| capture.ident.id == hir_ident.id); if pos.is_none() { - self.lambda_stack[lambda_index] - .captures - .push(HirCapturedVar { ident: hir_ident, transitive_capture_index }); + self.lambda_stack[lambda_index].captures.push(HirCapturedVar { + ident: hir_ident.clone(), + transitive_capture_index, + }); } if lambda_index + 1 < self.lambda_stack.len() { @@ -1250,14 +1305,13 @@ impl<'a> Resolver<'a> { Literal::Unit => HirLiteral::Unit, }), ExpressionKind::Variable(path) => { - if let Some((hir_expr, object_type)) = self.resolve_trait_generic_path(&path) { - let expr_id = self.interner.push_expr(hir_expr); - self.interner.push_expr_location(expr_id, expr.span, self.file); - self.interner.select_impl_for_expression( - expr_id, - TraitImplKind::Assumed { object_type }, - ); - return expr_id; + if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) + { + HirExpression::Ident(HirIdent { + location: Location::new(expr.span, self.file), + id: self.interner.trait_method_id(method), + impl_kind: ImplKind::TraitMethod(method, constraint, assumed), + }) } else { // If the Path is being used as an Expression, then it is referring to a global from a separate module // Otherwise, then it is referring to an Identifier @@ -1292,7 +1346,7 @@ impl<'a> Resolver<'a> { } DefinitionKind::Local(_) => { // only local variables can be captured by closures. - self.resolve_local_variable(hir_ident, var_scope_index); + self.resolve_local_variable(hir_ident.clone(), var_scope_index); } } } @@ -1632,7 +1686,7 @@ impl<'a> Resolver<'a> { fn resolve_trait_static_method_by_self( &mut self, path: &Path, - ) -> Option<(HirExpression, Type)> { + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { let trait_id = self.trait_id?; if path.kind == PathKind::Plain && path.segments.len() == 2 { @@ -1642,15 +1696,23 @@ impl<'a> Resolver<'a> { if name == SELF_TYPE_NAME { let the_trait = self.interner.get_trait(trait_id); let method = the_trait.find_method(method.0.contents.as_str())?; - let self_type = self.self_type.clone()?; - return Some((HirExpression::TraitMethodReference(method), self_type)); + + let constraint = TraitConstraint { + typ: self.self_type.clone()?, + trait_generics: Type::from_generics(&the_trait.generics), + trait_id, + }; + return Some((method, constraint, false)); } } None } // this resolves TraitName::some_static_method - fn resolve_trait_static_method(&mut self, path: &Path) -> Option<(HirExpression, Type)> { + fn resolve_trait_static_method( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { if path.kind == PathKind::Plain && path.segments.len() == 2 { let method = &path.segments[1]; @@ -1660,17 +1722,27 @@ impl<'a> Resolver<'a> { let the_trait = self.interner.get_trait(trait_id); let method = the_trait.find_method(method.0.contents.as_str())?; - let self_type = Type::type_variable(the_trait.self_type_typevar_id); - return Some((HirExpression::TraitMethodReference(method), self_type)); + let constraint = TraitConstraint { + typ: Type::TypeVariable( + the_trait.self_type_typevar.clone(), + TypeVariableKind::Normal, + ), + trait_generics: Type::from_generics(&the_trait.generics), + trait_id, + }; + return Some((method, constraint, false)); } None } - // this resolves a static trait method T::trait_method by iterating over the where clause + // This resolves a static trait method T::trait_method by iterating over the where clause + // + // Returns the trait method, object type, and the trait generics. + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_method_by_named_generic( &mut self, path: &Path, - ) -> Option<(HirExpression, Type)> { + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { if path.segments.len() != 2 { return None; } @@ -1691,8 +1763,14 @@ impl<'a> Resolver<'a> { if let Some(method) = the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) { - let self_type = self.resolve_type(typ.clone()); - return Some((HirExpression::TraitMethodReference(method), self_type)); + let constraint = TraitConstraint { + trait_id, + typ: self.resolve_type(typ.clone()), + trait_generics: vecmap(trait_bound.trait_generics, |typ| { + self.resolve_type(typ) + }), + }; + return Some((method, constraint, true)); } } } @@ -1700,7 +1778,14 @@ impl<'a> Resolver<'a> { None } - fn resolve_trait_generic_path(&mut self, path: &Path) -> Option<(HirExpression, Type)> { + // Try to resolve the given trait method path. + // + // Returns the trait method, object type, and the trait generics. + // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` + fn resolve_trait_generic_path( + &mut self, + path: &Path, + ) -> Option<(TraitMethodId, TraitConstraint, bool)> { self.resolve_trait_static_method_by_self(path) .or_else(|| self.resolve_trait_static_method(path)) .or_else(|| self.resolve_trait_method_by_named_generic(path)) @@ -1765,7 +1850,8 @@ impl<'a> Resolver<'a> { let variable = scope_tree.find(ident_name); if let Some((old_value, _)) = variable { old_value.num_times_used += 1; - let expr_id = self.interner.push_expr(HirExpression::Ident(old_value.ident)); + let ident = HirExpression::Ident(old_value.ident.clone()); + let expr_id = self.interner.push_expr(ident); self.interner.push_expr_location(expr_id, call_expr_span, self.file); fmt_str_idents.push(expr_id); } else if ident_name.parse::().is_ok() { diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/structs.rs b/noir/compiler/noirc_frontend/src/hir/resolution/structs.rs index 72a7b736436..cf3e3436c88 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/structs.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/structs.rs @@ -24,6 +24,10 @@ pub(crate) fn resolve_structs( crate_id: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; + // This is necessary to avoid cloning the entire struct map + // when adding checks after each struct field is resolved. + let struct_ids = structs.keys().copied().collect::>(); + // Resolve each field in each struct. // Each struct should already be present in the NodeInterner after def collection. for (type_id, typ) in structs { @@ -35,6 +39,28 @@ pub(crate) fn resolve_structs( struct_def.generics = generics; }); } + + // Check whether the struct fields have nested slices + // We need to check after all structs are resolved to + // make sure every struct's fields is accurately set. + for id in struct_ids { + let struct_type = context.def_interner.get_struct(id); + // Only handle structs without generics as any generics args will be checked + // after monomorphization when performing SSA codegen + if struct_type.borrow().generics.is_empty() { + let fields = struct_type.borrow().get_fields(&[]); + for field in fields.iter() { + if field.1.is_nested_slice() { + errors.push(( + ResolverError::NestedSlices { span: struct_type.borrow().location.span } + .into(), + struct_type.borrow().location.file, + )); + } + } + } + } + errors } @@ -49,5 +75,6 @@ fn resolve_struct_fields( let (generics, fields, errors) = Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file_id) .resolve_struct_fields(unresolved.struct_def); + (generics, fields, errors) } diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/traits.rs b/noir/compiler/noirc_frontend/src/hir/resolution/traits.rs index 545a46fd8e4..f08d9c50c84 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -18,7 +18,7 @@ use crate::{ }, hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}, node_interner::{FuncId, NodeInterner, TraitId}, - Path, Shared, TraitItem, Type, TypeBinding, TypeVariableKind, + Generics, Path, Shared, TraitItem, Type, TypeBinding, TypeVariable, TypeVariableKind, }; use super::{ @@ -38,8 +38,14 @@ pub(crate) fn resolve_traits( for (trait_id, unresolved_trait) in &traits { context.def_interner.push_empty_trait(*trait_id, unresolved_trait); } - let mut res: Vec<(CompilationError, FileId)> = vec![]; + let mut all_errors = Vec::new(); + for (trait_id, unresolved_trait) in traits { + let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { + let id = context.def_interner.next_type_variable_id(); + (id, TypeVariable::unbound(id)) + }); + // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) let _ = resolve_trait_types(context, crate_id, &unresolved_trait); @@ -47,10 +53,13 @@ pub(crate) fn resolve_traits( let _ = resolve_trait_constants(context, crate_id, &unresolved_trait); // 3. Trait Methods let (methods, errors) = - resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait); - res.extend(errors); + resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait, &generics); + + all_errors.extend(errors); + context.def_interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); + trait_def.generics = generics; }); // This check needs to be after the trait's methods are set since @@ -60,7 +69,7 @@ pub(crate) fn resolve_traits( context.def_interner.try_add_operator_trait(trait_id); } } - res + all_errors } fn resolve_trait_types( @@ -85,6 +94,7 @@ fn resolve_trait_methods( trait_id: TraitId, crate_id: CrateId, unresolved_trait: &UnresolvedTrait, + trait_generics: &Generics, ) -> (Vec, Vec<(CompilationError, FileId)>) { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; @@ -109,12 +119,15 @@ fn resolve_trait_methods( } = item { let the_trait = interner.get_trait(trait_id); - let self_type = - Type::TypeVariable(the_trait.self_type_typevar.clone(), TypeVariableKind::Normal); + let self_typevar = the_trait.self_type_typevar.clone(); + let self_type = Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); + let name_span = the_trait.name.span(); let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); resolver.add_generics(generics); - resolver.set_self_type(Some(self_type)); + resolver.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); + resolver.add_existing_generic("Self", name_span, self_typevar); + resolver.set_self_type(Some(self_type.clone())); let func_id = unresolved_trait.method_ids[&name.0.contents]; let (_, func_meta) = resolver.resolve_trait_function( @@ -129,16 +142,17 @@ fn resolve_trait_methods( let arguments = vecmap(parameters, |param| resolver.resolve_type(param.1.clone())); let return_type = resolver.resolve_type(return_type.get_type().into_owned()); - let mut generics = vecmap(resolver.get_generics(), |(_, type_var, _)| match &*type_var - .borrow() - { - TypeBinding::Unbound(id) => (*id, type_var.clone()), - TypeBinding::Bound(binding) => unreachable!("Trait generic was bound to {binding}"), - }); + let generics = + vecmap(resolver.get_generics(), |(_, type_var, _)| match &*type_var.borrow() { + TypeBinding::Unbound(id) => (*id, type_var.clone()), + TypeBinding::Bound(binding) => { + unreachable!("Trait generic was bound to {binding}") + } + }); // Ensure the trait is generic over the Self type as well - let the_trait = resolver.interner.get_trait(trait_id); - generics.push((the_trait.self_type_typevar_id, the_trait.self_type_typevar.clone())); + // let the_trait = resolver.interner.get_trait(trait_id); + // generics.push((the_trait.self_type_typevar_id, the_trait.self_type_typevar.clone())); let default_impl_list: Vec<_> = unresolved_trait .fns_with_default_impl @@ -382,9 +396,12 @@ pub(crate) fn resolve_trait_impls( let mut resolver = Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id); resolver.add_generics(&trait_impl.generics); - let self_type = resolver.resolve_type(unresolved_type.clone()); - let generics = resolver.get_generics().to_vec(); + let trait_generics = + vecmap(&trait_impl.trait_generics, |generic| resolver.resolve_type(generic.clone())); + + let self_type = resolver.resolve_type(unresolved_type.clone()); + let impl_generics = resolver.get_generics().to_vec(); let impl_id = interner.next_trait_impl_id(); let mut impl_methods = functions::resolve_function_set( @@ -394,7 +411,7 @@ pub(crate) fn resolve_trait_impls( trait_impl.methods.clone(), Some(self_type.clone()), Some(impl_id), - generics.clone(), + impl_generics.clone(), errors, ); @@ -414,7 +431,7 @@ pub(crate) fn resolve_trait_impls( let mut new_resolver = Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id); - new_resolver.set_generics(generics); + new_resolver.set_generics(impl_generics.clone()); new_resolver.set_self_type(Some(self_type.clone())); if let Some(trait_id) = maybe_trait_id { @@ -422,7 +439,10 @@ pub(crate) fn resolve_trait_impls( &mut new_resolver, &impl_methods, trait_id, + trait_impl.trait_path.span(), + trait_impl.trait_generics, trait_impl.generics.len(), + trait_impl.file_id, errors, ); @@ -432,19 +452,28 @@ pub(crate) fn resolve_trait_impls( .flat_map(|item| new_resolver.resolve_trait_constraint(item)) .collect(); + let resolver_errors = new_resolver.take_errors().into_iter(); + errors.extend(resolver_errors.map(|error| (error.into(), trait_impl.file_id))); + let resolved_trait_impl = Shared::new(TraitImpl { ident: trait_impl.trait_path.last_segment().clone(), typ: self_type.clone(), trait_id, + trait_generics: trait_generics.clone(), file: trait_impl.file_id, where_clause, methods: vecmap(&impl_methods, |(_, func_id)| *func_id), }); + let impl_generics = + vecmap(impl_generics, |(_, type_variable, _)| (type_variable.id(), type_variable)); + if let Err((prev_span, prev_file)) = interner.add_trait_implementation( self_type.clone(), trait_id, + trait_generics, impl_id, + impl_generics, resolved_trait_impl, ) { let error = DefCollectorErrorKind::OverlappingImpl { diff --git a/noir/compiler/noirc_frontend/src/hir/type_check/expr.rs b/noir/compiler/noirc_frontend/src/hir/type_check/expr.rs index 50ed98a794a..b583959bfb1 100644 --- a/noir/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/noir/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -6,7 +6,7 @@ use crate::{ hir_def::{ expr::{ self, HirArrayLiteral, HirBinaryOp, HirExpression, HirLiteral, HirMethodCallExpression, - HirMethodReference, HirPrefixExpression, + HirMethodReference, HirPrefixExpression, ImplKind, }, types::Type, }, @@ -18,7 +18,7 @@ use super::{errors::TypeCheckError, TypeChecker}; impl<'interner> TypeChecker<'interner> { fn check_if_deprecated(&mut self, expr: &ExprId) { - if let HirExpression::Ident(expr::HirIdent { location, id }) = + if let HirExpression::Ident(expr::HirIdent { location, id, impl_kind: _ }) = self.interner.expression(expr) { if let Some(DefinitionKind::Function(func_id)) = @@ -52,6 +52,10 @@ impl<'interner> TypeChecker<'interner> { // We must instantiate identifiers at every call site to replace this T with a new type // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); + + // This instantiate's a trait's generics as well which need to be set + // when the constraint below is later solved for when the function is + // finished. How to link the two? let (typ, bindings) = t.instantiate(self.interner); // Push any trait constraints required by this definition to the context @@ -59,13 +63,30 @@ impl<'interner> TypeChecker<'interner> { if let Some(definition) = self.interner.try_definition(ident.id) { if let DefinitionKind::Function(function) = definition.kind { let function = self.interner.function_meta(&function); + for mut constraint in function.trait_constraints.clone() { - constraint.typ = constraint.typ.substitute(&bindings); + constraint.apply_bindings(&bindings); self.trait_constraints.push((constraint, *expr_id)); } } } + if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { + constraint.apply_bindings(&bindings); + if assumed { + let trait_impl = TraitImplKind::Assumed { + object_type: constraint.typ, + trait_generics: constraint.trait_generics, + }; + self.interner.select_impl_for_expression(*expr_id, trait_impl); + } else { + // Currently only one impl can be selected per expr_id, so this + // constraint needs to be pushed after any other constraints so + // that monomorphization can resolve this trait method to the correct impl. + self.trait_constraints.push((constraint, *expr_id)); + } + } + self.interner.store_instantiation_bindings(*expr_id, bindings); typ } @@ -141,7 +162,14 @@ impl<'interner> TypeChecker<'interner> { Ok((typ, use_impl)) => { if use_impl { let id = infix_expr.trait_method_id; - self.verify_trait_constraint(&lhs_type, id.trait_id, *expr_id, span); + // Assume operators have no trait generics + self.verify_trait_constraint( + &lhs_type, + id.trait_id, + &[], + *expr_id, + span, + ); self.typecheck_operator_method(*expr_id, id, &lhs_type, span); } typ @@ -207,11 +235,12 @@ impl<'interner> TypeChecker<'interner> { .trait_id }) } - HirMethodReference::TraitMethodId(method) => Some(method.trait_id), + HirMethodReference::TraitMethodId(method, _) => Some(method.trait_id), }; let (function_id, function_call) = method_call.into_function_call( - method_ref.clone(), + &method_ref, + object_type.clone(), location, self.interner, ); @@ -220,7 +249,15 @@ impl<'interner> TypeChecker<'interner> { let ret = self.check_method_call(&function_id, method_ref, args, span); if let Some(trait_id) = trait_id { - self.verify_trait_constraint(&object_type, trait_id, function_id, span); + // Assume no trait generics were specified + // TODO: Fill in type variables + self.verify_trait_constraint( + &object_type, + trait_id, + &[], + function_id, + span, + ); } self.interner.replace_expr(expr_id, function_call); @@ -298,30 +335,6 @@ impl<'interner> TypeChecker<'interner> { Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) } - HirExpression::TraitMethodReference(method) => { - let the_trait = self.interner.get_trait(method.trait_id); - let typ2 = &the_trait.methods[method.method_index].typ; - let (typ, mut bindings) = typ2.instantiate(self.interner); - - // We must also remember to apply these substitutions to the object_type - // referenced by the selected trait impl, if one has yet to be selected. - let impl_kind = self.interner.get_selected_impl_for_expression(*expr_id); - if let Some(TraitImplKind::Assumed { object_type }) = impl_kind { - let the_trait = self.interner.get_trait(method.trait_id); - let object_type = object_type.substitute(&bindings); - bindings.insert( - the_trait.self_type_typevar_id, - (the_trait.self_type_typevar.clone(), object_type.clone()), - ); - self.interner.select_impl_for_expression( - *expr_id, - TraitImplKind::Assumed { object_type }, - ); - } - - self.interner.store_instantiation_bindings(*expr_id, bindings); - typ - } }; self.interner.push_expr_type(expr_id, typ.clone()); @@ -332,11 +345,14 @@ impl<'interner> TypeChecker<'interner> { &mut self, object_type: &Type, trait_id: TraitId, + trait_generics: &[Type], function_ident_id: ExprId, span: Span, ) { - match self.interner.lookup_trait_implementation(object_type, trait_id) { - Ok(impl_kind) => self.interner.select_impl_for_expression(function_ident_id, impl_kind), + match self.interner.lookup_trait_implementation(object_type, trait_id, trait_generics) { + Ok(impl_kind) => { + self.interner.select_impl_for_expression(function_ident_id, impl_kind); + } Err(erroring_constraints) => { // Don't show any errors where try_get_trait returns None. // This can happen if a trait is used that was never declared. @@ -344,7 +360,12 @@ impl<'interner> TypeChecker<'interner> { .into_iter() .map(|constraint| { let r#trait = self.interner.try_get_trait(constraint.trait_id)?; - Some((constraint.typ, r#trait.name.to_string())) + let mut name = r#trait.name.to_string(); + if !constraint.trait_generics.is_empty() { + let generics = vecmap(&constraint.trait_generics, ToString::to_string); + name += &format!("<{}>", generics.join(", ")); + } + Some((constraint.typ, name)) }) .collect::>>(); @@ -554,7 +575,7 @@ impl<'interner> TypeChecker<'interner> { arguments: Vec<(Type, ExprId, Span)>, span: Span, ) -> Type { - let (fn_typ, param_len) = match method_ref { + let (fn_typ, param_len, generic_bindings) = match method_ref { HirMethodReference::FuncId(func_id) => { if func_id == FuncId::dummy_id() { return Type::Error; @@ -562,12 +583,22 @@ impl<'interner> TypeChecker<'interner> { let func_meta = self.interner.function_meta(&func_id); let param_len = func_meta.parameters.len(); - (func_meta.typ.clone(), param_len) + (func_meta.typ.clone(), param_len, TypeBindings::new()) } - HirMethodReference::TraitMethodId(method) => { + HirMethodReference::TraitMethodId(method, generics) => { let the_trait = self.interner.get_trait(method.trait_id); let method = &the_trait.methods[method.method_index]; - (method.typ.clone(), method.arguments().len()) + + // These are any bindings from the trait's generics itself, + // rather than an impl or method's generics. + let generic_bindings = the_trait + .generics + .iter() + .zip(generics) + .map(|((id, var), arg)| (*id, (var.clone(), arg))) + .collect(); + + (method.typ.clone(), method.arguments().len(), generic_bindings) } }; @@ -581,11 +612,12 @@ impl<'interner> TypeChecker<'interner> { }); } - let (function_type, instantiation_bindings) = fn_typ.instantiate(self.interner); + let (function_type, instantiation_bindings) = + fn_typ.instantiate_with_bindings(generic_bindings, self.interner); self.interner.store_instantiation_bindings(*function_ident_id, instantiation_bindings); self.interner.push_expr_type(function_ident_id, function_type.clone()); - self.bind_function_type(function_type, arguments, span) + self.bind_function_type(function_type.clone(), arguments, span) } fn check_if_expr(&mut self, if_expr: &expr::HirIfExpression, expr_id: &ExprId) -> Type { @@ -926,7 +958,10 @@ impl<'interner> TypeChecker<'interner> { trait_id: constraint.trait_id, method_index, }; - return Some(HirMethodReference::TraitMethodId(trait_method)); + return Some(HirMethodReference::TraitMethodId( + trait_method, + constraint.trait_generics.clone(), + )); } } } @@ -1233,15 +1268,17 @@ impl<'interner> TypeChecker<'interner> { // We must also remember to apply these substitutions to the object_type // referenced by the selected trait impl, if one has yet to be selected. let impl_kind = self.interner.get_selected_impl_for_expression(expr_id); - if let Some(TraitImplKind::Assumed { object_type }) = impl_kind { + if let Some(TraitImplKind::Assumed { object_type, trait_generics }) = impl_kind { let the_trait = self.interner.get_trait(trait_method_id.trait_id); let object_type = object_type.substitute(&bindings); bindings.insert( the_trait.self_type_typevar_id, (the_trait.self_type_typevar.clone(), object_type.clone()), ); - self.interner - .select_impl_for_expression(expr_id, TraitImplKind::Assumed { object_type }); + self.interner.select_impl_for_expression( + expr_id, + TraitImplKind::Assumed { object_type, trait_generics }, + ); } self.interner.store_instantiation_bindings(expr_id, bindings); diff --git a/noir/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/compiler/noirc_frontend/src/hir/type_check/mod.rs index c05c233fe34..3c2a970ee84 100644 --- a/noir/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -61,8 +61,9 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec Vec Vec TypeChecker<'interner> { typ.follow_bindings() }; - (typ.clone(), HirLValue::Ident(*ident, typ), mutable) + (typ.clone(), HirLValue::Ident(ident.clone(), typ), mutable) } HirLValue::MemberAccess { object, field_name, .. } => { let (lhs_type, object, mut mutable) = self.check_lvalue(object, assign_span); @@ -216,8 +216,8 @@ impl<'interner> TypeChecker<'interner> { // we eventually reassign to it. let id = DefinitionId::dummy_id(); let location = Location::new(span, fm::FileId::dummy()); - let tmp_value = - HirLValue::Ident(HirIdent { location, id }, Type::Error); + let ident = HirIdent::non_trait_method(id, location); + let tmp_value = HirLValue::Ident(ident, Type::Error); let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); *object_ref = Box::new(HirLValue::Dereference { lvalue, element_type }); diff --git a/noir/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/compiler/noirc_frontend/src/hir_def/expr.rs index 7c04398ca88..fe1cd78b5ed 100644 --- a/noir/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/compiler/noirc_frontend/src/hir_def/expr.rs @@ -6,6 +6,7 @@ use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, T use crate::{BinaryOp, BinaryOpKind, Ident, Shared, UnaryOp}; use super::stmt::HirPattern; +use super::traits::TraitConstraint; use super::types::{StructType, Type}; /// A HirExpression is the result of an Expression in the AST undergoing @@ -29,7 +30,6 @@ pub enum HirExpression { If(HirIfExpression), Tuple(Vec), Lambda(HirLambda), - TraitMethodReference(TraitMethodId), Error, } @@ -41,10 +41,45 @@ impl HirExpression { } /// Corresponds to a variable in the source code -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub struct HirIdent { pub location: Location, pub id: DefinitionId, + + /// If this HirIdent refers to a trait method, this field stores + /// whether the impl for this method is known or not. + pub impl_kind: ImplKind, +} + +impl HirIdent { + pub fn non_trait_method(id: DefinitionId, location: Location) -> Self { + Self { id, location, impl_kind: ImplKind::NotATraitMethod } + } +} + +#[derive(Debug, Clone)] +pub enum ImplKind { + /// This ident is not a trait method + NotATraitMethod, + + /// This ident refers to a trait method and its impl needs to be verified, + /// and eventually linked to this id. The boolean indicates whether the impl + /// is already assumed to exist - e.g. when resolving a path such as `T::default` + /// when there is a corresponding `T: Default` constraint in scope. + TraitMethod(TraitMethodId, TraitConstraint, bool), +} + +impl Eq for HirIdent {} +impl PartialEq for HirIdent { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl std::hash::Hash for HirIdent { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -162,28 +197,35 @@ pub enum HirMethodReference { /// Or a method can come from a Trait impl block, in which case /// the actual function called will depend on the instantiated type, /// which can be only known during monomorphization. - TraitMethodId(TraitMethodId), + TraitMethodId(TraitMethodId, /*trait generics:*/ Vec), } impl HirMethodCallExpression { pub fn into_function_call( mut self, - method: HirMethodReference, + method: &HirMethodReference, + object_type: Type, location: Location, interner: &mut NodeInterner, ) -> (ExprId, HirExpression) { let mut arguments = vec![self.object]; arguments.append(&mut self.arguments); - let expr = match method { + let (id, impl_kind) = match method { HirMethodReference::FuncId(func_id) => { - let id = interner.function_definition_id(func_id); - HirExpression::Ident(HirIdent { location, id }) + (interner.function_definition_id(*func_id), ImplKind::NotATraitMethod) } - HirMethodReference::TraitMethodId(method_id) => { - HirExpression::TraitMethodReference(method_id) + HirMethodReference::TraitMethodId(method_id, generics) => { + let id = interner.trait_method_id(*method_id); + let constraint = TraitConstraint { + typ: object_type, + trait_id: method_id.trait_id, + trait_generics: generics.clone(), + }; + (id, ImplKind::TraitMethod(*method_id, constraint, false)) } }; + let expr = HirExpression::Ident(HirIdent { location, id, impl_kind }); let func = interner.push_expr(expr); (func, HirExpression::Call(HirCallExpression { func, arguments, location })) } diff --git a/noir/compiler/noirc_frontend/src/hir_def/stmt.rs b/noir/compiler/noirc_frontend/src/hir_def/stmt.rs index 21f9b431b3a..34c9302c251 100644 --- a/noir/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/noir/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -28,8 +28,8 @@ pub struct HirLetStatement { impl HirLetStatement { pub fn ident(&self) -> HirIdent { - match self.pattern { - HirPattern::Identifier(ident) => ident, + match &self.pattern { + HirPattern::Identifier(ident) => ident.clone(), _ => panic!("can only fetch hir ident from HirPattern::Identifier"), } } diff --git a/noir/compiler/noirc_frontend/src/hir_def/traits.rs b/noir/compiler/noirc_frontend/src/hir_def/traits.rs index 1d0449b6568..85c292ac5f3 100644 --- a/noir/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/noir/compiler/noirc_frontend/src/hir_def/traits.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, - Generics, Ident, NoirFunction, Type, TypeVariable, TypeVariableId, + Generics, Ident, NoirFunction, Type, TypeBindings, TypeVariable, TypeVariableId, }; use fm::FileId; use noirc_errors::{Location, Span}; @@ -70,6 +70,7 @@ pub struct TraitImpl { pub ident: Ident, pub typ: Type, pub trait_id: TraitId, + pub trait_generics: Vec, pub file: FileId, pub methods: Vec, // methods[i] is the implementation of trait.methods[i] for Type typ @@ -84,12 +85,20 @@ pub struct TraitImpl { pub struct TraitConstraint { pub typ: Type, pub trait_id: TraitId, - // pub trait_generics: Generics, TODO + pub trait_generics: Vec, } impl TraitConstraint { - pub fn new(typ: Type, trait_id: TraitId) -> Self { - Self { typ, trait_id } + pub fn new(typ: Type, trait_id: TraitId, trait_generics: Vec) -> Self { + Self { typ, trait_id, trait_generics } + } + + pub fn apply_bindings(&mut self, type_bindings: &TypeBindings) { + self.typ = self.typ.substitute(type_bindings); + + for typ in &mut self.trait_generics { + *typ = typ.substitute(type_bindings); + } } } diff --git a/noir/compiler/noirc_frontend/src/hir_def/types.rs b/noir/compiler/noirc_frontend/src/hir_def/types.rs index 981d7e41b6e..f59341e5b1c 100644 --- a/noir/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/compiler/noirc_frontend/src/hir_def/types.rs @@ -66,7 +66,7 @@ pub enum Type { /// `impl Trait` when used in a type position. /// These are only matched based on the TraitId. The trait name paramer is only /// used for displaying error messages using the name of the trait. - TraitAsType(TraitId, /*name:*/ Rc), + TraitAsType(TraitId, /*name:*/ Rc, /*generics:*/ Vec), /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. @@ -143,6 +143,43 @@ impl Type { | Type::Error => unreachable!("This type cannot exist as a parameter to main"), } } + + pub(crate) fn is_nested_slice(&self) -> bool { + match self { + Type::Array(size, elem) => { + if let Type::NotConstant = size.as_ref() { + elem.as_ref().contains_slice() + } else { + false + } + } + _ => false, + } + } + + fn contains_slice(&self) -> bool { + match self { + Type::Array(size, _) => matches!(size.as_ref(), Type::NotConstant), + Type::Struct(struct_typ, generics) => { + let fields = struct_typ.borrow().get_fields(generics); + for field in fields.iter() { + if field.1.contains_slice() { + return true; + } + } + false + } + Type::Tuple(types) => { + for typ in types.iter() { + if typ.contains_slice() { + return true; + } + } + false + } + _ => false, + } + } } /// A list of TypeVariableIds to bind to a type. Storing the @@ -423,6 +460,10 @@ impl TypeVariable { TypeVariable(id, Shared::new(TypeBinding::Unbound(id))) } + pub fn id(&self) -> TypeVariableId { + self.0 + } + /// Bind this type variable to a value. /// /// Panics if this TypeVariable is already Bound. @@ -734,8 +775,13 @@ impl std::fmt::Display for Type { write!(f, "{}<{}>", s.borrow(), args.join(", ")) } } - Type::TraitAsType(_id, name) => { - write!(f, "impl {}", name) + Type::TraitAsType(_id, name, generics) => { + write!(f, "impl {}", name)?; + if !generics.is_empty() { + let generics = vecmap(generics, ToString::to_string).join(", "); + write!(f, "<{generics}>")?; + } + Ok(()) } Type::Tuple(elements) => { let elements = vecmap(elements, ToString::to_string); @@ -1251,6 +1297,29 @@ impl Type { } } + /// Instantiate this type with the given type bindings. + /// If any type variables which would be instantiated are contained in the + /// given type bindings instead, the value from the type bindings is used. + pub fn instantiate_with_bindings( + &self, + mut bindings: TypeBindings, + interner: &NodeInterner, + ) -> (Type, TypeBindings) { + match self { + Type::Forall(typevars, typ) => { + for (id, var) in typevars { + bindings + .entry(*id) + .or_insert_with(|| (var.clone(), interner.next_type_variable())); + } + + let instantiated = typ.force_substitute(&bindings); + (instantiated, bindings) + } + other => (other.clone(), bindings), + } + } + /// Instantiate this type, replacing any type variables it is quantified /// over with fresh type variables. If this type is not a Type::Forall, /// it is unchanged. @@ -1272,82 +1341,6 @@ impl Type { } } - /// Replace each NamedGeneric (and TypeVariable) in this type with a fresh type variable - pub(crate) fn instantiate_type_variables( - &self, - interner: &NodeInterner, - ) -> (Type, TypeBindings) { - let mut type_variables = HashMap::new(); - self.find_all_unbound_type_variables(&mut type_variables); - - let substitutions = type_variables - .into_iter() - .map(|(id, type_var)| (id, (type_var, interner.next_type_variable()))) - .collect(); - - (self.substitute(&substitutions), substitutions) - } - - /// For each unbound type variable in the current type, add a type binding to the given list - /// to bind the unbound type variable to a fresh type variable. - fn find_all_unbound_type_variables( - &self, - type_variables: &mut HashMap, - ) { - match self { - Type::FieldElement - | Type::Integer(_, _) - | Type::Bool - | Type::Unit - | Type::TraitAsType(..) - | Type::Constant(_) - | Type::NotConstant - | Type::Error => (), - Type::Array(length, elem) => { - length.find_all_unbound_type_variables(type_variables); - elem.find_all_unbound_type_variables(type_variables); - } - Type::String(length) => length.find_all_unbound_type_variables(type_variables), - Type::FmtString(length, env) => { - length.find_all_unbound_type_variables(type_variables); - env.find_all_unbound_type_variables(type_variables); - } - Type::Struct(_, generics) => { - for generic in generics { - generic.find_all_unbound_type_variables(type_variables); - } - } - Type::Tuple(fields) => { - for field in fields { - field.find_all_unbound_type_variables(type_variables); - } - } - Type::Function(args, ret, env) => { - for arg in args { - arg.find_all_unbound_type_variables(type_variables); - } - ret.find_all_unbound_type_variables(type_variables); - env.find_all_unbound_type_variables(type_variables); - } - Type::MutableReference(elem) => { - elem.find_all_unbound_type_variables(type_variables); - } - Type::Forall(_, typ) => typ.find_all_unbound_type_variables(type_variables), - Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _) => { - match &*type_variable.borrow() { - TypeBinding::Bound(binding) => { - binding.find_all_unbound_type_variables(type_variables); - } - TypeBinding::Unbound(id) => { - if !type_variables.contains_key(id) { - type_variables.insert(*id, type_variable.clone()); - } - } - } - } - } - } - /// Substitute any type variables found within this type with the /// given bindings if found. If a type variable is not found within /// the given TypeBindings, it is unchanged. @@ -1554,6 +1547,10 @@ impl Type { | NotConstant => self.clone(), } } + + pub fn from_generics(generics: &Generics) -> Vec { + vecmap(generics, |(_, var)| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) + } } /// Wraps a given `expression` in `expression.as_slice()` @@ -1569,7 +1566,7 @@ fn convert_array_expression_to_slice( let as_slice_id = interner.function_definition_id(as_slice_method); let location = interner.expr_location(&expression); - let as_slice = HirExpression::Ident(HirIdent { location, id: as_slice_id }); + let as_slice = HirExpression::Ident(HirIdent::non_trait_method(as_slice_id, location)); let func = interner.push_expr(as_slice); let arguments = vec![expression]; diff --git a/noir/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/compiler/noirc_frontend/src/monomorphization/mod.rs index 3510475e881..ac11e00ad20 100644 --- a/noir/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -431,11 +431,6 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Lambda(lambda) => self.lambda(lambda, expr), - HirExpression::TraitMethodReference(method) => { - let function_type = self.interner.id_type(expr); - self.resolve_trait_method_reference(expr, function_type, method) - } - HirExpression::MethodCall(hir_method_call) => { unreachable!("Encountered HirExpression::MethodCall during monomorphization {hir_method_call:?}") } @@ -682,6 +677,12 @@ impl<'interner> Monomorphizer<'interner> { } fn ident(&mut self, ident: HirIdent, expr_id: node_interner::ExprId) -> ast::Expression { + let typ = self.interner.id_type(expr_id); + + if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { + return self.resolve_trait_method_reference(expr_id, typ, method); + } + let definition = self.interner.definition(ident.id); match &definition.kind { DefinitionKind::Function(func_id) => { @@ -866,8 +867,12 @@ impl<'interner> Monomorphizer<'interner> { self.interner.get_trait_implementation(impl_id).borrow().methods [method.method_index] } - node_interner::TraitImplKind::Assumed { object_type } => { - match self.interner.lookup_trait_implementation(&object_type, method.trait_id) { + node_interner::TraitImplKind::Assumed { object_type, trait_generics } => { + match self.interner.lookup_trait_implementation( + &object_type, + method.trait_id, + &trait_generics, + ) { Ok(TraitImplKind::Normal(impl_id)) => { self.interner.get_trait_implementation(impl_id).borrow().methods [method.method_index] @@ -889,14 +894,12 @@ impl<'interner> Monomorphizer<'interner> { } }; - let func_def = self.lookup_function(func_id, expr_id, &function_type, Some(method)); - let func_id = match func_def { + let func_id = match self.lookup_function(func_id, expr_id, &function_type, Some(method)) { Definition::Function(func_id) => func_id, _ => unreachable!(), }; let the_trait = self.interner.get_trait(method.trait_id); - ast::Expression::Ident(ast::Ident { definition: Definition::Function(func_id), mutable: false, diff --git a/noir/compiler/noirc_frontend/src/node_interner.rs b/noir/compiler/noirc_frontend/src/node_interner.rs index 4c4bb6804bc..173bac95877 100644 --- a/noir/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/compiler/noirc_frontend/src/node_interner.rs @@ -153,6 +153,19 @@ pub enum TraitImplKind { /// Assumed impls don't have an impl id since they don't link back to any concrete part of the source code. Assumed { object_type: Type, + + /// The trait generics to use - if specified. + /// This is allowed to be empty when they are inferred. E.g. for: + /// + /// ``` + /// trait Into { + /// fn into(self) -> T; + /// } + /// ``` + /// + /// The reference `Into::into(x)` would have inferred generics, but + /// `x.into()` with a `X: Into` in scope would not. + trait_generics: Vec, }, } @@ -941,6 +954,16 @@ impl NodeInterner { self.function_definition_ids[&function] } + /// Returns the DefinitionId of a trait's method, panics if the given trait method + /// is not a valid method of the trait or if the trait has not yet had + /// its methods ids set during name resolution. + pub fn trait_method_id(&self, trait_method: TraitMethodId) -> DefinitionId { + let the_trait = self.get_trait(trait_method.trait_id); + let method_name = &the_trait.methods[trait_method.method_index].name; + let function_id = the_trait.method_ids[&method_name.0.contents]; + self.function_definition_id(function_id) + } + /// Adds a non-trait method to a type. /// /// Returns `Some(duplicate)` if a matching method was already defined. @@ -996,8 +1019,11 @@ impl NodeInterner { &self, object_type: &Type, trait_id: TraitId, + trait_generics: &[Type], ) -> Result> { - let (impl_kind, bindings) = self.try_lookup_trait_implementation(object_type, trait_id)?; + let (impl_kind, bindings) = + self.try_lookup_trait_implementation(object_type, trait_id, trait_generics)?; + Type::apply_type_bindings(bindings); Ok(impl_kind) } @@ -1007,11 +1033,13 @@ impl NodeInterner { &self, object_type: &Type, trait_id: TraitId, + trait_generics: &[Type], ) -> Result<(TraitImplKind, TypeBindings), Vec> { let mut bindings = TypeBindings::new(); let impl_kind = self.lookup_trait_implementation_helper( object_type, trait_id, + trait_generics, &mut bindings, IMPL_SEARCH_RECURSION_LIMIT, )?; @@ -1022,10 +1050,12 @@ impl NodeInterner { &self, object_type: &Type, trait_id: TraitId, + trait_generics: &[Type], type_bindings: &mut TypeBindings, recursion_limit: u32, ) -> Result> { - let make_constraint = || TraitConstraint::new(object_type.clone(), trait_id); + let make_constraint = + || TraitConstraint::new(object_type.clone(), trait_id, trait_generics.to_vec()); // Prevent infinite recursion when looking for impls if recursion_limit == 0 { @@ -1037,12 +1067,35 @@ impl NodeInterner { let impls = self.trait_implementation_map.get(&trait_id).ok_or_else(|| vec![make_constraint()])?; - for (existing_object_type, impl_kind) in impls { + for (existing_object_type2, impl_kind) in impls { + // Bug: We're instantiating only the object type's generics here, not all of the trait's generics like we need to let (existing_object_type, instantiation_bindings) = - existing_object_type.instantiate(self); + existing_object_type2.instantiate(self); let mut fresh_bindings = TypeBindings::new(); + let mut check_trait_generics = |impl_generics: &[Type]| { + trait_generics.iter().zip(impl_generics).all(|(trait_generic, impl_generic2)| { + let impl_generic = impl_generic2.substitute(&instantiation_bindings); + trait_generic.try_unify(&impl_generic, &mut fresh_bindings).is_ok() + }) + }; + + let generics_match = match impl_kind { + TraitImplKind::Normal(id) => { + let shared_impl = self.get_trait_implementation(*id); + let shared_impl = shared_impl.borrow(); + check_trait_generics(&shared_impl.trait_generics) + } + TraitImplKind::Assumed { trait_generics, .. } => { + check_trait_generics(trait_generics) + } + }; + + if !generics_match { + continue; + } + if object_type.try_unify(&existing_object_type, &mut fresh_bindings).is_ok() { // The unification was successful so we can append fresh_bindings to our bindings list type_bindings.extend(fresh_bindings); @@ -1085,9 +1138,15 @@ impl NodeInterner { let constraint_type = constraint.typ.force_substitute(instantiation_bindings); let constraint_type = constraint_type.substitute(type_bindings); + let trait_generics = vecmap(&constraint.trait_generics, |generic| { + let generic = generic.force_substitute(instantiation_bindings); + generic.substitute(type_bindings) + }); + self.lookup_trait_implementation_helper( &constraint_type, constraint.trait_id, + &trait_generics, // Use a fresh set of type bindings here since the constraint_type originates from // our impl list, which we don't want to bind to. &mut TypeBindings::new(), @@ -1109,14 +1168,15 @@ impl NodeInterner { &mut self, object_type: Type, trait_id: TraitId, + trait_generics: Vec, ) -> bool { // Make sure there are no overlapping impls - if self.try_lookup_trait_implementation(&object_type, trait_id).is_ok() { + if self.try_lookup_trait_implementation(&object_type, trait_id, &trait_generics).is_ok() { return false; } let entries = self.trait_implementation_map.entry(trait_id).or_default(); - entries.push((object_type.clone(), TraitImplKind::Assumed { object_type })); + entries.push((object_type.clone(), TraitImplKind::Assumed { object_type, trait_generics })); true } @@ -1126,7 +1186,9 @@ impl NodeInterner { &mut self, object_type: Type, trait_id: TraitId, + trait_generics: Vec, impl_id: TraitImplId, + impl_generics: Generics, trait_impl: Shared, ) -> Result<(), (Span, FileId)> { assert_eq!(impl_id.0, self.trait_implementations.len(), "trait impl defined out of order"); @@ -1137,12 +1199,20 @@ impl NodeInterner { // It should never happen since impls are defined at global scope, but even // if they were, we should never prevent defining a new impl because a where // clause already assumes it exists. - let (instantiated_object_type, substitutions) = - object_type.instantiate_type_variables(self); - if let Ok((TraitImplKind::Normal(existing), _)) = - self.try_lookup_trait_implementation(&instantiated_object_type, trait_id) - { + // Replace each generic with a fresh type variable + let substitutions = impl_generics + .into_iter() + .map(|(id, typevar)| (id, (typevar, self.next_type_variable()))) + .collect(); + + let instantiated_object_type = object_type.substitute(&substitutions); + + if let Ok((TraitImplKind::Normal(existing), _)) = self.try_lookup_trait_implementation( + &instantiated_object_type, + trait_id, + &trait_generics, + ) { let existing_impl = self.get_trait_implementation(existing); let existing_impl = existing_impl.borrow(); return Err((existing_impl.ident.span(), existing_impl.file)); @@ -1156,6 +1226,7 @@ impl NodeInterner { // The object type is generalized so that a generic impl will apply // to any type T, rather than just the generic type named T. let generalized_object_type = object_type.generalize_from_substitutions(substitutions); + let entries = self.trait_implementation_map.entry(trait_id).or_default(); entries.push((generalized_object_type, TraitImplKind::Normal(impl_id))); Ok(()) diff --git a/noir/compiler/noirc_frontend/src/parser/parser.rs b/noir/compiler/noirc_frontend/src/parser/parser.rs index 954b531abff..cdfdc570949 100644 --- a/noir/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/compiler/noirc_frontend/src/parser/parser.rs @@ -413,13 +413,7 @@ fn trait_definition() -> impl NoirParser { .then_ignore(just(Token::LeftBrace)) .then(trait_body()) .then_ignore(just(Token::RightBrace)) - .validate(|(((name, generics), where_clause), items), span, emit| { - if !generics.is_empty() { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Generic traits"), - span, - )); - } + .map_with_span(|(((name, generics), where_clause), items), span| { TopLevelStatement::Trait(NoirTrait { name, generics, where_clause, span, items }) }) } diff --git a/noir/compiler/noirc_frontend/src/tests.rs b/noir/compiler/noirc_frontend/src/tests.rs index 10684f76169..a56c3a7755f 100644 --- a/noir/compiler/noirc_frontend/src/tests.rs +++ b/noir/compiler/noirc_frontend/src/tests.rs @@ -118,6 +118,12 @@ mod test { fn eq(self, other: Foo) -> bool { self.a == other.a } } + impl Default for u64 { + fn default() -> Self { + 0 + } + } + impl Default for Foo { fn default() -> Self { Foo { a: Default::default() } diff --git a/noir/compiler/wasm/src/compile.rs b/noir/compiler/wasm/src/compile.rs index 54fdccf1369..151dde2eea6 100644 --- a/noir/compiler/wasm/src/compile.rs +++ b/noir/compiler/wasm/src/compile.rs @@ -198,7 +198,7 @@ pub fn compile( let compile_output = generate_contract_artifact(optimized_contract); Ok(JsCompileResult::new(compile_output)) } else { - let compiled_program = compile_main(&mut context, crate_id, &compile_options, None, true) + let compiled_program = compile_main(&mut context, crate_id, &compile_options, None) .map_err(|errs| { CompileError::with_file_diagnostics( "Failed to compile program", diff --git a/noir/compiler/wasm/src/compile_new.rs b/noir/compiler/wasm/src/compile_new.rs index 9ac080ffcae..3cb20bd0b5c 100644 --- a/noir/compiler/wasm/src/compile_new.rs +++ b/noir/compiler/wasm/src/compile_new.rs @@ -97,7 +97,7 @@ impl CompilerContext { let root_crate_id = *self.context.root_crate_id(); let compiled_program = - compile_main(&mut self.context, root_crate_id, &compile_options, None, true) + compile_main(&mut self.context, root_crate_id, &compile_options, None) .map_err(|errs| { CompileError::with_file_diagnostics( "Failed to compile program", diff --git a/noir/compiler/wasm/src/errors.rs b/noir/compiler/wasm/src/errors.rs index 9aafcadc27f..ef56dcfc911 100644 --- a/noir/compiler/wasm/src/errors.rs +++ b/noir/compiler/wasm/src/errors.rs @@ -124,7 +124,10 @@ impl CompileError { let diagnostics: Vec<_> = file_diagnostics .iter() .map(|err| { - Diagnostic::new(err, file_manager.path(err.file_id).to_str().unwrap().to_string()) + let file_path = file_manager + .path(err.file_id) + .expect("File must exist to have caused diagnostics"); + Diagnostic::new(err, file_path.to_str().unwrap().to_string()) }) .collect(); diff --git a/noir/cspell.json b/noir/cspell.json index 6864e863740..0547b956d72 100644 --- a/noir/cspell.json +++ b/noir/cspell.json @@ -172,7 +172,9 @@ "wasi", "wasmer", "Weierstraß", - "zshell" + "zshell", + "nouner", + "devcontainer" ], "ignorePaths": [ "./**/node_modules/**", diff --git a/noir/docs/docs/explainers/explainer-oracle.md b/noir/docs/docs/explainers/explainer-oracle.md new file mode 100644 index 00000000000..b84ca5dd986 --- /dev/null +++ b/noir/docs/docs/explainers/explainer-oracle.md @@ -0,0 +1,57 @@ +--- +title: Oracles +description: This guide provides an in-depth understanding of how Oracles work in Noir programming. Learn how to use outside calculations in your programs, constrain oracles, and understand their uses and limitations. +keywords: + - Noir Programming + - Oracles + - JSON-RPC + - Foreign Call Handlers + - Constrained Functions + - Blockchain Programming +sidebar_position: 1 +--- + +If you've seen "The Matrix" you may recall "The Oracle" as Gloria Foster smoking cigarettes and baking cookies. While she appears to "know things", she is actually providing a calculation of a pre-determined future. Noir Oracles are similar, in a way. They don't calculate the future (yet), but they allow you to use outside calculations in your programs. + +![matrix oracle prediction](@site/static/img/memes/matrix_oracle.jpeg) + +A Noir program is usually self-contained. You can pass certain inputs to it, and it will generate a deterministic output for those inputs. But what if you wanted to defer some calculation to an outside process or source? + +Oracles are functions that provide this feature. + +## Use cases + +An example usage for Oracles is proving something on-chain. For example, proving that the ETH-USDC quote was below a certain target at a certain block time. Or even making more complex proofs like proving the ownership of an NFT as an anonymous login method. + +Another interesting use case is to defer expensive calculations to be made outside of the Noir program, and then constraining the result; similar to the use of [unconstrained functions](../noir/concepts//unconstrained.md). + +In short, anything that can be constrained in a Noir program but needs to be fetched from an external source is a great candidate to be used in oracles. + +## Constraining oracles + +Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information. + +To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have a oracle call like this: + +```rust +#[oracle(getNoun)] +unconstrained fn get_noun(address: Field) -> Field +``` + +This oracle could naively resolve with the number of Nouns she possesses. However, it is useless as a trusted source, as the oracle could resolve to anything Alice wants. In order to make this oracle call actually useful, Alice would need to constrain the response from the oracle, by proving her address and the noun count belongs to the state tree of the contract. + +In short, **Oracles don't prove anything. Your Noir program does.** + +:::danger + +If you don't constrain the return of your oracle, you could be clearly opening an attack vector on your Noir program. Make double-triple sure that the return of an oracle call is constrained! + +::: + +## How to use Oracles + +On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running. + +In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they matches the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. + +If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that. diff --git a/noir/docs/docs/how_to/how-to-oracles.md b/noir/docs/docs/how_to/how-to-oracles.md new file mode 100644 index 00000000000..bdb2b1332ba --- /dev/null +++ b/noir/docs/docs/how_to/how-to-oracles.md @@ -0,0 +1,280 @@ +--- +title: How to use Oracles +description: Learn how to use oracles in your Noir program with examples in both Nargo and NoirJS. This guide also covers writing a JSON RPC server and providing custom foreign call handlers for NoirJS. +keywords: + - Noir Programming + - Oracles + - Nargo + - NoirJS + - JSON RPC Server + - Foreign Call Handlers +sidebar_position: 1 +--- + +This guide shows you how to use oracles in your Noir program. For the sake of clarity, it assumes that: + +- You have read the [explainer on Oracles](../explainers/explainer-oracle.md) and are comfortable with the concept. +- You have a Noir program to add oracles to. You can create one using the [vite-hardhat starter](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) as a boilerplate. +- You understand the concept of a JSON-RPC server. Visit the [JSON-RPC website](https://www.jsonrpc.org/) if you need a refresher. +- You are comfortable with server-side JavaScript (e.g. Node.js, managing packages, etc.). + +For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/how_to_oracles/code-snippets/how-to-oracles). + +## Rundown + +This guide has 3 major steps: + +1. How to modify our Noir program to make use of oracle calls as unconstrained functions +2. How to write a JSON RPC Server to resolve these oracle calls with Nargo +3. How to use them in Nargo and how to provide a custom resolver in NoirJS + +## Step 1 - Modify your Noir program + +An oracle is defined in a Noir program by defining two methods: + +- An unconstrained method - This tells the compiler that it is executing an [unconstrained functions](../noir/concepts//unconstrained.md). +- A decorated oracle method - This tells the compiler that this method is an RPC call. + +An example of an oracle that returns a `Field` would be: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(number: Field) -> Field { } + +unconstrained fn get_sqrt(number: Field) -> Field { + sqrt(number) +} +``` + +In this example, we're wrapping our oracle function in a unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); +} +``` + +In the next section, we will make this `getSqrt` (defined on the `sqrt` decorator) be a method of the RPC server Noir will use. + +:::danger + +As explained in the [Oracle Explainer](../explainers/explainer-oracle.md), this `main` function is unsafe unless you constrain its return value. For example: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); + assert(sqrt.pow_32(2) as u64 == input as u64); // <---- constrain the return of an oracle! +} +``` + +::: + +:::info + +Currently, oracles only work with single params or array params. For example: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt([Field; 2]) -> [Field; 2] { } +``` + +::: + +## Step 2 - Write an RPC server + +Brillig will call *one* RPC server. Most likely you will have to write your own, and you can do it in whatever language you prefer. In this guide, we will do it in Javascript. + +Let's use the above example of an oracle that consumes an array with two `Field` and returns their square roots: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] { } + +unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] { + sqrt(input) +} + +fn main(input: [Field; 2]) { + let sqrt = get_sqrt(input); + assert(sqrt[0].pow_32(2) as u64 == input[0] as u64); + assert(sqrt[1].pow_32(2) as u64 == input[1] as u64); +} +``` + +:::info + +Why square root? + +In general, computing square roots is computationally more expensive than multiplications, which takes a toll when speaking about ZK applications. In this case, instead of calculating the square root in Noir, we are using our oracle to offload that computation to be made in plain. In our circuit we can simply multiply the two values. + +::: + +Now, we should write the correspondent RPC server, starting with the [default JSON-RPC 2.0 boilerplate](https://www.npmjs.com/package/json-rpc-2.0#example): + +```js +import { JSONRPCServer } from "json-rpc-2.0"; +import express from "express"; +import bodyParser from "body-parser"; + +const app = express(); +app.use(bodyParser.json()); + +const server = new JSONRPCServer(); +app.post("/", (req, res) => { + const jsonRPCRequest = req.body; + server.receive(jsonRPCRequest).then((jsonRPCResponse) => { + if (jsonRPCResponse) { + res.json(jsonRPCResponse); + } else { + res.sendStatus(204); + } + }); +}); + +app.listen(5555); +``` + +Now, we will add our `getSqrt` method, as expected by the `#[oracle(getSqrt)]` decorator in our Noir code. It maps through the params array and returns their square roots: + +```js +server.addMethod("getSqrt", async (params) => { + const values = params[0].Array.map(({ inner }) => { + return { inner: `${Math.sqrt(parseInt(inner, 16))}` }; + }); + return { values: [{ Array: values }] }; +}); +``` + +:::tip + +Brillig expects an object with an array of values. Each value is an object declaring to be `Single` or `Array` and returning a `inner` property *as a string*. For example: + +```json +{ "values": [{ "Array": [{ "inner": "1" }, { "inner": "2"}]}]} +{ "values": [{ "Single": { "inner": "1" }}]} +{ "values": [{ "Single": { "inner": "1" }}, { "Array": [{ "inner": "1", { "inner": "2" }}]}]} +``` + +If you're using Typescript, the following types may be helpful in understanding the expected return value and making sure they're easy to follow: + +```js +interface Value { + inner: string, +} + +interface SingleForeignCallParam { + Single: Value, +} + +interface ArrayForeignCallParam { + Array: Value[], +} + +type ForeignCallParam = SingleForeignCallParam | ArrayForeignCallParam; + +interface ForeignCallResult { + values: ForeignCallParam[], +} +``` + +::: + +## Step 3 - Usage with Nargo + +Using the [`nargo` CLI tool](../getting_started/installation/index.md), you can use oracles in the `nargo test`, `nargo execute` and `nargo prove` commands by passing a value to `--oracle-resolver`. For example: + +```bash +nargo test --oracle-resolver http://localhost:5555 +``` + +This tells `nargo` to use your RPC Server URL whenever it finds an oracle decorator. + +## Step 4 - Usage with NoirJS + +In a JS environment, an RPC server is not strictly necessary, as you may want to resolve your oracles without needing any JSON call at all. NoirJS simply expects that you pass a callback function when you generate proofs, and that callback function can be anything. + +For example, if your Noir program expects the host machine to provide CPU pseudo-randomness, you could simply pass it as the `foreignCallHandler`. You don't strictly need to create an RPC server to serve pseudo-randomness, as you may as well get it directly in your app: + +```js +const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc + +await noir.generateFinalProof(inputs, foreignCallHandler) +``` + +As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. + +:::tip + +Does this mean you don't have to write an RPC server like in [Step #2](#step-2---write-an-rpc-server)? + +You don't technically have to, but then how would you run `nargo test` or `nargo prove`? To use both `Nargo` and `NoirJS` in your development flow, you will have to write a JSON RPC server. + +::: + +In this case, let's make `foreignCallHandler` call the JSON RPC Server we created in [Step #2](#step-2---write-an-rpc-server), by making it a JSON RPC Client. + +For example, using the same `getSqrt` program in [Step #1](#step-1---modify-your-noir-program) (comments in the code): + +```js +import { JSONRPCClient } from "json-rpc-2.0"; + +// declaring the JSONRPCClient +const client = new JSONRPCClient((jsonRPCRequest) => { +// hitting the same JSON RPC Server we coded above + return fetch("http://localhost:5555", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(jsonRPCRequest), + }).then((response) => { + if (response.status === 200) { + return response + .json() + .then((jsonRPCResponse) => client.receive(jsonRPCResponse)); + } else if (jsonRPCRequest.id !== undefined) { + return Promise.reject(new Error(response.statusText)); + } + }); +}); + +// declaring a function that takes the name of the foreign call (getSqrt) and the inputs +const foreignCallHandler = async (name, input) => { + // notice that the "inputs" parameter contains *all* the inputs + // in this case we to make the RPC request with the first parameter "numbers", which would be input[0] + const oracleReturn = await client.request(name, [ + { Array: input[0].map((i) => ({ inner: i.toString("hex") })) }, + ]); + return [oracleReturn.values[0].Array.map((x) => x.inner)]; +}; + +// the rest of your NoirJS code +const input = { input: [4, 16] }; +const { witness } = await noir.execute(numbers, foreignCallHandler); +``` + +:::tip + +If you're in a NoirJS environment running your RPC server together with a frontend app, you'll probably hit a familiar problem in full-stack development: requests being blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. For development only, you can simply install and use the [`cors` npm package](https://www.npmjs.com/package/cors) to get around the problem: + +```bash +yarn add cors +``` + +and use it as a middleware: + +```js +import cors from "cors"; + +const app = express(); +app.use(cors()) +``` + +::: + +## Conclusion + +Hopefully by the end of this guide, you should be able to: + +- Write your own logic around Oracles and how to write a JSON RPC server to make them work with your Nargo commands. +- Provide custom foreign call handlers for NoirJS. diff --git a/noir/docs/docs/noir/concepts/comments.md b/noir/docs/docs/noir/concepts/comments.md index f76ab49094b..b51a85f5c94 100644 --- a/noir/docs/docs/noir/concepts/comments.md +++ b/noir/docs/docs/noir/concepts/comments.md @@ -5,7 +5,7 @@ description: ignored by the compiler, but it can be read by programmers. Single-line and multi-line comments are supported in Noir. keywords: [Noir programming language, comments, single-line comments, multi-line comments] -sidebar_position: 9 +sidebar_position: 10 --- A comment is a line in your codebase which the compiler ignores, however it can be read by diff --git a/noir/docs/docs/noir/concepts/data_bus.md b/noir/docs/docs/noir/concepts/data_bus.md index 6c7e9b60891..e54fc861257 100644 --- a/noir/docs/docs/noir/concepts/data_bus.md +++ b/noir/docs/docs/noir/concepts/data_bus.md @@ -1,6 +1,6 @@ --- title: Data Bus -sidebar_position: 12 +sidebar_position: 13 --- **Disclaimer** this feature is experimental, do not use it! diff --git a/noir/docs/docs/noir/concepts/data_types/arrays.md b/noir/docs/docs/noir/concepts/data_types/arrays.md index 075d39dadd4..7f275a2d771 100644 --- a/noir/docs/docs/noir/concepts/data_types/arrays.md +++ b/noir/docs/docs/noir/concepts/data_types/arrays.md @@ -70,6 +70,10 @@ You can define multidimensional arrays: let array : [[Field; 2]; 2]; let element = array[0][0]; ``` +However, multidimensional slices are not supported. For example, the following code will error at compile time: +```rust +let slice : [[Field]] = []; +``` ## Types diff --git a/noir/docs/docs/noir/concepts/data_types/function_types.md b/noir/docs/docs/noir/concepts/data_types/function_types.md index e224e860d59..f6121af17e2 100644 --- a/noir/docs/docs/noir/concepts/data_types/function_types.md +++ b/noir/docs/docs/noir/concepts/data_types/function_types.md @@ -23,4 +23,4 @@ fn main() { ``` A function type also has an optional capture environment - this is necessary to support closures. -See [Lambdas](@site/docs/noir/concepts/lambdas.md) for more details. +See [Lambdas](../lambdas.md) for more details. diff --git a/noir/docs/docs/noir/concepts/distinct.md b/noir/docs/docs/noir/concepts/distinct.md index b59e0296b23..6c993b8b5e0 100644 --- a/noir/docs/docs/noir/concepts/distinct.md +++ b/noir/docs/docs/noir/concepts/distinct.md @@ -1,6 +1,6 @@ --- title: Distinct Witnesses -sidebar_position: 10 +sidebar_position: 11 --- The `distinct` keyword prevents repetitions of witness indices in the program's ABI. This ensures diff --git a/noir/docs/docs/noir/concepts/generics.md b/noir/docs/docs/noir/concepts/generics.md index ec2db95839a..ddd42bf1f9b 100644 --- a/noir/docs/docs/noir/concepts/generics.md +++ b/noir/docs/docs/noir/concepts/generics.md @@ -2,7 +2,7 @@ title: Generics description: Learn how to use Generics in Noir keywords: [Noir, Rust, generics, functions, structs] -sidebar_position: 6 +sidebar_position: 7 --- Generics allow you to use the same functions with multiple different concrete data types. You can diff --git a/noir/docs/docs/noir/concepts/lambdas.md b/noir/docs/docs/noir/concepts/lambdas.md index e0a267adfda..be3c7e0b5ca 100644 --- a/noir/docs/docs/noir/concepts/lambdas.md +++ b/noir/docs/docs/noir/concepts/lambdas.md @@ -2,7 +2,7 @@ title: Lambdas description: Learn how to use anonymous functions in Noir programming language. keywords: [Noir programming language, lambda, closure, function, anonymous function] -sidebar_position: 8 +sidebar_position: 9 --- ## Introduction diff --git a/noir/docs/docs/noir/concepts/mutability.md b/noir/docs/docs/noir/concepts/mutability.md index 6abfae3cfa7..9cc10429cb4 100644 --- a/noir/docs/docs/noir/concepts/mutability.md +++ b/noir/docs/docs/noir/concepts/mutability.md @@ -4,7 +4,7 @@ description: Learn about mutable variables, constants, and globals in Noir programming language. Discover how to declare, modify, and use them in your programs. keywords: [noir programming language, mutability in noir, mutable variables, constants, globals] -sidebar_position: 7 +sidebar_position: 8 --- Variables in noir can be declared mutable via the `mut` keyword. Mutable variables can be reassigned diff --git a/noir/docs/docs/noir/concepts/oracles.md b/noir/docs/docs/noir/concepts/oracles.md new file mode 100644 index 00000000000..2e6a6818d48 --- /dev/null +++ b/noir/docs/docs/noir/concepts/oracles.md @@ -0,0 +1,23 @@ +--- +title: Oracles +description: Dive into how Noir supports Oracles via RPC calls, and learn how to declare an Oracle in Noir with our comprehensive guide. +keywords: + - Noir + - Oracles + - RPC Calls + - Unconstrained Functions + - Programming + - Blockchain +sidebar_position: 6 +--- + +Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. + +Since Oracles are not resolved by Noir, they are [`unconstrained` functions](./unconstrained.md) + +You can declare an Oracle through the `#[oracle()]` flag. Example: + +```rust +#[oracle(get_number_sequence)] +unconstrained fn get_number_sequence(_size: Field) -> [Field] {} +``` diff --git a/noir/docs/docs/noir/concepts/shadowing.md b/noir/docs/docs/noir/concepts/shadowing.md index b5a6b6b38b9..5ce6130d201 100644 --- a/noir/docs/docs/noir/concepts/shadowing.md +++ b/noir/docs/docs/noir/concepts/shadowing.md @@ -1,6 +1,6 @@ --- title: Shadowing -sidebar_position: 11 +sidebar_position: 12 --- Noir allows for inheriting variables' values and re-declaring them with the same name similar to Rust, known as shadowing. diff --git a/noir/docs/docs/noir/concepts/traits.md b/noir/docs/docs/noir/concepts/traits.md index 7ba07e74f40..ef1445a5907 100644 --- a/noir/docs/docs/noir/concepts/traits.md +++ b/noir/docs/docs/noir/concepts/traits.md @@ -4,6 +4,7 @@ description: Traits in Noir can be used to abstract out a common interface for functions across several data types. keywords: [noir programming language, traits, interfaces, generic, protocol] +sidebar_position: 14 --- ## Overview @@ -168,6 +169,46 @@ impl Eq for [T; N] where T: Eq { } ``` +## Generic Traits + +Traits themselves can also be generic by placing the generic arguments after the trait name. These generics are in +scope of every item within the trait. + +```rust +trait Into { + // Convert `self` to type `T` + fn into(self) -> T; +} +``` + +When implementing generic traits the generic arguments of the trait must be specified. This is also true anytime +when referencing a generic trait (e.g. in a `where` clause). + +```rust +struct MyStruct { + array: [Field; 2], +} + +impl Into<[Field; 2]> for MyStruct { + fn into(self) -> [Field; 2] { + self.array + } +} + +fn as_array(x: T) -> [Field; 2] + where T: Into<[Field; 2]> +{ + x.into() +} + +fn main() { + let array = [1, 2]; + let my_struct = MyStruct { array }; + + assert_eq(as_array(my_struct), array); +} +``` + ## Trait Methods With No `self` A trait can contain any number of methods, each of which have access to the `Self` type which represents each type diff --git a/noir/docs/docs/reference/nargo_commands.md b/noir/docs/docs/reference/nargo_commands.md index ff3dee8973f..fc2671b2bfc 100644 --- a/noir/docs/docs/reference/nargo_commands.md +++ b/noir/docs/docs/reference/nargo_commands.md @@ -162,6 +162,7 @@ Runs the Noir program and prints its return value. | `--print-acir` | Display the ACIR for compiled circuit | | `--deny-warnings` | Treat all warnings as errors | | `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | | `-h, --help` | Print help | _Usage_ @@ -188,6 +189,7 @@ Creates a proof for the program. | `--print-acir` | Display the ACIR for compiled circuit | | `--deny-warnings` | Treat all warnings as errors | | `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | | `-h, --help` | Print help | ## `nargo verify` @@ -226,6 +228,7 @@ See an example on the [testing page](../getting_started/tooling/testing.md). | `--print-acir` | Display the ACIR for compiled circuit | | `--deny-warnings` | Treat all warnings as errors | | `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | | `-h, --help` | Print help | ## `nargo info` diff --git a/noir/docs/package.json b/noir/docs/package.json index c53ce58f727..1e3efcfe3d1 100644 --- a/noir/docs/package.json +++ b/noir/docs/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "yarn version::stables && docusaurus start", + "start": "docusaurus start", "build": "yarn version::stables && docusaurus build", "version::stables": "ts-node ./scripts/setStable.ts", "serve": "serve build" diff --git a/noir/docs/static/img/memes/matrix_oracle.jpeg b/noir/docs/static/img/memes/matrix_oracle.jpeg new file mode 100644 index 00000000000..478af092aca Binary files /dev/null and b/noir/docs/static/img/memes/matrix_oracle.jpeg differ diff --git a/noir/docs/versioned_docs/version-v0.22.0/explainers/explainer-oracle.md b/noir/docs/versioned_docs/version-v0.22.0/explainers/explainer-oracle.md new file mode 100644 index 00000000000..76dd0e36d6c --- /dev/null +++ b/noir/docs/versioned_docs/version-v0.22.0/explainers/explainer-oracle.md @@ -0,0 +1,57 @@ +--- +title: Oracles +description: This guide provides an in-depth understanding of how Oracles work in Noir programming. Learn how to use outside calculations in your programs, constrain oracles, and understand their uses and limitations. +keywords: + - Noir Programming + - Oracles + - JSON-RPC + - Foreign Call Handlers + - Constrained Functions + - Blockchain Programming +sidebar_position: 1 +--- + +If you've seen "The Matrix" you may recall "The Oracle" as Gloria Foster smoking cigarettes and baking cookies. While she appears to "know things", she is actually providing a calculation of a pre-determined future. Noir Oracles are similar, in a way. They don't calculate the future (yet), but they allow you to use outside calculations in your programs. + +![matrix oracle prediction](@site/static/img/memes/matrix_oracle.jpeg) + +A Noir program is usually self-contained. You can pass certain inputs to it, and it will generate a deterministic output for those inputs. But what if you wanted to defer some calculation to an outside process or source? + +Oracles are functions that provide this feature. + +## Use cases + +An example usage for Oracles is proving something on-chain. For example, proving that the ETH-USDC quote was below a certain target at a certain block time. Or even making more complex proofs like proving the ownership of an NFT as an anonymous login method. + +Another interesting use case is to defer expensive calculations to be made outside of the Noir program, and then constraining the result; similar to the use of [unconstrained functions](../noir/syntax/unconstrained.md). + +In short, anything that can be constrained in a Noir program but needs to be fetched from an external source is a great candidate to be used in oracles. + +## Constraining oracles + +Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information. + +To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have a oracle call like this: + +```rust +#[oracle(getNoun)] +unconstrained fn get_noun(address: Field) -> Field +``` + +This oracle could naively resolve with the number of Nouns she possesses. However, it is useless as a trusted source, as the oracle could resolve to anything Alice wants. In order to make this oracle call actually useful, Alice would need to constrain the response from the oracle, by proving her address and the noun count belongs to the state tree of the contract. + +In short, **Oracles don't prove anything. Your Noir program does.** + +:::danger + +If you don't constrain the return of your oracle, you could be clearly opening an attack vector on your Noir program. Make double-triple sure that the return of an oracle call is constrained! + +::: + +## How to use Oracles + +On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running. + +In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they matches the expected types the developer defines. Refer to [Foreign Call Handler](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) to learn more about NoirJS's call handling. + +If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that. diff --git a/noir/docs/versioned_docs/version-v0.22.0/how_to/how-to-oracles.md b/noir/docs/versioned_docs/version-v0.22.0/how_to/how-to-oracles.md new file mode 100644 index 00000000000..61cabe586e6 --- /dev/null +++ b/noir/docs/versioned_docs/version-v0.22.0/how_to/how-to-oracles.md @@ -0,0 +1,280 @@ +--- +title: How to use Oracles +description: Learn how to use oracles in your Noir program with examples in both Nargo and NoirJS. This guide also covers writing a JSON RPC server and providing custom foreign call handlers for NoirJS. +keywords: + - Noir Programming + - Oracles + - Nargo + - NoirJS + - JSON RPC Server + - Foreign Call Handlers +sidebar_position: 1 +--- + +This guide shows you how to use oracles in your Noir program. For the sake of clarity, it assumes that: + +- You have read the [explainer on Oracles](../explainers/explainer-oracle.md) and are comfortable with the concept. +- You have a Noir program to add oracles to. You can create one using the [vite-hardhat starter](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) as a boilerplate. +- You understand the concept of a JSON-RPC server. Visit the [JSON-RPC website](https://www.jsonrpc.org/) if you need a refresher. +- You are comfortable with server-side JavaScript (e.g. Node.js, managing packages, etc.). + +For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/how_to_oracles/code-snippets/how-to-oracles). + +## Rundown + +This guide has 3 major steps: + +1. How to modify our Noir program to make use of oracle calls as unconstrained functions +2. How to write a JSON RPC Server to resolve these oracle calls with Nargo +3. How to use them in Nargo and how to provide a custom resolver in NoirJS + +## Step 1 - Modify your Noir program + +An oracle is defined in a Noir program by defining two methods: + +- An unconstrained method - This tells the compiler that it is executing an [unconstrained functions](../noir/syntax/unconstrained.md). +- A decorated oracle method - This tells the compiler that this method is an RPC call. + +An example of an oracle that returns a `Field` would be: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(number: Field) -> Field { } + +unconstrained fn get_sqrt(number: Field) -> Field { + sqrt(number) +} +``` + +In this example, we're wrapping our oracle function in a unconstrained method, and decorating it with `oracle(getSqrt)`. We can then call the unconstrained function as we would call any other function: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); +} +``` + +In the next section, we will make this `getSqrt` (defined on the `sqrt` decorator) be a method of the RPC server Noir will use. + +:::danger + +As explained in the [Oracle Explainer](../explainers/explainer-oracle.md), this `main` function is unsafe unless you constrain its return value. For example: + +```rust +fn main(input: Field) { + let sqrt = get_sqrt(input); + assert(sqrt[0].pow_32(2) as u64 == input as u64); // <---- constrain the return of an oracle! +} +``` + +::: + +:::info + +Currently, oracles only work with single params or array params. For example: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt([Field; 2]) -> [Field; 2] { } +``` + +::: + +## Step 2 - Write an RPC server + +Brillig will call *one* RPC server. Most likely you will have to write your own, and you can do it in whatever language you prefer. In this guide, we will do it in Javascript. + +Let's use the above example of an oracle that consumes an array with two `Field` and returns their square roots: + +```rust +#[oracle(getSqrt)] +unconstrained fn sqrt(input: [Field; 2]) -> [Field; 2] { } + +unconstrained fn get_sqrt(input: [Field; 2]) -> [Field; 2] { + sqrt(input) +} + +fn main(input: [Field; 2]) { + let sqrt = get_sqrt(input); + assert(sqrt[0].pow_32(2) as u64 == input[0] as u64); + assert(sqrt[1].pow_32(2) as u64 == input[1] as u64); +} +``` + +:::info + +Why square root? + +In general, computing square roots is computationally more expensive than multiplications, which takes a toll when speaking about ZK applications. In this case, instead of calculating the square root in Noir, we are using our oracle to offload that computation to be made in plain. In our circuit we can simply multiply the two values. + +::: + +Now, we should write the correspondent RPC server, starting with the [default JSON-RPC 2.0 boilerplate](https://www.npmjs.com/package/json-rpc-2.0#example): + +```js +import { JSONRPCServer } from "json-rpc-2.0"; +import express from "express"; +import bodyParser from "body-parser"; + +const app = express(); +app.use(bodyParser.json()); + +const server = new JSONRPCServer(); +app.post("/", (req, res) => { + const jsonRPCRequest = req.body; + server.receive(jsonRPCRequest).then((jsonRPCResponse) => { + if (jsonRPCResponse) { + res.json(jsonRPCResponse); + } else { + res.sendStatus(204); + } + }); +}); + +app.listen(5555); +``` + +Now, we will add our `getSqrt` method, as expected by the `#[oracle(getSqrt)]` decorator in our Noir code. It maps through the params array and returns their square roots: + +```js +server.addMethod("getSqrt", async (params) => { + const values = params[0].Array.map(({ inner }) => { + return { inner: `${Math.sqrt(parseInt(inner, 16))}` }; + }); + return { values: [{ Array: values }] }; +}); +``` + +:::tip + +Brillig expects an object with an array of values. Each value is an object declaring to be `Single` or `Array` and returning a `inner` property *as a string*. For example: + +```json +{ "values": [{ "Array": [{ "inner": "1" }, { "inner": "2"}]}]} +{ "values": [{ "Single": { "inner": "1" }}]} +{ "values": [{ "Single": { "inner": "1" }}, { "Array": [{ "inner": "1", { "inner": "2" }}]}]} +``` + +If you're using Typescript, the following types may be helpful in understanding the expected return value and making sure they're easy to follow: + +```js +interface Value { + inner: string, +} + +interface SingleForeignCallParam { + Single: Value, +} + +interface ArrayForeignCallParam { + Array: Value[], +} + +type ForeignCallParam = SingleForeignCallParam | ArrayForeignCallParam; + +interface ForeignCallResult { + values: ForeignCallParam[], +} +``` + +::: + +## Step 3 - Usage with Nargo + +Using the [`nargo` CLI tool](../getting_started/installation/index.md), you can use oracles in the `nargo test`, `nargo execute` and `nargo prove` commands by passing a value to `--oracle-resolver`. For example: + +```bash +nargo test --oracle-resolver http://localhost:5555 +``` + +This tells `nargo` to use your RPC Server URL whenever it finds an oracle decorator. + +## Step 4 - Usage with NoirJS + +In a JS environment, an RPC server is not strictly necessary, as you may want to resolve your oracles without needing any JSON call at all. NoirJS simply expects that you pass a callback function when you generate proofs, and that callback function can be anything. + +For example, if your Noir program expects the host machine to provide CPU pseudo-randomness, you could simply pass it as the `foreignCallHandler`. You don't strictly need to create an RPC server to serve pseudo-randomness, as you may as well get it directly in your app: + +```js +const foreignCallHandler = (name, inputs) => crypto.randomBytes(16) // etc + +await noir.generateFinalProof(inputs, foreignCallHandler) +``` + +As one can see, in NoirJS, the [`foreignCallHandler`](../reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md) function simply means "a callback function that returns a value of type [`ForeignCallOutput`](../reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md). It doesn't have to be an RPC call like in the case for Nargo. + +:::tip + +Does this mean you don't have to write an RPC server like in [Step #2](#step-2---write-an-rpc-server)? + +You don't technically have to, but then how would you run `nargo test` or `nargo prove`? To use both `Nargo` and `NoirJS` in your development flow, you will have to write a JSON RPC server. + +::: + +In this case, let's make `foreignCallHandler` call the JSON RPC Server we created in [Step #2](#step-2---write-an-rpc-server), by making it a JSON RPC Client. + +For example, using the same `getSqrt` program in [Step #1](#step-1---modify-your-noir-program) (comments in the code): + +```js +import { JSONRPCClient } from "json-rpc-2.0"; + +// declaring the JSONRPCClient +const client = new JSONRPCClient((jsonRPCRequest) => { +// hitting the same JSON RPC Server we coded above + return fetch("http://localhost:5555", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify(jsonRPCRequest), + }).then((response) => { + if (response.status === 200) { + return response + .json() + .then((jsonRPCResponse) => client.receive(jsonRPCResponse)); + } else if (jsonRPCRequest.id !== undefined) { + return Promise.reject(new Error(response.statusText)); + } + }); +}); + +// declaring a function that takes the name of the foreign call (getSqrt) and the inputs +const foreignCallHandler = async (name, input) => { + // notice that the "inputs" parameter contains *all* the inputs + // in this case we to make the RPC request with the first parameter "numbers", which would be input[0] + const oracleReturn = await client.request(name, [ + { Array: input[0].map((i) => ({ inner: i.toString("hex") })) }, + ]); + return [oracleReturn.values[0].Array.map((x) => x.inner)]; +}; + +// the rest of your NoirJS code +const input = { input: [4, 16] }; +const { witness } = await noir.execute(numbers, foreignCallHandler); +``` + +:::tip + +If you're in a NoirJS environment running your RPC server together with a frontend app, you'll probably hit a familiar problem in full-stack development: requests being blocked by [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. For development only, you can simply install and use the [`cors` npm package](https://www.npmjs.com/package/cors) to get around the problem: + +```bash +yarn add cors +``` + +and use it as a middleware: + +```js +import cors from "cors"; + +const app = express(); +app.use(cors()) +``` + +::: + +## Conclusion + +Hopefully by the end of this guide, you should be able to: + +- Write your own logic around Oracles and how to write a JSON RPC server to make them work with your Nargo commands. +- Provide custom foreign call handlers for NoirJS. diff --git a/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/function_types.md b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/function_types.md index 61e4076adaf..f6121af17e2 100644 --- a/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/function_types.md +++ b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/function_types.md @@ -23,4 +23,4 @@ fn main() { ``` A function type also has an optional capture environment - this is necessary to support closures. -See [Lambdas](@site/docs/noir/syntax/lambdas.md) for more details. +See [Lambdas](../lambdas.md) for more details. diff --git a/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/index.md b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/index.md index 01cd0431a68..f09bca0ee04 100644 --- a/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/index.md +++ b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/data_types/index.md @@ -79,7 +79,7 @@ fn main() { } ``` -Type aliases can also be used with [generics](@site/docs/noir/syntax/generics.md): +Type aliases can also be used with [generics](../generics.md): ```rust type Id = Size; diff --git a/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/oracles.md b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/oracles.md new file mode 100644 index 00000000000..2e6a6818d48 --- /dev/null +++ b/noir/docs/versioned_docs/version-v0.22.0/noir/syntax/oracles.md @@ -0,0 +1,23 @@ +--- +title: Oracles +description: Dive into how Noir supports Oracles via RPC calls, and learn how to declare an Oracle in Noir with our comprehensive guide. +keywords: + - Noir + - Oracles + - RPC Calls + - Unconstrained Functions + - Programming + - Blockchain +sidebar_position: 6 +--- + +Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. + +Since Oracles are not resolved by Noir, they are [`unconstrained` functions](./unconstrained.md) + +You can declare an Oracle through the `#[oracle()]` flag. Example: + +```rust +#[oracle(get_number_sequence)] +unconstrained fn get_number_sequence(_size: Field) -> [Field] {} +``` diff --git a/noir/docs/versioned_sidebars/version-v..-sidebars.json b/noir/docs/versioned_sidebars/version-v0.22.0-sidebars.json similarity index 100% rename from noir/docs/versioned_sidebars/version-v..-sidebars.json rename to noir/docs/versioned_sidebars/version-v0.22.0-sidebars.json diff --git a/noir/test_programs/execution_success/brillig_nested_slices/Nargo.toml b/noir/test_programs/compile_failure/brillig_nested_slices/Nargo.toml similarity index 100% rename from noir/test_programs/execution_success/brillig_nested_slices/Nargo.toml rename to noir/test_programs/compile_failure/brillig_nested_slices/Nargo.toml diff --git a/noir/test_programs/execution_success/brillig_nested_slices/Prover.toml b/noir/test_programs/compile_failure/brillig_nested_slices/Prover.toml similarity index 100% rename from noir/test_programs/execution_success/brillig_nested_slices/Prover.toml rename to noir/test_programs/compile_failure/brillig_nested_slices/Prover.toml diff --git a/noir/test_programs/execution_success/brillig_nested_slices/src/main.nr b/noir/test_programs/compile_failure/brillig_nested_slices/src/main.nr similarity index 100% rename from noir/test_programs/execution_success/brillig_nested_slices/src/main.nr rename to noir/test_programs/compile_failure/brillig_nested_slices/src/main.nr diff --git a/noir/test_programs/compile_failure/nested_slice_declared_type/Nargo.toml b/noir/test_programs/compile_failure/nested_slice_declared_type/Nargo.toml new file mode 100644 index 00000000000..b0ba05135ed --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_declared_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_slice_declared_type" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] \ No newline at end of file diff --git a/noir/test_programs/compile_failure/nested_slice_declared_type/src/main.nr b/noir/test_programs/compile_failure/nested_slice_declared_type/src/main.nr new file mode 100644 index 00000000000..417f9a092e0 --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_declared_type/src/main.nr @@ -0,0 +1,6 @@ +fn main(x: Field, y: pub Field) { + assert(x != y); + + let slice: [[Field]] = []; + assert(slice.len() != 10); +} diff --git a/noir/test_programs/compile_failure/nested_slice_literal/Nargo.toml b/noir/test_programs/compile_failure/nested_slice_literal/Nargo.toml new file mode 100644 index 00000000000..db919955de5 --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_literal/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_slice_literal" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] \ No newline at end of file diff --git a/noir/test_programs/compile_failure/nested_slice_literal/src/main.nr b/noir/test_programs/compile_failure/nested_slice_literal/src/main.nr new file mode 100644 index 00000000000..3140818d0b2 --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_literal/src/main.nr @@ -0,0 +1,23 @@ +struct FooParent { + parent_arr: [Field; 3], + foos: [Foo], +} + +struct Bar { + inner: [Field; 3], +} + +struct Foo { + a: Field, + b: T, + bar: Bar, +} + +fn main(x: Field, y: pub Field) { + assert(x != y); + + let foo = Foo { a: 7, b: [8, 9, 22].as_slice(), bar: Bar { inner: [106, 107, 108] } }; + let mut slice = [foo, foo]; + slice = slice.push_back(foo); + assert(slice.len() == 3); +} diff --git a/noir/test_programs/compile_failure/nested_slice_struct/Nargo.toml b/noir/test_programs/compile_failure/nested_slice_struct/Nargo.toml new file mode 100644 index 00000000000..a37fe6c3390 --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_struct/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_slice_struct" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] \ No newline at end of file diff --git a/noir/test_programs/compile_failure/nested_slice_struct/src/main.nr b/noir/test_programs/compile_failure/nested_slice_struct/src/main.nr new file mode 100644 index 00000000000..9fed4cfc299 --- /dev/null +++ b/noir/test_programs/compile_failure/nested_slice_struct/src/main.nr @@ -0,0 +1,18 @@ +struct FooParent { + parent_arr: [Field; 3], + foos: [Foo], +} + +struct Bar { + inner: [Field; 3], +} + +struct Foo { + a: Field, + b: [Field], + bar: Bar, +} + +fn main(x: Field, y: pub Field) { + assert(x != y); +} diff --git a/noir/test_programs/compile_success_empty/trait_generics/Nargo.toml b/noir/test_programs/compile_success_empty/trait_generics/Nargo.toml index c1b5d0aaa6c..7fdd5975541 100644 --- a/noir/test_programs/compile_success_empty/trait_generics/Nargo.toml +++ b/noir/test_programs/compile_success_empty/trait_generics/Nargo.toml @@ -2,5 +2,6 @@ name = "trait_generics" type = "bin" authors = [""] +compiler_version = ">=0.22.0" [dependencies] diff --git a/noir/test_programs/compile_success_empty/trait_generics/src/main.nr b/noir/test_programs/compile_success_empty/trait_generics/src/main.nr index bb6d6e74726..9a3c54c3fa1 100644 --- a/noir/test_programs/compile_success_empty/trait_generics/src/main.nr +++ b/noir/test_programs/compile_success_empty/trait_generics/src/main.nr @@ -1,59 +1,57 @@ -struct Empty {} -trait Foo { - fn foo(self) -> u32; -} +fn main() { + let xs: [Field; 1] = [3]; + let ys: [u32; 1] = [3]; + foo(xs, ys); -impl Foo for Empty { - fn foo(_self: Self) -> u32 { 32 } + assert_eq(15, sum(Data { a: 5, b: 10 })); + assert_eq(15, sum_static(Data { a: 5, b: 10 })); } -impl Foo for Empty { - fn foo(_self: Self) -> u32 { 64 } +fn foo(x: T, u: U) where T: Into, U: Eq { + assert(x.into() == u); } -fn main() { - let x: Empty = Empty {}; - let y: Empty = Empty {}; - let z = Empty {}; - - assert(x.foo() == 32); - assert(y.foo() == 64); - // Types matching multiple impls will currently choose - // the first matching one instead of erroring - assert(z.foo() == 32); - - call_impl_with_generic_struct(); - call_impl_with_generic_function(); +trait Into { + fn into(self) -> T; } -// Ensure we can call a generic impl -fn call_impl_with_generic_struct() { - let x: u8 = 7; - let y: i8 = 8; - let s2_u8 = S2 { x }; - let s2_i8 = S2 { x: y }; - assert(s2_u8.t2().x == 7); - assert(s2_i8.t2().x == 8); + +impl Into<[U; N]> for [T; N] where T: Into { + fn into(self) -> [U; N] { + self.map(|x: T| x.into()) + } } -trait T2 { - fn t2(self) -> Self; +impl Into for Field { + fn into(self) -> u32 { + self as u32 + } } -struct S2 { x: T } +/// Serialize example + +trait Serializable { + fn serialize(self) -> [Field; N]; +} -impl T2 for S2 { - fn t2(self) -> Self { self } +struct Data { + a: Field, + b: Field, } -fn call_impl_with_generic_function() { - assert(3.t3(7) == 7); +impl Serializable<2> for Data { + fn serialize(self) -> [Field; 2] { + [self.a, self.b] + } } -trait T3 { - fn t3(self, x: T) -> T; +fn sum(data: T) -> Field where T: Serializable { + let serialized = data.serialize(); + serialized.fold(0, |acc, elem| acc + elem) } -impl T3 for u32 { - fn t3(self, y: U) -> U { y } +// Test static trait method syntax +fn sum_static(data: T) -> Field where T: Serializable { + let serialized = Serializable::serialize(data); + serialized.fold(0, |acc, elem| acc + elem) } diff --git a/noir/test_programs/execution_success/slice_struct_field/Nargo.toml b/noir/test_programs/compile_success_empty/trait_impl_generics/Nargo.toml similarity index 65% rename from noir/test_programs/execution_success/slice_struct_field/Nargo.toml rename to noir/test_programs/compile_success_empty/trait_impl_generics/Nargo.toml index 9530ebf9271..b10b5dab6aa 100644 --- a/noir/test_programs/execution_success/slice_struct_field/Nargo.toml +++ b/noir/test_programs/compile_success_empty/trait_impl_generics/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "slice_struct_field" +name = "trait_impl_generics" type = "bin" authors = [""] diff --git a/noir/test_programs/compile_success_empty/trait_impl_generics/src/main.nr b/noir/test_programs/compile_success_empty/trait_impl_generics/src/main.nr new file mode 100644 index 00000000000..c46c41cbdd7 --- /dev/null +++ b/noir/test_programs/compile_success_empty/trait_impl_generics/src/main.nr @@ -0,0 +1,59 @@ +struct Empty {} + +trait Foo { + fn foo(self) -> u32; +} + +impl Foo for Empty { + fn foo(_self: Self) -> u32 { 32 } +} + +impl Foo for Empty { + fn foo(_self: Self) -> u32 { 64 } +} + +fn main() { + let x: Empty = Empty {}; + let y: Empty = Empty {}; + let z = Empty {}; + + assert(x.foo() == 32); + assert(y.foo() == 64); + // Types matching multiple impls will currently choose + // the first matching one instead of erroring + assert(z.foo() == 32); + + call_impl_with_generic_struct(); + call_impl_with_generic_function(); +} +// Ensure we can call a generic impl +fn call_impl_with_generic_struct() { + let x: u8 = 7; + let y: i8 = 8; + let s2_u8 = S2 { x }; + let s2_i8 = S2 { x: y }; + assert(s2_u8.t2().x == 7); + assert(s2_i8.t2().x == 8); +} + +trait T2 { + fn t2(self) -> Self; +} + +struct S2 { x: T } + +impl T2 for S2 { + fn t2(self) -> Self { self } +} + +fn call_impl_with_generic_function() { + assert(3.t3(7) == 7); +} + +trait T3 { + fn t3(self, x: T) -> T; +} + +impl T3 for u32 { + fn t3(_self: Self, y: U) -> U { y } +} diff --git a/noir/test_programs/execution_success/brillig_set_slice_of_slice/Nargo.toml b/noir/test_programs/execution_success/brillig_set_slice_of_slice/Nargo.toml deleted file mode 100644 index 071254266f4..00000000000 --- a/noir/test_programs/execution_success/brillig_set_slice_of_slice/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "brillig_set_slice_of_slice" -type = "bin" -authors = [""] -compiler_version = ">=0.19.4" - -[dependencies] \ No newline at end of file diff --git a/noir/test_programs/execution_success/brillig_set_slice_of_slice/src/main.nr b/noir/test_programs/execution_success/brillig_set_slice_of_slice/src/main.nr deleted file mode 100644 index c0e9c7d172f..00000000000 --- a/noir/test_programs/execution_success/brillig_set_slice_of_slice/src/main.nr +++ /dev/null @@ -1,51 +0,0 @@ -struct Property -{ - key : [u8], - value : [u8], -} - -struct JSON -{ - doc : [Property] -} - -unconstrained fn slice_eq(self: [u8], other: [u8]) -> bool { - let mut equal = true; - for i in 0..self.len() { - if self[i] != other[i] { - equal = false; - } - } - equal -} - -// This test acts a regression for issue #3476 -unconstrained fn main() { - let mut json = JSON { doc: [] }; - let mut prop = Property { key: [], value:[] }; - - let other_prop = Property { key: [0, 1, 2], value:[10] }; - json.doc = json.doc.push_back(other_prop); - - for i in 0..3 { - prop.key = prop.key.push_back(i as u8); - } - prop.value = prop.value.push_back(5); - - // add property to json or replace existing - let len : Field = json.doc.len(); - let mut found = false; - for i in 0..len - { - if (!found) - { - if (slice_eq(prop.key, json.doc[i].key)) - { - json.doc[i].value = prop.value; - found = true; - } - } - } - assert(found == true); - assert(json.doc[0].value[0] == 5); -} \ No newline at end of file diff --git a/noir/test_programs/execution_success/slice_struct_field/Prover.toml b/noir/test_programs/execution_success/slice_struct_field/Prover.toml deleted file mode 100644 index 7127baac5bf..00000000000 --- a/noir/test_programs/execution_success/slice_struct_field/Prover.toml +++ /dev/null @@ -1 +0,0 @@ -y = "3" diff --git a/noir/test_programs/execution_success/slice_struct_field/src/main.nr b/noir/test_programs/execution_success/slice_struct_field/src/main.nr deleted file mode 100644 index a5b971ada4b..00000000000 --- a/noir/test_programs/execution_success/slice_struct_field/src/main.nr +++ /dev/null @@ -1,472 +0,0 @@ -struct FooParent { - parent_arr: [Field; 3], - foos: [Foo], -} - -struct Bar { - inner: [Field; 3], -} - -struct Foo { - a: Field, - b: [Field], - bar: Bar, -} - -fn main(y: pub Field) { - let mut b_one = [2, 3, 20]; - b_one = b_one.push_back(20); - let foo_one = Foo { a: 1, b: b_one, bar: Bar { inner: [100, 101, 102] } }; - - let mut b_two = [5, 6, 21]; - b_two = b_two.push_back(21); - let foo_two = Foo { a: 4, b: b_two, bar: Bar { inner: [103, 104, 105] } }; - - let foo_three = Foo { a: 7, b: [8, 9, 22], bar: Bar { inner: [106, 107, 108] } }; - let mut foo_four = Foo { a: 10, b: [11, 12, 23], bar: Bar { inner: [109, 110, 111] } }; - - let mut x = [foo_one, foo_two]; - x = x.push_back(foo_three); - x = x.push_back(foo_four); - - assert(x[y - 3].a == 1); - let struct_slice = x[y - 3].b; - for i in 0..4 { - assert(struct_slice[i] == b_one[i]); - } - - assert(x[y - 2].a == 4); - let struct_slice = x[y - 2].b; - for i in 0..4 { - assert(struct_slice[i] == b_two[i]); - } - - assert(x[y - 1].a == 7); - let struct_slice = x[y - 1].b; - assert(struct_slice[0] == 8); - assert(struct_slice[1] == 9); - assert(struct_slice[2] == 22); - - assert(x[y].a == 10); - let struct_slice = x[y].b; - assert(struct_slice[0] == 11); - assert(struct_slice[1] == 12); - assert(struct_slice[2] == 23); - assert(x[y].bar.inner == [109, 110, 111]); - - assert(x[y - 3].bar.inner == [100, 101, 102]); - assert(x[y - 2].bar.inner == [103, 104, 105]); - assert(x[y - 1].bar.inner == [106, 107, 108]); - assert(x[y].bar.inner == [109, 110, 111]); - // Check that switching the lhs and rhs is still valid - assert([109, 110, 111] == x[y].bar.inner); - - assert(x[y - 3].bar.inner == [100, 101, 102]); - assert(x[y - 2].bar.inner == [103, 104, 105]); - assert(x[y - 1].bar.inner == [106, 107, 108]); - assert(x[y].bar.inner == [109, 110, 111]); - // Check that switching the lhs and rhs is still valid - assert([109, 110, 111] == x[y].bar.inner); - - // TODO: Enable merging nested slices - // if y != 2 { - // x[y].a = 50; - // } else { - // x[y].a = 100; - // } - // assert(x[3].a == 50); - // if y == 2 { - // x[y - 1].b = [50, 51, 52]; - // } else { - // x[y - 1].b = [100, 101, 102]; - // } - // assert(x[2].b[0] == 100); - // assert(x[2].b[1] == 101); - // assert(x[2].b[2] == 102); - - let q = x.push_back(foo_four); - let foo_parent_one = FooParent { parent_arr: [0, 1, 2], foos: x }; - let foo_parent_two = FooParent { parent_arr: [3, 4, 5], foos: q }; - let mut foo_parents = [foo_parent_one]; - foo_parents = foo_parents.push_back(foo_parent_two); - // TODO: make a separate test for entirely compile time - // foo_parents[1].foos.push_back(foo_four); - // TODO: Merging nested slices is broken - // if y == 3 { - // foo_parents[y - 2].foos[y - 1].b[y - 1] = 5000; - // } else { - // foo_parents[y - 2].foos[y - 1].b[y - 1] = 1000; - // } - - assert(foo_parents[y - 2].foos[y - 2].b[y - 1] == 21); - foo_parents[y - 2].foos[y - 2].b[y - 1] = 5000; - assert(foo_parents[y - 2].foos[y - 2].b[y - 1] == 5000); - - let b_array = foo_parents[y - 2].foos[y - 3].b; - assert(foo_parents[y - 2].foos[y - 3].a == 1); - assert(b_array[0] == 2); - assert(b_array[1] == 3); - assert(b_array[2] == 20); - assert(b_array[3] == 20); - - let b_array = foo_parents[y - 2].foos[y - 2].b; - assert(foo_parents[y - 2].foos[y - 2].a == 4); - assert(b_array[0] == 5); - assert(b_array[1] == 6); - assert(b_array[2] == 5000); - assert(b_array[3] == 21); - - assert(foo_parents[y - 2].foos[y - 1].a == 7); - foo_parents[y - 2].foos[y - 1].a = 50; - assert(foo_parents[y - 2].foos[y - 1].a == 50); - - let b_array = foo_parents[y - 2].foos[y - 1].b; - assert(b_array[0] == 8); - assert(b_array[1] == 9); - assert(b_array[2] == 22); - assert(b_array.len() == 3); - - // // Test setting a nested array with non-dynamic - let x = [5, 6, 5000, 21, 100, 101].as_slice(); - foo_parents[y - 2].foos[y - 1].b = x; - - assert(foo_parents[y - 2].foos[y - 1].b.len() == 6); - assert(foo_parents[y - 2].foos[y - 1].b[4] == 100); - assert(foo_parents[y - 2].foos[y - 1].b[5] == 101); - - // Need to account for that foo_parents is not modified outside of this function - test_basic_intrinsics_nested_slices(foo_parents, y); - test_complex_intrinsic_nested_slices(foo_parents, y); - - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.push_back(500); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 7); - assert(foo_parents[y - 2].foos[y - 1].b[6] == 500); - - let (popped_slice, last_elem) = foo_parents[y - 2].foos[y - 1].b.pop_back(); - foo_parents[y - 2].foos[y - 1].b = popped_slice; - assert(foo_parents[y - 2].foos[y - 1].b.len() == 6); - assert(last_elem == 500); - - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.push_front(11); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 7); - assert(foo_parents[y - 2].foos[y - 1].b[0] == 11); - - assert(foo_parents[y - 2].foos.len() == 5); - foo_four.a = 40; - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 6); - assert(foo_parents[y - 2].foos[y + 2].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 7); - assert(foo_parents[y - 2].foos[6].a == 40); - assert(foo_parents[y - 2].foos[5].bar.inner == [109, 110, 111]); - assert(foo_parents[y - 2].foos[6].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 8); - assert(foo_parents[y - 2].foos[6].a == 40); - assert(foo_parents[y - 2].foos[5].bar.inner == [109, 110, 111]); - assert(foo_parents[y - 2].foos[6].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 9); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 10); - - let b_array = foo_parents[y - 2].foos[y - 1].b; - assert(b_array[0] == 11); - assert(b_array[1] == 5); - assert(b_array[2] == 6); - assert(b_array[3] == 5000); - - let b_array = foo_parents[y - 2].foos[y].b; - assert(foo_parents[y - 2].foos[y].a == 10); - assert(b_array[0] == 11); - assert(b_array[1] == 12); - assert(b_array[2] == 23); - - assert(foo_parents[y - 2].foos[y - 3].bar.inner == [100, 101, 102]); - assert(foo_parents[y - 2].foos[y - 2].bar.inner == [103, 104, 105]); - assert(foo_parents[y - 2].foos[y - 1].bar.inner == [106, 107, 108]); - assert(foo_parents[y - 2].foos[y].bar.inner == [109, 110, 111]); -} - -fn test_basic_intrinsics_nested_slices(mut foo_parents: [FooParent], y: Field) { - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.push_back(500); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 7); - assert(foo_parents[y - 2].foos[y - 1].b[6] == 500); - - let (popped_slice, last_elem) = foo_parents[y - 2].foos[y - 1].b.pop_back(); - foo_parents[y - 2].foos[y - 1].b = popped_slice; - assert(foo_parents[y - 2].foos[y - 1].b.len() == 6); - assert(last_elem == 500); - - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.push_front(11); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 7); - assert(foo_parents[y - 2].foos[y - 1].b[0] == 11); - - let (first_elem, rest_of_slice) = foo_parents[y - 2].foos[y - 1].b.pop_front(); - foo_parents[y - 2].foos[y - 1].b = rest_of_slice; - assert(foo_parents[y - 2].foos[y - 1].b.len() == 6); - assert(first_elem == 11); - - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.insert(2, 20); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 7); - assert(foo_parents[y - 2].foos[y - 1].b[y - 1] == 20); - assert(foo_parents[y - 2].foos[y - 1].b[y] == 5000); - assert(foo_parents[y - 2].foos[y - 1].b[6] == 101); - - let (rest_of_slice, removed_elem) = foo_parents[y - 2].foos[y - 1].b.remove(3); - foo_parents[y - 2].foos[y - 1].b = rest_of_slice; - assert(removed_elem == 5000); - assert(foo_parents[y - 2].foos[y - 1].b.len() == 6); - assert(foo_parents[y - 2].foos[y - 1].b[2] == 20); - assert(foo_parents[y - 2].foos[y - 1].b[3] == 21); -} - -// This method test intrinsics on nested slices with complex inputs such as -// pushing a `Foo` struct onto a slice in `FooParents`. -fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) { - let mut foo = Foo { a: 13, b: [14, 15, 16], bar: Bar { inner: [109, 110, 111] } }; - assert(foo_parents[y - 2].foos.len() == 5); - foo.a = 40; - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); - assert(foo_parents[1].foos.len() == 6); - assert(foo_parents[1].foos[5].a == 40); - assert(foo_parents[1].foos[5].b[0] == 14); - assert(foo_parents[1].foos[5].b[2] == 16); - assert(foo_parents[1].foos[5].b.len() == 3); - assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos[y - 1].b = foo_parents[y - 2].foos[y - 1].b.push_back(500); - assert(foo_parents[1].foos[2].b.len() == 7); - assert(foo_parents[1].foos[2].b[6] == 500); - assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - assert(foo_parents[1].foos[5].a == 40); - assert(foo_parents[1].foos[5].b[0] == 14); - assert(foo_parents[1].foos[5].b[2] == 16); - assert(foo_parents[1].foos[5].b.len() == 3); - assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); - - let (popped_slice, last_foo) = foo_parents[y - 2].foos.pop_back(); - foo_parents[y - 2].foos = popped_slice; - assert(foo_parents[y - 2].foos.len() == 5); - assert(last_foo.a == 40); - assert(last_foo.b[0] == 14); - assert(last_foo.b[1] == 15); - assert(last_foo.b[2] == 16); - assert(last_foo.bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_front(foo); - assert(foo_parents[1].foos.len() == 6); - assert(foo_parents[1].foos[0].a == 40); - assert(foo_parents[1].foos[0].b[0] == 14); - assert(foo_parents[1].foos[0].b[1] == 15); - assert(foo_parents[1].foos[0].b[2] == 16); - assert(foo_parents[1].foos[5].a == 10); - assert(foo_parents[1].foos[5].b.len() == 3); - assert(foo_parents[1].foos[5].b[0] == 11); - assert(foo_parents[1].foos[5].b[2] == 23); - assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[1].a == 1); - assert(foo_parents[1].foos[1].bar.inner == [100, 101, 102]); - - let (first_foo, rest_of_slice) = foo_parents[y - 2].foos.pop_front(); - - foo_parents[y - 2].foos = rest_of_slice; - assert(first_foo.a == 40); - assert(first_foo.b[0] == 14); - assert(first_foo.b[1] == 15); - assert(first_foo.b[2] == 16); - assert(first_foo.bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[0].a == 1); - assert(foo_parents[1].foos[0].b[0] == 2); - assert(foo_parents[1].foos[0].b[1] == 3); - assert(foo_parents[1].foos[0].b[2] == 20); - assert(foo_parents[1].foos[0].b[3] == 20); - assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); - - test_insert_remove_const_index(foo_parents, y, foo); - - // Check values before insertion - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos.len() == 5); - assert(foo_parents[1].foos[2].a == 50); - assert(foo_parents[1].foos[2].b[0] == 5); - assert(foo_parents[1].foos[2].b[2] == 5000); - assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[3].a == 10); - assert(foo_parents[1].foos[3].b[0] == 11); - assert(foo_parents[1].foos[3].b[2] == 23); - assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.insert(y - 1, foo); - assert(foo_parents[1].foos.len() == 6); - - // Check values correctly moved after insertion - assert(foo_parents[1].foos[0].a == 1); - assert(foo_parents[1].foos[0].b[0] == 2); - assert(foo_parents[1].foos[0].b[1] == 3); - assert(foo_parents[1].foos[0].b[2] == 20); - assert(foo_parents[1].foos[0].b[3] == 20); - assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); - - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos[2].a == 40); - assert(foo_parents[1].foos[2].b[0] == 14); - assert(foo_parents[1].foos[2].b[2] == 16); - assert(foo_parents[1].foos[2].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[3].a == 50); - assert(foo_parents[1].foos[3].b[0] == 5); - assert(foo_parents[1].foos[3].b[2] == 5000); - assert(foo_parents[1].foos[3].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[4].a == 10); - assert(foo_parents[1].foos[4].b[0] == 11); - assert(foo_parents[1].foos[4].b[2] == 23); - assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[5].a == 10); - assert(foo_parents[1].foos[5].b[0] == 11); - assert(foo_parents[1].foos[5].b[2] == 23); - assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); - - let (rest_of_slice, removed_elem) = foo_parents[y - 2].foos.remove(y - 1); - foo_parents[1].foos = rest_of_slice; - - // Check that the accurate element was removed - assert(removed_elem.a == 40); - assert(removed_elem.b[0] == 14); - assert(removed_elem.b[2] == 16); - assert(removed_elem.bar.inner == [109, 110, 111]); - - // Check that we have altered our slice accurately following a removal - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos[2].a == 50); - assert(foo_parents[1].foos[2].b[0] == 5); - assert(foo_parents[1].foos[2].b[2] == 5000); - assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[3].a == 10); - assert(foo_parents[1].foos[3].b[0] == 11); - assert(foo_parents[1].foos[3].b[2] == 23); - assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[4].b[0] == 11); - assert(foo_parents[1].foos[4].b[2] == 23); - assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); -} - -fn test_insert_remove_const_index(mut foo_parents: [FooParent], y: Field, foo: Foo) { - // Check values before insertion - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos.len() == 5); - assert(foo_parents[1].foos[2].a == 50); - assert(foo_parents[1].foos[2].b[0] == 5); - assert(foo_parents[1].foos[2].b[2] == 5000); - assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[3].a == 10); - assert(foo_parents[1].foos[3].b[0] == 11); - assert(foo_parents[1].foos[3].b[2] == 23); - assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); - - foo_parents[y - 2].foos = foo_parents[y - 2].foos.insert(2, foo); - assert(foo_parents[1].foos.len() == 6); - - // Check values correctly moved after insertion - assert(foo_parents[1].foos[0].a == 1); - assert(foo_parents[1].foos[0].b[0] == 2); - assert(foo_parents[1].foos[0].b[1] == 3); - assert(foo_parents[1].foos[0].b[2] == 20); - assert(foo_parents[1].foos[0].b[3] == 20); - assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); - - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos[2].a == 40); - assert(foo_parents[1].foos[2].b[0] == 14); - assert(foo_parents[1].foos[2].b[2] == 16); - assert(foo_parents[1].foos[2].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[3].a == 50); - assert(foo_parents[1].foos[3].b[0] == 5); - assert(foo_parents[1].foos[3].b[2] == 5000); - assert(foo_parents[1].foos[3].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[4].a == 10); - assert(foo_parents[1].foos[4].b[0] == 11); - assert(foo_parents[1].foos[4].b[2] == 23); - assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[5].a == 10); - assert(foo_parents[1].foos[5].b[0] == 11); - assert(foo_parents[1].foos[5].b[2] == 23); - assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); - - let (rest_of_slice, removed_elem) = foo_parents[y - 2].foos.remove(2); - foo_parents[1].foos = rest_of_slice; - - // Check that the accurate element was removed - assert(removed_elem.a == 40); - assert(removed_elem.b[0] == 14); - assert(removed_elem.b[2] == 16); - assert(removed_elem.bar.inner == [109, 110, 111]); - - // Check that we have altered our slice accurately following a removal - assert(foo_parents[1].foos[1].a == 4); - assert(foo_parents[1].foos[1].b[0] == 5); - assert(foo_parents[1].foos[1].b[1] == 6); - assert(foo_parents[1].foos[1].b[2] == 5000); - assert(foo_parents[1].foos[1].b[3] == 21); - assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); - - assert(foo_parents[1].foos[2].a == 50); - assert(foo_parents[1].foos[2].b[0] == 5); - assert(foo_parents[1].foos[2].b[2] == 5000); - assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - - assert(foo_parents[1].foos[3].a == 10); - assert(foo_parents[1].foos[3].b[0] == 11); - assert(foo_parents[1].foos[3].b[2] == 23); - assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); - - assert(foo_parents[1].foos[4].b[0] == 11); - assert(foo_parents[1].foos[4].b[2] == 23); - assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); -} diff --git a/noir/tooling/lsp/src/lib.rs b/noir/tooling/lsp/src/lib.rs index 97f8b6ffd85..1099ad60269 100644 --- a/noir/tooling/lsp/src/lib.rs +++ b/noir/tooling/lsp/src/lib.rs @@ -152,10 +152,10 @@ fn get_package_tests_in_crate( .map(|(func_name, test_function)| { let location = context.function_meta(&test_function.get_id()).name.location; let file_id = location.file; - + let file_path = fm.path(file_id).expect("file must exist to contain tests"); let range = byte_span_to_range(files, file_id, location.span.into()).unwrap_or_default(); - let file_uri = Url::from_file_path(fm.path(file_id)) + let file_uri = Url::from_file_path(file_path) .expect("Expected a valid file path that can be converted into a URI"); NargoTest { diff --git a/noir/tooling/lsp/src/notifications/mod.rs b/noir/tooling/lsp/src/notifications/mod.rs index aec3bf61f4e..0cd86803efa 100644 --- a/noir/tooling/lsp/src/notifications/mod.rs +++ b/noir/tooling/lsp/src/notifications/mod.rs @@ -170,7 +170,9 @@ fn process_noir_document( .filter_map(|FileDiagnostic { file_id, diagnostic, call_stack: _ }| { // Ignore diagnostics for any file that wasn't the file we saved // TODO: In the future, we could create "related" diagnostics for these files - if fm.path(file_id) != file_path { + if fm.path(file_id).expect("file must exist to have emitted diagnostic") + != file_path + { return None; } diff --git a/noir/tooling/lsp/src/requests/code_lens_request.rs b/noir/tooling/lsp/src/requests/code_lens_request.rs index 4b1d38a137e..b16c19457f0 100644 --- a/noir/tooling/lsp/src/requests/code_lens_request.rs +++ b/noir/tooling/lsp/src/requests/code_lens_request.rs @@ -102,7 +102,7 @@ pub(crate) fn collect_lenses_for_package( // Ignore diagnostics for any file that wasn't the file we saved // TODO: In the future, we could create "related" diagnostics for these files if let Some(file_path) = file_path { - if fm.path(file_id) != *file_path { + if fm.path(file_id).expect("file must exist to contain tests") != *file_path { continue; } } @@ -125,6 +125,7 @@ pub(crate) fn collect_lenses_for_package( lenses.push(test_lens); } + if package.is_binary() { if let Some(main_func_id) = context.get_main_function(&crate_id) { let location = context.function_meta(&main_func_id).name.location; @@ -133,7 +134,9 @@ pub(crate) fn collect_lenses_for_package( // Ignore diagnostics for any file that wasn't the file we saved // TODO: In the future, we could create "related" diagnostics for these files if let Some(file_path) = file_path { - if fm.path(file_id) != *file_path { + if fm.path(file_id).expect("file must exist to contain `main` function") + != *file_path + { return lenses; } } @@ -192,7 +195,7 @@ pub(crate) fn collect_lenses_for_package( // Ignore diagnostics for any file that wasn't the file we saved // TODO: In the future, we could create "related" diagnostics for these files if let Some(file_path) = file_path { - if fm.path(file_id) != *file_path { + if fm.path(file_id).expect("file must exist to contain a contract") != *file_path { continue; } } diff --git a/noir/tooling/nargo/src/artifacts/debug.rs b/noir/tooling/nargo/src/artifacts/debug.rs index 633fc7a8ded..3f5df801b66 100644 --- a/noir/tooling/nargo/src/artifacts/debug.rs +++ b/noir/tooling/nargo/src/artifacts/debug.rs @@ -34,14 +34,12 @@ impl DebugArtifact { .collect(); for file_id in files_with_debug_symbols { - let file_source = file_manager.fetch_file(file_id); + let file_path = file_manager.path(file_id).expect("file should exist"); + let file_source = file_manager.fetch_file(file_id).expect("file should exist"); file_map.insert( file_id, - DebugFile { - source: file_source.to_string(), - path: file_manager.path(file_id).to_path_buf(), - }, + DebugFile { source: file_source.to_string(), path: file_path.to_path_buf() }, ); } diff --git a/noir/tooling/nargo/src/ops/compile.rs b/noir/tooling/nargo/src/ops/compile.rs index bd395d03f67..043e2a367a5 100644 --- a/noir/tooling/nargo/src/ops/compile.rs +++ b/noir/tooling/nargo/src/ops/compile.rs @@ -74,12 +74,7 @@ pub fn compile_program( debug_artifact_path.set_file_name(format!("debug_{}.json", package.name)); let (program, warnings) = - match noirc_driver::compile_main(&mut context, crate_id, compile_options, None, true) { - Ok(program_and_warnings) => program_and_warnings, - Err(errors) => { - return Err(errors); - } - }; + noirc_driver::compile_main(&mut context, crate_id, compile_options, None)?; // Apply backend specific optimizations. let optimized_program = crate::ops::optimize_program(program, expression_width); diff --git a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs index 31105ebe68f..8bc35080fd7 100644 --- a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -176,33 +176,25 @@ fn compile_program( read_program_from_file(program_artifact_path), read_debug_artifact_from_file(debug_artifact_path), ) { - Some(CompiledProgram { - hash: program_artifact.hash, - circuit: program_artifact.bytecode, - abi: program_artifact.abi, - noir_version: program_artifact.noir_version, - debug: debug_artifact.debug_symbols.remove(0), - file_map: debug_artifact.file_map, - warnings: debug_artifact.warnings, - }) + if program_artifact.noir_version == NOIR_ARTIFACT_VERSION_STRING { + Some(CompiledProgram { + hash: program_artifact.hash, + circuit: program_artifact.bytecode, + abi: program_artifact.abi, + noir_version: program_artifact.noir_version, + debug: debug_artifact.debug_symbols.remove(0), + file_map: debug_artifact.file_map, + warnings: debug_artifact.warnings, + }) + } else { + None + } } else { None }; - let force_recompile = - cached_program.as_ref().map_or(false, |p| p.noir_version != NOIR_ARTIFACT_VERSION_STRING); - let (program, warnings) = match noirc_driver::compile_main( - &mut context, - crate_id, - compile_options, - cached_program, - force_recompile, - ) { - Ok(program_and_warnings) => program_and_warnings, - Err(errors) => { - return Err(errors); - } - }; + let (program, warnings) = + noirc_driver::compile_main(&mut context, crate_id, compile_options, cached_program)?; // Apply backend specific optimizations. let optimized_program = nargo::ops::optimize_program(program, expression_width); diff --git a/noir/tooling/nargo_cli/src/cli/fmt_cmd.rs b/noir/tooling/nargo_cli/src/cli/fmt_cmd.rs index 78678559547..0bd25a3a0ce 100644 --- a/noir/tooling/nargo_cli/src/cli/fmt_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/fmt_cmd.rs @@ -40,6 +40,7 @@ pub(crate) fn run(args: FormatCommand, config: NargoConfig) -> Result<(), CliErr for package in &workspace { visit_noir_files(&package.root_dir.join("src"), &mut |entry| { let file_id = workspace_file_manager.name_to_id(entry.path().to_path_buf()).expect("The file should exist since we added all files in the package into the file manager"); + let (parsed_module, errors) = parse_file(&workspace_file_manager, file_id); let is_all_warnings = errors.iter().all(ParserError::is_warning); @@ -61,7 +62,7 @@ pub(crate) fn run(args: FormatCommand, config: NargoConfig) -> Result<(), CliErr return Ok(()); } - let original = workspace_file_manager.fetch_file(file_id); + let original = workspace_file_manager.fetch_file(file_id).expect("The file should exist since we added all files in the package into the file manager"); let formatted = nargo_fmt::format(original, parsed_module, &config); if check_mode {