diff --git a/Cargo.lock b/Cargo.lock index 9992c601bf0..e782e7a6ecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2176,6 +2176,10 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" name = "nargo" version = "0.3.2" dependencies = [ + "acvm 0.9.0", + "iter-extended", + "noirc_abi", + "noirc_driver", "rustc_version 0.4.0", "serde", "thiserror", diff --git a/crates/arena/src/lib.rs b/crates/arena/src/lib.rs index 51c73d7f6a8..fc19f44ab6e 100644 --- a/crates/arena/src/lib.rs +++ b/crates/arena/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] // For now we use a wrapper around generational-arena pub use generational_arena::{Arena, Index}; diff --git a/crates/fm/src/lib.rs b/crates/fm/src/lib.rs index 49fac9b0178..cc87129fc0d 100644 --- a/crates/fm/src/lib.rs +++ b/crates/fm/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] mod file_map; mod file_reader; @@ -136,7 +137,7 @@ mod tests { let file_id = fm.add_file(&file_path, FileType::Normal).unwrap(); - assert!(fm.path(file_id).ends_with("foo")) + assert!(fm.path(file_id).ends_with("foo")); } #[test] fn path_resolve_sub_module() { diff --git a/crates/iter-extended/src/lib.rs b/crates/iter-extended/src/lib.rs index a022ad00b9e..aef89b58b30 100644 --- a/crates/iter-extended/src/lib.rs +++ b/crates/iter-extended/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] use std::collections::BTreeMap; diff --git a/crates/nargo/Cargo.toml b/crates/nargo/Cargo.toml index 317ae1e7063..8d3c9fbd3cd 100644 --- a/crates/nargo/Cargo.toml +++ b/crates/nargo/Cargo.toml @@ -11,6 +11,10 @@ edition.workspace = true rustc_version = "0.4.0" [dependencies] +acvm.workspace = true +noirc_abi.workspace = true +noirc_driver.workspace = true +iter-extended.workspace = true toml.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/crates/nargo/src/artifacts/contract.rs b/crates/nargo/src/artifacts/contract.rs new file mode 100644 index 00000000000..95f1ce9576d --- /dev/null +++ b/crates/nargo/src/artifacts/contract.rs @@ -0,0 +1,41 @@ +use acvm::acir::circuit::Circuit; +use noirc_abi::Abi; +use noirc_driver::ContractFunctionType; +use serde::{Deserialize, Serialize}; + +/// `PreprocessedContract` represents a Noir contract which has been preprocessed by a particular backend proving system. +/// +/// This differs from a generic Noir contract artifact in that: +/// - The ACIR bytecode has had an optimization pass applied to tailor it for the backend. +/// - Proving and verification keys have been pregenerated based on this ACIR. +#[derive(Serialize, Deserialize)] +pub struct PreprocessedContract { + /// The name of the contract. + pub name: String, + /// The identifier of the proving backend which this contract has been compiled for. + pub backend: String, + /// Each of the contract's functions are compiled into a separate program stored in this `Vec`. + pub functions: Vec, +} + +/// Each function in the contract will be compiled as a separate noir program. +/// +/// A contract function unlike a regular Noir program however can have additional properties. +/// One of these being a function type. +#[derive(Debug, Serialize, Deserialize)] +pub struct PreprocessedContractFunction { + pub name: String, + + pub function_type: ContractFunctionType, + + pub abi: Abi, + + #[serde( + serialize_with = "super::serialize_circuit", + deserialize_with = "super::deserialize_circuit" + )] + pub bytecode: Circuit, + + pub proving_key: Vec, + pub verification_key: Vec, +} diff --git a/crates/nargo/src/artifacts/mod.rs b/crates/nargo/src/artifacts/mod.rs new file mode 100644 index 00000000000..400254bfb0d --- /dev/null +++ b/crates/nargo/src/artifacts/mod.rs @@ -0,0 +1,31 @@ +//! This module defines the structure of Nargo's different compilation artifacts. +//! +//! These artifacts are intended to remain independent of any applications being built on top of Noir. +//! Should any projects require/desire a different artifact format, it's expected that they will write a transformer +//! to generate them using these artifacts as a starting point. + +use acvm::acir::circuit::Circuit; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub mod contract; +pub mod program; + +// TODO: move these down into ACVM. +fn serialize_circuit(circuit: &Circuit, s: S) -> Result +where + S: Serializer, +{ + let mut circuit_bytes: Vec = Vec::new(); + circuit.write(&mut circuit_bytes).unwrap(); + + circuit_bytes.serialize(s) +} + +fn deserialize_circuit<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let circuit_bytes = Vec::::deserialize(deserializer)?; + let circuit = Circuit::read(&*circuit_bytes).unwrap(); + Ok(circuit) +} diff --git a/crates/nargo/src/artifacts/program.rs b/crates/nargo/src/artifacts/program.rs new file mode 100644 index 00000000000..288a5dba99b --- /dev/null +++ b/crates/nargo/src/artifacts/program.rs @@ -0,0 +1,23 @@ +use acvm::acir::circuit::Circuit; +use noirc_abi::Abi; +use serde::{Deserialize, Serialize}; + +/// `PreprocessedProgram` represents a Noir program which has been preprocessed by a particular backend proving system. +/// +/// This differs from a generic Noir program artifact in that: +/// - The ACIR bytecode has had an optimization pass applied to tailor it for the backend. +/// - Proving and verification keys have been pregenerated based on this ACIR. +#[derive(Serialize, Deserialize, Debug)] +pub struct PreprocessedProgram { + pub backend: String, + pub abi: Abi, + + #[serde( + serialize_with = "super::serialize_circuit", + deserialize_with = "super::deserialize_circuit" + )] + pub bytecode: Circuit, + + pub proving_key: Vec, + pub verification_key: Vec, +} diff --git a/crates/nargo/src/errors.rs b/crates/nargo/src/errors.rs new file mode 100644 index 00000000000..59cec4552ef --- /dev/null +++ b/crates/nargo/src/errors.rs @@ -0,0 +1,13 @@ +use acvm::OpcodeResolutionError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NargoError { + /// Error while compiling Noir into ACIR. + #[error("Failed to compile circuit")] + CompilationError, + + /// ACIR circuit solving error + #[error(transparent)] + SolvingError(#[from] OpcodeResolutionError), +} diff --git a/crates/nargo/src/lib.rs b/crates/nargo/src/lib.rs index 9e98f9e3581..24605de7849 100644 --- a/crates/nargo/src/lib.rs +++ b/crates/nargo/src/lib.rs @@ -1,9 +1,15 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] //! Nargo is the package manager for Noir //! This name was used because it sounds like `cargo` and //! Noir Package Manager abbreviated is npm, which is already taken. +pub mod artifacts; +mod errors; pub mod manifest; +pub mod ops; + +pub use self::errors::NargoError; diff --git a/crates/nargo/src/ops/codegen_verifier.rs b/crates/nargo/src/ops/codegen_verifier.rs new file mode 100644 index 00000000000..ead125699b4 --- /dev/null +++ b/crates/nargo/src/ops/codegen_verifier.rs @@ -0,0 +1,10 @@ +use acvm::SmartContract; + +use crate::NargoError; + +pub fn codegen_verifier( + backend: &impl SmartContract, + verification_key: &[u8], +) -> Result { + Ok(backend.eth_contract_from_vk(verification_key)) +} diff --git a/crates/nargo/src/ops/execute.rs b/crates/nargo/src/ops/execute.rs new file mode 100644 index 00000000000..eb82df60d41 --- /dev/null +++ b/crates/nargo/src/ops/execute.rs @@ -0,0 +1,20 @@ +use acvm::PartialWitnessGenerator; +use acvm::{acir::circuit::Circuit, pwg::block::Blocks}; +use noirc_abi::WitnessMap; + +use crate::NargoError; + +pub fn execute_circuit( + backend: &impl PartialWitnessGenerator, + circuit: Circuit, + mut initial_witness: WitnessMap, +) -> Result { + let mut blocks = Blocks::default(); + let (unresolved_opcodes, oracles) = + backend.solve(&mut initial_witness, &mut blocks, circuit.opcodes)?; + if !unresolved_opcodes.is_empty() || !oracles.is_empty() { + todo!("Add oracle support to nargo execute") + } + + Ok(initial_witness) +} diff --git a/crates/nargo/src/ops/mod.rs b/crates/nargo/src/ops/mod.rs new file mode 100644 index 00000000000..5d1f096ecf4 --- /dev/null +++ b/crates/nargo/src/ops/mod.rs @@ -0,0 +1,11 @@ +pub use self::codegen_verifier::codegen_verifier; +pub use self::execute::execute_circuit; +pub use self::preprocess::{preprocess_contract, preprocess_program}; +pub use self::prove::prove_execution; +pub use self::verify::verify_proof; + +mod codegen_verifier; +mod execute; +mod preprocess; +mod prove; +mod verify; diff --git a/crates/nargo/src/ops/preprocess.rs b/crates/nargo/src/ops/preprocess.rs new file mode 100644 index 00000000000..f8d4eb5a825 --- /dev/null +++ b/crates/nargo/src/ops/preprocess.rs @@ -0,0 +1,60 @@ +use acvm::ProofSystemCompiler; +use iter_extended::vecmap; +use noirc_driver::{CompiledContract, CompiledProgram}; + +use crate::{ + artifacts::{ + contract::{PreprocessedContract, PreprocessedContractFunction}, + program::PreprocessedProgram, + }, + NargoError, +}; + +// TODO: pull this from backend. +const BACKEND_IDENTIFIER: &str = "acvm-backend-barretenberg"; + +pub fn preprocess_program( + backend: &impl ProofSystemCompiler, + compiled_program: CompiledProgram, +) -> Result { + // TODO: currently `compiled_program`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = compiled_program.circuit; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + Ok(PreprocessedProgram { + backend: String::from(BACKEND_IDENTIFIER), + abi: compiled_program.abi, + bytecode: optimized_bytecode, + proving_key, + verification_key, + }) +} + +pub fn preprocess_contract( + backend: &impl ProofSystemCompiler, + compiled_contract: CompiledContract, +) -> Result { + let preprocessed_contract_functions = vecmap(compiled_contract.functions, |func| { + // TODO: currently `func`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = func.bytecode; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedContractFunction { + name: func.name, + function_type: func.function_type, + abi: func.abi, + + bytecode: optimized_bytecode, + proving_key, + verification_key, + } + }); + + Ok(PreprocessedContract { + name: compiled_contract.name, + backend: String::from(BACKEND_IDENTIFIER), + functions: preprocessed_contract_functions, + }) +} diff --git a/crates/nargo/src/ops/prove.rs b/crates/nargo/src/ops/prove.rs new file mode 100644 index 00000000000..376220a8a74 --- /dev/null +++ b/crates/nargo/src/ops/prove.rs @@ -0,0 +1,16 @@ +use acvm::acir::circuit::Circuit; +use acvm::ProofSystemCompiler; +use noirc_abi::WitnessMap; + +use crate::NargoError; + +pub fn prove_execution( + backend: &impl ProofSystemCompiler, + circuit: &Circuit, + solved_witness: WitnessMap, + proving_key: &[u8], +) -> Result, NargoError> { + let proof = backend.prove_with_pk(circuit, solved_witness, proving_key); + + Ok(proof) +} diff --git a/crates/nargo/src/ops/verify.rs b/crates/nargo/src/ops/verify.rs new file mode 100644 index 00000000000..5109d2291db --- /dev/null +++ b/crates/nargo/src/ops/verify.rs @@ -0,0 +1,17 @@ +use acvm::acir::circuit::Circuit; +use acvm::ProofSystemCompiler; +use noirc_abi::WitnessMap; + +use crate::NargoError; + +pub fn verify_proof( + backend: &impl ProofSystemCompiler, + circuit: &Circuit, + proof: &[u8], + public_inputs: WitnessMap, + verification_key: &[u8], +) -> Result { + let valid_proof = backend.verify_with_vk(proof, public_inputs, circuit, verification_key); + + Ok(valid_proof) +} diff --git a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs index 6b10cf55b0b..6b4d5ee680f 100644 --- a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -8,6 +8,7 @@ use crate::{ }; use acvm::{ProofSystemCompiler, SmartContract}; use clap::Args; +use nargo::ops::{codegen_verifier, preprocess_program}; use noirc_driver::CompileOptions; /// Generates a Solidity verifier smart contract for the program @@ -23,30 +24,21 @@ pub(crate) struct CodegenVerifierCommand { pub(crate) fn run(args: CodegenVerifierCommand, config: NargoConfig) -> Result<(), CliError> { let backend = crate::backends::ConcreteBackend; - // Based on code in verify_cmd.rs // TODO(blaine): Should this be a utility function? let circuit_build_path = args .circuit_name .map(|circuit_name| config.program_dir.join(TARGET_DIR).join(circuit_name)); - let verification_key = match circuit_build_path { - Some(circuit_build_path) => { - let compiled_program = read_program_from_file(&circuit_build_path)?; - - let (_, verification_key) = - fetch_pk_and_vk(&compiled_program.circuit, circuit_build_path, false, true)?; - verification_key - } + let preprocessed_program = match circuit_build_path { + Some(circuit_build_path) => read_program_from_file(circuit_build_path)?, None => { let compiled_program = - compile_circuit(config.program_dir.as_ref(), &args.compile_options)?; - - let (_, verification_key) = backend.preprocess(&compiled_program.circuit); - verification_key + compile_circuit(&backend, program_dir.as_ref(), &compile_options)?; + preprocess_program(&backend, compiled_program)? } }; - let smart_contract_string = backend.eth_contract_from_vk(&verification_key); + let smart_contract_string = codegen_verifier(&backend, &preprocessed_program.verification_key)?; let contract_dir = config.program_dir.join(CONTRACT_DIR); create_named_dir(&contract_dir, "contract"); diff --git a/crates/nargo_cli/src/cli/compile_cmd.rs b/crates/nargo_cli/src/cli/compile_cmd.rs index d259006c909..0c68de9d58e 100644 --- a/crates/nargo_cli/src/cli/compile_cmd.rs +++ b/crates/nargo_cli/src/cli/compile_cmd.rs @@ -1,14 +1,16 @@ use acvm::ProofSystemCompiler; -use noirc_driver::{CompileOptions, CompiledContract, CompiledProgram, Driver}; +use iter_extended::try_vecmap; +use noirc_driver::{CompileOptions, CompiledProgram, Driver}; use std::path::Path; use clap::Args; +use nargo::ops::{preprocess_contract, preprocess_program}; + use crate::resolver::DependencyResolutionError; use crate::{constants::TARGET_DIR, errors::CliError, resolver::Resolver}; use super::fs::program::{save_contract_to_file, save_program_to_file}; -use super::preprocess_cmd::{save_preprocess_data, PreprocessedData}; use super::NargoConfig; /// Compile the program and its secret execution trace into ACIR format @@ -28,97 +30,39 @@ pub(crate) struct CompileCommand { pub(crate) fn run(args: CompileCommand, config: NargoConfig) -> Result<(), CliError> { let circuit_dir = config.program_dir.join(TARGET_DIR); + let backend = crate::backends::ConcreteBackend; + // If contracts is set we're compiling every function in a 'contract' rather than just 'main'. if args.contracts { - let mut driver = setup_driver(&config.program_dir)?; - let mut compiled_contracts = driver + let mut driver = setup_driver(&backend, &config.program_dir)?; + let compiled_contracts = driver .compile_contracts(&args.compile_options) .map_err(|_| CliError::CompilationError)?; - save_and_preprocess_contract(&mut compiled_contracts, &args.circuit_name, &circuit_dir) + let preprocessed_contracts = + try_vecmap(compiled_contracts, |contract| preprocess_contract(&backend, contract))?; + for contract in preprocessed_contracts { + save_contract_to_file(&contract, &args.circuit_name, &circuit_dir); + } } else { - let program = compile_circuit(&config.program_dir, &args.compile_options)?; - save_and_preprocess_program(&program, &args.circuit_name, &circuit_dir) + let program = compile_circuit(&backend, &config.program_dir, &args.compile_options)?; + let preprocessed_program = preprocess_program(&backend, program)?; + save_program_to_file(&preprocessed_program, &args.circuit_name, circuit_dir); } -} - -fn setup_driver(program_dir: &Path) -> Result { - let backend = crate::backends::ConcreteBackend; - Resolver::resolve_root_manifest(program_dir, backend.np_language()) -} - -/// Save a program to disk along with proving and verification keys. -fn save_and_preprocess_program( - compiled_program: &CompiledProgram, - circuit_name: &str, - circuit_dir: &Path, -) -> Result<(), CliError> { - save_program_to_file(compiled_program, circuit_name, circuit_dir); - - let preprocessed_data = PreprocessedData::from(&compiled_program.circuit); - save_preprocess_data(&preprocessed_data, circuit_name, circuit_dir)?; Ok(()) } -/// Save a contract to disk along with proving and verification keys. -/// - The contract ABI is saved as one file, which contains all of the -/// functions defined in the contract. -/// - The proving and verification keys are namespaced since the file -/// could contain multiple contracts with the same name. The verification key is saved inside -/// of the ABI. -fn save_and_preprocess_contract( - compiled_contracts: &mut [CompiledContract], - circuit_name: &str, - circuit_dir: &Path, -) -> Result<(), CliError> { - for compiled_contract in compiled_contracts { - // Preprocess all contract data - // We are patching the verification key in our contract functions - // so when we save it to disk, the ABI will have the verification key. - let mut contract_preprocess_data = Vec::new(); - for contract_function in &mut compiled_contract.functions { - let preprocessed_data = PreprocessedData::from(&contract_function.bytecode); - contract_function.verification_key = Some(preprocessed_data.verification_key.clone()); - contract_preprocess_data.push(preprocessed_data); - } - - // Unique identifier for a contract. - let contract_id = format!("{}-{}", circuit_name, &compiled_contract.name); - - // Save contract ABI to file using the contract ID. - // This includes the verification keys for each contract function. - save_contract_to_file(compiled_contract, &contract_id, circuit_dir); - - // Save preprocessed data to disk - // - // TODO: This also includes the verification key, for now we save it in twice - // TODO, once in ABI and once to disk as we did before. - // TODO: A possible fix is to use optional fields in PreprocessedData - // TODO struct. Then make VK None before saving so it is not saved to disk - for (contract_function, preprocessed_data) in - compiled_contract.functions.iter().zip(contract_preprocess_data) - { - // Create a name which uniquely identifies this contract function - // over multiple contracts. - let uniquely_identifying_program_name = - format!("{}-{}", contract_id, contract_function.name); - // Each program in a contract is preprocessed - // Note: This can potentially be quite a long running process - - save_preprocess_data( - &preprocessed_data, - &uniquely_identifying_program_name, - circuit_dir, - )?; - } - } - - Ok(()) +fn setup_driver( + backend: &impl ProofSystemCompiler, + program_dir: &Path, +) -> Result { + Resolver::resolve_root_manifest(program_dir, backend.np_language()) } pub(crate) fn compile_circuit( + backend: &impl ProofSystemCompiler, program_dir: &Path, compile_options: &CompileOptions, ) -> Result { - let mut driver = setup_driver(program_dir)?; + let mut driver = setup_driver(backend, program_dir)?; driver.compile_main(compile_options).map_err(|_| CliError::CompilationError) } diff --git a/crates/nargo_cli/src/cli/execute_cmd.rs b/crates/nargo_cli/src/cli/execute_cmd.rs index 4a3d89585bf..9d1429bbda7 100644 --- a/crates/nargo_cli/src/cli/execute_cmd.rs +++ b/crates/nargo_cli/src/cli/execute_cmd.rs @@ -1,6 +1,5 @@ use std::path::Path; -use acvm::pwg::block::Blocks; use acvm::PartialWitnessGenerator; use clap::Args; use noirc_abi::input_parser::{Format, InputValue}; @@ -47,13 +46,15 @@ fn execute_with_path( program_dir: &Path, compile_options: &CompileOptions, ) -> Result<(Option, WitnessMap), CliError> { - let compiled_program = compile_circuit(program_dir, compile_options)?; + let backend = crate::backends::ConcreteBackend; + + let compiled_program = compile_circuit(&backend, program_dir, compile_options)?; // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(program_dir, PROVER_INPUT_FILE, Format::Toml, &compiled_program.abi)?; - let solved_witness = execute_program(&compiled_program, &inputs_map)?; + let solved_witness = execute_program(&backend, &compiled_program, &inputs_map)?; let public_abi = compiled_program.abi.public_abi(); let (_, return_value) = public_abi.decode(&solved_witness)?; @@ -62,21 +63,14 @@ fn execute_with_path( } pub(crate) fn execute_program( + backend: &impl PartialWitnessGenerator, compiled_program: &CompiledProgram, inputs_map: &InputMap, ) -> Result { - let mut solved_witness = compiled_program.abi.encode(inputs_map, None)?; + let initial_witness = compiled_program.abi.encode(inputs_map, None)?; - let backend = crate::backends::ConcreteBackend; - let mut blocks = Blocks::default(); - let (unresolved_opcodes, oracles) = backend.solve( - &mut solved_witness, - &mut blocks, - compiled_program.circuit.opcodes.clone(), - )?; - if !unresolved_opcodes.is_empty() || !oracles.is_empty() { - todo!("Add oracle support to nargo execute") - } + let solved_witness = + nargo::ops::execute_circuit(backend, compiled_program.circuit.clone(), initial_witness)?; Ok(solved_witness) } diff --git a/crates/nargo_cli/src/cli/fs/keys.rs b/crates/nargo_cli/src/cli/fs/keys.rs deleted file mode 100644 index bbb84876913..00000000000 --- a/crates/nargo_cli/src/cli/fs/keys.rs +++ /dev/null @@ -1,89 +0,0 @@ -use super::{create_named_dir, load_hex_data, program::checksum_acir, write_to_file}; -use crate::{ - constants::{ACIR_CHECKSUM, PK_EXT, VK_EXT}, - errors::CliError, -}; -use acvm::acir::circuit::Circuit; -use std::path::{Path, PathBuf}; - -pub(crate) fn save_key_to_dir>( - key: &[u8], - key_name: &str, - key_dir: P, - is_proving_key: bool, -) -> Result { - create_named_dir(key_dir.as_ref(), key_name); - - let extension = if is_proving_key { PK_EXT } else { VK_EXT }; - let key_path = key_dir.as_ref().join(key_name).with_extension(extension); - - write_to_file(hex::encode(key).as_bytes(), &key_path); - - Ok(key_path) -} - -pub(crate) fn fetch_pk_and_vk>( - circuit: &Circuit, - circuit_build_path: P, - prove_circuit: bool, - check_proof: bool, -) -> Result<(Vec, Vec), CliError> { - let acir_hash_path = circuit_build_path.as_ref().with_extension(ACIR_CHECKSUM); - - let expected_acir_checksum = load_hex_data(acir_hash_path.clone())?; - let new_acir_checksum = checksum_acir(circuit); - - if new_acir_checksum[..] != expected_acir_checksum { - return Err(CliError::MismatchedAcir(acir_hash_path)); - } - - // This flag exists to avoid an unnecessary read of the proving key during verification - // as this method is used by both `nargo prove` and `nargo verify` - let proving_key = if prove_circuit { - let proving_key_path = circuit_build_path.as_ref().with_extension(PK_EXT); - load_hex_data(proving_key_path)? - } else { - // We can return an empty Vec here as `prove_circuit` should only be false when running `nargo verify` - vec![] - }; - - let verification_key = if check_proof { - let verification_key_path = circuit_build_path.as_ref().with_extension(VK_EXT); - load_hex_data(verification_key_path)? - } else { - // We can return an empty Vec here as the verification key is used only is `check_proof` is true - vec![] - }; - - Ok((proving_key, verification_key)) -} - -#[cfg(test)] -mod tests { - use super::fetch_pk_and_vk; - use crate::cli::fs::{ - keys::save_key_to_dir, - program::{checksum_acir, save_acir_checksum_to_dir}, - }; - use acvm::acir::circuit::Circuit; - use tempdir::TempDir; - - #[test] - fn fetching_pk_and_vk_loads_expected_keys() { - let circuit = Circuit::default(); - let circuit_name = "my_circuit"; - let mut circuit_build_path = TempDir::new("temp_circuit_hash_dir").unwrap().into_path(); - - // These values are not meaningful, we just need distinct values. - let pk: Vec = vec![0]; - let vk: Vec = vec![1, 2]; - save_key_to_dir(&pk, circuit_name, &circuit_build_path, true).unwrap(); - save_key_to_dir(&vk, circuit_name, &circuit_build_path, false).unwrap(); - - save_acir_checksum_to_dir(checksum_acir(&circuit), circuit_name, &circuit_build_path); - circuit_build_path.push(circuit_name); - - let loaded_keys = fetch_pk_and_vk(&circuit, circuit_build_path, true, true).unwrap(); - assert_eq!(loaded_keys, (pk, vk)); - } -} diff --git a/crates/nargo_cli/src/cli/fs/mod.rs b/crates/nargo_cli/src/cli/fs/mod.rs index 0e7b643f2c7..d860f722fd1 100644 --- a/crates/nargo_cli/src/cli/fs/mod.rs +++ b/crates/nargo_cli/src/cli/fs/mod.rs @@ -7,7 +7,6 @@ use std::{ use crate::errors::CliError; pub(super) mod inputs; -pub(super) mod keys; pub(super) mod program; pub(super) mod proof; pub(super) mod witness; diff --git a/crates/nargo_cli/src/cli/fs/program.rs b/crates/nargo_cli/src/cli/fs/program.rs index f327c81f609..a3b5f4026bd 100644 --- a/crates/nargo_cli/src/cli/fs/program.rs +++ b/crates/nargo_cli/src/cli/fs/program.rs @@ -1,21 +1,20 @@ use std::path::{Path, PathBuf}; -use acvm::{acir::circuit::Circuit, checksum_constraint_system}; -use noirc_driver::{CompiledContract, CompiledProgram}; +use nargo::artifacts::{contract::PreprocessedContract, program::PreprocessedProgram}; -use crate::{constants::ACIR_CHECKSUM, errors::CliError}; +use crate::errors::CliError; use super::{create_named_dir, write_to_file}; pub(crate) fn save_program_to_file>( - compiled_program: &CompiledProgram, + compiled_program: &PreprocessedProgram, circuit_name: &str, circuit_dir: P, ) -> PathBuf { save_build_artifact_to_file(compiled_program, circuit_name, circuit_dir) } pub(crate) fn save_contract_to_file>( - compiled_contract: &CompiledContract, + compiled_contract: &PreprocessedContract, circuit_name: &str, circuit_dir: P, ) -> PathBuf { @@ -34,23 +33,9 @@ fn save_build_artifact_to_file, T: ?Sized + serde::Serialize>( circuit_path } -pub(crate) fn checksum_acir(circuit: &Circuit) -> [u8; 4] { - checksum_constraint_system(circuit).to_be_bytes() -} -pub(crate) fn save_acir_checksum_to_dir>( - acir_checksum: [u8; 4], - hash_name: &str, - hash_dir: P, -) -> PathBuf { - let hash_path = hash_dir.as_ref().join(hash_name).with_extension(ACIR_CHECKSUM); - write_to_file(hex::encode(acir_checksum).as_bytes(), &hash_path); - - hash_path -} - pub(crate) fn read_program_from_file>( circuit_path: P, -) -> Result { +) -> Result { let file_path = circuit_path.as_ref().with_extension("json"); let input_string = std::fs::read(&file_path).map_err(|_| CliError::PathNotValid(file_path))?; diff --git a/crates/nargo_cli/src/cli/gates_cmd.rs b/crates/nargo_cli/src/cli/gates_cmd.rs index 71edd4101fe..a5093b4d775 100644 --- a/crates/nargo_cli/src/cli/gates_cmd.rs +++ b/crates/nargo_cli/src/cli/gates_cmd.rs @@ -23,10 +23,11 @@ fn count_gates_with_path>( program_dir: P, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let compiled_program = compile_circuit(program_dir.as_ref(), compile_options)?; - let num_opcodes = compiled_program.circuit.opcodes.len(); let backend = crate::backends::ConcreteBackend; + let compiled_program = compile_circuit(&backend, program_dir.as_ref(), compile_options)?; + let num_opcodes = compiled_program.circuit.opcodes.len(); + println!( "Total ACIR opcodes generated for language {:?}: {}", backend.np_language(), diff --git a/crates/nargo_cli/src/cli/mod.rs b/crates/nargo_cli/src/cli/mod.rs index 9e21bf472cd..e713bdd47fc 100644 --- a/crates/nargo_cli/src/cli/mod.rs +++ b/crates/nargo_cli/src/cli/mod.rs @@ -16,7 +16,6 @@ mod compile_cmd; mod execute_cmd; mod gates_cmd; mod new_cmd; -mod preprocess_cmd; mod print_acir_cmd; mod prove_cmd; mod test_cmd; @@ -56,7 +55,6 @@ enum NargoCommand { Execute(execute_cmd::ExecuteCommand), Prove(prove_cmd::ProveCommand), Verify(verify_cmd::VerifyCommand), - Preprocess(preprocess_cmd::PreprocessCommand), Test(test_cmd::TestCommand), Gates(gates_cmd::GatesCommand), PrintAcir(print_acir_cmd::PrintAcirCommand), @@ -77,7 +75,6 @@ pub fn start_cli() -> eyre::Result<()> { NargoCommand::Execute(args) => execute_cmd::run(args, config), NargoCommand::Prove(args) => prove_cmd::run(args, config), NargoCommand::Verify(args) => verify_cmd::run(args, config), - NargoCommand::Preprocess(args) => preprocess_cmd::run(args, config), NargoCommand::Test(args) => test_cmd::run(args, config), NargoCommand::Gates(args) => gates_cmd::run(args, config), NargoCommand::CodegenVerifier(args) => codegen_verifier_cmd::run(args, config), diff --git a/crates/nargo_cli/src/cli/preprocess_cmd.rs b/crates/nargo_cli/src/cli/preprocess_cmd.rs deleted file mode 100644 index 05f6edd60a4..00000000000 --- a/crates/nargo_cli/src/cli/preprocess_cmd.rs +++ /dev/null @@ -1,67 +0,0 @@ -use acvm::acir::circuit::Circuit; -use acvm::ProofSystemCompiler; -use std::path::{Path, PathBuf}; - -use clap::Args; - -use crate::{constants::TARGET_DIR, errors::CliError}; - -use super::fs::{ - keys::save_key_to_dir, - program::{checksum_acir, read_program_from_file, save_acir_checksum_to_dir}, -}; -use super::NargoConfig; - -/// Generate proving and verification keys for a circuit. -#[derive(Debug, Clone, Args)] -pub(crate) struct PreprocessCommand { - /// The name of the program build artifact. - artifact_name: String, -} - -pub(crate) fn run(args: PreprocessCommand, config: NargoConfig) -> Result<(), CliError> { - let circuit_dir = config.program_dir.join(TARGET_DIR); - - let program = read_program_from_file(circuit_dir.join(&args.artifact_name))?; - let preprocess_data = PreprocessedData::from(&program.circuit); - save_preprocess_data(&preprocess_data, &args.artifact_name, circuit_dir)?; - - Ok(()) -} -/// The result of preprocessing the ACIR bytecode. -/// The proving, verification key and circuit are backend specific. -/// -/// The circuit is backend specific because at the end of compilation -/// an optimization pass is applied which will transform the bytecode into -/// a format that the backend will accept; removing unsupported gates -/// is one example of this. -pub(crate) struct PreprocessedData { - pub(crate) proving_key: Vec, - pub(crate) verification_key: Vec, - pub(crate) program_checksum: [u8; 4], -} - -impl From<&Circuit> for PreprocessedData { - fn from(circuit: &Circuit) -> Self { - let backend = crate::backends::ConcreteBackend; - let (proving_key, verification_key) = backend.preprocess(circuit); - let program_checksum = checksum_acir(circuit); - - PreprocessedData { proving_key, verification_key, program_checksum } - } -} - -pub(crate) fn save_preprocess_data>( - data: &PreprocessedData, - key_name: &str, - preprocess_dir: P, -) -> Result<(PathBuf, PathBuf), CliError> { - // Save a checksum of the circuit to compare against during proving and verification. - // If hash doesn't match then the circuit has been updated and keys are stale. - save_acir_checksum_to_dir(data.program_checksum, key_name, &preprocess_dir); - - let pk_path = save_key_to_dir(&data.proving_key, key_name, &preprocess_dir, true)?; - let vk_path = save_key_to_dir(&data.verification_key, key_name, preprocess_dir, false)?; - - Ok((pk_path, vk_path)) -} diff --git a/crates/nargo_cli/src/cli/print_acir_cmd.rs b/crates/nargo_cli/src/cli/print_acir_cmd.rs index dbc0fea86de..589cc490f40 100644 --- a/crates/nargo_cli/src/cli/print_acir_cmd.rs +++ b/crates/nargo_cli/src/cli/print_acir_cmd.rs @@ -22,7 +22,9 @@ fn print_acir_with_path>( program_dir: P, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let compiled_program = compile_circuit(program_dir.as_ref(), compile_options)?; + let backend = crate::backends::ConcreteBackend; + + let compiled_program = compile_circuit(&backend, program_dir.as_ref(), compile_options)?; println!("{}", compiled_program.circuit); Ok(()) diff --git a/crates/nargo_cli/src/cli/prove_cmd.rs b/crates/nargo_cli/src/cli/prove_cmd.rs index 5f0f9d831cf..fd60f004e2b 100644 --- a/crates/nargo_cli/src/cli/prove_cmd.rs +++ b/crates/nargo_cli/src/cli/prove_cmd.rs @@ -1,17 +1,20 @@ use std::path::{Path, PathBuf}; -use acvm::ProofSystemCompiler; use clap::Args; +use nargo::artifacts::program::PreprocessedProgram; +use nargo::ops::{preprocess_program, prove_execution}; use noirc_abi::input_parser::Format; -use noirc_driver::CompileOptions; +use noirc_driver::{CompileOptions, CompiledProgram}; -use super::fs::{ - inputs::{read_inputs_from_file, write_inputs_to_file}, - keys::fetch_pk_and_vk, - program::read_program_from_file, - proof::save_proof_to_dir, -}; use super::NargoConfig; +use super::{ + compile_cmd::compile_circuit, + fs::{ + inputs::{read_inputs_from_file, write_inputs_to_file}, + program::read_program_from_file, + proof::save_proof_to_dir, + }, +}; use crate::{ cli::{execute_cmd::execute_program, verify_cmd::verify_proof}, constants::{PROOFS_DIR, PROVER_INPUT_FILE, TARGET_DIR, VERIFIER_INPUT_FILE}, @@ -62,24 +65,21 @@ pub(crate) fn prove_with_path>( check_proof: bool, compile_options: &CompileOptions, ) -> Result, CliError> { - let (compiled_program, proving_key, verification_key) = match circuit_build_path { - Some(circuit_build_path) => { - let compiled_program = read_program_from_file(&circuit_build_path)?; + let backend = crate::backends::ConcreteBackend; - let (proving_key, verification_key) = - fetch_pk_and_vk(&compiled_program.circuit, circuit_build_path, true, true)?; - (compiled_program, proving_key, verification_key) - } + let preprocessed_program = match circuit_build_path { + Some(circuit_build_path) => read_program_from_file(circuit_build_path)?, None => { let compiled_program = - super::compile_cmd::compile_circuit(program_dir.as_ref(), compile_options)?; - - let backend = crate::backends::ConcreteBackend; - let (proving_key, verification_key) = backend.preprocess(&compiled_program.circuit); - (compiled_program, proving_key, verification_key) + compile_circuit(&backend, program_dir.as_ref(), compile_options)?; + preprocess_program(&backend, compiled_program)? } }; + let PreprocessedProgram { abi, bytecode, proving_key, verification_key, .. } = + preprocessed_program; + let compiled_program = CompiledProgram { abi, circuit: bytecode }; + // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file( &program_dir, @@ -88,7 +88,7 @@ pub(crate) fn prove_with_path>( &compiled_program.abi, )?; - let solved_witness = execute_program(&compiled_program, &inputs_map)?; + let solved_witness = execute_program(&backend, &compiled_program, &inputs_map)?; // Write public inputs into Verifier.toml let public_abi = compiled_program.abi.clone().public_abi(); @@ -102,12 +102,12 @@ pub(crate) fn prove_with_path>( Format::Toml, )?; - let backend = crate::backends::ConcreteBackend; - let proof = backend.prove_with_pk(&compiled_program.circuit, solved_witness, &proving_key); + let proof = prove_execution(&backend, &compiled_program.circuit, solved_witness, &proving_key)?; if check_proof { let no_proof_name = "".into(); verify_proof( + &backend, &compiled_program, public_inputs, return_value, diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index 665591f188d..d168e6c39ca 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -1,7 +1,8 @@ use std::{collections::BTreeMap, io::Write, path::Path}; -use acvm::{pwg::block::Blocks, PartialWitnessGenerator, ProofSystemCompiler}; +use acvm::ProofSystemCompiler; use clap::Args; +use nargo::ops::execute_circuit; use noirc_driver::{CompileOptions, Driver}; use noirc_frontend::node_interner::FuncId; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; @@ -79,23 +80,15 @@ fn run_test( config: &CompileOptions, ) -> Result<(), CliError> { let backend = crate::backends::ConcreteBackend; - let mut blocks = Blocks::default(); let program = driver .compile_no_check(config, main) .map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?; - let mut solved_witness = BTreeMap::new(); - // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. - match backend.solve(&mut solved_witness, &mut blocks, program.circuit.opcodes) { - Ok((unresolved_opcodes, oracles)) => { - if !unresolved_opcodes.is_empty() || !oracles.is_empty() { - todo!("Add oracle support to nargo test") - } - Ok(()) - } + match execute_circuit(&backend, program.circuit, BTreeMap::new()) { + Ok(_) => Ok(()), Err(error) => { let writer = StandardStream::stderr(ColorChoice::Always); let mut writer = writer.lock(); diff --git a/crates/nargo_cli/src/cli/verify_cmd.rs b/crates/nargo_cli/src/cli/verify_cmd.rs index 021b10c9404..cf2e4859091 100644 --- a/crates/nargo_cli/src/cli/verify_cmd.rs +++ b/crates/nargo_cli/src/cli/verify_cmd.rs @@ -1,14 +1,14 @@ -use super::fs::{ - inputs::read_inputs_from_file, keys::fetch_pk_and_vk, load_hex_data, - program::read_program_from_file, -}; -use super::{compile_cmd::compile_circuit, InputMap, NargoConfig}; +use super::compile_cmd::compile_circuit; +use super::fs::{inputs::read_inputs_from_file, load_hex_data, program::read_program_from_file}; +use super::{InputMap, NargoConfig}; use crate::{ constants::{PROOFS_DIR, PROOF_EXT, TARGET_DIR, VERIFIER_INPUT_FILE}, errors::CliError, }; use acvm::ProofSystemCompiler; use clap::Args; +use nargo::artifacts::program::PreprocessedProgram; +use nargo::ops::preprocess_program; use noirc_abi::input_parser::{Format, InputValue}; use noirc_driver::{CompileOptions, CompiledProgram}; use std::path::{Path, PathBuf}; @@ -43,29 +43,27 @@ fn verify_with_path>( circuit_build_path: Option

, compile_options: CompileOptions, ) -> Result<(), CliError> { - let (compiled_program, verification_key) = match circuit_build_path { - Some(circuit_build_path) => { - let compiled_program = read_program_from_file(&circuit_build_path)?; + let backend = crate::backends::ConcreteBackend; - let (_, verification_key) = - fetch_pk_and_vk(&compiled_program.circuit, circuit_build_path, false, true)?; - (compiled_program, verification_key) - } + let preprocessed_program = match circuit_build_path { + Some(circuit_build_path) => read_program_from_file(circuit_build_path)?, None => { - let compiled_program = compile_circuit(program_dir.as_ref(), &compile_options)?; - - let backend = crate::backends::ConcreteBackend; - let (_, verification_key) = backend.preprocess(&compiled_program.circuit); - (compiled_program, verification_key) + let compiled_program = + compile_circuit(&backend, program_dir.as_ref(), &compile_options)?; + preprocess_program(&backend, compiled_program)? } }; + let PreprocessedProgram { abi, bytecode, verification_key, .. } = preprocessed_program; + let compiled_program = CompiledProgram { abi, circuit: bytecode }; + // Load public inputs (if any) from `VERIFIER_INPUT_FILE`. let public_abi = compiled_program.abi.clone().public_abi(); let (public_inputs_map, return_value) = read_inputs_from_file(program_dir, VERIFIER_INPUT_FILE, Format::Toml, &public_abi)?; verify_proof( + &backend, &compiled_program, public_inputs_map, return_value, @@ -76,6 +74,7 @@ fn verify_with_path>( } pub(crate) fn verify_proof( + backend: &impl ProofSystemCompiler, compiled_program: &CompiledProgram, public_inputs_map: InputMap, return_value: Option, @@ -86,9 +85,13 @@ pub(crate) fn verify_proof( let public_abi = compiled_program.abi.clone().public_abi(); let public_inputs = public_abi.encode(&public_inputs_map, return_value)?; - let backend = crate::backends::ConcreteBackend; - let valid_proof = - backend.verify_with_vk(proof, public_inputs, &compiled_program.circuit, verification_key); + let valid_proof = nargo::ops::verify_proof( + backend, + &compiled_program.circuit, + proof, + public_inputs, + verification_key, + )?; if valid_proof { Ok(()) diff --git a/crates/nargo_cli/src/constants.rs b/crates/nargo_cli/src/constants.rs index ba7ba3e7675..d3e6b7f28e1 100644 --- a/crates/nargo_cli/src/constants.rs +++ b/crates/nargo_cli/src/constants.rs @@ -21,9 +21,3 @@ pub(crate) const PKG_FILE: &str = "Nargo.toml"; pub(crate) const PROOF_EXT: &str = "proof"; /// The extension for files containing proof witnesses. pub(crate) const WITNESS_EXT: &str = "tr"; -/// The extension for proving keys. -pub(crate) const PK_EXT: &str = "pk"; -/// The extension for verification keys. -pub(crate) const VK_EXT: &str = "vk"; -/// The extension for ACIR hash files. -pub(crate) const ACIR_CHECKSUM: &str = "json.checksum"; diff --git a/crates/nargo_cli/src/errors.rs b/crates/nargo_cli/src/errors.rs index 1fd86818852..f6537b550ea 100644 --- a/crates/nargo_cli/src/errors.rs +++ b/crates/nargo_cli/src/errors.rs @@ -1,5 +1,5 @@ -use acvm::OpcodeResolutionError; use hex::FromHexError; +use nargo::NargoError; use noirc_abi::errors::{AbiError, InputParserError}; use std::path::PathBuf; use thiserror::Error; @@ -20,8 +20,7 @@ pub(crate) enum CliError { " Error: cannot find {0}.toml file.\n Expected location: {1:?} \n Please generate this file at the expected location." )] MissingTomlFile(String, PathBuf), - #[error("Error: the circuit you are trying to prove differs from the build artifact at {}\nYou must call `nargo compile` to generate the correct proving and verification keys for this circuit", .0.display())] - MismatchedAcir(PathBuf), + #[error("Failed to verify proof {}", .0.display())] InvalidProof(PathBuf), @@ -40,7 +39,7 @@ pub(crate) enum CliError { #[error(transparent)] AbiError(#[from] AbiError), - /// ACIR circuit solving error + /// Error from Nargo #[error(transparent)] - SolvingError(#[from] OpcodeResolutionError), + NargoError(#[from] NargoError), } diff --git a/crates/nargo_cli/src/lib.rs b/crates/nargo_cli/src/lib.rs index cc25a09f802..da4dee6fd59 100644 --- a/crates/nargo_cli/src/lib.rs +++ b/crates/nargo_cli/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] //! Nargo is the package manager for Noir //! This name was used because it sounds like `cargo` and diff --git a/crates/nargo_cli/src/preprocess.rs b/crates/nargo_cli/src/preprocess.rs new file mode 100644 index 00000000000..249b6647e30 --- /dev/null +++ b/crates/nargo_cli/src/preprocess.rs @@ -0,0 +1,57 @@ +use acvm::ProofSystemCompiler; +use iter_extended::vecmap; +use noirc_driver::{CompiledContract, CompiledProgram}; + +// TODO: migrate to `nargo_cli` + +use crate::artifacts::{ + contract::{PreprocessedContract, PreprocessedContractFunction}, + program::PreprocessedProgram, +}; + +// TODO: pull this from backend. +const BACKEND_IDENTIFIER: &str = "acvm-backend-barretenberg"; + +pub(crate) fn preprocess_program(compiled_program: CompiledProgram) -> PreprocessedProgram { + let backend = crate::backends::ConcreteBackend; + + // TODO: currently `compiled_program`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = compiled_program.circuit; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedProgram { + backend: String::from(BACKEND_IDENTIFIER), + abi: compiled_program.abi, + bytecode: optimized_bytecode, + proving_key, + verification_key, + } +} + +pub(crate) fn preprocess_contract(compiled_contract: CompiledContract) -> PreprocessedContract { + let backend = crate::backends::ConcreteBackend; + + let preprocessed_contract_functions = vecmap(compiled_contract.functions, |func| { + // TODO: currently `func`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = func.bytecode; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedContractFunction { + name: func.name, + function_type: func.function_type, + abi: func.abi, + + bytecode: optimized_bytecode, + proving_key, + verification_key, + } + }); + + PreprocessedContract { + name: compiled_contract.name, + backend: String::from(BACKEND_IDENTIFIER), + functions: preprocessed_contract_functions, + } +} diff --git a/crates/noirc_abi/src/lib.rs b/crates/noirc_abi/src/lib.rs index 1a5293b160c..dbd935dcde0 100644 --- a/crates/noirc_abi/src/lib.rs +++ b/crates/noirc_abi/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] use std::{collections::BTreeMap, str}; @@ -283,11 +284,11 @@ impl Abi { InputValue::String(string) => { let str_as_fields = string.bytes().map(|byte| FieldElement::from_be_bytes_reduce(&[byte])); - encoded_value.extend(str_as_fields) + encoded_value.extend(str_as_fields); } InputValue::Struct(object) => { for value in object.into_values() { - encoded_value.extend(Self::encode_value(value)?) + encoded_value.extend(Self::encode_value(value)?); } } } @@ -442,6 +443,6 @@ mod test { } // We also decode the return value (we can do this immediately as we know it shares a witness with an input). - assert_eq!(return_value.unwrap(), reconstructed_inputs["thing2"]) + assert_eq!(return_value.unwrap(), reconstructed_inputs["thing2"]); } } diff --git a/crates/noirc_driver/src/contract.rs b/crates/noirc_driver/src/contract.rs index ed9bd8d4dcd..a5600c3d215 100644 --- a/crates/noirc_driver/src/contract.rs +++ b/crates/noirc_driver/src/contract.rs @@ -6,8 +6,7 @@ use serde::{Deserialize, Serialize}; /// Unlike the similar enum in noirc_frontend, 'open' and 'unconstrained' /// are mutually exclusive here. In the case a function is both, 'unconstrained' /// takes precedence. -#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum ContractFunctionType { /// This function will be executed in a private /// context. @@ -20,8 +19,6 @@ pub enum ContractFunctionType { Unconstrained, } -#[derive(serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] pub struct CompiledContract { /// The name of the contract. pub name: String, @@ -36,27 +33,19 @@ pub struct CompiledContract { /// A contract function unlike a regular Noir program /// however can have additional properties. /// One of these being a function type. -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct ContractFunction { pub name: String, pub function_type: ContractFunctionType, - #[serde(flatten)] pub abi: Abi, - #[serde( - serialize_with = "crate::program::serialize_circuit", - deserialize_with = "crate::program::deserialize_circuit" - )] pub bytecode: Circuit, - - pub verification_key: Option>, } impl ContractFunctionType { - pub fn new(kind: noirc_frontend::ContractFunctionType, is_unconstrained: bool) -> Self { + pub(super) fn new(kind: noirc_frontend::ContractFunctionType, is_unconstrained: bool) -> Self { match (kind, is_unconstrained) { (_, true) => Self::Unconstrained, (noirc_frontend::ContractFunctionType::Secret, false) => Self::Secret, diff --git a/crates/noirc_driver/src/lib.rs b/crates/noirc_driver/src/lib.rs index 04e4a82c6f6..c6d0a08e4d8 100644 --- a/crates/noirc_driver/src/lib.rs +++ b/crates/noirc_driver/src/lib.rs @@ -1,12 +1,13 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] use acvm::Language; use clap::Args; -use contract::{ContractFunction, ContractFunctionType}; +use contract::ContractFunction; use fm::FileType; -use iter_extended::{try_vecmap, vecmap}; +use iter_extended::try_vecmap; use noirc_abi::FunctionSignature; use noirc_errors::{reporter, ReportedError}; use noirc_evaluator::create_circuit; @@ -21,7 +22,7 @@ use std::path::{Path, PathBuf}; mod contract; mod program; -pub use contract::CompiledContract; +pub use contract::{CompiledContract, ContractFunctionType}; pub use program::CompiledProgram; pub struct Driver { @@ -203,30 +204,24 @@ impl Driver { options: &CompileOptions, ) -> Result { let functions = try_vecmap(&contract.functions, |function_id| { - let function_name = self.function_name(*function_id).to_owned(); + let name = self.function_name(*function_id).to_owned(); let function = self.compile_no_check(options, *function_id)?; let func_meta = self.context.def_interner.function_meta(function_id); let func_type = func_meta .contract_function_type .expect("Expected contract function to have a contract visibility"); - let func_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); + let function_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); - Ok((function_name, func_type, function)) - })?; - - let converted_functions = - vecmap(functions, |(name, function_type, function)| ContractFunction { + Ok(ContractFunction { name, function_type, abi: function.abi, bytecode: function.circuit, - // Since we have not called the proving system yet - // we do not have a verification key - verification_key: None, - }); + }) + })?; - Ok(CompiledContract { name: contract.name, functions: converted_functions }) + Ok(CompiledContract { name: contract.name, functions }) } /// Returns the FuncId of the 'main' function. diff --git a/crates/noirc_errors/src/lib.rs b/crates/noirc_errors/src/lib.rs index 9bb1ebaef81..ab154639d13 100644 --- a/crates/noirc_errors/src/lib.rs +++ b/crates/noirc_errors/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] mod position; pub mod reporter; diff --git a/crates/noirc_evaluator/src/lib.rs b/crates/noirc_evaluator/src/lib.rs index 3880a32fd99..4c1b05381f5 100644 --- a/crates/noirc_evaluator/src/lib.rs +++ b/crates/noirc_evaluator/src/lib.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] mod errors; mod ssa; @@ -175,7 +176,7 @@ impl Evaluator { AbiType::Array { length, typ } => { let witnesses = self.generate_array_witnesses(length, typ)?; - ir_gen.abi_array(name, Some(def), typ.as_ref(), *length, witnesses.clone()); + ir_gen.abi_array(name, Some(def), typ.as_ref(), *length, &witnesses); witnesses } AbiType::Integer { sign: _, width } => { @@ -203,13 +204,13 @@ impl Evaluator { let mut struct_witnesses: BTreeMap> = BTreeMap::new(); self.generate_struct_witnesses(&mut struct_witnesses, &new_fields)?; - ir_gen.abi_struct(name, Some(def), fields, struct_witnesses.clone()); - struct_witnesses.values().flatten().cloned().collect() + ir_gen.abi_struct(name, Some(def), fields, &struct_witnesses); + struct_witnesses.values().flatten().copied().collect() } AbiType::String { length } => { let typ = AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; let witnesses = self.generate_array_witnesses(length, &typ)?; - ir_gen.abi_array(name, Some(def), &typ, *length, witnesses.clone()); + ir_gen.abi_array(name, Some(def), &typ, *length, &witnesses); witnesses } }; @@ -253,7 +254,7 @@ impl Evaluator { let new_name = format!("{name}.{inner_name}"); new_fields.insert(new_name, value.clone()); } - self.generate_struct_witnesses(struct_witnesses, &new_fields)? + self.generate_struct_witnesses(struct_witnesses, &new_fields)?; } AbiType::String { length } => { let typ = AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs b/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs index 978bfe49008..8257e0c9f9a 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs @@ -361,7 +361,7 @@ pub(crate) fn bound_constraint_with_offset( 0 => evaluator.push_opcode(AcirOpcode::Arithmetic(aof)), 1 => { let expr = boolean_expr(&aof, evaluator); - evaluator.push_opcode(AcirOpcode::Arithmetic(expr)) + evaluator.push_opcode(AcirOpcode::Arithmetic(expr)); } 2 => { let y = expression_to_witness(boolean_expr(&aof, evaluator), evaluator); diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs b/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs index a640c9db601..8e6e16776a9 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs @@ -36,7 +36,7 @@ impl InternalVar { &self.expression } pub(crate) fn set_id(&mut self, id: NodeId) { - self.id = Some(id) + self.id = Some(id); } pub(crate) fn get_id(&self) -> Option { self.id diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs index 73e46c266dc..7d6f7e2c32c 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs @@ -149,7 +149,7 @@ fn prepare_inputs( let mut inputs: Vec = Vec::new(); for argument in arguments { - inputs.extend(resolve_node_id(argument, acir_gen, cfg, evaluator)) + inputs.extend(resolve_node_id(argument, acir_gen, cfg, evaluator)); } inputs } @@ -212,7 +212,7 @@ fn resolve_array( arr_element.set_witness(witness); acir_gen.memory.insert(array.id, i, arr_element); - inputs.push(func_input) + inputs.push(func_input); } inputs @@ -329,7 +329,7 @@ fn evaluate_println( fn format_field_string(field: FieldElement) -> String { let mut trimmed_field = field.to_hex().trim_start_matches('0').to_owned(); if trimmed_field.len() % 2 != 0 { - trimmed_field = "0".to_owned() + &trimmed_field + trimmed_field = "0".to_owned() + &trimmed_field; }; "0x".to_owned() + &trimmed_field } diff --git a/crates/noirc_evaluator/src/ssa/conditional.rs b/crates/noirc_evaluator/src/ssa/conditional.rs index 36eb2b41f09..c7a9adc7a02 100644 --- a/crates/noirc_evaluator/src/ssa/conditional.rs +++ b/crates/noirc_evaluator/src/ssa/conditional.rs @@ -855,14 +855,14 @@ impl DecisionTree { && left_arrays.is_empty() && right_arrays.is_empty() => { - candidates.push(Segment::new(left_node, right_node)) + candidates.push(Segment::new(left_node, right_node)); } ( Operation::Store { array_id: left_array, index: left_index, .. }, Operation::Store { array_id: right_array, index: right_index, .. }, ) if left_array == right_array && left_index == right_index => { - candidates.push(Segment::new(left_node, right_node)) + candidates.push(Segment::new(left_node, right_node)); } _ => (), } diff --git a/crates/noirc_evaluator/src/ssa/context.rs b/crates/noirc_evaluator/src/ssa/context.rs index da528e21d05..c7d4dba9799 100644 --- a/crates/noirc_evaluator/src/ssa/context.rs +++ b/crates/noirc_evaluator/src/ssa/context.rs @@ -163,7 +163,7 @@ impl SsaContext { result = format!("{var}"); } if result.is_empty() { - result = format!("unknown {:?}", id.0.into_raw_parts().0) + result = format!("unknown {:?}", id.0.into_raw_parts().0); } result } @@ -250,7 +250,7 @@ impl SsaContext { pub(crate) fn print_instructions(&self, instructions: &[NodeId]) { for id in instructions { - self.print_node(*id) + self.print_node(*id); } } @@ -1205,6 +1205,7 @@ impl SsaContext { Type::Function(..) => ObjectType::Function, Type::Tuple(_) => todo!("Conversion to ObjectType is unimplemented for tuples"), Type::String(_) => todo!("Conversion to ObjectType is unimplemented for strings"), + Type::Vec(_) => todo!("Conversion to ObjectType is unimplemented for Vecs"), } } diff --git a/crates/noirc_evaluator/src/ssa/inline.rs b/crates/noirc_evaluator/src/ssa/inline.rs index 08aac4975ff..2cab6018f58 100644 --- a/crates/noirc_evaluator/src/ssa/inline.rs +++ b/crates/noirc_evaluator/src/ssa/inline.rs @@ -241,7 +241,7 @@ fn inline( decision, )?; if result && nested_call { - result = false + result = false; } } Ok(result) diff --git a/crates/noirc_evaluator/src/ssa/integer.rs b/crates/noirc_evaluator/src/ssa/integer.rs index 9a48286f42f..2bdbf80c9e8 100644 --- a/crates/noirc_evaluator/src/ssa/integer.rs +++ b/crates/noirc_evaluator/src/ssa/integer.rs @@ -319,7 +319,7 @@ fn block_overflow( if let Some(r_const) = ctx.get_as_constant(rhs) { let r_type = ctx[rhs].get_type(); if r_const.to_u128() > r_type.bits() as u128 { - ins.mark = Mark::ReplaceWith(ctx.zero_with_type(ins.res_type)) + ins.mark = Mark::ReplaceWith(ctx.zero_with_type(ins.res_type)); } else { let rhs = ctx .get_or_create_const(FieldElement::from(2_i128).pow(&r_const), r_type); diff --git a/crates/noirc_evaluator/src/ssa/node.rs b/crates/noirc_evaluator/src/ssa/node.rs index 215308162d4..8819a96e1c3 100644 --- a/crates/noirc_evaluator/src/ssa/node.rs +++ b/crates/noirc_evaluator/src/ssa/node.rs @@ -1225,7 +1225,7 @@ impl Operation { Cond { condition, val_true: lhs, val_false: rhs } => { *condition = f(*condition); *lhs = f(*lhs); - *rhs = f(*rhs) + *rhs = f(*rhs); } Load { index, .. } => *index = f(*index), Store { index, value, predicate, .. } => { @@ -1291,7 +1291,7 @@ impl Operation { Nop => (), Call { func, arguments, .. } => { f(*func); - arguments.iter().copied().for_each(f) + arguments.iter().copied().for_each(f); } Return(values) => values.iter().copied().for_each(f), Result { call_instruction, .. } => { diff --git a/crates/noirc_evaluator/src/ssa/optimizations.rs b/crates/noirc_evaluator/src/ssa/optimizations.rs index d92a04d1fd6..2e9370961fc 100644 --- a/crates/noirc_evaluator/src/ssa/optimizations.rs +++ b/crates/noirc_evaluator/src/ssa/optimizations.rs @@ -469,7 +469,7 @@ fn cse_block_with_anchor( let mut activate_cse = true; // We do not want to replace any print intrinsics as we want them to remain in order and unchanged if let builtin::Opcode::Println(_) = opcode { - activate_cse = false + activate_cse = false; } for arg in args { diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen.rs b/crates/noirc_evaluator/src/ssa/ssa_gen.rs index e819d83f55d..8205dc8e10c 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen.rs @@ -10,7 +10,7 @@ use crate::{ {block, builtin, node, ssa_form}, }, }; -use acvm::FieldElement; +use acvm::{acir::native_types::Witness, FieldElement}; use iter_extended::vecmap; use noirc_errors::Location; use noirc_frontend::{ @@ -98,7 +98,7 @@ impl IrGenerator { ident_def: Option, el_type: &noirc_abi::AbiType, len: u64, - witness: Vec, + witness: &[Witness], ) -> NodeId { let element_type = self.get_object_type_from_abi(el_type); let (v_id, array_idx) = self.new_array(name, element_type, len as u32, ident_def); @@ -125,30 +125,24 @@ impl IrGenerator { struct_name: &str, ident_def: Option, fields: &BTreeMap, - witnesses: BTreeMap>, + witnesses: &BTreeMap>, ) -> Value { let values = vecmap(fields, |(name, field_typ)| { let new_name = format!("{struct_name}.{name}"); match field_typ { noirc_abi::AbiType::Array { length, typ } => { - let v_id = - self.abi_array(&new_name, None, typ, *length, witnesses[&new_name].clone()); + let v_id = self.abi_array(&new_name, None, typ, *length, &witnesses[&new_name]); Value::Node(v_id) } noirc_abi::AbiType::Struct { fields, .. } => { let new_name = format!("{struct_name}.{name}"); - self.abi_struct(&new_name, None, fields, witnesses.clone()) + self.abi_struct(&new_name, None, fields, witnesses) } noirc_abi::AbiType::String { length } => { let typ = noirc_abi::AbiType::Integer { sign: noirc_abi::Sign::Unsigned, width: 8 }; - let v_id = self.abi_array( - &new_name, - None, - &typ, - *length, - witnesses[&new_name].clone(), - ); + let v_id = + self.abi_array(&new_name, None, &typ, *length, &witnesses[&new_name]); Value::Node(v_id) } _ => { diff --git a/crates/noirc_evaluator/src/ssa/value.rs b/crates/noirc_evaluator/src/ssa/value.rs index 30c07ccd567..915effe480b 100644 --- a/crates/noirc_evaluator/src/ssa/value.rs +++ b/crates/noirc_evaluator/src/ssa/value.rs @@ -96,6 +96,7 @@ impl Value { Type::Unit | Type::Function(..) | Type::Array(..) + | Type::Vec(..) | Type::String(..) | Type::Integer(..) | Type::Bool diff --git a/crates/noirc_frontend/src/ast/mod.rs b/crates/noirc_frontend/src/ast/mod.rs index 2e6592e3c87..6bd5c148d66 100644 --- a/crates/noirc_frontend/src/ast/mod.rs +++ b/crates/noirc_frontend/src/ast/mod.rs @@ -35,6 +35,14 @@ pub enum UnresolvedType { /// A Named UnresolvedType can be a struct type or a type variable Named(Path, Vec), + /// A vector of some element type. + /// It is expected the length of the generics is 1 so the inner Vec is technically unnecessary, + /// but we keep them all around to verify generic count after parsing for better error messages. + /// + /// The Span here encompasses the entire type and is used to issue an error if exactly 1 + /// generic argument is not given. + Vec(Vec, Span), + // Note: Tuples have no visibility, instead each of their elements may have one. Tuple(Vec), @@ -100,6 +108,10 @@ impl std::fmt::Display for UnresolvedType { let args = vecmap(args, ToString::to_string); write!(f, "fn({}) -> {ret}", args.join(", ")) } + Vec(args, _span) => { + let args = vecmap(args, ToString::to_string); + write!(f, "Vec<{}>", args.join(", ")) + } Unit => write!(f, "()"), Error => write!(f, "error"), Unspecified => write!(f, "unspecified"), diff --git a/crates/noirc_frontend/src/graph/mod.rs b/crates/noirc_frontend/src/graph/mod.rs index 1e054ee6699..47426606da1 100644 --- a/crates/noirc_frontend/src/graph/mod.rs +++ b/crates/noirc_frontend/src/graph/mod.rs @@ -120,9 +120,9 @@ impl CrateGraph { return; } for dep in graph[source].dependencies.iter() { - go(graph, visited, res, dep.crate_id) + go(graph, visited, res, dep.crate_id); } - res.push(source) + res.push(source); } } @@ -163,7 +163,7 @@ impl CrateGraph { } impl CrateData { fn add_dep(&mut self, name: CrateName, crate_id: CrateId) { - self.dependencies.push(Dependency { crate_id, name }) + self.dependencies.push(Dependency { crate_id, name }); } } impl std::ops::Index for CrateGraph { diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs index e932f91f75a..55f2464dc62 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -30,7 +30,7 @@ pub struct UnresolvedFunctions { impl UnresolvedFunctions { pub fn push_fn(&mut self, mod_id: LocalModuleId, func_id: FuncId, func: NoirFunction) { - self.functions.push((mod_id, func_id, func)) + self.functions.push((mod_id, func_id, func)); } } @@ -138,8 +138,7 @@ impl DefCollector { let name = resolved_import.name; for ns in resolved_import.resolved_namespace.iter_defs() { let result = current_def_map.modules[resolved_import.module_scope.0] - .scope - .add_item_to_namespace(name.clone(), ns); + .import(name.clone(), ns); if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::DuplicateImport { first_def, second_def }; @@ -224,14 +223,13 @@ fn collect_impls( extend_errors(errors, unresolved.file_id, resolver.take_errors()); if let Some(type_module) = get_local_id_from_type(&typ) { - // Grab the scope defined by the struct type. Note that impls are a case - // where the scope the methods are added to is not the same as the scope + // Grab the module defined by the struct type. Note that impls are a case + // where the module the methods are added to is not the same as the module // they are resolved in. - let scope = &mut def_maps.get_mut(&crate_id).unwrap().modules[type_module.0].scope; + let module = &mut def_maps.get_mut(&crate_id).unwrap().modules[type_module.0]; - // .define_func_def(name, func_id); for (_, method_id, method) in &unresolved.functions { - let result = scope.define_func_def(method.name_ident().clone(), *method_id); + let result = module.declare_function(method.name_ident().clone(), *method_id); if let Err((first_def, second_def)) = result { let err = @@ -245,7 +243,7 @@ fn collect_impls( } else if typ != Type::Error && crate_id == LOCAL_CRATE { let span = *span; let error = DefCollectorErrorKind::NonStructTypeInImpl { span }; - errors.push(error.into_file_diagnostic(unresolved.file_id)) + errors.push(error.into_file_diagnostic(unresolved.file_id)); } } } @@ -263,7 +261,7 @@ where Errs: IntoIterator, Err: Into, { - errors.extend(new_errors.into_iter().map(|err| err.into().in_file(file))) + errors.extend(new_errors.into_iter().map(|err| err.into().in_file(file))); } /// Separate the globals Vec into two. The first element in the tuple will be the @@ -478,6 +476,6 @@ fn type_check_functions( errors: &mut Vec, ) { for (file, func) in file_func_ids { - extend_errors(errors, file, type_check_func(interner, func)) + extend_errors(errors, file, type_check_func(interner, func)); } } diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs index 1a58decda99..989d87e0720 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -37,7 +37,7 @@ pub fn collect_defs( // First resolve the module declarations for decl in ast.module_decls { - collector.parse_module_declaration(context, &decl, crate_id, errors) + collector.parse_module_declaration(context, &decl, crate_id, errors); } collector.collect_submodules(context, crate_id, ast.submodules, file_id, errors); @@ -75,9 +75,8 @@ impl<'a> ModCollector<'a> { let stmt_id = context.def_interner.push_empty_global(); // Add the statement to the scope so its path can be looked up later - let result = self.def_collector.def_map.modules[self.module_id.0] - .scope - .define_global(name, stmt_id); + let result = + self.def_collector.def_map.modules[self.module_id.0].declare_global(name, stmt_id); if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::DuplicateGlobal { first_def, second_def }; @@ -137,8 +136,7 @@ impl<'a> ModCollector<'a> { // Add function to scope/ns of the module let result = self.def_collector.def_map.modules[self.module_id.0] - .scope - .define_func_def(name, func_id); + .declare_function(name, func_id); if let Err((first_def, second_def)) = result { let error = DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; @@ -167,9 +165,8 @@ impl<'a> ModCollector<'a> { }; // Add the struct to scope so its path can be looked up later - let result = self.def_collector.def_map.modules[self.module_id.0] - .scope - .define_struct_def(name, id); + let result = + self.def_collector.def_map.modules[self.module_id.0].declare_struct(name, id); if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; @@ -288,7 +285,7 @@ impl<'a> ModCollector<'a> { }; if let Err((first_def, second_def)) = - modules[self.module_id.0].scope.define_module_def(mod_name.to_owned(), mod_id) + modules[self.module_id.0].declare_child_module(mod_name.to_owned(), mod_id) { let err = DefCollectorErrorKind::DuplicateModuleDecl { first_def, second_def }; errors.push(err.into_file_diagnostic(self.file_id)); diff --git a/crates/noirc_frontend/src/hir/def_map/item_scope.rs b/crates/noirc_frontend/src/hir/def_map/item_scope.rs index 6a819550e72..52201f7ade3 100644 --- a/crates/noirc_frontend/src/hir/def_map/item_scope.rs +++ b/crates/noirc_frontend/src/hir/def_map/item_scope.rs @@ -1,8 +1,5 @@ use super::{namespace::PerNs, ModuleDefId, ModuleId}; -use crate::{ - node_interner::{FuncId, StmtId, StructId}, - Ident, -}; +use crate::{node_interner::FuncId, Ident}; use std::collections::{hash_map::Entry, HashMap}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -55,30 +52,6 @@ impl ItemScope { } } - pub fn define_module_def( - &mut self, - name: Ident, - mod_id: ModuleId, - ) -> Result<(), (Ident, Ident)> { - self.add_definition(name, mod_id.into()) - } - - pub fn define_func_def(&mut self, name: Ident, local_id: FuncId) -> Result<(), (Ident, Ident)> { - self.add_definition(name, local_id.into()) - } - - pub fn define_struct_def( - &mut self, - name: Ident, - local_id: StructId, - ) -> Result<(), (Ident, Ident)> { - self.add_definition(name, ModuleDefId::TypeId(local_id)) - } - - pub fn define_global(&mut self, name: Ident, stmt_id: StmtId) -> Result<(), (Ident, Ident)> { - self.add_definition(name, ModuleDefId::GlobalId(stmt_id)) - } - pub fn find_module_with_name(&self, mod_name: &Ident) -> Option<&ModuleId> { let (module_def, _) = self.types.get(mod_name)?; match module_def { @@ -86,6 +59,7 @@ impl ItemScope { _ => None, } } + pub fn find_func_with_name(&self, func_name: &Ident) -> Option { let (module_def, _) = self.values.get(func_name)?; match module_def { @@ -93,6 +67,7 @@ impl ItemScope { _ => None, } } + pub fn find_name(&self, name: &Ident) -> PerNs { PerNs { types: self.types.get(name).cloned(), values: self.values.get(name).cloned() } } @@ -100,9 +75,11 @@ impl ItemScope { pub fn definitions(&self) -> Vec { self.defs.clone() } + pub fn types(&self) -> &HashMap { &self.types } + pub fn values(&self) -> &HashMap { &self.values } diff --git a/crates/noirc_frontend/src/hir/def_map/mod.rs b/crates/noirc_frontend/src/hir/def_map/mod.rs index abf07c8d2a8..25e0488a7b6 100644 --- a/crates/noirc_frontend/src/hir/def_map/mod.rs +++ b/crates/noirc_frontend/src/hir/def_map/mod.rs @@ -110,7 +110,7 @@ impl CrateDefMap { // This function accepts an Ident, so we attach a dummy span to // "main". Equality is implemented only on the contents. - root_module.scope.find_func_with_name(&MAIN_FUNCTION.into()) + root_module.find_func_with_name(&MAIN_FUNCTION.into()) } pub fn root_file_id(&self) -> FileId { @@ -129,8 +129,10 @@ impl CrateDefMap { interner: &'a NodeInterner, ) -> impl Iterator + 'a { self.modules.iter().flat_map(|(_, module)| { - let functions = module.scope.values().values().filter_map(|(id, _)| id.as_function()); - functions.filter(|id| interner.function_meta(id).attributes == Some(Attribute::Test)) + module + .value_definitions() + .filter_map(|id| id.as_function()) + .filter(|id| interner.function_meta(id).attributes == Some(Attribute::Test)) }) } @@ -141,13 +143,8 @@ impl CrateDefMap { .iter() .filter_map(|(id, module)| { if module.is_contract { - let functions = module - .scope - .values() - .values() - .filter_map(|(id, _)| id.as_function()) - .collect(); - + let functions = + module.value_definitions().filter_map(|id| id.as_function()).collect(); let name = self.get_module_path(id, module.parent); Some(Contract { name, functions }) } else { diff --git a/crates/noirc_frontend/src/hir/def_map/module_data.rs b/crates/noirc_frontend/src/hir/def_map/module_data.rs index d437a4d1b6c..20906885ad9 100644 --- a/crates/noirc_frontend/src/hir/def_map/module_data.rs +++ b/crates/noirc_frontend/src/hir/def_map/module_data.rs @@ -2,9 +2,12 @@ use std::collections::HashMap; use fm::FileId; -use crate::Ident; +use crate::{ + node_interner::{FuncId, StmtId, StructId}, + Ident, +}; -use super::{ItemScope, LocalModuleId}; +use super::{ItemScope, LocalModuleId, ModuleDefId, ModuleId, PerNs}; /// Contains the actual contents of a module: its parent (if one exists), /// children, and scope with all definitions defined within the scope. @@ -12,7 +15,13 @@ use super::{ItemScope, LocalModuleId}; pub struct ModuleData { pub parent: Option, pub children: HashMap, - pub scope: ItemScope, + + /// Contains all definitions visible to the current module. This includes + /// all definitions in self.definitions as well as all imported definitions. + scope: ItemScope, + + /// Contains only the definitions directly defined in the current module + definitions: ItemScope, pub origin: ModuleOrigin, @@ -30,10 +39,57 @@ impl ModuleData { parent, children: HashMap::new(), scope: ItemScope::default(), + definitions: ItemScope::default(), origin, is_contract, } } + + fn declare(&mut self, name: Ident, item_id: ModuleDefId) -> Result<(), (Ident, Ident)> { + self.scope.add_definition(name.clone(), item_id)?; + + // definitions is a subset of self.scope so it is expected if self.scope.define_func_def + // returns without error, so will self.definitions.define_func_def. + self.definitions.add_definition(name, item_id) + } + + pub fn declare_function(&mut self, name: Ident, id: FuncId) -> Result<(), (Ident, Ident)> { + self.declare(name, id.into()) + } + + pub fn declare_global(&mut self, name: Ident, id: StmtId) -> Result<(), (Ident, Ident)> { + self.declare(name, id.into()) + } + + pub fn declare_struct(&mut self, name: Ident, id: StructId) -> Result<(), (Ident, Ident)> { + self.declare(name, ModuleDefId::TypeId(id)) + } + + pub fn declare_child_module( + &mut self, + name: Ident, + child_id: ModuleId, + ) -> Result<(), (Ident, Ident)> { + self.declare(name, child_id.into()) + } + + pub fn find_func_with_name(&self, name: &Ident) -> Option { + self.scope.find_func_with_name(name) + } + + pub fn import(&mut self, name: Ident, id: ModuleDefId) -> Result<(), (Ident, Ident)> { + self.scope.add_item_to_namespace(name, id) + } + + pub fn find_name(&self, name: &Ident) -> PerNs { + self.scope.find_name(name) + } + + /// Return an iterator over all definitions defined within this module, + /// excluding any type definitions. + pub fn value_definitions(&self) -> impl Iterator + '_ { + self.definitions.values().values().map(|(id, _)| *id) + } } #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/crates/noirc_frontend/src/hir/resolution/errors.rs b/crates/noirc_frontend/src/hir/resolution/errors.rs index 3ce7d9d0249..9406474a226 100644 --- a/crates/noirc_frontend/src/hir/resolution/errors.rs +++ b/crates/noirc_frontend/src/hir/resolution/errors.rs @@ -2,7 +2,7 @@ pub use noirc_errors::Span; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; -use crate::{parser::ParserError, Ident, Shared, StructType, Type}; +use crate::{parser::ParserError, Ident, Type}; use super::import::PathResolutionError; @@ -53,12 +53,7 @@ pub enum ResolverError { #[error("Cannot apply generics on Self type")] GenericsOnSelfType { span: Span }, #[error("Incorrect amount of arguments to generic type constructor")] - IncorrectGenericCount { - span: Span, - struct_type: Shared, - actual: usize, - expected: usize, - }, + IncorrectGenericCount { span: Span, struct_type: String, actual: usize, expected: usize }, #[error("{0}")] ParserError(ParserError), #[error("Function is not defined in a contract yet sets its contract visibility")] @@ -238,7 +233,7 @@ impl From for Diagnostic { let actual_plural = if actual == 1 { "is" } else { "are" }; Diagnostic::simple_error( - format!("The struct type {} has {expected} generic{expected_plural} but {actual} {actual_plural} given here", struct_type.borrow()), + format!("The struct type {struct_type} has {expected} generic{expected_plural} but {actual} {actual_plural} given here"), "Incorrect number of generic arguments".into(), span, ) diff --git a/crates/noirc_frontend/src/hir/resolution/import.rs b/crates/noirc_frontend/src/hir/resolution/import.rs index 836333ee5d9..79176d74afc 100644 --- a/crates/noirc_frontend/src/hir/resolution/import.rs +++ b/crates/noirc_frontend/src/hir/resolution/import.rs @@ -135,7 +135,7 @@ fn resolve_name_in_module( let mut import_path = import_path.iter(); let first_segment = import_path.next().expect("ice: could not fetch first segment"); - let mut current_ns = current_mod.scope.find_name(first_segment); + let mut current_ns = current_mod.find_name(first_segment); if current_ns.is_none() { return Err(PathResolutionError::Unresolved(first_segment.clone())); } @@ -158,7 +158,7 @@ fn resolve_name_in_module( current_mod = &def_maps[&new_module_id.krate].modules[new_module_id.local_id.0]; // Check if namespace - let found_ns = current_mod.scope.find_name(segment); + let found_ns = current_mod.find_name(segment); if found_ns.is_none() { return Err(PathResolutionError::Unresolved(segment.clone())); } @@ -168,7 +168,7 @@ fn resolve_name_in_module( return Err(PathResolutionError::ExternalContractUsed(segment.clone())); } - current_ns = found_ns + current_ns = found_ns; } Ok(current_ns) diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index d01952f7c49..cfb354498ab 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -121,7 +121,7 @@ impl<'a> Resolver<'a> { } fn push_err(&mut self, err: ResolverError) { - self.errors.push(err) + self.errors.push(err); } fn current_lambda_index(&self) -> usize { @@ -347,6 +347,20 @@ impl<'a> Resolver<'a> { let ret = Box::new(self.resolve_type_inner(*ret, new_variables)); Type::Function(args, ret) } + UnresolvedType::Vec(mut args, span) => { + let arg = if args.len() != 1 { + self.push_err(ResolverError::IncorrectGenericCount { + span, + struct_type: "Vec".into(), + actual: args.len(), + expected: 1, + }); + Type::Error + } else { + self.resolve_type_inner(args.remove(0), new_variables) + }; + Type::Vec(Box::new(arg)) + } } } @@ -390,13 +404,13 @@ impl<'a> Resolver<'a> { if args.len() != expected_generic_count { self.push_err(ResolverError::IncorrectGenericCount { span, - struct_type: struct_type.clone(), + struct_type: struct_type.borrow().to_string(), actual: args.len(), expected: expected_generic_count, }); // Fix the generic count so we can continue typechecking - args.resize_with(expected_generic_count, || self.interner.next_type_variable()) + args.resize_with(expected_generic_count, || Type::Error); } Type::Struct(struct_type, args) @@ -541,7 +555,7 @@ impl<'a> Resolver<'a> { name: generic.0.contents.clone(), first_span: *first_span, second_span: span, - }) + }); } else { self.generics.push((name, typevar.clone(), span)); } @@ -602,7 +616,7 @@ impl<'a> Resolver<'a> { for (pattern, typ, visibility) in func.parameters().iter().cloned() { if visibility == noirc_abi::AbiVisibility::Public && !self.pub_allowed(func) { - self.push_err(ResolverError::UnnecessaryPub { ident: func.name_ident().clone() }) + self.push_err(ResolverError::UnnecessaryPub { ident: func.name_ident().clone() }); } let pattern = self.resolve_pattern(pattern, DefinitionKind::Local(None)); @@ -620,13 +634,13 @@ impl<'a> Resolver<'a> { && return_type.as_ref() != &Type::Unit && func.def.return_visibility != noirc_abi::AbiVisibility::Public { - self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }) + self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }); } if attributes == Some(Attribute::Test) && !parameters.is_empty() { self.push_err(ResolverError::TestFunctionHasParameters { span: func.name_ident().span(), - }) + }); } let mut typ = Type::Function(parameter_types, return_type); @@ -751,6 +765,7 @@ impl<'a> Resolver<'a> { } } } + Type::Vec(element) => Self::find_numeric_generics_in_type(element, found), } } @@ -1002,7 +1017,7 @@ impl<'a> Resolver<'a> { } Pattern::Mutable(pattern, span) => { if let Some(first_mut) = mutable { - self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }) + self.push_err(ResolverError::UnnecessaryMut { first_mut, second_mut: span }); } let pattern = self.resolve_pattern_mutable(*pattern, Some(span), definition); @@ -1476,7 +1491,7 @@ mod test { fn path_unresolved_error(err: ResolverError, expected_unresolved_path: &str) { match err { ResolverError::PathResolutionError(PathResolutionError::Unresolved(name)) => { - assert_eq!(name.to_string(), expected_unresolved_path) + assert_eq!(name.to_string(), expected_unresolved_path); } _ => unimplemented!("expected an unresolved path"), } diff --git a/crates/noirc_frontend/src/hir/scope/mod.rs b/crates/noirc_frontend/src/hir/scope/mod.rs index 85b7e2e62e9..1a9087a7408 100644 --- a/crates/noirc_frontend/src/hir/scope/mod.rs +++ b/crates/noirc_frontend/src/hir/scope/mod.rs @@ -90,7 +90,7 @@ impl ScopeTree { } pub fn push_scope(&mut self) { - self.0.push(Scope::default()) + self.0.push(Scope::default()); } pub fn pop_scope(&mut self) -> Scope { @@ -135,7 +135,7 @@ impl ScopeForest { } fn extend_current_scope_tree(&mut self) { - self.current_scope_tree().push_scope() + self.current_scope_tree().push_scope(); } fn remove_scope_tree_extension(&mut self) -> Scope { @@ -145,7 +145,7 @@ impl ScopeForest { /// Starting a function requires a new scope tree, as you do not want the functions scope to /// have access to the scope of the caller pub fn start_function(&mut self) { - self.0.push(ScopeTree::default()) + self.0.push(ScopeTree::default()); } /// Ending a function requires that we removes it's whole tree of scope @@ -157,7 +157,7 @@ impl ScopeForest { /// The beginning of a scope always correlates with the start of a block {}. /// This can be in if expressions, for loops, or functions. pub fn start_scope(&mut self) { - self.extend_current_scope_tree() + self.extend_current_scope_tree(); } /// Ends the current scope - this should correspond with the end of a BlockExpression. diff --git a/crates/noirc_frontend/src/hir/type_check/mod.rs b/crates/noirc_frontend/src/hir/type_check/mod.rs index 23907f6b3b4..97b1c71a0bc 100644 --- a/crates/noirc_frontend/src/hir/type_check/mod.rs +++ b/crates/noirc_frontend/src/hir/type_check/mod.rs @@ -101,7 +101,7 @@ impl<'interner> TypeChecker<'interner> { span: Span, make_error: impl FnOnce() -> TypeCheckError, ) { - actual.unify(expected, span, &mut self.errors, make_error) + actual.unify(expected, span, &mut self.errors, make_error); } /// Wrapper of Type::make_subtype_of using self.errors @@ -112,7 +112,7 @@ impl<'interner> TypeChecker<'interner> { span: Span, make_error: impl FnOnce() -> TypeCheckError, ) { - actual.make_subtype_of(expected, span, &mut self.errors, make_error) + actual.make_subtype_of(expected, span, &mut self.errors, make_error); } } @@ -362,7 +362,7 @@ mod test { for ((hir_func, meta), func_id) in func_meta.into_iter().zip(func_ids.clone()) { interner.update_fn(func_id, hir_func); - interner.push_fn_meta(meta, func_id) + interner.push_fn_meta(meta, func_id); } // Type check section diff --git a/crates/noirc_frontend/src/hir_def/types.rs b/crates/noirc_frontend/src/hir_def/types.rs index f22ebb621d7..be7d90e089f 100644 --- a/crates/noirc_frontend/src/hir_def/types.rs +++ b/crates/noirc_frontend/src/hir_def/types.rs @@ -70,6 +70,11 @@ pub enum Type { /// A functions with arguments, and a return type. Function(Vec, Box), + /// A variable-sized Vector type. + /// Unlike arrays, this type can have a dynamic size and can grow/shrink dynamically via .push, + /// .pop, and similar methods. + Vec(Box), + /// A type generic over the given type variables. /// Storing both the TypeVariableId and TypeVariable isn't necessary /// but it makes handling them both easier. The TypeVariableId should @@ -121,7 +126,7 @@ pub type Generics = Vec<(TypeVariableId, TypeVariable)>; impl std::hash::Hash for StructType { fn hash(&self, state: &mut H) { - self.id.hash(state) + self.id.hash(state); } } @@ -225,7 +230,7 @@ pub struct Shared(Rc>); impl std::hash::Hash for Shared { fn hash(&self, state: &mut H) { - self.0.borrow().hash(state) + self.0.borrow().hash(state); } } @@ -589,6 +594,7 @@ impl Type { } }) } + Type::Vec(element) => element.contains_numeric_typevar(target_id), } } } @@ -645,6 +651,9 @@ impl std::fmt::Display for Type { let args = vecmap(args, ToString::to_string); write!(f, "fn({}) -> {}", args.join(", "), ret) } + Type::Vec(element) => { + write!(f, "Vec<{}>", element) + } } } } @@ -696,7 +705,7 @@ impl Type { pub fn set_comp_time_span(&mut self, new_span: Span) { match self { Type::FieldElement(comptime) | Type::Integer(comptime, _, _) => { - comptime.set_span(new_span) + comptime.set_span(new_span); } Type::PolymorphicInteger(span, binding) => { if let TypeBinding::Bound(binding) = &mut *binding.borrow_mut() { @@ -856,7 +865,7 @@ impl Type { make_error: impl FnOnce() -> TypeCheckError, ) { if let Err(err_span) = self.try_unify(expected, span) { - Self::issue_errors(expected, err_span, errors, make_error) + Self::issue_errors(expected, err_span, errors, make_error); } } @@ -975,6 +984,8 @@ impl Type { } } + (Vec(elem_a), Vec(elem_b)) => elem_a.try_unify(elem_b, span), + (other_a, other_b) => { if other_a == other_b { Ok(()) @@ -995,7 +1006,7 @@ impl Type { make_error: impl FnOnce() -> TypeCheckError, ) { if let Err(err_span) = self.is_subtype_of(expected, span) { - Self::issue_errors(expected, err_span, errors, make_error) + Self::issue_errors(expected, err_span, errors, make_error); } } @@ -1106,6 +1117,8 @@ impl Type { } } + (Vec(elem_a), Vec(elem_b)) => elem_a.is_subtype_of(elem_b, span), + (other_a, other_b) => { if other_a == other_b { Ok(()) @@ -1176,6 +1189,7 @@ impl Type { Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), Type::Function(_, _) => unreachable!(), + Type::Vec(_) => unreachable!("Vecs cannot be used in the abi"), } } @@ -1289,6 +1303,7 @@ impl Type { let ret = Box::new(ret.substitute(type_bindings)); Type::Function(args, ret) } + Type::Vec(element) => Type::Vec(Box::new(element.substitute(type_bindings))), Type::FieldElement(_) | Type::Integer(_, _, _) @@ -1318,6 +1333,7 @@ impl Type { Type::Function(args, ret) => { args.iter().any(|arg| arg.occurs(target_id)) || ret.occurs(target_id) } + Type::Vec(element) => element.occurs(target_id), Type::FieldElement(_) | Type::Integer(_, _, _) @@ -1359,6 +1375,7 @@ impl Type { let ret = Box::new(ret.follow_bindings()); Function(args, ret) } + Vec(element) => Vec(Box::new(element.follow_bindings())), // Expect that this function should only be called on instantiated types Forall(..) => unreachable!(), diff --git a/crates/noirc_frontend/src/lexer/lexer.rs b/crates/noirc_frontend/src/lexer/lexer.rs index da4af04c001..c1ff328a3ed 100644 --- a/crates/noirc_frontend/src/lexer/lexer.rs +++ b/crates/noirc_frontend/src/lexer/lexer.rs @@ -216,7 +216,7 @@ impl<'a> Lexer<'a> { // Therefore, the current character which triggered this function will need to be appended let mut word = String::new(); if let Some(init_char) = initial_char { - word.push(init_char) + word.push(init_char); } // Keep checking that we are not at the EOF diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index 84939641bd7..0df1fc39938 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -439,6 +439,7 @@ pub enum Keyword { Struct, Unconstrained, Use, + Vec, While, } @@ -471,6 +472,7 @@ impl fmt::Display for Keyword { Keyword::Struct => write!(f, "struct"), Keyword::Unconstrained => write!(f, "unconstrained"), Keyword::Use => write!(f, "use"), + Keyword::Vec => write!(f, "Vec"), Keyword::While => write!(f, "while"), } } @@ -506,6 +508,7 @@ impl Keyword { "struct" => Keyword::Struct, "unconstrained" => Keyword::Unconstrained, "use" => Keyword::Use, + "Vec" => Keyword::Vec, "while" => Keyword::While, "true" => return Some(Token::Bool(true)), @@ -536,7 +539,7 @@ mod keywords { resolved_token, Token::Keyword(keyword), "Keyword::lookup_keyword returns unexpected Keyword" - ) + ); } } } diff --git a/crates/noirc_frontend/src/lib.rs b/crates/noirc_frontend/src/lib.rs index 2e375e637e0..e7d95d3dfc8 100644 --- a/crates/noirc_frontend/src/lib.rs +++ b/crates/noirc_frontend/src/lib.rs @@ -8,6 +8,7 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] pub mod ast; pub mod graph; diff --git a/crates/noirc_frontend/src/main.rs b/crates/noirc_frontend/src/main.rs index f68c52e3129..b1a5c0f950e 100644 --- a/crates/noirc_frontend/src/main.rs +++ b/crates/noirc_frontend/src/main.rs @@ -41,18 +41,15 @@ fn main() { // Get root module let root = def_map.root(); let module = def_map.modules().get(root.0).unwrap(); - for (name, (def_id, vis)) in module.scope.values() { - println!("func name is {:?}", name); + for def_id in module.value_definitions() { let func_id = match def_id { ModuleDefId::FunctionId(func_id) => func_id, _ => unreachable!(), }; // Get the HirFunction for that Id - let hir = context.def_interner.function(func_id); - + let hir = context.def_interner.function(&func_id); println!("func hir is {:?}", hir); - println!("func vis is {:?}", vis); } // diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index 7920676aa7d..938a937405c 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -202,6 +202,7 @@ pub enum Type { String(/*len:*/ u64), // String(4) = str[4] Unit, Tuple(Vec), + Vec(Box), Function(/*args:*/ Vec, /*ret:*/ Box), } @@ -301,6 +302,7 @@ impl std::fmt::Display for Type { let args = vecmap(args, ToString::to_string); write!(f, "fn({}) -> {}", args.join(", "), ret) } + Type::Vec(element) => write!(f, "Vec<{element}>"), } } } diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 26009c0227e..bfce292d2eb 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -382,8 +382,8 @@ impl<'interner> Monomorphizer<'interner> { }, )), - ast::Type::Array(_, _) | ast::Type::String(_) => { - unreachable!("Nested arrays and arrays of strings are not supported") + ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Vec(_) => { + unreachable!("Nested arrays, arrays of strings, and Vecs are not supported") } } } @@ -425,8 +425,8 @@ impl<'interner> Monomorphizer<'interner> { })) } - ast::Type::Array(_, _) | ast::Type::String(_) => { - unreachable!("Nested arrays and arrays of strings are not supported") + ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Vec(_) => { + unreachable!("Nested arrays and arrays of strings or Vecs are not supported") } } } @@ -663,6 +663,11 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Function(args, ret) } + HirType::Vec(element) => { + let element = Self::convert_type(element); + ast::Type::Vec(Box::new(element)) + } + HirType::Forall(_, _) | HirType::Constant(_) | HirType::Error => { unreachable!("Unexpected type {} found", typ) } @@ -683,7 +688,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Tuple(vecmap(elements, |typ| Self::aos_to_soa_type(length, typ))) } - ast::Type::Array(_, _) | ast::Type::String(_) => { + ast::Type::Array(_, _) | ast::Type::String(_) | ast::Type::Vec(_) => { unreachable!("Nested arrays and arrays of strings are not supported") } } @@ -941,6 +946,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Function(parameter_types, ret_type) => { self.create_zeroed_function(parameter_types, ret_type) } + ast::Type::Vec(_) => panic!("Cannot create a zeroed Vec value. This type is currently unimplemented and meant to be unusable outside of unconstrained functions"), } } diff --git a/crates/noirc_frontend/src/node_interner.rs b/crates/noirc_frontend/src/node_interner.rs index 417c7bedc66..d8ea11ae89c 100644 --- a/crates/noirc_frontend/src/node_interner.rs +++ b/crates/noirc_frontend/src/node_interner.rs @@ -320,7 +320,7 @@ impl NodeInterner { pub fn update_struct(&mut self, type_id: StructId, f: impl FnOnce(&mut StructType)) { let mut value = self.structs.get_mut(&type_id).unwrap().borrow_mut(); - f(&mut value) + f(&mut value); } /// Returns the interned statement corresponding to `stmt_id` @@ -654,6 +654,7 @@ enum TypeMethodKey { Unit, Tuple, Function, + Vec, } fn get_type_method_key(typ: &Type) -> Option { @@ -669,6 +670,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), Type::Function(_, _) => Some(Function), + Type::Vec(_) => Some(Vec), // We do not support adding methods to these types Type::TypeVariable(_) diff --git a/crates/noirc_frontend/src/parser/mod.rs b/crates/noirc_frontend/src/parser/mod.rs index fa0903ee659..788c0eec895 100644 --- a/crates/noirc_frontend/src/parser/mod.rs +++ b/crates/noirc_frontend/src/parser/mod.rs @@ -263,7 +263,7 @@ impl ParsedModule { } fn push_global(&mut self, global: LetStatement) { - self.globals.push(global) + self.globals.push(global); } } diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 463fbee248e..f4793d06368 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/noirc_frontend/src/parser/parser.rs @@ -537,11 +537,12 @@ fn parse_type_inner( choice(( field_type(), int_type(), + bool_type(), + string_type(), named_type(recursive_type_parser.clone()), array_type(recursive_type_parser.clone()), tuple_type(recursive_type_parser.clone()), - bool_type(), - string_type(), + vec_type(recursive_type_parser.clone()), function_type(recursive_type_parser), )) } @@ -593,6 +594,12 @@ fn named_type(type_parser: impl NoirParser) -> impl NoirParser) -> impl NoirParser { + keyword(Keyword::Vec) + .ignore_then(generic_type_args(type_parser)) + .map_with_span(UnresolvedType::Vec) +} + fn generic_type_args( type_parser: impl NoirParser, ) -> impl NoirParser> { @@ -849,7 +856,7 @@ where emit(ParserError::with_reason( "Arrays must have at least one element".to_owned(), span, - )) + )); } ExpressionKind::array(elements) }) @@ -1128,7 +1135,7 @@ mod test { match expr_to_array(expr) { ArrayLiteral::Standard(elements) => assert_eq!(elements.len(), 5), ArrayLiteral::Repeated { length, .. } => { - assert_eq!(length.kind, ExpressionKind::integer(5i128.into())) + assert_eq!(length.kind, ExpressionKind::integer(5i128.into())); } } } @@ -1360,7 +1367,7 @@ mod test { for (src, expected_path_kind) in cases { let path = parse_with(path(), src).unwrap(); - assert_eq!(path.kind, expected_path_kind) + assert_eq!(path.kind, expected_path_kind); } parse_all_failing( diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index 3deee5624d6..2a659b94965 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -1,6 +1,8 @@ #![forbid(unsafe_code)] #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] + use gloo_utils::format::JsValueSerdeExt; use log::Level; use serde::{Deserialize, Serialize}; diff --git a/noir_stdlib/src/collections.nr b/noir_stdlib/src/collections.nr new file mode 100644 index 00000000000..e06c662e658 --- /dev/null +++ b/noir_stdlib/src/collections.nr @@ -0,0 +1 @@ +mod vec; diff --git a/noir_stdlib/src/collections/vec.nr b/noir_stdlib/src/collections/vec.nr new file mode 100644 index 00000000000..130dfdfc2a6 --- /dev/null +++ b/noir_stdlib/src/collections/vec.nr @@ -0,0 +1,24 @@ + +// These methods are all stubs currently and aren't implemented internally yet. +// For a similar reason, no constructor for Vec is exposed yet since the type +// is still in-progress. +impl Vec { + /// Get an element from the vector at the given index. + /// Fails with a constraint error if the given index + /// points beyond the end of the vector. + #[builtin(vec_get)] + fn get(_self: Self, _index: Field) -> T { } + + /// Push a new element to the end of the vector, returning a + /// new vector with a length one greater than the + /// original unmodified vector. + #[builtin(vec_push)] + fn push(_self: Self, _elem: T) -> Self { } + + /// Pop an element from the end of the given vector, returning + /// a new vector with a length of one less than the given vector, + /// as well as the popped element. + /// Fails with a constraint error if the given vector's length is zero. + #[builtin(vec_pop)] + fn pop(_self: Self) -> (Self, T) { } +} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 16383c2c704..f0af06b97ba 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -9,6 +9,7 @@ mod sha512; mod field; mod ec; mod unsafe; +mod collections; #[builtin(println)] fn println(_input : T) {}